@agentuity/cli 1.0.36 → 1.0.38

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.
Files changed (79) hide show
  1. package/dist/cmd/cloud/sandbox/cp.d.ts.map +1 -1
  2. package/dist/cmd/cloud/sandbox/cp.js +31 -0
  3. package/dist/cmd/cloud/sandbox/cp.js.map +1 -1
  4. package/dist/cmd/cloud/sandbox/exec.d.ts.map +1 -1
  5. package/dist/cmd/cloud/sandbox/exec.js +44 -11
  6. package/dist/cmd/cloud/sandbox/exec.js.map +1 -1
  7. package/dist/cmd/cloud/task/create.js +1 -1
  8. package/dist/cmd/cloud/task/create.js.map +1 -1
  9. package/dist/cmd/cloud/task/delete.js +3 -3
  10. package/dist/cmd/cloud/task/delete.js.map +1 -1
  11. package/dist/cmd/cloud/task/get.d.ts.map +1 -1
  12. package/dist/cmd/cloud/task/get.js +58 -4
  13. package/dist/cmd/cloud/task/get.js.map +1 -1
  14. package/dist/cmd/cloud/task/index.d.ts.map +1 -1
  15. package/dist/cmd/cloud/task/index.js +14 -2
  16. package/dist/cmd/cloud/task/index.js.map +1 -1
  17. package/dist/cmd/cloud/task/list.d.ts.map +1 -1
  18. package/dist/cmd/cloud/task/list.js +1 -2
  19. package/dist/cmd/cloud/task/list.js.map +1 -1
  20. package/dist/cmd/cloud/task/project.d.ts +2 -0
  21. package/dist/cmd/cloud/task/project.d.ts.map +1 -0
  22. package/dist/cmd/cloud/task/project.js +206 -0
  23. package/dist/cmd/cloud/task/project.js.map +1 -0
  24. package/dist/cmd/cloud/task/update.d.ts.map +1 -1
  25. package/dist/cmd/cloud/task/update.js +4 -1
  26. package/dist/cmd/cloud/task/update.js.map +1 -1
  27. package/dist/cmd/cloud/task/user.d.ts +2 -0
  28. package/dist/cmd/cloud/task/user.d.ts.map +1 -0
  29. package/dist/cmd/cloud/task/user.js +179 -0
  30. package/dist/cmd/cloud/task/user.js.map +1 -0
  31. package/dist/cmd/cloud/task/util.d.ts +1 -0
  32. package/dist/cmd/cloud/task/util.d.ts.map +1 -1
  33. package/dist/cmd/cloud/task/util.js +13 -0
  34. package/dist/cmd/cloud/task/util.js.map +1 -1
  35. package/dist/cmd/coder/hub-url.d.ts +35 -0
  36. package/dist/cmd/coder/hub-url.d.ts.map +1 -0
  37. package/dist/cmd/coder/hub-url.js +101 -0
  38. package/dist/cmd/coder/hub-url.js.map +1 -0
  39. package/dist/cmd/coder/index.d.ts +2 -0
  40. package/dist/cmd/coder/index.d.ts.map +1 -0
  41. package/dist/cmd/coder/index.js +27 -0
  42. package/dist/cmd/coder/index.js.map +1 -0
  43. package/dist/cmd/coder/inspect.d.ts +2 -0
  44. package/dist/cmd/coder/inspect.d.ts.map +1 -0
  45. package/dist/cmd/coder/inspect.js +145 -0
  46. package/dist/cmd/coder/inspect.js.map +1 -0
  47. package/dist/cmd/coder/list.d.ts +2 -0
  48. package/dist/cmd/coder/list.d.ts.map +1 -0
  49. package/dist/cmd/coder/list.js +109 -0
  50. package/dist/cmd/coder/list.js.map +1 -0
  51. package/dist/cmd/coder/start.d.ts +2 -0
  52. package/dist/cmd/coder/start.d.ts.map +1 -0
  53. package/dist/cmd/coder/start.js +384 -0
  54. package/dist/cmd/coder/start.js.map +1 -0
  55. package/dist/cmd/dev/index.d.ts.map +1 -1
  56. package/dist/cmd/dev/index.js +4 -0
  57. package/dist/cmd/dev/index.js.map +1 -1
  58. package/dist/cmd/index.d.ts.map +1 -1
  59. package/dist/cmd/index.js +1 -0
  60. package/dist/cmd/index.js.map +1 -1
  61. package/package.json +6 -6
  62. package/src/cmd/cloud/sandbox/cp.ts +32 -0
  63. package/src/cmd/cloud/sandbox/exec.ts +62 -13
  64. package/src/cmd/cloud/task/create.ts +1 -1
  65. package/src/cmd/cloud/task/delete.ts +3 -3
  66. package/src/cmd/cloud/task/get.ts +68 -4
  67. package/src/cmd/cloud/task/index.ts +14 -2
  68. package/src/cmd/cloud/task/list.ts +1 -2
  69. package/src/cmd/cloud/task/project.ts +227 -0
  70. package/src/cmd/cloud/task/update.ts +4 -1
  71. package/src/cmd/cloud/task/user.ts +202 -0
  72. package/src/cmd/cloud/task/util.ts +18 -0
  73. package/src/cmd/coder/hub-url.ts +105 -0
  74. package/src/cmd/coder/index.ts +27 -0
  75. package/src/cmd/coder/inspect.ts +200 -0
  76. package/src/cmd/coder/list.ts +143 -0
  77. package/src/cmd/coder/start.ts +419 -0
  78. package/src/cmd/dev/index.ts +5 -0
  79. package/src/cmd/index.ts +1 -0
@@ -0,0 +1,419 @@
1
+ import { z } from 'zod';
2
+ import { existsSync } from 'node:fs';
3
+ import { resolve, dirname, join } from 'node:path';
4
+ import { createSubcommand } from '../../types';
5
+ import * as tui from '../../tui';
6
+ import { getCommand } from '../../command-prefix';
7
+ import { ErrorCode } from '../../errors';
8
+ import { resolveHubWsUrl, resolveHubUrl, hubFetchHeaders } from './hub-url';
9
+
10
+ /**
11
+ * Resolve the Coder extension path.
12
+ *
13
+ * Priority:
14
+ * 1. --extension flag (explicit override)
15
+ * 2. AGENTUITY_CODER_EXTENSION env var
16
+ * 3. Installed @agentuity/coder package (node_modules)
17
+ * 4. Local dev path relative to CLI package (SDK monorepo)
18
+ */
19
+ function resolveExtensionPath(flagPath?: string): string | null {
20
+ // 1. Explicit flag
21
+ if (flagPath) {
22
+ const resolved = resolve(flagPath);
23
+ if (existsSync(resolved)) return resolved;
24
+ return null;
25
+ }
26
+
27
+ // 2. Env var
28
+ const envPath = process.env.AGENTUITY_CODER_EXTENSION;
29
+ if (envPath) {
30
+ const resolved = resolve(envPath);
31
+ if (existsSync(resolved)) return resolved;
32
+ }
33
+
34
+ // 3. Installed npm package in cwd
35
+ const cwdNodeModules = resolve(process.cwd(), 'node_modules', '@agentuity', 'coder');
36
+ if (existsSync(cwdNodeModules)) return cwdNodeModules;
37
+
38
+ // 4. SDK monorepo sibling (for development)
39
+ // This file is at packages/cli/src/cmd/coder/start.ts — 5 levels up to SDK root
40
+ try {
41
+ const cliDir = dirname(new URL(import.meta.url).pathname);
42
+ const sdkRoot = resolve(cliDir, '..', '..', '..', '..', '..');
43
+ const coderPath = join(sdkRoot, 'packages', 'coder');
44
+ if (existsSync(join(coderPath, 'src', 'index.ts'))) return coderPath;
45
+ } catch {
46
+ // Not in SDK monorepo
47
+ }
48
+
49
+ return null;
50
+ }
51
+
52
+ /**
53
+ * Find the `pi` binary.
54
+ *
55
+ * Priority:
56
+ * 1. --pi flag (explicit override)
57
+ * 2. AGENTUITY_CODER_PI_PATH env var
58
+ * 3. `pi` on PATH (default)
59
+ */
60
+ function resolvePiBinary(flagPath?: string): string {
61
+ if (flagPath) return flagPath;
62
+ const envPath = process.env.AGENTUITY_CODER_PI_PATH;
63
+ if (envPath) return envPath;
64
+ return 'pi';
65
+ }
66
+
67
+ export const startSubcommand = createSubcommand({
68
+ name: 'start',
69
+ description: 'Start a Pi coding session connected to the Coder Hub',
70
+ tags: ['fast', 'requires-auth'],
71
+ examples: [
72
+ {
73
+ command: getCommand('coder start'),
74
+ description: 'Start Pi with auto-detected Hub and extension',
75
+ },
76
+ {
77
+ command: getCommand('coder start --hub-url ws://127.0.0.1:3500/api/ws'),
78
+ description: 'Start with explicit Hub URL',
79
+ },
80
+ {
81
+ command: getCommand('coder start --extension ~/repos/agentuity/sdk/packages/coder'),
82
+ description: 'Start with explicit extension path',
83
+ },
84
+ {
85
+ command: getCommand('coder start --agent scout'),
86
+ description: 'Start as a specific agent role',
87
+ },
88
+ {
89
+ command: getCommand('coder start --remote codesess_abc123'),
90
+ description: 'Connect to an existing sandbox session remotely',
91
+ },
92
+ {
93
+ command: getCommand('coder start --remote'),
94
+ description: 'Browse and select a sandbox session to connect to',
95
+ },
96
+ {
97
+ command: getCommand('coder start --sandbox "Build an auth system"'),
98
+ description: 'Create a new sandbox session and attach',
99
+ },
100
+ {
101
+ command: getCommand(
102
+ 'coder start --sandbox "Build auth" --repo https://github.com/org/repo'
103
+ ),
104
+ description: 'Create a sandbox with a git repo cloned',
105
+ },
106
+ ],
107
+ schema: {
108
+ options: z.object({
109
+ hubUrl: z.string().optional().describe('Hub WebSocket URL override'),
110
+ extension: z.string().optional().describe('Coder extension path override'),
111
+ pi: z.string().optional().describe('Path to pi binary'),
112
+ agent: z.string().optional().describe('Agent role (e.g. scout, builder)'),
113
+ task: z.string().optional().describe('Initial task to execute'),
114
+ remote: z
115
+ .union([z.boolean(), z.string()])
116
+ .optional()
117
+ .describe('Connect to existing sandbox session (pass session ID or omit for picker)'),
118
+ sandbox: z
119
+ .string()
120
+ .optional()
121
+ .describe('Create a new sandbox session with the given task and attach'),
122
+ repo: z
123
+ .string()
124
+ .optional()
125
+ .describe('Git repo URL to clone in the sandbox (used with --sandbox)'),
126
+ }),
127
+ },
128
+ async handler(ctx) {
129
+ const { opts, options } = ctx;
130
+
131
+ // Resolve Hub URL
132
+ const hubWsUrl = await resolveHubWsUrl(opts?.hubUrl);
133
+ if (!hubWsUrl) {
134
+ tui.fatal(
135
+ 'Could not find a running Coder Hub.\n\nEither:\n - Start the Hub with: bun run dev\n - Set AGENTUITY_CODER_HUB_URL environment variable\n - Pass --hub-url flag',
136
+ ErrorCode.NETWORK_ERROR
137
+ );
138
+ return;
139
+ }
140
+
141
+ // Resolve extension path
142
+ const extensionPath = resolveExtensionPath(opts?.extension);
143
+ if (!extensionPath) {
144
+ tui.fatal(
145
+ 'Could not find the Agentuity Coder extension.\n\nEither:\n - Install it: npm install @agentuity/coder\n - Set AGENTUITY_CODER_EXTENSION environment variable\n - Pass --extension flag',
146
+ ErrorCode.CONFIG_INVALID
147
+ );
148
+ return;
149
+ }
150
+
151
+ // Resolve pi binary
152
+ const piBinary = resolvePiBinary(opts?.pi);
153
+
154
+ // ── Remote mode: resolve session ID ──
155
+ let remoteSessionId: string | undefined;
156
+ if (opts?.remote !== undefined) {
157
+ // --remote was passed (might be bare flag → boolean true, or a session ID string)
158
+ const remoteValue = typeof opts.remote === 'string' ? opts.remote.trim() : '';
159
+ if (remoteValue) {
160
+ remoteSessionId = remoteValue;
161
+ } else {
162
+ // No session ID — fetch connectable sessions and show picker
163
+ const hubHttpUrl = await resolveHubUrl(opts?.hubUrl);
164
+ if (!hubHttpUrl) {
165
+ tui.fatal('Could not find Hub URL for session picker.', ErrorCode.NETWORK_ERROR);
166
+ return;
167
+ }
168
+ try {
169
+ type SessionInfo = {
170
+ id: string;
171
+ label: string;
172
+ status: string;
173
+ task: string | null;
174
+ createdAt: string;
175
+ };
176
+
177
+ const sessions = await tui.spinner({
178
+ message: 'Fetching connectable sessions…',
179
+ callback: async () => {
180
+ const resp = await fetch(`${hubHttpUrl}/api/hub/sessions/connectable`, {
181
+ headers: hubFetchHeaders(),
182
+ signal: AbortSignal.timeout(10_000),
183
+ });
184
+ if (!resp.ok) {
185
+ throw new Error(`${resp.status} ${resp.statusText}`);
186
+ }
187
+ const data = (await resp.json()) as { sessions: SessionInfo[] };
188
+ return data.sessions;
189
+ },
190
+ });
191
+
192
+ if (sessions.length === 0) {
193
+ tui.fatal(
194
+ 'No connectable sandbox sessions found.\n\nCreate one with: ag-dev coder session create --task "your task"',
195
+ ErrorCode.CONFIG_INVALID
196
+ );
197
+ return;
198
+ }
199
+
200
+ const prompt = tui.createPrompt();
201
+ remoteSessionId = await prompt.select<string>({
202
+ message: 'Select a sandbox session to connect to',
203
+ options: sessions.map((s) => {
204
+ const age = timeSince(new Date(s.createdAt));
205
+ const taskPreview = s.task ? s.task.slice(0, 55) : null;
206
+ const label = taskPreview
207
+ ? `${s.label} ${tui.muted(`(${s.status}, ${age})`)} — ${taskPreview}`
208
+ : `${s.label} ${tui.muted(`(${s.status}, ${age})`)}`;
209
+ return {
210
+ value: s.id,
211
+ label,
212
+ hint: s.id,
213
+ };
214
+ }),
215
+ });
216
+ } catch (err) {
217
+ const msg = err instanceof Error ? err.message : String(err);
218
+ if (msg === 'User cancelled') return;
219
+ tui.fatal(`Failed to fetch connectable sessions: ${msg}`, ErrorCode.NETWORK_ERROR);
220
+ return;
221
+ }
222
+ }
223
+ }
224
+
225
+ // ── Remote mode: native Pi TUI backed by Hub WebSocket ──
226
+ // Uses remote-tui.ts which creates AgentSession + InteractiveMode directly,
227
+ // with the coder extension loaded for Hub UI (footer, /hub, commands).
228
+ // Agent.emit() drives native rendering — no [remote_message] blocks.
229
+ if (remoteSessionId) {
230
+ if (!options.json) {
231
+ tui.newline();
232
+ tui.output(` Hub: ${tui.bold(hubWsUrl)}`);
233
+ tui.output(` Extension: ${tui.bold(extensionPath)}`);
234
+ tui.output(` Remote: ${tui.bold(remoteSessionId)}`);
235
+ tui.newline();
236
+ }
237
+
238
+ try {
239
+ const { runRemoteTui } = await import(join(extensionPath, 'src', 'remote-tui.ts'));
240
+ await runRemoteTui({
241
+ hubWsUrl,
242
+ sessionId: remoteSessionId,
243
+ });
244
+ } catch (err) {
245
+ const msg = err instanceof Error ? err.message : String(err);
246
+ tui.fatal(`Remote TUI failed: ${msg}`, ErrorCode.NETWORK_ERROR);
247
+ }
248
+ return;
249
+ }
250
+
251
+ // ── Sandbox mode: create sandbox + attach ──
252
+ if (opts?.sandbox !== undefined) {
253
+ const task = opts.sandbox?.trim();
254
+ if (!task) {
255
+ tui.fatal(
256
+ '--sandbox requires a task description.\n\nExample: --sandbox "Build an authentication system"',
257
+ ErrorCode.CONFIG_INVALID
258
+ );
259
+ return;
260
+ }
261
+
262
+ const hubHttpUrl = await resolveHubUrl(opts?.hubUrl);
263
+ if (!hubHttpUrl) {
264
+ tui.fatal('Could not find Hub URL for sandbox creation.', ErrorCode.NETWORK_ERROR);
265
+ return;
266
+ }
267
+
268
+ // Build request body
269
+ const body: Record<string, unknown> = { task };
270
+ if (opts?.repo) {
271
+ body.repo = { url: opts.repo };
272
+ }
273
+
274
+ // Create sandbox session via Hub API
275
+ tui.newline();
276
+ tui.output(` Creating sandbox session...`);
277
+
278
+ let sessionId: string;
279
+ try {
280
+ const resp = await fetch(`${hubHttpUrl}/api/hub/session`, {
281
+ method: 'POST',
282
+ headers: hubFetchHeaders({ 'Content-Type': 'application/json' }),
283
+ body: JSON.stringify(body),
284
+ signal: AbortSignal.timeout(10_000),
285
+ });
286
+ if (!resp.ok) {
287
+ const errText = await resp.text();
288
+ tui.fatal(
289
+ `Failed to create sandbox session: ${resp.status} ${errText}`,
290
+ ErrorCode.NETWORK_ERROR
291
+ );
292
+ return;
293
+ }
294
+ const sessionInfo = (await resp.json()) as { sessionId: string };
295
+ sessionId = sessionInfo.sessionId;
296
+ } catch (err) {
297
+ const msg = err instanceof Error ? err.message : String(err);
298
+ tui.fatal(`Failed to create sandbox session: ${msg}`, ErrorCode.NETWORK_ERROR);
299
+ return;
300
+ }
301
+
302
+ tui.output(` Session: ${tui.bold(sessionId)}`);
303
+ tui.output(` Task: ${task.slice(0, 80)}`);
304
+ if (opts?.repo) tui.output(` Repo: ${opts.repo}`);
305
+ tui.output(` Waiting for sandbox driver to connect...`);
306
+
307
+ // Poll until driver (lead) connects
308
+ const POLL_TIMEOUT = 120_000; // 2 min (matches Hub's DRIVER_CONNECT_TIMEOUT)
309
+ const POLL_INTERVAL = 2_000;
310
+ const pollStart = Date.now();
311
+ let driverConnected = false;
312
+
313
+ while (Date.now() - pollStart < POLL_TIMEOUT) {
314
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL));
315
+ try {
316
+ const pollResp = await fetch(`${hubHttpUrl}/api/hub/session/${sessionId}`, {
317
+ headers: hubFetchHeaders(),
318
+ signal: AbortSignal.timeout(5_000),
319
+ });
320
+ if (pollResp.ok) {
321
+ const data = (await pollResp.json()) as {
322
+ participants?: Array<{ role: string }>;
323
+ };
324
+ if (data.participants?.some((p) => p.role === 'lead')) {
325
+ driverConnected = true;
326
+ break;
327
+ }
328
+ }
329
+ } catch {
330
+ // Network blip — keep polling
331
+ }
332
+ }
333
+
334
+ if (!driverConnected) {
335
+ tui.fatal(
336
+ `Sandbox driver did not connect within ${POLL_TIMEOUT / 1000}s.\n\nThe sandbox may still be starting. Try attaching later with:\n ${getCommand(`coder start --remote ${sessionId}`)}`,
337
+ ErrorCode.NETWORK_ERROR
338
+ );
339
+ return;
340
+ }
341
+
342
+ tui.output(` Driver connected. Attaching...`);
343
+ tui.newline();
344
+
345
+ try {
346
+ const { runRemoteTui } = await import(join(extensionPath, 'src', 'remote-tui.ts'));
347
+ await runRemoteTui({
348
+ hubWsUrl,
349
+ sessionId,
350
+ });
351
+ } catch (err) {
352
+ const msg = err instanceof Error ? err.message : String(err);
353
+ tui.fatal(`Remote TUI failed: ${msg}`, ErrorCode.NETWORK_ERROR);
354
+ }
355
+ return;
356
+ }
357
+
358
+ // ── Normal mode: spawn pi with extension ──
359
+ const env: Record<string, string> = {
360
+ ...(process.env as Record<string, string>),
361
+ AGENTUITY_CODER_HUB_URL: hubWsUrl,
362
+ };
363
+ // TODO: Remove/Change when we get Agentuity service level auth enabled, this is just temporary
364
+ const cliApiKey = process.env.AGENTUITY_CODER_API_KEY;
365
+ if (cliApiKey) env.AGENTUITY_CODER_API_KEY = cliApiKey;
366
+
367
+ if (opts?.agent) {
368
+ env.AGENTUITY_CODER_AGENT = opts.agent;
369
+ }
370
+
371
+ // Build pi command args
372
+ const piArgs = ['-e', extensionPath];
373
+
374
+ if (!options.json) {
375
+ tui.newline();
376
+ tui.output(` Hub: ${tui.bold(hubWsUrl)}`);
377
+ tui.output(` Extension: ${tui.bold(extensionPath)}`);
378
+ tui.output(` Pi: ${tui.bold(piBinary)}`);
379
+ if (opts?.agent) tui.output(` Agent: ${tui.bold(opts.agent)}`);
380
+ tui.newline();
381
+ }
382
+
383
+ // Spawn pi as a child process, inheriting stdio for interactive TUI
384
+ try {
385
+ const proc = Bun.spawn([piBinary, ...piArgs], {
386
+ env,
387
+ cwd: process.cwd(),
388
+ stdin: 'inherit',
389
+ stdout: 'inherit',
390
+ stderr: 'inherit',
391
+ });
392
+
393
+ const exitCode = await proc.exited;
394
+ process.exit(exitCode);
395
+ } catch (err) {
396
+ const msg = err instanceof Error ? err.message : String(err);
397
+ if (msg.includes('ENOENT') || msg.includes('not found')) {
398
+ tui.fatal(
399
+ `Could not find pi binary at '${piBinary}'.\n\nInstall Pi: https://pi.dev\nOr pass --pi flag with the path to the pi binary.`,
400
+ ErrorCode.CONFIG_INVALID
401
+ );
402
+ } else {
403
+ tui.fatal(`Failed to start Pi: ${msg}`, ErrorCode.NETWORK_ERROR);
404
+ }
405
+ }
406
+ },
407
+ });
408
+
409
+ /** Format a duration since a given date. */
410
+ function timeSince(date: Date): string {
411
+ const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
412
+ if (seconds < 60) return `${seconds}s ago`;
413
+ const minutes = Math.floor(seconds / 60);
414
+ if (minutes < 60) return `${minutes}m ago`;
415
+ const hours = Math.floor(minutes / 60);
416
+ if (hours < 24) return `${hours}h ago`;
417
+ const days = Math.floor(hours / 24);
418
+ return `${days}d ago`;
419
+ }
@@ -232,6 +232,7 @@ export const command = createCommand({
232
232
  .boolean()
233
233
  .optional()
234
234
  .describe('Skip TypeScript type checking on startup and restarts'),
235
+ resume: z.string().optional().describe('Resume a paused Hub session by ID'),
235
236
  }),
236
237
  },
237
238
  optional: { project: true },
@@ -1089,6 +1090,10 @@ export const command = createCommand({
1089
1090
  process.env.AGENTUITY_BASE_URL =
1090
1091
  process.env.AGENTUITY_BASE_URL || `http://localhost:${opts.port}`;
1091
1092
 
1093
+ if (opts.resume) {
1094
+ process.env.AGENTUITY_CODER_RESUME_SESSION = opts.resume;
1095
+ }
1096
+
1092
1097
  if (project) {
1093
1098
  // Set environment variables for LLM provider patches
1094
1099
  // These must be set so the bundled patches can route LLM calls through AI Gateway
package/src/cmd/index.ts CHANGED
@@ -8,6 +8,7 @@ export async function discoverCommands(): Promise<CommandDefinition[]> {
8
8
  import('./build').then((m) => m.command),
9
9
  import('./canary').then((m) => m.command),
10
10
  import('./cloud').then((m) => m.command),
11
+ import('./coder').then((m) => m.command),
11
12
  import('./dev').then((m) => m.command),
12
13
  import('./git').then((m) => m.gitCommand),
13
14
  import('./help').then((m) => m.command),