front-end progress; Socket.IO messaging API; inquirer for credentials
This commit is contained in:
parent
56ba613cc7
commit
db0d1586d6
@ -29,6 +29,7 @@ The IDE uses the following color palette (CSS custom properties):
|
|||||||
```
|
```
|
||||||
|
|
||||||
Font stack:
|
Font stack:
|
||||||
|
|
||||||
- Primary: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif
|
- Primary: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif
|
||||||
- Code/Retro: Courier New, Courier, monospace
|
- Code/Retro: Courier New, Courier, monospace
|
||||||
|
|
||||||
@ -81,7 +82,7 @@ Implementation: `frontend/src/components/StatusBar.tsx`
|
|||||||
Between the header and status bars is the Content Area. It uses React Router for URL-based navigation:
|
Between the header and status bars is the Content Area. It uses React Router for URL-based navigation:
|
||||||
|
|
||||||
| Route | View |
|
| Route | View |
|
||||||
|-------|------|
|
| ----------------- | ------------------------------------------------- |
|
||||||
| `/` | Home (authenticated dashboard or unauthenticated) |
|
| `/` | Home (authenticated dashboard or unauthenticated) |
|
||||||
| `/projects` | Project Manager (list view) |
|
| `/projects` | Project Manager (list view) |
|
||||||
| `/projects/:slug` | Project Manager (project selected) |
|
| `/projects/:slug` | Project Manager (project selected) |
|
||||||
@ -132,6 +133,7 @@ Select a project or chat session from the sidebar to get started.
|
|||||||
```
|
```
|
||||||
|
|
||||||
Components:
|
Components:
|
||||||
|
|
||||||
1. Clock - local time in AM/PM format, current date
|
1. Clock - local time in AM/PM format, current date
|
||||||
2. Projects list - links to `/projects/:slug`, [+]/link navigates to `/projects`
|
2. Projects list - links to `/projects/:slug`, [+]/link navigates to `/projects`
|
||||||
3. Drones list - status indicator (green=available, yellow=busy, gray=offline)
|
3. Drones list - status indicator (green=available, yellow=busy, gray=offline)
|
||||||
@ -174,6 +176,7 @@ When a project is selected:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
|
|
||||||
- Left sidebar: Project list with [+ New Project] button
|
- Left sidebar: Project list with [+ New Project] button
|
||||||
- Project Inspector: Shows name, slug, gitUrl, status, createdAt
|
- Project Inspector: Shows name, slug, gitUrl, status, createdAt
|
||||||
- Delete: Confirmation before deletion
|
- Delete: Confirmation before deletion
|
||||||
@ -205,12 +208,13 @@ Triggered by [New Project] button or `/projects/new` route:
|
|||||||
All API requests include the JWT in the Authorization header:
|
All API requests include the JWT in the Authorization header:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
headers['Authorization'] = `Bearer ${token}`;
|
headers["Authorization"] = `Bearer ${token}`;
|
||||||
```
|
```
|
||||||
|
|
||||||
### Backend Session Restoration
|
### Backend Session Restoration
|
||||||
|
|
||||||
The backend restores user sessions from:
|
The backend restores user sessions from:
|
||||||
|
|
||||||
1. `Authorization: Bearer <token>` header (JWT)
|
1. `Authorization: Bearer <token>` header (JWT)
|
||||||
2. Express session (fallback)
|
2. Express session (fallback)
|
||||||
|
|
||||||
@ -221,6 +225,7 @@ The `requireUser()` middleware ensures endpoints are authenticated.
|
|||||||
### Password Field Handling
|
### Password Field Handling
|
||||||
|
|
||||||
Password credentials are NEVER exposed:
|
Password credentials are NEVER exposed:
|
||||||
|
|
||||||
- Mongoose `select: false` on password fields in models
|
- Mongoose `select: false` on password fields in models
|
||||||
- Population uses `select: "-passwordSalt -password"` to exclude from queries
|
- Population uses `select: "-passwordSalt -password"` to exclude from queries
|
||||||
- Frontend User interface only includes: `_id`, `email`, `displayName`, `flags`
|
- Frontend User interface only includes: `_id`, `email`, `displayName`, `flags`
|
||||||
@ -235,7 +240,7 @@ Work Area | Session Status
|
|||||||
Chat Messages | Chat: name
|
Chat Messages | Chat: name
|
||||||
| ID: ...
|
| ID: ...
|
||||||
| Model: ...
|
| Model: ...
|
||||||
------------------------------------------|---------------
|
----------------------------------------------|---------------
|
||||||
[Prompt input ][Expand][Send]| TC | FO | SA
|
[Prompt input ][Expand][Send]| TC | FO | SA
|
||||||
----------------------------------------------|---------------
|
----------------------------------------------|---------------
|
||||||
Log | Files
|
Log | Files
|
||||||
@ -243,6 +248,7 @@ Log | Files
|
|||||||
```
|
```
|
||||||
|
|
||||||
Implemented components:
|
Implemented components:
|
||||||
|
|
||||||
- Chat Messages (stubbed)
|
- Chat Messages (stubbed)
|
||||||
- Prompt Input (stubbed)
|
- Prompt Input (stubbed)
|
||||||
- Session Status sidebar (stubbed)
|
- Session Status sidebar (stubbed)
|
||||||
|
|||||||
@ -108,3 +108,17 @@ export const projectApi = {
|
|||||||
api.put<Project>(`/api/v1/projects/${id}`, data),
|
api.put<Project>(`/api/v1/projects/${id}`, data),
|
||||||
delete: (id: string) => api.delete<void>(`/api/v1/projects/${id}`),
|
delete: (id: string) => api.delete<void>(`/api/v1/projects/${id}`),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface DroneRegistration {
|
||||||
|
_id: string;
|
||||||
|
hostname: string;
|
||||||
|
workspaceDir: string;
|
||||||
|
status: 'starting' | 'available' | 'busy' | 'offline';
|
||||||
|
user: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const droneApi = {
|
||||||
|
getAll: () => api.get<DroneRegistration[]>('/api/v1/drone/registration'),
|
||||||
|
};
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import type { User, Project } from '../lib/api';
|
import type { User, Project, DroneRegistration } from '../lib/api';
|
||||||
import { projectApi } from '../lib/api';
|
import { projectApi, droneApi } from '../lib/api';
|
||||||
import Clock from '../components/Clock';
|
import Clock from '../components/Clock';
|
||||||
|
|
||||||
const ansiColors = [
|
const ansiColors = [
|
||||||
@ -51,23 +51,79 @@ function AnsiLogo() {
|
|||||||
|
|
||||||
interface DashboardSidebarProps {
|
interface DashboardSidebarProps {
|
||||||
onNavigate: (view: string, data?: unknown) => void;
|
onNavigate: (view: string, data?: unknown) => void;
|
||||||
|
selectedDrone: DroneRegistration | null;
|
||||||
|
onSelectDrone: (drone: DroneRegistration | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DashboardSidebar({ onNavigate }: DashboardSidebarProps) {
|
function DroneInspector({ drone, onClose }: { drone: DroneRegistration; onClose: () => void }) {
|
||||||
|
return (
|
||||||
|
<div className="flex-1 flex items-center justify-center p-8">
|
||||||
|
<div className="max-w-lg w-full">
|
||||||
|
<div className="flex items-center justify-between mb-6">
|
||||||
|
<h2 className="text-xl font-semibold">Drone Inspector</h2>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="text-text-secondary hover:text-text-primary text-sm"
|
||||||
|
>
|
||||||
|
← Back to Dashboard
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="bg-bg-secondary border border-border-default rounded p-4 space-y-4">
|
||||||
|
<div>
|
||||||
|
<div className="text-sm text-text-muted">Hostname</div>
|
||||||
|
<div className="font-mono text-text-primary">{drone.hostname}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-sm text-text-muted">Workspace</div>
|
||||||
|
<div className="font-mono text-text-primary text-sm truncate">{drone.workspaceDir}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-sm text-text-muted">Status</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span
|
||||||
|
className={`w-2 h-2 rounded-full ${
|
||||||
|
drone.status === 'available'
|
||||||
|
? 'bg-green-500'
|
||||||
|
: drone.status === 'busy'
|
||||||
|
? 'bg-yellow-500'
|
||||||
|
: 'bg-gray-600'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
<span className="text-text-primary capitalize">{drone.status}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-sm text-text-muted">Registered</div>
|
||||||
|
<div className="text-text-primary text-sm">
|
||||||
|
{new Date(drone.createdAt).toLocaleString()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DashboardSidebar({ onNavigate, selectedDrone, onSelectDrone }: DashboardSidebarProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [projects, setProjects] = useState<Project[]>([]);
|
const [projects, setProjects] = useState<Project[]>([]);
|
||||||
|
const [drones, setDrones] = useState<DroneRegistration[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadProjects();
|
loadData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadProjects = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await projectApi.getAll();
|
const [projectsData, dronesData] = await Promise.all([
|
||||||
setProjects(data);
|
projectApi.getAll(),
|
||||||
|
droneApi.getAll(),
|
||||||
|
]);
|
||||||
|
setProjects(projectsData);
|
||||||
|
setDrones(dronesData);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load projects', err);
|
console.error('Failed to load dashboard data', err);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@ -110,9 +166,51 @@ function DashboardSidebar({ onNavigate }: DashboardSidebarProps) {
|
|||||||
<div className="text-xs font-semibold text-text-muted uppercase tracking-wider mb-2">
|
<div className="text-xs font-semibold text-text-muted uppercase tracking-wider mb-2">
|
||||||
Drones
|
Drones
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
{loading ? (
|
||||||
<p className="text-sm text-text-muted px-2">Loading...</p>
|
<p className="text-sm text-text-muted px-2">Loading...</p>
|
||||||
|
) : drones.length === 0 ? (
|
||||||
|
<p className="text-sm text-text-muted px-2">No drones available.</p>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-1">
|
||||||
|
{drones
|
||||||
|
.filter((d) => d.status === 'available' || d.status === 'busy')
|
||||||
|
.map((drone) => (
|
||||||
|
<button
|
||||||
|
key={drone._id}
|
||||||
|
onClick={() => onSelectDrone(drone)}
|
||||||
|
className={`w-full text-left px-2 py-1 rounded transition-colors ${
|
||||||
|
selectedDrone?._id === drone._id
|
||||||
|
? 'bg-brand text-white'
|
||||||
|
: 'text-text-secondary hover:bg-bg-tertiary hover:text-text-primary'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span
|
||||||
|
className={`w-2 h-2 rounded-full shrink-0 ${
|
||||||
|
drone.status === 'available'
|
||||||
|
? 'bg-green-500'
|
||||||
|
: 'bg-yellow-500'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
<div className="overflow-hidden">
|
||||||
|
<div className="text-sm truncate">{drone.hostname}</div>
|
||||||
|
{drone.workspaceDir && (
|
||||||
|
<div className={`text-xs truncate ${
|
||||||
|
selectedDrone?._id === drone._id
|
||||||
|
? 'text-white/70'
|
||||||
|
: 'text-text-muted'
|
||||||
|
}`}>
|
||||||
|
{drone.workspaceDir.length > 24
|
||||||
|
? '...' + drone.workspaceDir.slice(-21)
|
||||||
|
: drone.workspaceDir}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-3 border-t border-border-subtle">
|
<div className="p-3 border-t border-border-subtle">
|
||||||
@ -133,6 +231,7 @@ interface HomeProps {
|
|||||||
|
|
||||||
export default function Home({ user }: HomeProps) {
|
export default function Home({ user }: HomeProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const [selectedDrone, setSelectedDrone] = useState<DroneRegistration | null>(null);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return (
|
return (
|
||||||
@ -144,6 +243,9 @@ export default function Home({ user }: HomeProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 flex bg-bg-primary">
|
<div className="flex-1 flex bg-bg-primary">
|
||||||
|
{selectedDrone ? (
|
||||||
|
<DroneInspector drone={selectedDrone} onClose={() => setSelectedDrone(null)} />
|
||||||
|
) : (
|
||||||
<div className="flex-1 flex items-center justify-center p-8">
|
<div className="flex-1 flex items-center justify-center p-8">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h1 className="text-2xl font-semibold mb-4">
|
<h1 className="text-2xl font-semibold mb-4">
|
||||||
@ -165,12 +267,17 @@ export default function Home({ user }: HomeProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<DashboardSidebar onNavigate={(view) => {
|
<DashboardSidebar
|
||||||
|
onNavigate={(view) => {
|
||||||
if (view === 'project' && typeof navigate === 'function') {
|
if (view === 'project' && typeof navigate === 'function') {
|
||||||
navigate('/projects');
|
navigate('/projects');
|
||||||
}
|
}
|
||||||
}} />
|
}}
|
||||||
|
selectedDrone={selectedDrone}
|
||||||
|
onSelectDrone={setSelectedDrone}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -229,6 +229,18 @@ export default function ProjectManager({ user }: ProjectManagerProps) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-y-auto p-2">
|
<div className="flex-1 overflow-y-auto p-2">
|
||||||
|
{showNewForm ? (
|
||||||
|
<div className="p-2">
|
||||||
|
<p className="text-sm text-text-muted mb-2">Creating new project...</p>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowNewForm(false)}
|
||||||
|
className="text-sm text-text-secondary hover:text-text-primary transition-colors"
|
||||||
|
>
|
||||||
|
← Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<div className="text-xs font-semibold text-text-muted uppercase tracking-wider mb-2 px-2">
|
<div className="text-xs font-semibold text-text-muted uppercase tracking-wider mb-2 px-2">
|
||||||
Projects ({projects.length})
|
Projects ({projects.length})
|
||||||
</div>
|
</div>
|
||||||
@ -251,6 +263,8 @@ export default function ProjectManager({ user }: ProjectManagerProps) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
|
|||||||
@ -26,7 +26,9 @@ export class DroneApiControllerV1 extends DtpController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async start(): Promise<void> {
|
async start(): Promise<void> {
|
||||||
|
const requireUser = this.requireUser();
|
||||||
const multer = this.createMulter(this.slug, {});
|
const multer = this.createMulter(this.slug, {});
|
||||||
|
|
||||||
this.router.param("registrationId", populateDroneRegistrationById(this));
|
this.router.param("registrationId", populateDroneRegistrationById(this));
|
||||||
|
|
||||||
this.router.post(
|
this.router.post(
|
||||||
@ -40,6 +42,12 @@ export class DroneApiControllerV1 extends DtpController {
|
|||||||
this.putRegistrationStatus.bind(this),
|
this.putRegistrationStatus.bind(this),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.router.get(
|
||||||
|
"/registration",
|
||||||
|
requireUser,
|
||||||
|
this.getRegistrations.bind(this),
|
||||||
|
);
|
||||||
|
|
||||||
this.router.delete(
|
this.router.delete(
|
||||||
"/registration/:registrationId",
|
"/registration/:registrationId",
|
||||||
this.deleteRegistration.bind(this),
|
this.deleteRegistration.bind(this),
|
||||||
@ -82,6 +90,24 @@ export class DroneApiControllerV1 extends DtpController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getRegistrations(req: Request, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
const registrations = await DroneService.getForUser(req.user);
|
||||||
|
res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
data: registrations,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.log.error("failed to present drone registrations for User", {
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
res.status((error as Error).statusCode || 500).json({
|
||||||
|
success: false,
|
||||||
|
message: (error as Error).message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async deleteRegistration(_req: Request, res: Response): Promise<void> {
|
async deleteRegistration(_req: Request, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await DroneService.unregister(res.locals.registration);
|
await DroneService.unregister(res.locals.registration);
|
||||||
|
|||||||
@ -17,6 +17,7 @@ export const DroneRegistrationSchema = new Schema<IDroneRegistration>({
|
|||||||
default: DroneStatus.Starting,
|
default: DroneStatus.Starting,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
chatSessionId: { type: String, required: false },
|
||||||
currentJobId: { type: String, required: false },
|
currentJobId: { type: String, required: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -4,13 +4,19 @@
|
|||||||
|
|
||||||
import { PopulateOptions, Types } from "mongoose";
|
import { PopulateOptions, Types } from "mongoose";
|
||||||
|
|
||||||
import { IUser, DroneStatus, IDroneRegistration } from "@gadget/api";
|
import {
|
||||||
|
IUser,
|
||||||
|
DroneStatus,
|
||||||
|
IDroneRegistration,
|
||||||
|
IChatSession,
|
||||||
|
} from "@gadget/api";
|
||||||
import DroneRegistration from "@/models/drone-registration.js";
|
import DroneRegistration from "@/models/drone-registration.js";
|
||||||
|
|
||||||
import { DtpService } from "../lib/service.js";
|
import { DtpService } from "../lib/service.js";
|
||||||
|
|
||||||
export interface IDroneDefinition {
|
export interface IDroneDefinition {
|
||||||
hostname: string;
|
hostname: string;
|
||||||
|
workspaceDir: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class DroneService extends DtpService {
|
class DroneService extends DtpService {
|
||||||
@ -42,6 +48,13 @@ class DroneService extends DtpService {
|
|||||||
this.log.info("service stopped");
|
this.log.info("service stopped");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The drone calls this service to register itself into the platform as
|
||||||
|
* belonging to a specific User. Users can only ever access their own drones.
|
||||||
|
* @param user The user for which a drone is being registered
|
||||||
|
* @param definition The definition of the drone being registered
|
||||||
|
* @returns the new drone registration
|
||||||
|
*/
|
||||||
async register(
|
async register(
|
||||||
user: IUser,
|
user: IUser,
|
||||||
definition: IDroneDefinition,
|
definition: IDroneDefinition,
|
||||||
@ -53,6 +66,7 @@ class DroneService extends DtpService {
|
|||||||
registration.updatedAt = NOW;
|
registration.updatedAt = NOW;
|
||||||
registration.user = user._id;
|
registration.user = user._id;
|
||||||
registration.hostname = definition.hostname;
|
registration.hostname = definition.hostname;
|
||||||
|
registration.workspaceDir = definition.workspaceDir;
|
||||||
registration.status = DroneStatus.Starting;
|
registration.status = DroneStatus.Starting;
|
||||||
|
|
||||||
this.log.info("registering drone", { hostname: registration.hostname });
|
this.log.info("registering drone", { hostname: registration.hostname });
|
||||||
@ -61,6 +75,13 @@ class DroneService extends DtpService {
|
|||||||
return registration.populate(this.populateDroneRegistration);
|
return registration.populate(this.populateDroneRegistration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The drone calls this service to unregister from the platform when shutting
|
||||||
|
* down. This will set the status to offline and remove any associated
|
||||||
|
* tasks.
|
||||||
|
* @param registration The drone registration being unregistered (offline)
|
||||||
|
* @returns the updated drone registration
|
||||||
|
*/
|
||||||
async unregister(
|
async unregister(
|
||||||
registration: IDroneRegistration,
|
registration: IDroneRegistration,
|
||||||
): Promise<IDroneRegistration> {
|
): Promise<IDroneRegistration> {
|
||||||
@ -68,6 +89,12 @@ class DroneService extends DtpService {
|
|||||||
return this.setStatus(registration, DroneStatus.Offline);
|
return this.setStatus(registration, DroneStatus.Offline);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a drone registration by _id. Throws if the registration doesn't
|
||||||
|
* exist.
|
||||||
|
* @param registrationId The _id of the drone registration to be fetched
|
||||||
|
* @returns the drone registration document
|
||||||
|
*/
|
||||||
async getById(registrationId: Types.ObjectId): Promise<IDroneRegistration> {
|
async getById(registrationId: Types.ObjectId): Promise<IDroneRegistration> {
|
||||||
const registration = await DroneRegistration.findById(
|
const registration = await DroneRegistration.findById(
|
||||||
registrationId,
|
registrationId,
|
||||||
@ -80,6 +107,11 @@ class DroneService extends DtpService {
|
|||||||
return registration;
|
return registration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request a User's list of registered drones.
|
||||||
|
* @param user The user for which a list of registered drones is being requested
|
||||||
|
* @returns The list of drone registrations for the user.
|
||||||
|
*/
|
||||||
async getForUser(user: IUser): Promise<IDroneRegistration[]> {
|
async getForUser(user: IUser): Promise<IDroneRegistration[]> {
|
||||||
const registrations = await DroneRegistration.find({ user: user._id })
|
const registrations = await DroneRegistration.find({ user: user._id })
|
||||||
.sort({ hostname: 1, workspaceDir: 1 })
|
.sort({ hostname: 1, workspaceDir: 1 })
|
||||||
@ -87,6 +119,12 @@ class DroneService extends DtpService {
|
|||||||
return registrations;
|
return registrations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The drone calls this to update it's status in the database.
|
||||||
|
* @param registration The registration for which a status is being updated.
|
||||||
|
* @param status The new status of the drone.
|
||||||
|
* @returns the updated drone registration
|
||||||
|
*/
|
||||||
async setStatus(
|
async setStatus(
|
||||||
registration: IDroneRegistration,
|
registration: IDroneRegistration,
|
||||||
status: DroneStatus,
|
status: DroneStatus,
|
||||||
@ -108,6 +146,31 @@ class DroneService extends DtpService {
|
|||||||
}
|
}
|
||||||
return newRegistration;
|
return newRegistration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async requestChatSessionLock(
|
||||||
|
registration: IDroneRegistration,
|
||||||
|
session: IChatSession,
|
||||||
|
): Promise<IDroneRegistration> {
|
||||||
|
/*
|
||||||
|
* TODO: Send socket message to drone requesting session lock
|
||||||
|
* If drone acknowledges lock, update the registration with the chatSessionId.
|
||||||
|
* If the drone denies the lock, throw a descriptive error.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Update the registration with the chatSessionId
|
||||||
|
const updatedRegistration = await DroneRegistration.findOneAndUpdate(
|
||||||
|
{ _id: registration._id },
|
||||||
|
{ $set: { chatSessionId: session._id } },
|
||||||
|
{ new: true, populate: this.populateDroneRegistration },
|
||||||
|
);
|
||||||
|
if (!updatedRegistration) {
|
||||||
|
const error = new Error("drone registration has been removed");
|
||||||
|
error.statusCode = 404;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedRegistration;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new DroneService();
|
export default new DroneService();
|
||||||
|
|||||||
@ -58,6 +58,11 @@ import { User } from "./models/user.js";
|
|||||||
import { SocketSessionType } from "./lib/socket-session.js";
|
import { SocketSessionType } from "./lib/socket-session.js";
|
||||||
import { CodeSession } from "./lib/code-session.js";
|
import { CodeSession } from "./lib/code-session.js";
|
||||||
import { DroneSession } from "./lib/drone-session.js";
|
import { DroneSession } from "./lib/drone-session.js";
|
||||||
|
import {
|
||||||
|
ClientToServerEvents,
|
||||||
|
ServerToClientEvents,
|
||||||
|
SocketData,
|
||||||
|
} from "@gadget/api";
|
||||||
|
|
||||||
class DtpWebAppServer implements DtpComponent {
|
class DtpWebAppServer implements DtpComponent {
|
||||||
private log: DtpLog;
|
private log: DtpLog;
|
||||||
@ -272,7 +277,12 @@ class DtpWebAppServer implements DtpComponent {
|
|||||||
/*
|
/*
|
||||||
* Create Socket.io server
|
* Create Socket.io server
|
||||||
*/
|
*/
|
||||||
this.io = new SocketIOServer(this.server, {
|
this.io = new SocketIOServer<
|
||||||
|
ClientToServerEvents,
|
||||||
|
ServerToClientEvents,
|
||||||
|
never,
|
||||||
|
SocketData
|
||||||
|
>(this.server, {
|
||||||
cors: {
|
cors: {
|
||||||
origin: "*",
|
origin: "*",
|
||||||
methods: ["GET", "POST"],
|
methods: ["GET", "POST"],
|
||||||
|
|||||||
80
gadget-code/tests/e2e/drones.test.ts
Normal file
80
gadget-code/tests/e2e/drones.test.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
test('Home page should display user drones from API', async ({ page }) => {
|
||||||
|
await page.goto('https://code-dev.g4dge7.com:5174/sign-in');
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
await page.fill('#email', 'rob@digitaltelepresence.com');
|
||||||
|
await page.fill('#password', 'ionfrali');
|
||||||
|
await page.click('button[type="submit"]');
|
||||||
|
await page.waitForTimeout(3000);
|
||||||
|
|
||||||
|
await page.goto('https://code-dev.g4dge7.com:5174/');
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
await page.waitForTimeout(2000);
|
||||||
|
|
||||||
|
const sidebar = page.locator('aside').first();
|
||||||
|
const sidebarText = await sidebar.textContent();
|
||||||
|
|
||||||
|
expect(sidebarText).toContain('Drones');
|
||||||
|
expect(sidebarText).toContain('mysterymachine');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('DroneInspector shows drone details', async ({ page }) => {
|
||||||
|
await page.goto('https://code-dev.g4dge7.com:5174/sign-in');
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
await page.fill('#email', 'rob@digitaltelepresence.com');
|
||||||
|
await page.fill('#password', 'ionfrali');
|
||||||
|
await page.click('button[type="submit"]');
|
||||||
|
await page.waitForTimeout(3000);
|
||||||
|
|
||||||
|
await page.goto('https://code-dev.g4dge7.com:5174/');
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
await page.waitForTimeout(2000);
|
||||||
|
|
||||||
|
const sidebar = page.locator('aside').first();
|
||||||
|
|
||||||
|
const droneButton = sidebar.locator('button').filter({ hasText: 'mysterymachine' }).first();
|
||||||
|
await droneButton.click();
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
|
||||||
|
const inspector = page.locator('text=Drone Inspector');
|
||||||
|
await expect(inspector).toBeVisible();
|
||||||
|
|
||||||
|
const backButton = page.locator('text=← Back to Dashboard');
|
||||||
|
await expect(backButton).toBeVisible();
|
||||||
|
|
||||||
|
const hostname = page.locator('text=Hostname');
|
||||||
|
await expect(hostname).toBeVisible();
|
||||||
|
|
||||||
|
const workspace = page.locator('text=Workspace');
|
||||||
|
await expect(workspace).toBeVisible();
|
||||||
|
|
||||||
|
await backButton.click();
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
|
||||||
|
await expect(inspector).not.toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Drones API returns available and busy drones', async ({ page }) => {
|
||||||
|
await page.goto('https://code-dev.g4dge7.com:5174/sign-in');
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
await page.fill('#email', 'rob@digitaltelepresence.com');
|
||||||
|
await page.fill('#password', 'ionfrali');
|
||||||
|
await page.click('button[type="submit"]');
|
||||||
|
await page.waitForTimeout(2000);
|
||||||
|
|
||||||
|
const token = await page.evaluate(() => localStorage.getItem('dtp_auth_token'));
|
||||||
|
expect(token).toBeDefined();
|
||||||
|
|
||||||
|
const dronesResponse = await page.request.get('https://code-dev.g4dge7.com:5174/api/v1/drone/registration', {
|
||||||
|
headers: { 'Authorization': `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(dronesResponse.status()).toBe(200);
|
||||||
|
const data = await dronesResponse.json() as { success: boolean; data: Array<{ status: string }> };
|
||||||
|
expect(data.success).toBe(true);
|
||||||
|
expect(data.data).toBeDefined();
|
||||||
|
|
||||||
|
const availableOrBusy = data.data.filter(d => d.status === 'available' || d.status === 'busy');
|
||||||
|
expect(availableOrBusy.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
@ -22,6 +22,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gadget/ai": "workspace:*",
|
"@gadget/ai": "workspace:*",
|
||||||
"@gadget/api": "workspace:*",
|
"@gadget/api": "workspace:*",
|
||||||
|
"@inquirer/prompts": "^8.4.2",
|
||||||
"ansicolor": "^2.0.3",
|
"ansicolor": "^2.0.3",
|
||||||
"dayjs": "^1.11.20",
|
"dayjs": "^1.11.20",
|
||||||
"dotenv": "^17.4.2",
|
"dotenv": "^17.4.2",
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import env from "./config/env.ts";
|
|||||||
import assert from "node:assert";
|
import assert from "node:assert";
|
||||||
|
|
||||||
import { io, ManagerOptions, SocketOptions, Socket } from "socket.io-client";
|
import { io, ManagerOptions, SocketOptions, Socket } from "socket.io-client";
|
||||||
|
import { input as inqInput, password as inqPassword } from "@inquirer/prompts";
|
||||||
|
|
||||||
import AgentService, { IAgentWorkOrder } from "./services/agent.ts";
|
import AgentService, { IAgentWorkOrder } from "./services/agent.ts";
|
||||||
import AiService from "./services/ai.ts";
|
import AiService from "./services/ai.ts";
|
||||||
@ -15,10 +16,18 @@ import PlatformService, {
|
|||||||
} from "./services/platform.ts";
|
} from "./services/platform.ts";
|
||||||
|
|
||||||
import { GadgetProcess } from "./lib/process.ts";
|
import { GadgetProcess } from "./lib/process.ts";
|
||||||
|
import { ClientToServerEvents, ServerToClientEvents } from "@gadget/api";
|
||||||
|
|
||||||
|
interface UserCredentials {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientSocket = Socket<ServerToClientEvents, ClientToServerEvents>;
|
||||||
|
|
||||||
class GadgetDrone extends GadgetProcess {
|
class GadgetDrone extends GadgetProcess {
|
||||||
private registration: PlatformRegistration | undefined;
|
private registration: PlatformRegistration | undefined;
|
||||||
private socket: Socket | undefined;
|
private socket: ClientSocket | undefined;
|
||||||
private isShuttingDown: boolean = false;
|
private isShuttingDown: boolean = false;
|
||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
@ -45,9 +54,13 @@ class GadgetDrone extends GadgetProcess {
|
|||||||
* Register this Drone with the Gadget Code web services platform.
|
* Register this Drone with the Gadget Code web services platform.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const email = "rob@digitaltelepresence.com";
|
const credentials = await this.getUserCredentials();
|
||||||
const password = "ionfrali";
|
const workspaceDir = process.cwd();
|
||||||
this.registration = await PlatformService.register(email, password);
|
this.registration = await PlatformService.register(
|
||||||
|
credentials.email,
|
||||||
|
credentials.password,
|
||||||
|
workspaceDir,
|
||||||
|
);
|
||||||
this.log.info("registered with platform", {
|
this.log.info("registered with platform", {
|
||||||
registration: this.registration,
|
registration: this.registration,
|
||||||
});
|
});
|
||||||
@ -159,6 +172,13 @@ class GadgetDrone extends GadgetProcess {
|
|||||||
process.exit(exitCode);
|
process.exit(exitCode);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getUserCredentials(): Promise<UserCredentials> {
|
||||||
|
return {
|
||||||
|
email: await inqInput({ message: "📧 Enter Drone Email: " }),
|
||||||
|
password: await inqPassword({ message: "🔑 Enter Password: " }),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|||||||
@ -55,12 +55,14 @@ class PlatformService extends GadgetService {
|
|||||||
async register(
|
async register(
|
||||||
email: string,
|
email: string,
|
||||||
password: string,
|
password: string,
|
||||||
|
workspaceDir: string,
|
||||||
): Promise<PlatformRegistration> {
|
): Promise<PlatformRegistration> {
|
||||||
const url = this.getApiUrl("/drone/registration");
|
const url = this.getApiUrl("/drone/registration");
|
||||||
const body = JSON.stringify({
|
const body = JSON.stringify({
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
hostname: os.hostname(),
|
hostname: os.hostname(),
|
||||||
|
workspaceDir,
|
||||||
});
|
});
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|||||||
@ -2,13 +2,22 @@
|
|||||||
// Copyright (C) 2026 Robert Colbert <rob.colbert@openplatform.us>
|
// Copyright (C) 2026 Robert Colbert <rob.colbert@openplatform.us>
|
||||||
// All Rights Reserved
|
// All Rights Reserved
|
||||||
|
|
||||||
export * from "./interfaces/ai-provider.ts";
|
/*
|
||||||
|
* Data Model Interfaces
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from "./interfaces/ai-provider.ts";
|
||||||
export * from "./interfaces/user.ts";
|
export * from "./interfaces/user.ts";
|
||||||
export * from "./interfaces/project.ts";
|
export * from "./interfaces/project.ts";
|
||||||
|
|
||||||
export * from "./interfaces/drone-registration.ts";
|
export * from "./interfaces/drone-registration.ts";
|
||||||
export * from "./interfaces/drone-monitor.ts";
|
export * from "./interfaces/drone-monitor.ts";
|
||||||
|
|
||||||
export * from "./interfaces/chat-session.ts";
|
export * from "./interfaces/chat-session.ts";
|
||||||
export * from "./interfaces/chat-turn.ts";
|
export * from "./interfaces/chat-turn.ts";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Socket.IO Interfaces
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from "./messages/ide.ts";
|
||||||
|
export * from "./messages/drone.ts";
|
||||||
|
export * from "./messages/socket.ts";
|
||||||
|
|||||||
@ -20,5 +20,6 @@ export interface IDroneRegistration extends Document {
|
|||||||
hostname: string;
|
hostname: string;
|
||||||
workspaceDir: string;
|
workspaceDir: string;
|
||||||
status: DroneStatus;
|
status: DroneStatus;
|
||||||
|
chatSessionId?: string;
|
||||||
currentJobId?: string;
|
currentJobId?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
7
packages/api/src/messages/drone.ts
Normal file
7
packages/api/src/messages/drone.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export type ThinkingMessage = (content: string) => void;
|
||||||
|
export type ResponseMessage = (content: string) => void;
|
||||||
|
export type ToolCallMessage = (
|
||||||
|
name: string,
|
||||||
|
params: string,
|
||||||
|
response: string,
|
||||||
|
) => void;
|
||||||
9
packages/api/src/messages/ide.ts
Normal file
9
packages/api/src/messages/ide.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { IChatSession } from "../interfaces/chat-session.ts";
|
||||||
|
import { IProject } from "../interfaces/project.ts";
|
||||||
|
|
||||||
|
export type RequestSessionLockMessage = (
|
||||||
|
project: IProject,
|
||||||
|
chatSession: IChatSession,
|
||||||
|
cb: (success: boolean, chatSessionId: string) => void,
|
||||||
|
) => void;
|
||||||
|
export type SubmitPromptMessage = (prompt: string) => void;
|
||||||
42
packages/api/src/messages/socket.ts
Normal file
42
packages/api/src/messages/socket.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// src/messages/gadget-code.ts
|
||||||
|
// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
|
||||||
|
// Licensed under the Apache License, Version 2.0
|
||||||
|
|
||||||
|
import { ResponseMessage, ThinkingMessage, ToolCallMessage } from "./drone.ts";
|
||||||
|
import { RequestSessionLockMessage, SubmitPromptMessage } from "./ide.ts";
|
||||||
|
|
||||||
|
export interface ServerToClientEvents {
|
||||||
|
/*
|
||||||
|
* GadgetCode => IDE
|
||||||
|
*/
|
||||||
|
|
||||||
|
thinking: ThinkingMessage;
|
||||||
|
response: ResponseMessage;
|
||||||
|
toolCall: ToolCallMessage;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Gadget Code => Drone
|
||||||
|
*/
|
||||||
|
|
||||||
|
requestSessionLock: RequestSessionLockMessage;
|
||||||
|
submitPrompt: SubmitPromptMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClientToServerEvents {
|
||||||
|
/*
|
||||||
|
* IDE => Gadget Code
|
||||||
|
*/
|
||||||
|
|
||||||
|
requestSessionLock: RequestSessionLockMessage;
|
||||||
|
submitPrompt: SubmitPromptMessage;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Drone => Gadget Code
|
||||||
|
*/
|
||||||
|
|
||||||
|
thinking: ThinkingMessage;
|
||||||
|
response: ResponseMessage;
|
||||||
|
toolCall: ToolCallMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SocketData {}
|
||||||
298
pnpm-lock.yaml
298
pnpm-lock.yaml
@ -251,6 +251,9 @@ importers:
|
|||||||
'@gadget/api':
|
'@gadget/api':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../packages/api
|
version: link:../packages/api
|
||||||
|
'@inquirer/prompts':
|
||||||
|
specifier: ^8.4.2
|
||||||
|
version: 8.4.2(@types/node@25.6.0)
|
||||||
ansicolor:
|
ansicolor:
|
||||||
specifier: ^2.0.3
|
specifier: ^2.0.3
|
||||||
version: 2.0.3
|
version: 2.0.3
|
||||||
@ -750,6 +753,140 @@ packages:
|
|||||||
resolution: {integrity: sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==}
|
resolution: {integrity: sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
'@inquirer/ansi@2.0.5':
|
||||||
|
resolution: {integrity: sha512-doc2sWgJpbFQ64UflSVd17ibMGDuxO1yKgOgLMwavzESnXjFWJqUeG8saYosqKpHp4kWiM5x1nXvEjbpx90gzw==}
|
||||||
|
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||||
|
|
||||||
|
'@inquirer/checkbox@5.1.4':
|
||||||
|
resolution: {integrity: sha512-w6KF8ZYRvqHhROkOTHXYC3qIV/KYEu5o12oLqQySvch61vrYtRxNSHTONSdJqWiFJPlCUQAHT5OgOIyuTr+MHQ==}
|
||||||
|
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': '>=18'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@inquirer/confirm@6.0.12':
|
||||||
|
resolution: {integrity: sha512-h9FgGun3QwVYNj5TWIZZ+slii73bMoBFjPfVIGtnFuL4t8gBiNDV9PcSfIzkuxvgquJKt9nr1QzszpBzTbH8Og==}
|
||||||
|
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': '>=18'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@inquirer/core@11.1.9':
|
||||||
|
resolution: {integrity: sha512-BDE4fG22uYh1bGSifcj7JSx119TVYNViMhMu85usp4Fswrzh6M0DV3yld64jA98uOAa2GSQ4Bg4bZRm2d2cwSg==}
|
||||||
|
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': '>=18'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@inquirer/editor@5.1.1':
|
||||||
|
resolution: {integrity: sha512-6y11LgmNpmn5D2aB5FgnCfBUBK8ZstwLCalyJmORcJZ/WrhOjm16mu6eSqIx8DnErxDqSLr+Jkp+GP8/Nwd5tA==}
|
||||||
|
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': '>=18'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@inquirer/expand@5.0.13':
|
||||||
|
resolution: {integrity: sha512-dF2zvrFo9LshkcB23/O1il13kBkBltWIXzut1evfbuBLXMiGIuC45c+ZQ0uukjCDsvI8OWqun4FRYMnzFCQa3g==}
|
||||||
|
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': '>=18'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@inquirer/external-editor@3.0.0':
|
||||||
|
resolution: {integrity: sha512-lDSwMgg+M5rq6JKBYaJwSX6T9e/HK2qqZ1oxmOwn4AQoJE5D+7TumsxLGC02PWS//rkIVqbZv3XA3ejsc9FYvg==}
|
||||||
|
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': '>=18'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@inquirer/figures@2.0.5':
|
||||||
|
resolution: {integrity: sha512-NsSs4kzfm12lNetHwAn3GEuH317IzpwrMCbOuMIVytpjnJ90YYHNwdRgYGuKmVxwuIqSgqk3M5qqQt1cDk0tGQ==}
|
||||||
|
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||||
|
|
||||||
|
'@inquirer/input@5.0.12':
|
||||||
|
resolution: {integrity: sha512-uiMFBl4LqFzJClh80Q3f9hbOFJ6kgkDWI4LjAeBuyO6EanVVMF69AgOvpi1qdqjDSjDN6578B6nky9ceEpI+1Q==}
|
||||||
|
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': '>=18'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@inquirer/number@4.0.12':
|
||||||
|
resolution: {integrity: sha512-/vrwhEf7Xsuh+YlHF4IjSy3g1cyrQuPaSiHIxCEbLu8qnfvrcvJyCkoktOOF+xV9gSb77/G0n3h04RbMDW2sIg==}
|
||||||
|
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': '>=18'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@inquirer/password@5.0.12':
|
||||||
|
resolution: {integrity: sha512-CBh7YHju623lxJRcAOo498ZUwIuMy63bqW/vVq0tQAZVv+lkWlHkP9ealYE1utWSisEShY5VMdzIXRmyEODzcQ==}
|
||||||
|
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': '>=18'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@inquirer/prompts@8.4.2':
|
||||||
|
resolution: {integrity: sha512-XJmn/wY4AX56l1BRU+ZjDrFtg9+2uBEi4JvJQj82kwJDQKiPgSn4CEsbfGGygS4Gw6rkL4W18oATjfVfaqub2Q==}
|
||||||
|
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': '>=18'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@inquirer/rawlist@5.2.8':
|
||||||
|
resolution: {integrity: sha512-Su7FQvp5buZmCymN3PPoYv31ZQQX4ve2j02k7piGgKAWgE+AQRB5YoYVveGXcl3TZ9ldgRMSxj56YfDFmmaqLg==}
|
||||||
|
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': '>=18'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@inquirer/search@4.1.8':
|
||||||
|
resolution: {integrity: sha512-fGiHKGD6DyPIYUWxoXnQTeXeyYqSOUrasDMABBmMHUalH/LxkuzY0xVRtimXAt1sUeeyYkVuKQx1bebMuN11Kw==}
|
||||||
|
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': '>=18'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@inquirer/select@5.1.4':
|
||||||
|
resolution: {integrity: sha512-2kWcGKPMLAXAWRp1AH1SLsQmX+j0QjeljyXMUji9WMZC8nRDO0b7qquIGr6143E7KMLt3VAIGNXzwa/6PXQs4Q==}
|
||||||
|
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': '>=18'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@inquirer/type@4.0.5':
|
||||||
|
resolution: {integrity: sha512-aetVUNeKNc/VriqXlw1NRSW0zhMBB0W4bNbWRJgzRl/3d0QNDQFfk0GO5SDdtjMZVg6o8ZKEiadd7SCCzoOn5Q==}
|
||||||
|
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': '>=18'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@ioredis/commands@1.5.1':
|
'@ioredis/commands@1.5.1':
|
||||||
resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==}
|
resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==}
|
||||||
|
|
||||||
@ -1416,6 +1553,9 @@ packages:
|
|||||||
character-parser@2.2.0:
|
character-parser@2.2.0:
|
||||||
resolution: {integrity: sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==}
|
resolution: {integrity: sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==}
|
||||||
|
|
||||||
|
chardet@2.1.1:
|
||||||
|
resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==}
|
||||||
|
|
||||||
chart.js@4.5.1:
|
chart.js@4.5.1:
|
||||||
resolution: {integrity: sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==}
|
resolution: {integrity: sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==}
|
||||||
engines: {pnpm: '>=8'}
|
engines: {pnpm: '>=8'}
|
||||||
@ -1424,6 +1564,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
||||||
engines: {node: '>= 8.10.0'}
|
engines: {node: '>= 8.10.0'}
|
||||||
|
|
||||||
|
cli-width@4.1.0:
|
||||||
|
resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
|
||||||
|
engines: {node: '>= 12'}
|
||||||
|
|
||||||
cliui@8.0.1:
|
cliui@8.0.1:
|
||||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@ -1758,6 +1902,15 @@ packages:
|
|||||||
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
|
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
|
||||||
engines: {node: '>=8.6.0'}
|
engines: {node: '>=8.6.0'}
|
||||||
|
|
||||||
|
fast-string-truncated-width@3.0.3:
|
||||||
|
resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==}
|
||||||
|
|
||||||
|
fast-string-width@3.0.2:
|
||||||
|
resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==}
|
||||||
|
|
||||||
|
fast-wrap-ansi@0.2.0:
|
||||||
|
resolution: {integrity: sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==}
|
||||||
|
|
||||||
fast-xml-builder@1.1.5:
|
fast-xml-builder@1.1.5:
|
||||||
resolution: {integrity: sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA==}
|
resolution: {integrity: sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA==}
|
||||||
|
|
||||||
@ -2348,6 +2501,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==}
|
resolution: {integrity: sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==}
|
||||||
engines: {node: '>= 10.16.0'}
|
engines: {node: '>= 10.16.0'}
|
||||||
|
|
||||||
|
mute-stream@3.0.0:
|
||||||
|
resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==}
|
||||||
|
engines: {node: ^20.17.0 || >=22.9.0}
|
||||||
|
|
||||||
mylas@2.1.14:
|
mylas@2.1.14:
|
||||||
resolution: {integrity: sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==}
|
resolution: {integrity: sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==}
|
||||||
engines: {node: '>=16.0.0'}
|
engines: {node: '>=16.0.0'}
|
||||||
@ -2797,6 +2954,10 @@ packages:
|
|||||||
siginfo@2.0.0:
|
siginfo@2.0.0:
|
||||||
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
||||||
|
|
||||||
|
signal-exit@4.1.0:
|
||||||
|
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
slash@3.0.0:
|
slash@3.0.0:
|
||||||
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
|
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -3485,6 +3646,125 @@ snapshots:
|
|||||||
|
|
||||||
'@fortawesome/fontawesome-free@6.7.2': {}
|
'@fortawesome/fontawesome-free@6.7.2': {}
|
||||||
|
|
||||||
|
'@inquirer/ansi@2.0.5': {}
|
||||||
|
|
||||||
|
'@inquirer/checkbox@5.1.4(@types/node@25.6.0)':
|
||||||
|
dependencies:
|
||||||
|
'@inquirer/ansi': 2.0.5
|
||||||
|
'@inquirer/core': 11.1.9(@types/node@25.6.0)
|
||||||
|
'@inquirer/figures': 2.0.5
|
||||||
|
'@inquirer/type': 4.0.5(@types/node@25.6.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/node': 25.6.0
|
||||||
|
|
||||||
|
'@inquirer/confirm@6.0.12(@types/node@25.6.0)':
|
||||||
|
dependencies:
|
||||||
|
'@inquirer/core': 11.1.9(@types/node@25.6.0)
|
||||||
|
'@inquirer/type': 4.0.5(@types/node@25.6.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/node': 25.6.0
|
||||||
|
|
||||||
|
'@inquirer/core@11.1.9(@types/node@25.6.0)':
|
||||||
|
dependencies:
|
||||||
|
'@inquirer/ansi': 2.0.5
|
||||||
|
'@inquirer/figures': 2.0.5
|
||||||
|
'@inquirer/type': 4.0.5(@types/node@25.6.0)
|
||||||
|
cli-width: 4.1.0
|
||||||
|
fast-wrap-ansi: 0.2.0
|
||||||
|
mute-stream: 3.0.0
|
||||||
|
signal-exit: 4.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/node': 25.6.0
|
||||||
|
|
||||||
|
'@inquirer/editor@5.1.1(@types/node@25.6.0)':
|
||||||
|
dependencies:
|
||||||
|
'@inquirer/core': 11.1.9(@types/node@25.6.0)
|
||||||
|
'@inquirer/external-editor': 3.0.0(@types/node@25.6.0)
|
||||||
|
'@inquirer/type': 4.0.5(@types/node@25.6.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/node': 25.6.0
|
||||||
|
|
||||||
|
'@inquirer/expand@5.0.13(@types/node@25.6.0)':
|
||||||
|
dependencies:
|
||||||
|
'@inquirer/core': 11.1.9(@types/node@25.6.0)
|
||||||
|
'@inquirer/type': 4.0.5(@types/node@25.6.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/node': 25.6.0
|
||||||
|
|
||||||
|
'@inquirer/external-editor@3.0.0(@types/node@25.6.0)':
|
||||||
|
dependencies:
|
||||||
|
chardet: 2.1.1
|
||||||
|
iconv-lite: 0.7.2
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/node': 25.6.0
|
||||||
|
|
||||||
|
'@inquirer/figures@2.0.5': {}
|
||||||
|
|
||||||
|
'@inquirer/input@5.0.12(@types/node@25.6.0)':
|
||||||
|
dependencies:
|
||||||
|
'@inquirer/core': 11.1.9(@types/node@25.6.0)
|
||||||
|
'@inquirer/type': 4.0.5(@types/node@25.6.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/node': 25.6.0
|
||||||
|
|
||||||
|
'@inquirer/number@4.0.12(@types/node@25.6.0)':
|
||||||
|
dependencies:
|
||||||
|
'@inquirer/core': 11.1.9(@types/node@25.6.0)
|
||||||
|
'@inquirer/type': 4.0.5(@types/node@25.6.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/node': 25.6.0
|
||||||
|
|
||||||
|
'@inquirer/password@5.0.12(@types/node@25.6.0)':
|
||||||
|
dependencies:
|
||||||
|
'@inquirer/ansi': 2.0.5
|
||||||
|
'@inquirer/core': 11.1.9(@types/node@25.6.0)
|
||||||
|
'@inquirer/type': 4.0.5(@types/node@25.6.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/node': 25.6.0
|
||||||
|
|
||||||
|
'@inquirer/prompts@8.4.2(@types/node@25.6.0)':
|
||||||
|
dependencies:
|
||||||
|
'@inquirer/checkbox': 5.1.4(@types/node@25.6.0)
|
||||||
|
'@inquirer/confirm': 6.0.12(@types/node@25.6.0)
|
||||||
|
'@inquirer/editor': 5.1.1(@types/node@25.6.0)
|
||||||
|
'@inquirer/expand': 5.0.13(@types/node@25.6.0)
|
||||||
|
'@inquirer/input': 5.0.12(@types/node@25.6.0)
|
||||||
|
'@inquirer/number': 4.0.12(@types/node@25.6.0)
|
||||||
|
'@inquirer/password': 5.0.12(@types/node@25.6.0)
|
||||||
|
'@inquirer/rawlist': 5.2.8(@types/node@25.6.0)
|
||||||
|
'@inquirer/search': 4.1.8(@types/node@25.6.0)
|
||||||
|
'@inquirer/select': 5.1.4(@types/node@25.6.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/node': 25.6.0
|
||||||
|
|
||||||
|
'@inquirer/rawlist@5.2.8(@types/node@25.6.0)':
|
||||||
|
dependencies:
|
||||||
|
'@inquirer/core': 11.1.9(@types/node@25.6.0)
|
||||||
|
'@inquirer/type': 4.0.5(@types/node@25.6.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/node': 25.6.0
|
||||||
|
|
||||||
|
'@inquirer/search@4.1.8(@types/node@25.6.0)':
|
||||||
|
dependencies:
|
||||||
|
'@inquirer/core': 11.1.9(@types/node@25.6.0)
|
||||||
|
'@inquirer/figures': 2.0.5
|
||||||
|
'@inquirer/type': 4.0.5(@types/node@25.6.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/node': 25.6.0
|
||||||
|
|
||||||
|
'@inquirer/select@5.1.4(@types/node@25.6.0)':
|
||||||
|
dependencies:
|
||||||
|
'@inquirer/ansi': 2.0.5
|
||||||
|
'@inquirer/core': 11.1.9(@types/node@25.6.0)
|
||||||
|
'@inquirer/figures': 2.0.5
|
||||||
|
'@inquirer/type': 4.0.5(@types/node@25.6.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/node': 25.6.0
|
||||||
|
|
||||||
|
'@inquirer/type@4.0.5(@types/node@25.6.0)':
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/node': 25.6.0
|
||||||
|
|
||||||
'@ioredis/commands@1.5.1': {}
|
'@ioredis/commands@1.5.1': {}
|
||||||
|
|
||||||
'@jridgewell/gen-mapping@0.3.13':
|
'@jridgewell/gen-mapping@0.3.13':
|
||||||
@ -4132,6 +4412,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-regex: 1.2.1
|
is-regex: 1.2.1
|
||||||
|
|
||||||
|
chardet@2.1.1: {}
|
||||||
|
|
||||||
chart.js@4.5.1:
|
chart.js@4.5.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@kurkle/color': 0.3.4
|
'@kurkle/color': 0.3.4
|
||||||
@ -4148,6 +4430,8 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
|
||||||
|
cli-width@4.1.0: {}
|
||||||
|
|
||||||
cliui@8.0.1:
|
cliui@8.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
string-width: 4.2.3
|
string-width: 4.2.3
|
||||||
@ -4536,6 +4820,16 @@ snapshots:
|
|||||||
merge2: 1.4.1
|
merge2: 1.4.1
|
||||||
micromatch: 4.0.8
|
micromatch: 4.0.8
|
||||||
|
|
||||||
|
fast-string-truncated-width@3.0.3: {}
|
||||||
|
|
||||||
|
fast-string-width@3.0.2:
|
||||||
|
dependencies:
|
||||||
|
fast-string-truncated-width: 3.0.3
|
||||||
|
|
||||||
|
fast-wrap-ansi@0.2.0:
|
||||||
|
dependencies:
|
||||||
|
fast-string-width: 3.0.2
|
||||||
|
|
||||||
fast-xml-builder@1.1.5:
|
fast-xml-builder@1.1.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
path-expression-matcher: 1.5.0
|
path-expression-matcher: 1.5.0
|
||||||
@ -5138,6 +5432,8 @@ snapshots:
|
|||||||
concat-stream: 2.0.0
|
concat-stream: 2.0.0
|
||||||
type-is: 1.6.18
|
type-is: 1.6.18
|
||||||
|
|
||||||
|
mute-stream@3.0.0: {}
|
||||||
|
|
||||||
mylas@2.1.14: {}
|
mylas@2.1.14: {}
|
||||||
|
|
||||||
nanoid@3.3.11: {}
|
nanoid@3.3.11: {}
|
||||||
@ -5628,6 +5924,8 @@ snapshots:
|
|||||||
|
|
||||||
siginfo@2.0.0: {}
|
siginfo@2.0.0: {}
|
||||||
|
|
||||||
|
signal-exit@4.1.0: {}
|
||||||
|
|
||||||
slash@3.0.0: {}
|
slash@3.0.0: {}
|
||||||
|
|
||||||
slug@11.0.1: {}
|
slug@11.0.1: {}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user