From c5e5d16a5199c64d3b52e2b8e7c631e6fab5c5a8 Mon Sep 17 00:00:00 2001 From: Rob Colbert Date: Sun, 3 May 2026 03:05:06 -0400 Subject: [PATCH] workspace mode management; drone status message socket events added --- gadget-code/frontend/package.json | 5 +- .../frontend/src/pages/ProjectManager.tsx | 249 ++++++++++++++---- gadget-code/pnpm-lock.yaml | 12 + gadget-code/src/lib/code-session.ts | 10 +- gadget-code/src/lib/drone-session.ts | 47 +++- gadget-drone/package.json | 1 + gadget-drone/src/gadget-drone.ts | 51 ++++ gadget-drone/src/services/workspace.ts | 158 +++++++---- packages/api/src/messages/drone.ts | 2 + packages/api/src/messages/socket.ts | 3 + pnpm-lock.yaml | 42 +++ 11 files changed, 465 insertions(+), 115 deletions(-) diff --git a/gadget-code/frontend/package.json b/gadget-code/frontend/package.json index 47198ca..9633e45 100644 --- a/gadget-code/frontend/package.json +++ b/gadget-code/frontend/package.json @@ -8,5 +8,8 @@ "build": "vite build" }, "author": "Robert Colbert ", - "license": "Apache-2.0" + "license": "Apache-2.0", + "dependencies": { + "slug": "^11.0.1" + } } \ No newline at end of file diff --git a/gadget-code/frontend/src/pages/ProjectManager.tsx b/gadget-code/frontend/src/pages/ProjectManager.tsx index 2b4573c..5306109 100644 --- a/gadget-code/frontend/src/pages/ProjectManager.tsx +++ b/gadget-code/frontend/src/pages/ProjectManager.tsx @@ -1,5 +1,6 @@ import { useState, useEffect } from "react"; import { useNavigate, useParams } from "react-router-dom"; +import slug from "slug"; import type { User, Project } from "../lib/api"; import { projectApi, @@ -114,13 +115,125 @@ function NewProjectForm({ ); } +interface EditProjectFormProps { + project: Project; + onCancel: () => void; + onSuccess: () => void; +} + +function EditProjectForm({ + project, + onCancel, + onSuccess, +}: EditProjectFormProps) { + const [name, setName] = useState(project.name); + const [slugValue, setSlugValue] = useState(project.slug); + const [gitUrl, setGitUrl] = useState(project.gitUrl || ""); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(""); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!name || !slugValue) { + setError("Name and slug are required"); + return; + } + + setSubmitting(true); + setError(""); + + try { + await projectApi.update(project._id, { + name, + slug: slug(slugValue), + gitUrl: gitUrl || undefined, + }); + onSuccess(); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to update project"); + } finally { + setSubmitting(false); + } + }; + + return ( +
+

Edit Project

+
+
+ + setName(e.target.value)} + className="w-full px-3 py-2 bg-bg-tertiary border border-border-default rounded text-text-primary focus:border-brand focus:outline-none" + placeholder="My Project" + /> +

+ Used for display purposes only +

+
+
+ + setSlugValue(e.target.value)} + className="w-full px-3 py-2 bg-bg-tertiary border border-border-default rounded text-text-primary focus:border-brand focus:outline-none" + placeholder="my-project" + /> +

+ Unique identifier for the project directory. Changing this will + affect the workspace directory name on drones. +

+
+
+ + setGitUrl(e.target.value)} + className="w-full px-3 py-2 bg-bg-tertiary border border-border-default rounded text-text-primary focus:border-brand focus:outline-none" + placeholder="https://github.com/user/repo.git" + /> +
+ {error &&

{error}

} +
+ + +
+
+
+ ); +} + interface ProjectInspectorProps { project: Project; onDelete: () => void; + onUpdate: () => void; } -function ProjectInspector({ project, onDelete }: ProjectInspectorProps) { +function ProjectInspector({ project, onDelete, onUpdate }: ProjectInspectorProps) { const [deleting, setDeleting] = useState(false); + const [editing, setEditing] = useState(false); const handleDelete = async () => { if ( @@ -145,63 +258,82 @@ function ProjectInspector({ project, onDelete }: ProjectInspectorProps) {

Project Inspector

-
-
-
-
Name
-
- {project.name} + {editing ? ( + setEditing(false)} + onSuccess={() => { + setEditing(false); + onUpdate(); + }} + /> + ) : ( + <> +
+
+
+
Name
+
+ {project.name} +
+
+
+
Slug
+
{project.slug}
+
+
+ +
+
Git URL
+
+ {project.gitUrl || ( + Not configured + )} +
+
+ +
+
+
Status
+
+ + {project.status} +
+
+
+
Created
+
+ {new Date(project.createdAt).toLocaleDateString()} +
+
+
+ +
+ +
-
-
Slug
-
{project.slug}
-
-
- -
-
Git URL
-
- {project.gitUrl || ( - Not configured - )} -
-
- -
-
-
Status
-
- - {project.status} -
-
-
-
Created
-
- {new Date(project.createdAt).toLocaleDateString()} -
-
-
- -
- -
-
+ + )}
); @@ -685,6 +817,10 @@ export default function ProjectManager({ user }: ProjectManagerProps) { navigate("/projects"); }; + const handleProjectUpdated = () => { + loadProjects(); + }; + const handleSelectDrone = (drone: DroneRegistration) => { setSelectedDrone(drone); }; @@ -795,6 +931,7 @@ export default function ProjectManager({ user }: ProjectManagerProps) { {/* Right Sidebar - Drones & Chat Sessions */} diff --git a/gadget-code/pnpm-lock.yaml b/gadget-code/pnpm-lock.yaml index c5db72f..f8055a0 100644 --- a/gadget-code/pnpm-lock.yaml +++ b/gadget-code/pnpm-lock.yaml @@ -229,6 +229,12 @@ importers: specifier: ^4.1.5 version: 4.1.5(@types/node@24.0.4)(jsdom@29.0.2)(vite@8.0.10(@types/node@24.0.4)(esbuild@0.25.5)(jiti@2.6.1)(less@4.3.0)(tsx@4.21.0)) + frontend: + dependencies: + slug: + specifier: ^11.0.1 + version: 11.0.1 + packages: '@adobe/css-tools@4.4.4': @@ -2714,6 +2720,10 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + slug@11.0.1: + resolution: {integrity: sha512-VrM060OM/E7rdLQSnp6JHrzFfJFmqQBp0+TMhZStnEB8PfNliaZ9UWYjTHGHLUFVJorZ8TjVd/aKvIxHWU2O7g==} + hasBin: true + socket.io-adapter@2.5.5: resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==} @@ -5604,6 +5614,8 @@ snapshots: slash@3.0.0: {} + slug@11.0.1: {} + socket.io-adapter@2.5.5: dependencies: debug: 4.3.7 diff --git a/gadget-code/src/lib/code-session.ts b/gadget-code/src/lib/code-session.ts index 6708c39..39f1fc1 100644 --- a/gadget-code/src/lib/code-session.ts +++ b/gadget-code/src/lib/code-session.ts @@ -37,7 +37,10 @@ export class CodeSession extends SocketSession { super.register(); this.socket.on("requestSessionLock", this.onRequestSessionLock.bind(this)); - this.socket.on("requestWorkspaceMode", this.onRequestWorkspaceMode.bind(this)); + this.socket.on( + "requestWorkspaceMode", + this.onRequestWorkspaceMode.bind(this), + ); this.socket.on("submitPrompt", this.onSubmitPrompt.bind(this)); } @@ -84,7 +87,6 @@ export class CodeSession extends SocketSession { this.project = project; SocketService.registerChatSession(chatSession._id, this); droneSession.setChatSessionId(chatSession._id); - droneSession.setCodeSession(this); } cb(success, chatSessionId); }, @@ -192,4 +194,8 @@ export class CodeSession extends SocketSession { this.workspaceMode = mode; this.socket.emit("workspaceModeChanged", mode); } + + onStatus(content: string): void { + this.socket.emit("status", content); + } } diff --git a/gadget-code/src/lib/drone-session.ts b/gadget-code/src/lib/drone-session.ts index b19bdda..8224057 100644 --- a/gadget-code/src/lib/drone-session.ts +++ b/gadget-code/src/lib/drone-session.ts @@ -23,7 +23,6 @@ export class DroneSession extends SocketSession { chatSessionId: GadgetId | undefined; currentTurnId: GadgetId | undefined; workspaceMode: WorkspaceMode = WorkspaceMode.Idle; - codeSession: import("./code-session.js").CodeSession | undefined; constructor(socket: GadgetSocket, registration: IDroneRegistration) { super(socket, registration.user as IUser); @@ -33,16 +32,41 @@ export class DroneSession extends SocketSession { register() { super.register(); + this.socket.on("status", this.onStatus.bind(this)); + this.socket.on( + "workspaceModeChanged", + this.onWorkspaceModeChanged.bind(this), + ); + this.socket.on("thinking", this.onThinking.bind(this)); this.socket.on("response", this.onResponse.bind(this)); this.socket.on("toolCall", this.onToolCall.bind(this)); + this.socket.on("workOrderComplete", this.onWorkOrderComplete.bind(this)); + this.socket.on( "requestCrashRecovery", this.onRequestCrashRecovery.bind(this), ); + this.socket.on("requestTermination", this.onRequestTermination.bind(this)); - this.socket.on("workspaceModeChanged", this.onWorkspaceModeChanged.bind(this)); + } + + async onStatus(message: string): Promise { + if (!this.chatSessionId) { + this.log.warn( + "drone status event received but no chat session is active", + ); + return; + } + try { + const codeSession = SocketService.getCodeSessionByChatSessionId( + this.chatSessionId, + ); + codeSession.socket.emit("status", message); + } catch (error) { + this.log.error("failed to route status message", { error }); + } } /** @@ -183,23 +207,22 @@ export class DroneSession extends SocketSession { this.currentTurnId = turnId; } - /** - * Sets the associated code session for routing events back to the IDE. - */ - setCodeSession(codeSession: import("./code-session.js").CodeSession): void { - this.codeSession = codeSession; - } - /** * Called when the drone emits a workspace mode change. */ async onWorkspaceModeChanged(mode: WorkspaceMode): Promise { + if (!this.chatSessionId) { + return; + } + this.workspaceMode = mode; this.log.info("workspace mode changed", { mode }); - if (this.codeSession) { - this.codeSession.onWorkspaceModeChanged(mode); - } + const codeSession = SocketService.getCodeSessionByChatSessionId( + this.chatSessionId, + ); + + codeSession.onWorkspaceModeChanged(mode); } /** diff --git a/gadget-drone/package.json b/gadget-drone/package.json index a25e072..702bfd6 100644 --- a/gadget-drone/package.json +++ b/gadget-drone/package.json @@ -32,6 +32,7 @@ "numeral": "^2.0.6", "ollama": "^0.6.3", "openai": "^6.34.0", + "simple-git": "^3.36.0", "socket.io-client": "^4.8.3" }, "devDependencies": { diff --git a/gadget-drone/src/gadget-drone.ts b/gadget-drone/src/gadget-drone.ts index 9093b88..7b34d27 100644 --- a/gadget-drone/src/gadget-drone.ts +++ b/gadget-drone/src/gadget-drone.ts @@ -226,6 +226,8 @@ class GadgetDrone extends GadgetProcess { chatSession: IChatSession, cb: RequestSessionLockCallback, ) { + assert(this.socket, "invalid application state"); + /* * Validate gadget-drone registration to ensure correct sync with IDE */ @@ -286,8 +288,45 @@ class GadgetDrone extends GadgetProcess { session: chatSession, }; this.workspaceMode = WorkspaceMode.Idle; + this.socket.emit("status", "session lock granted"); + /* + * Add the project to the workspace, lock to it, and deploy it. + */ + WorkspaceService.addProject({ + _id: project._id, + slug: project.slug, + gitUrl: project.gitUrl, + }); + WorkspaceService.updateLockedProject({ + _id: project._id, + slug: project.slug, + gitUrl: project.gitUrl, + }); + + const projectDir = WorkspaceService.getProjectDirectory(project.slug); + + let haveProjectDir = await WorkspaceService.exists(projectDir); + if (!haveProjectDir) { + this.socket.emit("status", `deploying project [slug=${project.slug}]`); + await WorkspaceService.deployProject(project); + + /* + * Make sure a project directory got deployed + */ + haveProjectDir = await WorkspaceService.exists(projectDir); + if (!haveProjectDir) { + return cb(false, "failed to deploy project directory"); + } + } + + /* + * Commit the changes to the workspace. + */ + await WorkspaceService.writeWorkspaceData(); cb(true, chatSession._id); + + this.socket.emit("status", "session lock granted"); } async onRequestWorkspaceMode( @@ -434,17 +473,29 @@ class GadgetDrone extends GadgetProcess { cb(true, "work order accepted"); // confirm that drone has the work order + const workspaceDir = + WorkspaceService.workspaceData?.workspaceDir || process.cwd(); + const projectDir = WorkspaceService.getProjectDirectory(project.slug); + process.chdir(projectDir); + this.isProcessingWorkOrder = true; try { + this.socket.emit("status", "processing work order"); await AgentService.process(order, this.socket); + this.socket.emit("status", "work order processing finished"); await WorkspaceService.removeWorkOrderCache(); } catch (error) { const err = error as Error; this.log.error("work order processing failed", { error: err.message, }); + this.socket.emit( + "status", + `failed to process work order: ${(error as Error).message}`, + ); // Leave cache in place for recovery } finally { + process.chdir(workspaceDir); this.isProcessingWorkOrder = false; this.log.info("work order processing complete", { isProcessingWorkOrder: this.isProcessingWorkOrder, diff --git a/gadget-drone/src/services/workspace.ts b/gadget-drone/src/services/workspace.ts index e7b09ca..a37f74d 100644 --- a/gadget-drone/src/services/workspace.ts +++ b/gadget-drone/src/services/workspace.ts @@ -2,11 +2,43 @@ // Copyright (C) 2026 Rob Colbert // Licensed under the Apache License, Version 2.0 +import assert from "node:assert"; import fs from "node:fs"; import path from "node:path"; import os from "node:os"; + +import { simpleGit, SimpleGit, SimpleGitOptions } from "simple-git"; + +import { IChatTurn, IProject } from "@gadget/api"; + import { GadgetService } from "../lib/service.ts"; -import { IChatTurn } from "@gadget/api"; + +export interface WorkspaceChatSession { + _id: string; // MongoDB ChatSession._id + name: string; // Session name for display + lockedAt: string; // ISO 8601 timestamp +} + +export interface WorkspaceLockedProject { + _id: string; // MongoDB Project._id + slug: string; // Project slug (directory name) + gitUrl?: string; // Remote git URL + lockedAt: string; // ISO 8601 timestamp +} + +export interface WorkspaceProject { + _id: string; + slug: string; + gitUrl?: string; + clonedAt: string | null; + lastSyncAt: string | null; +} + +export interface WorkspaceRegistration { + _id: string; // MongoDB DroneRegistration._id + status: string; // Current drone status + registeredAt: string; // ISO 8601 timestamp +} export interface WorkspaceData { workspaceId: string; // UUID v4, immutable once created @@ -15,35 +47,16 @@ export interface WorkspaceData { workspaceDir: string; // Absolute path to workspace directory // Active session state (null when idle) - chatSession: { - _id: string; // MongoDB ChatSession._id - name: string; // Session name for display - lockedAt: string; // ISO 8601 timestamp - } | null; + chatSession: WorkspaceChatSession | null; // Project currently being worked on (null when idle) - lockedProject: { - _id: string; // MongoDB Project._id - slug: string; // Project slug (directory name) - gitUrl: string; // Remote git URL - lockedAt: string; // ISO 8601 timestamp - } | null; + lockedProject: WorkspaceLockedProject | null; // All projects cloned into this workspace - projects: Array<{ - _id: string; - slug: string; - gitUrl: string; - clonedAt: string; - lastSyncAt: string; - }>; + projects: Array; // Drone registration (updated each startup) - registration: { - _id: string; // MongoDB DroneRegistration._id - status: string; // Current drone status - registeredAt: string; // ISO 8601 timestamp - } | null; + registration: WorkspaceRegistration | null; } export interface WorkOrderCache { @@ -57,9 +70,12 @@ export interface WorkOrderCache { class WorkspaceService extends GadgetService { private gadgetDir: string = ""; private cacheDir: string = ""; + private workspaceFile: string = ""; private _workspaceData: WorkspaceData | null = null; + private repos = new Map(); + get name(): string { return "WorkspaceService"; } @@ -111,7 +127,7 @@ class WorkspaceService extends GadgetService { */ private async loadOrCreateWorkspaceData(workspaceDir: string): Promise { try { - if (await this.fileExists(this.workspaceFile)) { + if (await this.exists(this.workspaceFile)) { // Load existing workspace const content = await fs.promises.readFile(this.workspaceFile, "utf-8"); this._workspaceData = JSON.parse(content) as WorkspaceData; @@ -175,33 +191,58 @@ class WorkspaceService extends GadgetService { * Updates the locked project in workspace data. */ updateLockedProject( - project: { _id: string; slug: string; gitUrl: string } | null, + project: { + _id: string; // MongoDB Project._id + slug: string; // Project slug (directory name) + gitUrl?: string; // Remote git URL + } | null, ): void { if (!this._workspaceData) return; - - this._workspaceData.lockedProject = project - ? { - ...project, - lockedAt: new Date().toISOString(), - } - : null; + if (!project) { + this._workspaceData.lockedProject = null; + return; + } + this._workspaceData.lockedProject = Object.assign( + { + lockedAt: new Date().toISOString(), + }, + project, + ); } /** * Adds a project to the workspace projects list. */ - addProject(project: { _id: string; slug: string; gitUrl: string }): void { + addProject(project: { _id: string; slug: string; gitUrl?: string }): void { if (!this._workspaceData) return; const existing = this._workspaceData.projects.find( (p) => p.slug === project.slug, ); - if (!existing) { - this._workspaceData.projects.push({ - ...project, - clonedAt: new Date().toISOString(), - lastSyncAt: new Date().toISOString(), - }); + if (existing) { + existing._id = project._id; + if (project.gitUrl) { + existing.gitUrl = project.gitUrl; + } else { + delete existing.gitUrl; + } + return; + } + + this._workspaceData.projects.push({ + ...project, + clonedAt: null, + lastSyncAt: null, + }); + } + + async deployProject(project: IProject): Promise { + assert(this.workspaceData, "workspace uninitialized"); + + if (project.gitUrl) { + const git: SimpleGit = this.getGitRepo(project.slug); + git.clone(project.gitUrl); + return; } } @@ -258,7 +299,7 @@ class WorkspaceService extends GadgetService { async removeWorkOrderCache(): Promise { const cacheFile = path.join(this.cacheDir, "work-order.json"); try { - if (await this.fileExists(cacheFile)) { + if (await this.exists(cacheFile)) { await fs.promises.unlink(cacheFile); this.log.info("work order cache removed"); } @@ -276,7 +317,7 @@ class WorkspaceService extends GadgetService { async readWorkOrderCache(): Promise { const cacheFile = path.join(this.cacheDir, "work-order.json"); try { - if (await this.fileExists(cacheFile)) { + if (await this.exists(cacheFile)) { const content = await fs.promises.readFile(cacheFile, "utf-8"); return JSON.parse(content) as WorkOrderCache; } @@ -290,9 +331,9 @@ class WorkspaceService extends GadgetService { } /** - * Checks if a file exists. + * Checks if a file or directory exists. */ - private async fileExists(filePath: string): Promise { + async exists(filePath: string): Promise { try { await fs.promises.access(filePath); return true; @@ -300,6 +341,35 @@ class WorkspaceService extends GadgetService { return false; } } + + getProjectDirectory(slug: string): string { + assert(this.workspaceData, "workspace uninitialized"); + return path.join(this.workspaceData?.workspaceDir, slug); + } + + getGitRepo(slug: string): SimpleGit { + assert(this.workspaceData, "workspace uninitialized"); + + /* + * If we have a SimpleGit instance for the project slug, return it. + */ + let git: SimpleGit | undefined = this.repos.get(slug); + if (git) { + return git; + } + + /* + * Create a new SimpleGit instance rooted in the project's workspace + * directory, add it to the repo cache, and return it. + */ + const options: Partial = { + baseDir: this.getProjectDirectory(slug), + }; + git = simpleGit(options); + this.repos.set(slug, git); + + return git; + } } export default new WorkspaceService(); diff --git a/packages/api/src/messages/drone.ts b/packages/api/src/messages/drone.ts index 834ebc6..bbba94f 100644 --- a/packages/api/src/messages/drone.ts +++ b/packages/api/src/messages/drone.ts @@ -18,6 +18,8 @@ export type ProcessWorkOrderMessage = ( cb: ProcessWorkOrderCallback, ) => void; +export type StatusMessage = (content: string) => void; + export type ThinkingMessage = (content: string) => void; export type ResponseMessage = (content: string) => void; diff --git a/packages/api/src/messages/socket.ts b/packages/api/src/messages/socket.ts index 3e1a863..4e9ca44 100644 --- a/packages/api/src/messages/socket.ts +++ b/packages/api/src/messages/socket.ts @@ -3,6 +3,7 @@ // Licensed under the Apache License, Version 2.0 import { + StatusMessage, ProcessWorkOrderMessage, ResponseMessage, ThinkingMessage, @@ -50,6 +51,7 @@ export interface ClientToServerEvents { * gadget-drone => gadget-code:web */ + status: StatusMessage; thinking: ThinkingMessage; response: ResponseMessage; toolCall: ToolCallMessage; @@ -74,6 +76,7 @@ export interface ServerToClientEvents { * gadget-code:web => gadget-code:ide */ + status: StatusMessage; thinking: ThinkingMessage; response: ResponseMessage; toolCall: ToolCallMessage; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c87b15..43d6047 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -287,6 +287,9 @@ importers: openai: specifier: ^6.34.0 version: 6.34.0(ws@8.18.3) + simple-git: + specifier: ^3.36.0 + version: 3.36.0 socket.io-client: specifier: ^4.8.3 version: 4.8.3 @@ -943,6 +946,12 @@ packages: '@kurkle/color@0.3.4': resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==} + '@kwsites/file-exists@1.1.1': + resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} + + '@kwsites/promise-deferred@1.1.1': + resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} + '@mediapipe/tasks-vision@0.10.17': resolution: {integrity: sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==} @@ -1150,6 +1159,12 @@ packages: '@rolldown/pluginutils@1.0.0-rc.7': resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} + '@simple-git/args-pathspec@1.0.3': + resolution: {integrity: sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA==} + + '@simple-git/argv-parser@1.1.1': + resolution: {integrity: sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==} + '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -3171,6 +3186,9 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-git@3.36.0: + resolution: {integrity: sha512-cGQjLjK8bxJw4QuYT7gxHw3/IouVESbhahSsHrX97MzCL1gu2u7oy38W6L2ZIGECEfIBG4BabsWDPjBxJENv9Q==} + slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -4103,6 +4121,14 @@ snapshots: '@kurkle/color@0.3.4': {} + '@kwsites/file-exists@1.1.1': + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@kwsites/promise-deferred@1.1.1': {} + '@mediapipe/tasks-vision@0.10.17': {} '@mongodb-js/saslprep@1.4.9': @@ -4265,6 +4291,12 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.7': {} + '@simple-git/args-pathspec@1.0.3': {} + + '@simple-git/argv-parser@1.1.1': + dependencies: + '@simple-git/args-pathspec': 1.0.3 + '@socket.io/component-emitter@3.1.2': {} '@standard-schema/spec@1.1.0': {} @@ -6421,6 +6453,16 @@ snapshots: signal-exit@4.1.0: {} + simple-git@3.36.0: + dependencies: + '@kwsites/file-exists': 1.1.1 + '@kwsites/promise-deferred': 1.1.1 + '@simple-git/args-pathspec': 1.0.3 + '@simple-git/argv-parser': 1.1.1 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + slash@3.0.0: {} slug@11.0.1: {}