@getpaseo/cli 0.1.96 → 0.1.97-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2 -0
- package/dist/commands/agent/run.d.ts +1 -0
- package/dist/commands/agent/run.js +64 -13
- package/dist/commands/hooks.d.ts +19 -0
- package/dist/commands/hooks.js +89 -0
- package/dist/commands/terminal/create.js +11 -1
- package/dist/commands/worktree/archive.d.ts +4 -0
- package/dist/commands/worktree/archive.js +7 -2
- package/dist/commands/worktree/create.js +4 -1
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,7 @@ import { createScheduleCommand } from "./commands/schedule/index.js";
|
|
|
9
9
|
import { createSpeechCommand } from "./commands/speech/index.js";
|
|
10
10
|
import { createTerminalCommand } from "./commands/terminal/index.js";
|
|
11
11
|
import { createWorktreeCommand } from "./commands/worktree/index.js";
|
|
12
|
+
import { createHooksCommand } from "./commands/hooks.js";
|
|
12
13
|
import { startCommand as daemonStartCommand } from "./commands/daemon/start.js";
|
|
13
14
|
import { runStatusCommand as runDaemonStatusCommand } from "./commands/daemon/status.js";
|
|
14
15
|
import { runRestartCommand as runDaemonRestartCommand } from "./commands/daemon/restart.js";
|
|
@@ -62,6 +63,7 @@ export function createCli() {
|
|
|
62
63
|
// Top-level local daemon shortcuts
|
|
63
64
|
program.addCommand(onboardCommand());
|
|
64
65
|
program.addCommand(daemonStartCommand());
|
|
66
|
+
program.addCommand(createHooksCommand());
|
|
65
67
|
addJsonOption(program
|
|
66
68
|
.command("status")
|
|
67
69
|
.description('Show local daemon status (alias for "paseo daemon status")'))
|
|
@@ -21,6 +21,7 @@ export function addRunOptions(cmd) {
|
|
|
21
21
|
.option("--mode <mode>", "Provider-specific mode (e.g., plan, default, bypass)")
|
|
22
22
|
.option("--worktree <name>", "Create agent in a new git worktree")
|
|
23
23
|
.option("--base <branch>", "Base branch for worktree (default: current branch)")
|
|
24
|
+
.option("--workspace <id>", "Run in an existing workspace (default: a new workspace is created per run; falls back to $PASEO_WORKSPACE_ID)")
|
|
24
25
|
.option("--image <path>", "Attach image(s) to the initial prompt (can be used multiple times)", collectMultiple, [])
|
|
25
26
|
.option("--cwd <path>", "Working directory (default: current)")
|
|
26
27
|
.option("--env <key=value>", "Set environment variable(s) for the agent process (can be used multiple times)", collectMultiple, [])
|
|
@@ -178,6 +179,20 @@ function validateRunOptions(prompt, options, outputSchema) {
|
|
|
178
179
|
details: "Usage: paseo agent run --worktree <name> --base <branch> <prompt>",
|
|
179
180
|
};
|
|
180
181
|
}
|
|
182
|
+
if (options.worktree && options.workspace) {
|
|
183
|
+
throw {
|
|
184
|
+
code: "INVALID_OPTIONS",
|
|
185
|
+
message: "--worktree and --workspace cannot be combined",
|
|
186
|
+
details: "--workspace runs in an existing workspace; --worktree mints a new one",
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
if (options.worktree && !options.workspace && process.env.PASEO_WORKSPACE_ID) {
|
|
190
|
+
throw {
|
|
191
|
+
code: "INVALID_OPTIONS",
|
|
192
|
+
message: "--worktree cannot be combined with an ambient PASEO_WORKSPACE_ID",
|
|
193
|
+
details: "PASEO_WORKSPACE_ID selects an existing workspace; --worktree mints a new one. Unset PASEO_WORKSPACE_ID to use --worktree.",
|
|
194
|
+
};
|
|
195
|
+
}
|
|
181
196
|
if (outputSchema && options.detach) {
|
|
182
197
|
throw {
|
|
183
198
|
code: "INVALID_OPTIONS",
|
|
@@ -274,6 +289,48 @@ async function connectToDaemonOrThrow(hostOption, host) {
|
|
|
274
289
|
};
|
|
275
290
|
}
|
|
276
291
|
}
|
|
292
|
+
// Workspace policy for `paseo run`. Precedence:
|
|
293
|
+
// 1. --workspace <id> -> run in that existing workspace
|
|
294
|
+
// 2. $PASEO_WORKSPACE_ID -> exported by workspace terminals
|
|
295
|
+
// 3. --worktree <name> -> mint a new worktree-backed workspace
|
|
296
|
+
// 4. bare run -> mint a new local-backed workspace for cwd
|
|
297
|
+
// --worktree is rejected alongside both --workspace and an ambient
|
|
298
|
+
// $PASEO_WORKSPACE_ID (validateRunOptions), so worktree resolution here never
|
|
299
|
+
// races an existing-workspace selection.
|
|
300
|
+
async function resolveRunWorkspace(client, options, cwd) {
|
|
301
|
+
// An explicit --worktree mints its own workspace; --workspace and an ambient
|
|
302
|
+
// PASEO_WORKSPACE_ID are both rejected alongside --worktree upstream.
|
|
303
|
+
const explicit = options.worktree
|
|
304
|
+
? undefined
|
|
305
|
+
: options.workspace?.trim() || process.env.PASEO_WORKSPACE_ID?.trim();
|
|
306
|
+
if (explicit) {
|
|
307
|
+
console.error(`Using workspace ${explicit}`);
|
|
308
|
+
return { id: explicit, cwd };
|
|
309
|
+
}
|
|
310
|
+
// TODO: thread the run `prompt` as firstAgentContext so workspace-level
|
|
311
|
+
// title/branch generation picks up the task description (U8/U6 deferred).
|
|
312
|
+
const result = options.worktree
|
|
313
|
+
? await client.createWorkspace({
|
|
314
|
+
source: {
|
|
315
|
+
kind: "worktree",
|
|
316
|
+
cwd,
|
|
317
|
+
worktreeSlug: options.worktree,
|
|
318
|
+
baseBranch: options.base,
|
|
319
|
+
},
|
|
320
|
+
})
|
|
321
|
+
: await client.createWorkspace({ source: { kind: "directory", path: cwd } });
|
|
322
|
+
if (!result.workspace) {
|
|
323
|
+
throw {
|
|
324
|
+
code: "WORKSPACE_CREATE_FAILED",
|
|
325
|
+
message: result.error ?? "Failed to create workspace for this run",
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
const branch = result.workspace.gitRuntime?.currentBranch;
|
|
329
|
+
const label = branch ? `${result.workspace.name} (${branch})` : result.workspace.name;
|
|
330
|
+
console.error(`Created workspace ${result.workspace.id} - ${label}`);
|
|
331
|
+
console.error("Tip: pass --workspace <id> (or set PASEO_WORKSPACE_ID) to run in an existing workspace.");
|
|
332
|
+
return { id: result.workspace.id, cwd: result.workspace.workspaceDirectory ?? cwd };
|
|
333
|
+
}
|
|
277
334
|
export async function runRunCommand(prompt, options, _command) {
|
|
278
335
|
const host = getDaemonHost({ host: options.host });
|
|
279
336
|
const outputSchema = options.outputSchema ? loadOutputSchema(options.outputSchema) : undefined;
|
|
@@ -295,23 +352,20 @@ export async function runRunCommand(prompt, options, _command) {
|
|
|
295
352
|
throw error;
|
|
296
353
|
}
|
|
297
354
|
const images = loadRunImages(options.image);
|
|
298
|
-
const git = options.worktree
|
|
299
|
-
? {
|
|
300
|
-
createWorktree: true,
|
|
301
|
-
worktreeSlug: options.worktree,
|
|
302
|
-
baseBranch: options.base,
|
|
303
|
-
}
|
|
304
|
-
: undefined;
|
|
305
355
|
const labels = parseRunLabels(options.label);
|
|
306
356
|
const env = parseRunEnv(options.env);
|
|
307
357
|
const requestEnv = Object.keys(env).length > 0 ? env : undefined;
|
|
358
|
+
const workspace = await resolveRunWorkspace(client, options, cwd);
|
|
359
|
+
const workspaceId = workspace.id;
|
|
360
|
+
const runCwd = workspace.cwd;
|
|
308
361
|
if (outputSchema) {
|
|
309
362
|
let structuredAgent = null;
|
|
310
363
|
const callStructuredTurn = async (structuredPrompt) => {
|
|
311
364
|
if (!structuredAgent) {
|
|
312
365
|
structuredAgent = await client.createAgent({
|
|
313
366
|
provider: resolvedProviderModel.provider,
|
|
314
|
-
cwd,
|
|
367
|
+
cwd: runCwd,
|
|
368
|
+
workspaceId,
|
|
315
369
|
title: resolvedTitle,
|
|
316
370
|
modeId: options.mode,
|
|
317
371
|
model: resolvedProviderModel.model,
|
|
@@ -320,8 +374,6 @@ export async function runRunCommand(prompt, options, _command) {
|
|
|
320
374
|
outputSchema,
|
|
321
375
|
images,
|
|
322
376
|
env: requestEnv,
|
|
323
|
-
git,
|
|
324
|
-
worktreeName: options.worktree,
|
|
325
377
|
labels: Object.keys(labels).length > 0 ? labels : undefined,
|
|
326
378
|
});
|
|
327
379
|
}
|
|
@@ -366,7 +418,8 @@ export async function runRunCommand(prompt, options, _command) {
|
|
|
366
418
|
// Create the agent
|
|
367
419
|
const agent = await client.createAgent({
|
|
368
420
|
provider: resolvedProviderModel.provider,
|
|
369
|
-
cwd,
|
|
421
|
+
cwd: runCwd,
|
|
422
|
+
workspaceId,
|
|
370
423
|
title: resolvedTitle,
|
|
371
424
|
modeId: options.mode,
|
|
372
425
|
model: resolvedProviderModel.model,
|
|
@@ -374,8 +427,6 @@ export async function runRunCommand(prompt, options, _command) {
|
|
|
374
427
|
initialPrompt: prompt,
|
|
375
428
|
images,
|
|
376
429
|
env: requestEnv,
|
|
377
|
-
git,
|
|
378
|
-
worktreeName: options.worktree,
|
|
379
430
|
labels: Object.keys(labels).length > 0 ? labels : undefined,
|
|
380
431
|
});
|
|
381
432
|
// Default run behavior is foreground: wait for completion unless --detach is set.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
interface HookEnvironment {
|
|
3
|
+
PASEO_TERMINAL_ID?: string;
|
|
4
|
+
PASEO_ACTIVITY_TOKEN?: string;
|
|
5
|
+
PASEO_TERMINAL_ACTIVITY_URL?: string;
|
|
6
|
+
}
|
|
7
|
+
interface HookInput {
|
|
8
|
+
[Symbol.asyncIterator](): AsyncIterator<string | Buffer>;
|
|
9
|
+
isTTY?: boolean;
|
|
10
|
+
}
|
|
11
|
+
interface HooksRuntime {
|
|
12
|
+
env: HookEnvironment;
|
|
13
|
+
input: HookInput;
|
|
14
|
+
fetch: typeof fetch;
|
|
15
|
+
}
|
|
16
|
+
export declare function createHooksCommand(): Command;
|
|
17
|
+
export declare function runHooksCommand(agent: string, event: string, runtime?: HooksRuntime): Promise<void>;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { resolveHookActivity } from "@getpaseo/server/agent-hooks";
|
|
3
|
+
export function createHooksCommand() {
|
|
4
|
+
return new Command("hooks")
|
|
5
|
+
.description("Record agent hook activity")
|
|
6
|
+
.argument("<agent>", "Agent hook source")
|
|
7
|
+
.argument("<event>", "Agent hook event")
|
|
8
|
+
.action((agent, event) => runHooksCommand(agent, event));
|
|
9
|
+
}
|
|
10
|
+
export async function runHooksCommand(agent, event, runtime = {
|
|
11
|
+
env: process.env,
|
|
12
|
+
input: process.stdin,
|
|
13
|
+
fetch,
|
|
14
|
+
}) {
|
|
15
|
+
const target = resolveTarget(runtime.env);
|
|
16
|
+
if (!target)
|
|
17
|
+
return;
|
|
18
|
+
const state = await resolveHookActivity({
|
|
19
|
+
provider: agent,
|
|
20
|
+
event,
|
|
21
|
+
input: {
|
|
22
|
+
isTTY: runtime.input.isTTY,
|
|
23
|
+
read: () => readInput(runtime.input),
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
if (!state)
|
|
27
|
+
return;
|
|
28
|
+
await postActivity(target, state, runtime.fetch);
|
|
29
|
+
}
|
|
30
|
+
function resolveTarget(env) {
|
|
31
|
+
const terminalId = env.PASEO_TERMINAL_ID;
|
|
32
|
+
const token = env.PASEO_ACTIVITY_TOKEN;
|
|
33
|
+
const url = env.PASEO_TERMINAL_ACTIVITY_URL;
|
|
34
|
+
if (!terminalId || !token || !url)
|
|
35
|
+
return null;
|
|
36
|
+
return { terminalId, token, url };
|
|
37
|
+
}
|
|
38
|
+
async function readInput(input) {
|
|
39
|
+
const iterator = input[Symbol.asyncIterator]();
|
|
40
|
+
const chunks = [];
|
|
41
|
+
while (true) {
|
|
42
|
+
const next = await withTimeout(iterator.next(), 100);
|
|
43
|
+
if (!next) {
|
|
44
|
+
await iterator.return?.();
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
if (next.done)
|
|
48
|
+
return chunks.join("");
|
|
49
|
+
chunks.push(String(next.value));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function withTimeout(promise, ms) {
|
|
53
|
+
let timeout;
|
|
54
|
+
try {
|
|
55
|
+
return await Promise.race([
|
|
56
|
+
promise,
|
|
57
|
+
new Promise((resolve) => {
|
|
58
|
+
timeout = setTimeout(() => resolve(null), ms);
|
|
59
|
+
}),
|
|
60
|
+
]);
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
if (timeout)
|
|
64
|
+
clearTimeout(timeout);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function postActivity(target, state, send) {
|
|
68
|
+
const controller = new AbortController();
|
|
69
|
+
const timeout = setTimeout(() => controller.abort(), 500);
|
|
70
|
+
try {
|
|
71
|
+
await send(target.url, {
|
|
72
|
+
method: "POST",
|
|
73
|
+
headers: { "content-type": "application/json" },
|
|
74
|
+
body: JSON.stringify({
|
|
75
|
+
terminalId: target.terminalId,
|
|
76
|
+
token: target.token,
|
|
77
|
+
state,
|
|
78
|
+
}),
|
|
79
|
+
signal: controller.signal,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
clearTimeout(timeout);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -4,7 +4,17 @@ export async function runCreateCommand(options, _command) {
|
|
|
4
4
|
const { client } = await connectTerminalClient(options.host);
|
|
5
5
|
const cwd = options.cwd ?? process.cwd();
|
|
6
6
|
try {
|
|
7
|
-
const
|
|
7
|
+
const opened = await client.openProject(cwd);
|
|
8
|
+
if (!opened.workspace) {
|
|
9
|
+
const error = {
|
|
10
|
+
code: "WORKSPACE_OPEN_FAILED",
|
|
11
|
+
message: opened.error ?? "Failed to open workspace",
|
|
12
|
+
};
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
const payload = await client.createTerminal(cwd, options.name, undefined, {
|
|
16
|
+
workspaceId: opened.workspace.id,
|
|
17
|
+
});
|
|
8
18
|
if (!payload.terminal) {
|
|
9
19
|
const error = {
|
|
10
20
|
code: "TERMINAL_CREATE_FAILED",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
|
+
import { connectToDaemon } from "../../utils/client.js";
|
|
2
3
|
import type { CommandOptions, SingleResult, OutputSchema } from "../../output/index.js";
|
|
3
4
|
/** Result type for worktree archive command */
|
|
4
5
|
export interface WorktreeArchiveResult {
|
|
@@ -13,4 +14,7 @@ export interface WorktreeArchiveOptions extends CommandOptions {
|
|
|
13
14
|
}
|
|
14
15
|
export type WorktreeArchiveCommandResult = SingleResult<WorktreeArchiveResult>;
|
|
15
16
|
export declare function runArchiveCommand(nameArg: string, options: WorktreeArchiveOptions, _command: Command): Promise<WorktreeArchiveCommandResult>;
|
|
17
|
+
export declare function runArchiveCommandWithDeps(nameArg: string, options: WorktreeArchiveOptions, deps: {
|
|
18
|
+
connectToDaemon: typeof connectToDaemon;
|
|
19
|
+
}): Promise<WorktreeArchiveCommandResult>;
|
|
16
20
|
//# sourceMappingURL=archive.d.ts.map
|
|
@@ -13,6 +13,9 @@ export const archiveSchema = {
|
|
|
13
13
|
],
|
|
14
14
|
};
|
|
15
15
|
export async function runArchiveCommand(nameArg, options, _command) {
|
|
16
|
+
return runArchiveCommandWithDeps(nameArg, options, { connectToDaemon });
|
|
17
|
+
}
|
|
18
|
+
export async function runArchiveCommandWithDeps(nameArg, options, deps) {
|
|
16
19
|
const host = getDaemonHost({ host: options.host });
|
|
17
20
|
// Validate arguments
|
|
18
21
|
if (!nameArg || nameArg.trim().length === 0) {
|
|
@@ -25,7 +28,7 @@ export async function runArchiveCommand(nameArg, options, _command) {
|
|
|
25
28
|
}
|
|
26
29
|
let client;
|
|
27
30
|
try {
|
|
28
|
-
client = await connectToDaemon({ host: options.host });
|
|
31
|
+
client = await deps.connectToDaemon({ host: options.host });
|
|
29
32
|
}
|
|
30
33
|
catch (err) {
|
|
31
34
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -59,9 +62,11 @@ export async function runArchiveCommand(nameArg, options, _command) {
|
|
|
59
62
|
};
|
|
60
63
|
throw error;
|
|
61
64
|
}
|
|
62
|
-
// Archive the worktree
|
|
65
|
+
// Archive the worktree. scope:"worktree" archives every active workspace on
|
|
66
|
+
// the directory and then removes the directory (Paseo-owned gated).
|
|
63
67
|
const response = await client.archivePaseoWorktree({
|
|
64
68
|
worktreePath: worktree.worktreePath,
|
|
69
|
+
scope: "worktree",
|
|
65
70
|
});
|
|
66
71
|
await client.close();
|
|
67
72
|
if (response.error) {
|
|
@@ -30,7 +30,10 @@ export async function runCreateCommand(options, _command) {
|
|
|
30
30
|
if (!workspace || response.error) {
|
|
31
31
|
throw cmdError("WORKTREE_CREATE_FAILED", `Failed to create worktree: ${response.error ?? "no workspace returned"}`);
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
if (!workspace.workspaceDirectory) {
|
|
34
|
+
throw cmdError("WORKTREE_CREATE_FAILED", "Failed to create worktree: workspace directory missing from daemon response");
|
|
35
|
+
}
|
|
36
|
+
const worktreePath = workspace.workspaceDirectory;
|
|
34
37
|
return {
|
|
35
38
|
type: "single",
|
|
36
39
|
data: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getpaseo/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.97-beta.1",
|
|
4
4
|
"description": "Paseo CLI - control your AI coding agents from the command line",
|
|
5
5
|
"bin": {
|
|
6
6
|
"paseo": "bin/paseo"
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@clack/prompts": "^1.0.0",
|
|
30
|
-
"@getpaseo/client": "0.1.
|
|
31
|
-
"@getpaseo/protocol": "0.1.
|
|
32
|
-
"@getpaseo/server": "0.1.
|
|
30
|
+
"@getpaseo/client": "0.1.97-beta.1",
|
|
31
|
+
"@getpaseo/protocol": "0.1.97-beta.1",
|
|
32
|
+
"@getpaseo/server": "0.1.97-beta.1",
|
|
33
33
|
"chalk": "^5.3.0",
|
|
34
34
|
"commander": "^12.0.0",
|
|
35
35
|
"mime-types": "^2.1.35",
|