@agentuity/cli 1.0.36 → 1.0.37
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/cmd/cloud/sandbox/cp.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/cp.js +31 -0
- package/dist/cmd/cloud/sandbox/cp.js.map +1 -1
- package/dist/cmd/cloud/sandbox/exec.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/exec.js +44 -11
- package/dist/cmd/cloud/sandbox/exec.js.map +1 -1
- package/dist/cmd/cloud/task/get.d.ts.map +1 -1
- package/dist/cmd/cloud/task/get.js +58 -4
- package/dist/cmd/cloud/task/get.js.map +1 -1
- package/dist/cmd/cloud/task/util.d.ts +1 -0
- package/dist/cmd/cloud/task/util.d.ts.map +1 -1
- package/dist/cmd/cloud/task/util.js +13 -0
- package/dist/cmd/cloud/task/util.js.map +1 -1
- package/dist/cmd/coder/hub-url.d.ts +35 -0
- package/dist/cmd/coder/hub-url.d.ts.map +1 -0
- package/dist/cmd/coder/hub-url.js +101 -0
- package/dist/cmd/coder/hub-url.js.map +1 -0
- package/dist/cmd/coder/index.d.ts +2 -0
- package/dist/cmd/coder/index.d.ts.map +1 -0
- package/dist/cmd/coder/index.js +27 -0
- package/dist/cmd/coder/index.js.map +1 -0
- package/dist/cmd/coder/inspect.d.ts +2 -0
- package/dist/cmd/coder/inspect.d.ts.map +1 -0
- package/dist/cmd/coder/inspect.js +145 -0
- package/dist/cmd/coder/inspect.js.map +1 -0
- package/dist/cmd/coder/list.d.ts +2 -0
- package/dist/cmd/coder/list.d.ts.map +1 -0
- package/dist/cmd/coder/list.js +109 -0
- package/dist/cmd/coder/list.js.map +1 -0
- package/dist/cmd/coder/start.d.ts +2 -0
- package/dist/cmd/coder/start.d.ts.map +1 -0
- package/dist/cmd/coder/start.js +384 -0
- package/dist/cmd/coder/start.js.map +1 -0
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/index.js +4 -0
- package/dist/cmd/dev/index.js.map +1 -1
- package/dist/cmd/index.d.ts.map +1 -1
- package/dist/cmd/index.js +1 -0
- package/dist/cmd/index.js.map +1 -1
- package/package.json +6 -6
- package/src/cmd/cloud/sandbox/cp.ts +32 -0
- package/src/cmd/cloud/sandbox/exec.ts +62 -13
- package/src/cmd/cloud/task/get.ts +68 -4
- package/src/cmd/cloud/task/util.ts +18 -0
- package/src/cmd/coder/hub-url.ts +105 -0
- package/src/cmd/coder/index.ts +27 -0
- package/src/cmd/coder/inspect.ts +200 -0
- package/src/cmd/coder/list.ts +143 -0
- package/src/cmd/coder/start.ts +419 -0
- package/src/cmd/dev/index.ts +5 -0
- 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
|
+
}
|
package/src/cmd/dev/index.ts
CHANGED
|
@@ -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),
|