diff --git a/src/cli/handlers/project.ts b/src/cli/handlers/project.ts index 84ff3cd3a24..cc5f32e2cd2 100644 --- a/src/cli/handlers/project.ts +++ b/src/cli/handlers/project.ts @@ -1,6 +1,7 @@ import type { Project, ProjectHostSetup, + ProjectHostSetupCloneArgs, ProjectHostSetupCreateArgs, ProjectHostSetupCreateResult, ProjectHostSetupDeleteResult, @@ -66,6 +67,26 @@ export const PROJECT_HANDLERS: Record = { ) printResult(result, json, formatProjectHostSetupResult) }, + 'project setup-clone': async ({ flags, client, cwd, json }) => { + const rawDestination = getRequiredStringFlag(flags, 'destination') + const args: ProjectHostSetupCloneArgs = { + projectId: getRequiredStringFlag(flags, 'project'), + hostId: getRequiredStringFlag(flags, 'host') as ProjectHostSetupCloneArgs['hostId'], + url: getRequiredStringFlag(flags, 'url'), + destination: resolveRepoPathArgument( + rawDestination, + cwd, + client.isRemote, + 'Project setup clone' + ), + displayName: getOptionalStringFlag(flags, 'display-name') + } + const result = await client.call<{ result: ProjectHostSetupResult }>( + 'projectHostSetup.clone', + args + ) + printResult(result, json, formatProjectHostSetupResult) + }, 'project setup-create': async ({ flags, client, cwd, json }) => { const path = getOptionalStringFlag(flags, 'path') const args: ProjectHostSetupCreateArgs = { diff --git a/src/cli/help.ts b/src/cli/help.ts index 33f69965fda..4c87a4d5cbd 100644 --- a/src/cli/help.ts +++ b/src/cli/help.ts @@ -33,6 +33,7 @@ Projects: project list List durable projects known to Orca project setups List project host setups project setup-existing-folder Make a project available on a host by importing an existing folder + project setup-clone Make a project available on a host by cloning a repository project setup-create Create independent project host setup metadata project setup-update Update project host setup metadata project setup-delete Remove a project host setup @@ -212,6 +213,7 @@ Common Commands: orca project list [--json] orca project setups [--project ] [--host ] [--json] orca project setup-existing-folder --project --host --path [--kind git|folder] [--display-name ] [--json] + orca project setup-clone --project --host --url --destination [--display-name ] [--json] orca project setup-create --project --host [--setup-id ] [--path ] [--kind git|folder] [--display-name ] [--worktree-base-path ] [--git-username ] [--state ready|not-set-up|setting-up|error|unsupported] [--method imported-existing-folder|cloned|provisioned] [--json] orca project setup-update --setup [--display-name ] [--path ] [--worktree-base-path ] [--git-username ] [--kind git|folder] [--state ready|not-set-up|setting-up|error|unsupported] [--method legacy-repo|imported-existing-folder|cloned|provisioned] [--json] orca project setup-delete --setup [--json] diff --git a/src/cli/specs/project.ts b/src/cli/specs/project.ts index 8b4e4d576d8..f9070da9f40 100644 --- a/src/cli/specs/project.ts +++ b/src/cli/specs/project.ts @@ -33,6 +33,21 @@ export const PROJECT_COMMAND_SPECS: CommandSpec[] = [ 'orca project setup-existing-folder --project github:stablyai/orca --host runtime:gpu --path /home/me/orca --kind git --json' ] }, + { + path: ['project', 'setup-clone'], + summary: 'Make a project available on a host by cloning a repository', + usage: + 'orca project setup-clone --project --host --url --destination [--display-name ] [--json]', + allowedFlags: [...GLOBAL_FLAGS, 'project', 'host', 'url', 'destination', 'display-name'], + notes: [ + 'For remote runtimes, --destination must be an absolute parent directory on the remote server.', + 'SSH targets are cloned through the desktop UI because the desktop client owns SSH connections.' + ], + examples: [ + 'orca project setup-clone --project github:stablyai/orca --host local --url https://github.com/stablyai/orca.git --destination ~/src', + 'orca project setup-clone --project github:stablyai/orca --host runtime:gpu --url https://github.com/stablyai/orca.git --destination /srv --json' + ] + }, { path: ['project', 'setup-create'], summary: 'Create independent project host setup metadata', diff --git a/src/main/runtime/orca-runtime.ts b/src/main/runtime/orca-runtime.ts index c7e8e7d7e36..07ed509c5f8 100644 --- a/src/main/runtime/orca-runtime.ts +++ b/src/main/runtime/orca-runtime.ts @@ -6520,6 +6520,18 @@ export class OrcaRuntimeService { return { project, setup, repo } } + async setupProjectClone(args: ProjectHostSetupCloneArgs): Promise { + const repo = await this.cloneRepo(args.url, args.destination) + return await this.setupProjectExistingFolder({ + projectId: args.projectId, + hostId: args.hostId, + path: repo.path, + kind: 'git', + displayName: args.displayName, + setupMethod: 'cloned' + }) + } + updateProjectHostSetup(args: ProjectHostSetupUpdateArgs): ProjectHostSetupUpdateResult { if (!this.store?.updateProjectHostSetup) { throw new Error('runtime_unavailable') diff --git a/src/main/runtime/rpc/methods/project-runtime-rpc-methods.ts b/src/main/runtime/rpc/methods/project-runtime-rpc-methods.ts index 93f64b35f7c..ba874fda2da 100644 --- a/src/main/runtime/rpc/methods/project-runtime-rpc-methods.ts +++ b/src/main/runtime/rpc/methods/project-runtime-rpc-methods.ts @@ -19,6 +19,21 @@ const ProjectHostSetupExistingFolder = z.object({ setupMethod: z.enum(['imported-existing-folder', 'cloned']).optional() }) +const ProjectHostSetupClone = z.object({ + projectId: requiredString('Missing project ID'), + hostId: requiredString('Missing host ID').transform((value, ctx) => { + const hostId = normalizeExecutionHostId(value) + if (!hostId) { + ctx.addIssue({ code: 'custom', message: 'Invalid host ID' }) + return z.NEVER + } + return hostId + }), + url: requiredString('Missing clone URL'), + destination: requiredString('Missing clone destination'), + displayName: OptionalString +}) + const ProjectHostSetupCreate = z.object({ projectId: requiredString('Missing project ID'), hostId: requiredString('Missing host ID').transform((value, ctx) => { @@ -83,6 +98,13 @@ export const PROJECT_RUNTIME_METHODS: RpcMethod[] = [ result: await runtime.setupProjectExistingFolder(params) }) }), + defineMethod({ + name: 'projectHostSetup.clone', + params: ProjectHostSetupClone, + handler: async (params, { runtime }) => ({ + result: await runtime.setupProjectClone(params) + }) + }), defineMethod({ name: 'projectHostSetup.update', params: ProjectHostSetupUpdate,