@agentuity/cli 2.0.6 → 2.0.8

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 (201) hide show
  1. package/README.md +11 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +4 -2
  4. package/dist/cli.js.map +1 -1
  5. package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
  6. package/dist/cmd/build/vite/route-discovery.js +6 -0
  7. package/dist/cmd/build/vite/route-discovery.js.map +1 -1
  8. package/dist/cmd/cloud/sandbox/fs/rm.d.ts.map +1 -1
  9. package/dist/cmd/cloud/sandbox/fs/rm.js +9 -3
  10. package/dist/cmd/cloud/sandbox/fs/rm.js.map +1 -1
  11. package/dist/cmd/cloud/sandbox/fs/rmdir.d.ts.map +1 -1
  12. package/dist/cmd/cloud/sandbox/fs/rmdir.js +9 -3
  13. package/dist/cmd/cloud/sandbox/fs/rmdir.js.map +1 -1
  14. package/dist/cmd/cloud/task/close.d.ts +3 -0
  15. package/dist/cmd/cloud/task/close.d.ts.map +1 -0
  16. package/dist/cmd/cloud/task/close.js +286 -0
  17. package/dist/cmd/cloud/task/close.js.map +1 -0
  18. package/dist/cmd/cloud/task/delete.d.ts +1 -5
  19. package/dist/cmd/cloud/task/delete.d.ts.map +1 -1
  20. package/dist/cmd/cloud/task/delete.js +15 -38
  21. package/dist/cmd/cloud/task/delete.js.map +1 -1
  22. package/dist/cmd/cloud/task/index.d.ts.map +1 -1
  23. package/dist/cmd/cloud/task/index.js +10 -0
  24. package/dist/cmd/cloud/task/index.js.map +1 -1
  25. package/dist/cmd/cloud/task/list.d.ts.map +1 -1
  26. package/dist/cmd/cloud/task/list.js +97 -3
  27. package/dist/cmd/cloud/task/list.js.map +1 -1
  28. package/dist/cmd/cloud/task/util.d.ts +10 -0
  29. package/dist/cmd/cloud/task/util.d.ts.map +1 -1
  30. package/dist/cmd/cloud/task/util.js +47 -3
  31. package/dist/cmd/cloud/task/util.js.map +1 -1
  32. package/dist/cmd/coder/archive.d.ts +2 -0
  33. package/dist/cmd/coder/archive.d.ts.map +1 -0
  34. package/dist/cmd/coder/archive.js +57 -0
  35. package/dist/cmd/coder/archive.js.map +1 -0
  36. package/dist/cmd/coder/create.d.ts +2 -0
  37. package/dist/cmd/coder/create.d.ts.map +1 -0
  38. package/dist/cmd/coder/create.js +245 -0
  39. package/dist/cmd/coder/create.js.map +1 -0
  40. package/dist/cmd/coder/delete.d.ts +2 -0
  41. package/dist/cmd/coder/delete.d.ts.map +1 -0
  42. package/dist/cmd/coder/delete.js +64 -0
  43. package/dist/cmd/coder/delete.js.map +1 -0
  44. package/dist/cmd/coder/events.d.ts +2 -0
  45. package/dist/cmd/coder/events.d.ts.map +1 -0
  46. package/dist/cmd/coder/events.js +99 -0
  47. package/dist/cmd/coder/events.js.map +1 -0
  48. package/dist/cmd/coder/extension-path.d.ts +8 -0
  49. package/dist/cmd/coder/extension-path.d.ts.map +1 -0
  50. package/dist/cmd/coder/extension-path.js +59 -0
  51. package/dist/cmd/coder/extension-path.js.map +1 -0
  52. package/dist/cmd/coder/get.d.ts +2 -0
  53. package/dist/cmd/coder/get.d.ts.map +1 -0
  54. package/dist/cmd/coder/{inspect.js → get.js} +37 -33
  55. package/dist/cmd/coder/get.js.map +1 -0
  56. package/dist/cmd/coder/index.d.ts.map +1 -1
  57. package/dist/cmd/coder/index.js +54 -4
  58. package/dist/cmd/coder/index.js.map +1 -1
  59. package/dist/cmd/coder/list.d.ts.map +1 -1
  60. package/dist/cmd/coder/list.js +25 -34
  61. package/dist/cmd/coder/list.js.map +1 -1
  62. package/dist/cmd/coder/loop.d.ts +2 -0
  63. package/dist/cmd/coder/loop.d.ts.map +1 -0
  64. package/dist/cmd/coder/loop.js +78 -0
  65. package/dist/cmd/coder/loop.js.map +1 -0
  66. package/dist/cmd/coder/participants.d.ts +2 -0
  67. package/dist/cmd/coder/participants.d.ts.map +1 -0
  68. package/dist/cmd/coder/participants.js +93 -0
  69. package/dist/cmd/coder/participants.js.map +1 -0
  70. package/dist/cmd/coder/replay.d.ts +2 -0
  71. package/dist/cmd/coder/replay.d.ts.map +1 -0
  72. package/dist/cmd/coder/replay.js +53 -0
  73. package/dist/cmd/coder/replay.js.map +1 -0
  74. package/dist/cmd/coder/resolve-repo.d.ts +27 -0
  75. package/dist/cmd/coder/resolve-repo.d.ts.map +1 -0
  76. package/dist/cmd/coder/resolve-repo.js +97 -0
  77. package/dist/cmd/coder/resolve-repo.js.map +1 -0
  78. package/dist/cmd/coder/skill/buckets.d.ts +2 -0
  79. package/dist/cmd/coder/skill/buckets.d.ts.map +1 -0
  80. package/dist/cmd/coder/skill/buckets.js +174 -0
  81. package/dist/cmd/coder/skill/buckets.js.map +1 -0
  82. package/dist/cmd/coder/skill/delete.d.ts +2 -0
  83. package/dist/cmd/coder/skill/delete.d.ts.map +1 -0
  84. package/dist/cmd/coder/skill/delete.js +64 -0
  85. package/dist/cmd/coder/skill/delete.js.map +1 -0
  86. package/dist/cmd/coder/skill/index.d.ts +2 -0
  87. package/dist/cmd/coder/skill/index.d.ts.map +1 -0
  88. package/dist/cmd/coder/skill/index.js +33 -0
  89. package/dist/cmd/coder/skill/index.js.map +1 -0
  90. package/dist/cmd/coder/skill/list.d.ts +2 -0
  91. package/dist/cmd/coder/skill/list.d.ts.map +1 -0
  92. package/dist/cmd/coder/skill/list.js +93 -0
  93. package/dist/cmd/coder/skill/list.js.map +1 -0
  94. package/dist/cmd/coder/skill/save.d.ts +2 -0
  95. package/dist/cmd/coder/skill/save.d.ts.map +1 -0
  96. package/dist/cmd/coder/skill/save.js +77 -0
  97. package/dist/cmd/coder/skill/save.js.map +1 -0
  98. package/dist/cmd/coder/start.d.ts.map +1 -1
  99. package/dist/cmd/coder/start.js +88 -117
  100. package/dist/cmd/coder/start.js.map +1 -1
  101. package/dist/cmd/coder/tui-init.d.ts +4 -1
  102. package/dist/cmd/coder/tui-init.d.ts.map +1 -1
  103. package/dist/cmd/coder/tui-init.js +9 -3
  104. package/dist/cmd/coder/tui-init.js.map +1 -1
  105. package/dist/cmd/coder/update.d.ts +2 -0
  106. package/dist/cmd/coder/update.d.ts.map +1 -0
  107. package/dist/cmd/coder/update.js +126 -0
  108. package/dist/cmd/coder/update.js.map +1 -0
  109. package/dist/cmd/coder/users.d.ts +2 -0
  110. package/dist/cmd/coder/users.d.ts.map +1 -0
  111. package/dist/cmd/coder/users.js +97 -0
  112. package/dist/cmd/coder/users.js.map +1 -0
  113. package/dist/cmd/coder/workspace/create.d.ts +2 -0
  114. package/dist/cmd/coder/workspace/create.d.ts.map +1 -0
  115. package/dist/cmd/coder/workspace/create.js +97 -0
  116. package/dist/cmd/coder/workspace/create.js.map +1 -0
  117. package/dist/cmd/coder/workspace/delete.d.ts +2 -0
  118. package/dist/cmd/coder/workspace/delete.d.ts.map +1 -0
  119. package/dist/cmd/coder/workspace/delete.js +64 -0
  120. package/dist/cmd/coder/workspace/delete.js.map +1 -0
  121. package/dist/cmd/coder/workspace/get.d.ts +2 -0
  122. package/dist/cmd/coder/workspace/get.d.ts.map +1 -0
  123. package/dist/cmd/coder/workspace/get.js +109 -0
  124. package/dist/cmd/coder/workspace/get.js.map +1 -0
  125. package/dist/cmd/coder/workspace/index.d.ts +2 -0
  126. package/dist/cmd/coder/workspace/index.d.ts.map +1 -0
  127. package/dist/cmd/coder/workspace/index.js +38 -0
  128. package/dist/cmd/coder/workspace/index.js.map +1 -0
  129. package/dist/cmd/coder/workspace/list.d.ts +2 -0
  130. package/dist/cmd/coder/workspace/list.d.ts.map +1 -0
  131. package/dist/cmd/coder/workspace/list.js +93 -0
  132. package/dist/cmd/coder/workspace/list.js.map +1 -0
  133. package/dist/cmd/dev/sync.js +5 -5
  134. package/dist/cmd/dev/sync.js.map +1 -1
  135. package/dist/coder-hub-url.d.ts +3 -0
  136. package/dist/coder-hub-url.d.ts.map +1 -0
  137. package/dist/coder-hub-url.js +32 -0
  138. package/dist/coder-hub-url.js.map +1 -0
  139. package/dist/config.d.ts +1 -0
  140. package/dist/config.d.ts.map +1 -1
  141. package/dist/config.js +14 -3
  142. package/dist/config.js.map +1 -1
  143. package/dist/internal-logger.d.ts +4 -0
  144. package/dist/internal-logger.d.ts.map +1 -1
  145. package/dist/internal-logger.js +64 -2
  146. package/dist/internal-logger.js.map +1 -1
  147. package/dist/keychain.d.ts +3 -0
  148. package/dist/keychain.d.ts.map +1 -1
  149. package/dist/keychain.js +47 -28
  150. package/dist/keychain.js.map +1 -1
  151. package/dist/types.d.ts +1 -1
  152. package/package.json +7 -6
  153. package/src/cli.ts +4 -2
  154. package/src/cmd/ai/prompt/agent.md +6 -6
  155. package/src/cmd/build/vite/route-discovery.ts +8 -0
  156. package/src/cmd/cloud/sandbox/fs/rm.ts +8 -3
  157. package/src/cmd/cloud/sandbox/fs/rmdir.ts +8 -3
  158. package/src/cmd/cloud/task/close.ts +319 -0
  159. package/src/cmd/cloud/task/delete.ts +15 -43
  160. package/src/cmd/cloud/task/index.ts +10 -0
  161. package/src/cmd/cloud/task/list.ts +111 -4
  162. package/src/cmd/cloud/task/util.ts +59 -5
  163. package/src/cmd/coder/archive.ts +59 -0
  164. package/src/cmd/coder/create.ts +268 -0
  165. package/src/cmd/coder/delete.ts +67 -0
  166. package/src/cmd/coder/events.ts +106 -0
  167. package/src/cmd/coder/extension-path.ts +71 -0
  168. package/src/cmd/coder/{inspect.ts → get.ts} +44 -45
  169. package/src/cmd/coder/index.ts +54 -4
  170. package/src/cmd/coder/list.ts +28 -65
  171. package/src/cmd/coder/loop.ts +85 -0
  172. package/src/cmd/coder/participants.ts +100 -0
  173. package/src/cmd/coder/replay.ts +58 -0
  174. package/src/cmd/coder/resolve-repo.ts +119 -0
  175. package/src/cmd/coder/skill/buckets.ts +191 -0
  176. package/src/cmd/coder/skill/delete.ts +67 -0
  177. package/src/cmd/coder/skill/index.ts +35 -0
  178. package/src/cmd/coder/skill/list.ts +97 -0
  179. package/src/cmd/coder/skill/save.ts +84 -0
  180. package/src/cmd/coder/start.ts +104 -141
  181. package/src/cmd/coder/tui-init.ts +13 -4
  182. package/src/cmd/coder/update.ts +128 -0
  183. package/src/cmd/coder/users.ts +101 -0
  184. package/src/cmd/coder/workspace/create.ts +104 -0
  185. package/src/cmd/coder/workspace/delete.ts +70 -0
  186. package/src/cmd/coder/workspace/get.ts +112 -0
  187. package/src/cmd/coder/workspace/index.ts +38 -0
  188. package/src/cmd/coder/workspace/list.ts +101 -0
  189. package/src/cmd/dev/sync.ts +5 -5
  190. package/src/coder-hub-url.ts +32 -0
  191. package/src/config.ts +17 -3
  192. package/src/internal-logger.ts +83 -2
  193. package/src/keychain.ts +68 -39
  194. package/dist/cmd/coder/hub-url.d.ts +0 -36
  195. package/dist/cmd/coder/hub-url.d.ts.map +0 -1
  196. package/dist/cmd/coder/hub-url.js +0 -106
  197. package/dist/cmd/coder/hub-url.js.map +0 -1
  198. package/dist/cmd/coder/inspect.d.ts +0 -2
  199. package/dist/cmd/coder/inspect.d.ts.map +0 -1
  200. package/dist/cmd/coder/inspect.js.map +0 -1
  201. package/src/cmd/coder/hub-url.ts +0 -111
@@ -1,85 +1,75 @@
1
+ import { dirname, resolve } from 'node:path';
1
2
  import { z } from 'zod';
2
- import { existsSync } from 'node:fs';
3
- import { resolve, dirname, join } from 'node:path';
3
+ import { CoderClient, type CoderSessionListItem } from '@agentuity/core/coder';
4
+ import { ValidationOutputError } from '@agentuity/core';
5
+ import { toCoderHubWsUrl } from '../../coder-hub-url';
4
6
  import { createSubcommand } from '../../types';
5
7
  import * as tui from '../../tui';
6
8
  import { getCommand } from '../../command-prefix';
7
9
  import { ErrorCode } from '../../errors';
8
- import { toHubWsUrl, resolveHubUrl, hubFetchHeaders } from './hub-url';
10
+ import { resolveExtensionPath, resolveExtensionRuntimeModulePath } from './extension-path';
9
11
  import { probeHubInitAccess } from './tui-init';
10
12
 
11
- /**
12
- * Resolve the Coder extension path.
13
- *
14
- * Priority:
15
- * 1. --extension flag (explicit override)
16
- * 2. AGENTUITY_CODER_EXTENSION env var
17
- * 3. Installed @agentuity/coder package (node_modules)
18
- * 4. Local dev path relative to CLI package (SDK monorepo)
19
- */
20
- function resolveExtensionPath(flagPath?: string): string | null {
21
- // 1. Explicit flag
22
- if (flagPath) {
23
- const resolved = resolve(flagPath);
24
- if (existsSync(resolved)) return resolved;
25
- return null;
26
- }
27
-
28
- // 2. Env var
29
- const envPath = process.env.AGENTUITY_CODER_EXTENSION;
30
- if (envPath) {
31
- const resolved = resolve(envPath);
32
- if (existsSync(resolved)) return resolved;
33
- }
34
-
35
- // 3. Installed npm package in cwd
36
- const cwdNodeModules = resolve(process.cwd(), 'node_modules', '@agentuity', 'coder');
37
- if (existsSync(cwdNodeModules)) return cwdNodeModules;
38
-
39
- // 4. SDK monorepo sibling (for development)
40
- // This file is at packages/cli/src/cmd/coder/start.ts — 5 levels up to SDK root
41
- try {
42
- const cliDir = dirname(new URL(import.meta.url).pathname);
43
- const sdkRoot = resolve(cliDir, '..', '..', '..', '..', '..');
44
- const coderPath = join(sdkRoot, 'packages', 'coder');
45
- if (existsSync(join(coderPath, 'src', 'index.ts'))) return coderPath;
46
- } catch {
47
- // Not in SDK monorepo
48
- }
49
-
50
- return null;
51
- }
52
-
53
13
  /**
54
14
  * Find the `pi` binary.
55
15
  *
56
16
  * Priority:
57
17
  * 1. --pi flag (explicit override)
58
18
  * 2. AGENTUITY_CODER_PI_PATH env var
59
- * 3. `pi` on PATH (default)
19
+ * 3. Bundled pi from coder-tui's node_modules/.bin/pi
20
+ * 4. `pi` on PATH (fallback)
60
21
  */
61
- function resolvePiBinary(flagPath?: string): string {
22
+ async function resolvePiBinary(flagPath?: string, extensionDir?: string): Promise<string> {
62
23
  if (flagPath) return flagPath;
63
24
  const envPath = process.env.AGENTUITY_CODER_PI_PATH;
64
25
  if (envPath) return envPath;
26
+
27
+ // Look for pi bundled with the coder-tui extension
28
+ if (extensionDir) {
29
+ // Prefer require.resolve via package.json — handles hoisted deps, Bun's .bun cache, etc.
30
+ try {
31
+ const pkgJson = require.resolve('@mariozechner/pi-coding-agent/package.json', {
32
+ paths: [extensionDir],
33
+ });
34
+ const piCli = resolve(dirname(pkgJson), 'dist', 'cli.js');
35
+ if (await Bun.file(piCli).exists()) return piCli;
36
+ } catch {
37
+ // Fallback: direct .bin symlink check
38
+ const bundledPi = resolve(extensionDir, 'node_modules', '.bin', 'pi');
39
+ if (await Bun.file(bundledPi).exists()) return bundledPi;
40
+ }
41
+ }
42
+
65
43
  return 'pi';
66
44
  }
67
45
 
46
+ function logValidationIssues(
47
+ ctx: { logger: { trace: (...args: unknown[]) => void } },
48
+ err: unknown
49
+ ): void {
50
+ if (err instanceof ValidationOutputError) {
51
+ ctx.logger.trace('Validation response URL: %s', err.url ?? 'unknown');
52
+ ctx.logger.trace('Validation issues: %s', JSON.stringify(err.issues, null, 2));
53
+ }
54
+ }
55
+
68
56
  export const startSubcommand = createSubcommand({
69
57
  name: 'start',
58
+ aliases: ['run'],
70
59
  description: 'Start a Pi coding session connected to the Coder Hub',
71
60
  tags: ['fast', 'requires-auth'],
61
+ requires: { auth: true, org: true },
72
62
  examples: [
73
63
  {
74
64
  command: getCommand('coder start'),
75
65
  description: 'Start Pi with auto-detected Hub and extension',
76
66
  },
77
67
  {
78
- command: getCommand('coder start --hub-url ws://127.0.0.1:3500/api/ws'),
68
+ command: getCommand('coder start --url ws://127.0.0.1:3500/api/ws'),
79
69
  description: 'Start with explicit Hub URL',
80
70
  },
81
71
  {
82
- command: getCommand('coder start --extension ~/repos/agentuity/sdk/packages/coder'),
72
+ command: getCommand('coder start --extension ~/repos/agentuity/sdk/packages/coder-tui'),
83
73
  description: 'Start with explicit extension path',
84
74
  },
85
75
  {
@@ -87,7 +77,7 @@ export const startSubcommand = createSubcommand({
87
77
  description: 'Start as a specific agent role',
88
78
  },
89
79
  {
90
- command: getCommand('coder start --remote codesess_abc123'),
80
+ command: getCommand('coder start --remote codesess_abc123456789'),
91
81
  description: 'Connect to an existing sandbox session remotely',
92
82
  },
93
83
  {
@@ -107,7 +97,7 @@ export const startSubcommand = createSubcommand({
107
97
  ],
108
98
  schema: {
109
99
  options: z.object({
110
- hubUrl: z.string().optional().describe('Hub WebSocket URL override'),
100
+ url: z.string().optional().describe('Coder API URL override'),
111
101
  extension: z.string().optional().describe('Coder extension path override'),
112
102
  pi: z.string().optional().describe('Path to pi binary'),
113
103
  agent: z.string().optional().describe('Agent role (e.g. scout, builder)'),
@@ -125,31 +115,25 @@ export const startSubcommand = createSubcommand({
125
115
  .optional()
126
116
  .describe('Git repo URL to clone in the sandbox (used with --sandbox)'),
127
117
  }),
118
+ aliases: {
119
+ remote: ['session'],
120
+ },
128
121
  },
129
122
  async handler(ctx) {
130
123
  const { opts, options } = ctx;
131
-
132
- // Resolve Hub URL
133
- const hubHttpUrl = await resolveHubUrl(opts?.hubUrl);
134
- if (!hubHttpUrl) {
135
- tui.fatal(
136
- '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',
137
- ErrorCode.NETWORK_ERROR
138
- );
139
- return;
140
- }
141
- const hubWsUrl = toHubWsUrl(hubHttpUrl);
142
-
143
- const initProbe = await probeHubInitAccess(hubHttpUrl);
124
+ const client = new CoderClient({
125
+ apiKey: ctx.auth.apiKey,
126
+ url: opts?.url,
127
+ orgId: ctx.orgId,
128
+ });
129
+
130
+ const hubHttpUrl = await client.getUrl();
131
+ const hubWsUrl = toCoderHubWsUrl(hubHttpUrl);
132
+
133
+ const initProbe = await probeHubInitAccess(hubHttpUrl, {
134
+ apiKey: ctx.auth.apiKey,
135
+ });
144
136
  if (!initProbe.ok) {
145
- if (initProbe.code === 'unauthorized') {
146
- tui.fatal(
147
- `Coder Hub at ${hubHttpUrl} requires authentication.\n\nSet AGENTUITY_CODER_API_KEY in your shell and retry.\n\nServer said: ${initProbe.message}`,
148
- ErrorCode.NETWORK_ERROR
149
- );
150
- return;
151
- }
152
-
153
137
  tui.fatal(
154
138
  `Could not bootstrap the Coder Hub at ${hubHttpUrl}: ${initProbe.message}`,
155
139
  ErrorCode.NETWORK_ERROR
@@ -158,17 +142,27 @@ export const startSubcommand = createSubcommand({
158
142
  }
159
143
 
160
144
  // Resolve extension path
161
- const extensionPath = resolveExtensionPath(opts?.extension);
145
+ const extensionPath = await resolveExtensionPath(opts?.extension);
162
146
  if (!extensionPath) {
163
147
  tui.fatal(
164
- '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',
148
+ 'Could not find the Agentuity Coder extension.\n\nThis CLI install should include it automatically. Try:\n - Reinstall or update @agentuity/cli\n - Install it locally: npm install @agentuity/coder-tui\n - Set AGENTUITY_CODER_EXTENSION environment variable\n - Pass --extension flag',
165
149
  ErrorCode.CONFIG_INVALID
166
150
  );
167
151
  return;
168
152
  }
169
153
 
154
+ const loadRemoteTui = async () => {
155
+ const modulePath = await resolveExtensionRuntimeModulePath(extensionPath);
156
+ if (!modulePath) {
157
+ throw new Error(
158
+ `Coder extension at ${extensionPath} is missing the remote TUI entrypoint`
159
+ );
160
+ }
161
+ return import(modulePath);
162
+ };
163
+
170
164
  // Resolve pi binary
171
- const piBinary = resolvePiBinary(opts?.pi);
165
+ const piBinary = await resolvePiBinary(opts?.pi, extensionPath);
172
166
 
173
167
  // ── Remote mode: resolve session ID ──
174
168
  let remoteSessionId: string | undefined;
@@ -180,32 +174,14 @@ export const startSubcommand = createSubcommand({
180
174
  } else {
181
175
  // No session ID — fetch connectable sessions and show picker
182
176
  try {
183
- type SessionInfo = {
184
- id: string;
185
- label: string;
186
- status: string;
187
- task: string | null;
188
- createdAt: string;
189
- };
190
-
191
177
  const sessions = await tui.spinner({
192
178
  message: 'Fetching connectable sessions…',
193
- callback: async () => {
194
- const resp = await fetch(`${hubHttpUrl}/api/hub/sessions/connectable`, {
195
- headers: hubFetchHeaders(),
196
- signal: AbortSignal.timeout(10_000),
197
- });
198
- if (!resp.ok) {
199
- throw new Error(`${resp.status} ${resp.statusText}`);
200
- }
201
- const data = (await resp.json()) as { sessions: SessionInfo[] };
202
- return data.sessions;
203
- },
179
+ callback: async () => (await client.listConnectableSessions()).sessions,
204
180
  });
205
181
 
206
182
  if (sessions.length === 0) {
207
183
  tui.fatal(
208
- 'No connectable sandbox sessions found.\n\nCreate one with: ag-dev coder session create --task "your task"',
184
+ `No connectable sandbox sessions found.\n\nCreate one with:\n ${getCommand('coder start --sandbox "your task"')}`,
209
185
  ErrorCode.CONFIG_INVALID
210
186
  );
211
187
  return;
@@ -214,20 +190,18 @@ export const startSubcommand = createSubcommand({
214
190
  const prompt = tui.createPrompt();
215
191
  remoteSessionId = await prompt.select<string>({
216
192
  message: 'Select a sandbox session to connect to',
217
- options: sessions.map((s) => {
193
+ options: sessions.map((s: CoderSessionListItem) => {
218
194
  const age = timeSince(new Date(s.createdAt));
219
- const taskPreview = s.task ? s.task.slice(0, 55) : null;
220
- const label = taskPreview
221
- ? `${s.label} ${tui.muted(`(${s.status}, ${age})`)} — ${taskPreview}`
222
- : `${s.label} ${tui.muted(`(${s.status}, ${age})`)}`;
195
+ const label = `${s.label} ${tui.muted(`(${s.status}, ${age})`)}`;
223
196
  return {
224
- value: s.id,
197
+ value: s.sessionId,
225
198
  label,
226
- hint: s.id,
199
+ hint: s.sessionId,
227
200
  };
228
201
  }),
229
202
  });
230
203
  } catch (err) {
204
+ logValidationIssues(ctx, err);
231
205
  const msg = err instanceof Error ? err.message : String(err);
232
206
  if (msg === 'User cancelled') return;
233
207
  tui.fatal(`Failed to fetch connectable sessions: ${msg}`, ErrorCode.NETWORK_ERROR);
@@ -250,10 +224,12 @@ export const startSubcommand = createSubcommand({
250
224
  }
251
225
 
252
226
  try {
253
- const { runRemoteTui } = await import(join(extensionPath, 'src', 'remote-tui.ts'));
227
+ const { runRemoteTui } = await loadRemoteTui();
254
228
  await runRemoteTui({
255
229
  hubWsUrl,
256
230
  sessionId: remoteSessionId,
231
+ apiKey: ctx.auth.apiKey,
232
+ orgId: ctx.orgId,
257
233
  });
258
234
  } catch (err) {
259
235
  const msg = err instanceof Error ? err.message : String(err);
@@ -273,14 +249,11 @@ export const startSubcommand = createSubcommand({
273
249
  return;
274
250
  }
275
251
 
276
- const hubHttpUrl = await resolveHubUrl(opts?.hubUrl);
277
- if (!hubHttpUrl) {
278
- tui.fatal('Could not find Hub URL for sandbox creation.', ErrorCode.NETWORK_ERROR);
279
- return;
280
- }
281
-
282
252
  // Build request body
283
- const body: Record<string, unknown> = { task };
253
+ const body: {
254
+ task: string;
255
+ repo?: { url: string };
256
+ } = { task };
284
257
  if (opts?.repo) {
285
258
  body.repo = { url: opts.repo };
286
259
  }
@@ -291,23 +264,10 @@ export const startSubcommand = createSubcommand({
291
264
 
292
265
  let sessionId: string;
293
266
  try {
294
- const resp = await fetch(`${hubHttpUrl}/api/hub/session`, {
295
- method: 'POST',
296
- headers: hubFetchHeaders({ 'Content-Type': 'application/json' }),
297
- body: JSON.stringify(body),
298
- signal: AbortSignal.timeout(10_000),
299
- });
300
- if (!resp.ok) {
301
- const errText = await resp.text();
302
- tui.fatal(
303
- `Failed to create sandbox session: ${resp.status} ${errText}`,
304
- ErrorCode.NETWORK_ERROR
305
- );
306
- return;
307
- }
308
- const sessionInfo = (await resp.json()) as { sessionId: string };
267
+ const sessionInfo = await client.createSession(body);
309
268
  sessionId = sessionInfo.sessionId;
310
269
  } catch (err) {
270
+ logValidationIssues(ctx, err);
311
271
  const msg = err instanceof Error ? err.message : String(err);
312
272
  tui.fatal(`Failed to create sandbox session: ${msg}`, ErrorCode.NETWORK_ERROR);
313
273
  return;
@@ -327,18 +287,10 @@ export const startSubcommand = createSubcommand({
327
287
  while (Date.now() - pollStart < POLL_TIMEOUT) {
328
288
  await new Promise((r) => setTimeout(r, POLL_INTERVAL));
329
289
  try {
330
- const pollResp = await fetch(`${hubHttpUrl}/api/hub/session/${sessionId}`, {
331
- headers: hubFetchHeaders(),
332
- signal: AbortSignal.timeout(5_000),
333
- });
334
- if (pollResp.ok) {
335
- const data = (await pollResp.json()) as {
336
- participants?: Array<{ role: string }>;
337
- };
338
- if (data.participants?.some((p) => p.role === 'lead')) {
339
- driverConnected = true;
340
- break;
341
- }
290
+ const data = await client.listParticipants(sessionId);
291
+ if (data.participants?.some((p) => p.role === 'lead')) {
292
+ driverConnected = true;
293
+ break;
342
294
  }
343
295
  } catch {
344
296
  // Network blip — keep polling
@@ -357,10 +309,12 @@ export const startSubcommand = createSubcommand({
357
309
  tui.newline();
358
310
 
359
311
  try {
360
- const { runRemoteTui } = await import(join(extensionPath, 'src', 'remote-tui.ts'));
312
+ const { runRemoteTui } = await loadRemoteTui();
361
313
  await runRemoteTui({
362
314
  hubWsUrl,
363
315
  sessionId,
316
+ apiKey: ctx.auth.apiKey,
317
+ orgId: ctx.orgId,
364
318
  });
365
319
  } catch (err) {
366
320
  const msg = err instanceof Error ? err.message : String(err);
@@ -374,9 +328,7 @@ export const startSubcommand = createSubcommand({
374
328
  ...(process.env as Record<string, string>),
375
329
  AGENTUITY_CODER_HUB_URL: hubWsUrl,
376
330
  };
377
- // TODO: Remove/Change when we get Agentuity service level auth enabled, this is just temporary
378
- const cliApiKey = process.env.AGENTUITY_CODER_API_KEY;
379
- if (cliApiKey) env.AGENTUITY_CODER_API_KEY = cliApiKey;
331
+ env.AGENTUITY_CODER_API_KEY = ctx.auth.apiKey;
380
332
 
381
333
  if (opts?.agent) {
382
334
  env.AGENTUITY_CODER_AGENT = opts.agent;
@@ -404,7 +356,18 @@ export const startSubcommand = createSubcommand({
404
356
  stderr: 'inherit',
405
357
  });
406
358
 
359
+ // Forward signals to the child process so Ctrl+C exits cleanly
360
+ const onSigInt = () => proc.kill(2);
361
+ const onSigTerm = () => proc.kill(15);
362
+ process.on('SIGINT', onSigInt);
363
+ process.on('SIGTERM', onSigTerm);
364
+
407
365
  const exitCode = await proc.exited;
366
+
367
+ // Clean up only our signal handlers (preserve other modules' listeners)
368
+ process.removeListener('SIGINT', onSigInt);
369
+ process.removeListener('SIGTERM', onSigTerm);
370
+
408
371
  process.exit(exitCode);
409
372
  } catch (err) {
410
373
  const msg = err instanceof Error ? err.message : String(err);
@@ -1,5 +1,3 @@
1
- import { hubFetchHeaders } from './hub-url';
2
-
3
1
  export type HubInitProbeResult =
4
2
  | { ok: true }
5
3
  | {
@@ -21,11 +19,22 @@ function normalizeErrorMessage(payload: unknown, fallback: string): string {
21
19
 
22
20
  export async function probeHubInitAccess(
23
21
  hubHttpUrl: string,
24
- fetchImpl: typeof fetch = fetch
22
+ options?: {
23
+ apiKey?: string | null;
24
+ fetchImpl?: typeof fetch;
25
+ }
25
26
  ): Promise<HubInitProbeResult> {
27
+ const fetchImpl = options?.fetchImpl ?? fetch;
28
+ const headers: Record<string, string> = {
29
+ accept: 'application/json',
30
+ };
31
+ if (options?.apiKey) {
32
+ headers['x-agentuity-auth-api-key'] = options.apiKey;
33
+ }
34
+
26
35
  try {
27
36
  const response = await fetchImpl(`${hubHttpUrl}/api/hub/init`, {
28
- headers: hubFetchHeaders({ accept: 'application/json' }),
37
+ headers,
29
38
  signal: AbortSignal.timeout(5_000),
30
39
  });
31
40
 
@@ -0,0 +1,128 @@
1
+ import { z } from 'zod';
2
+ import { CoderClient, normalizeVisibility } from '@agentuity/core/coder';
3
+ import { ValidationOutputError } from '@agentuity/core';
4
+ import { createSubcommand } from '../../types';
5
+ import * as tui from '../../tui';
6
+ import { getCommand } from '../../command-prefix';
7
+ import { ErrorCode } from '../../errors';
8
+
9
+ export const updateSubcommand = createSubcommand({
10
+ name: 'update',
11
+ description: 'Update a Coder session',
12
+ tags: ['mutating', 'requires-auth'],
13
+ requires: { auth: true, org: true },
14
+ aliases: ['set', 'edit'],
15
+ examples: [
16
+ {
17
+ command: getCommand('coder update codesess_abc123 --label "New Label"'),
18
+ description: 'Update the session label',
19
+ },
20
+ {
21
+ command: getCommand('coder update codesess_abc123 --visibility org'),
22
+ description: 'Make a session visible to the org',
23
+ },
24
+ {
25
+ command: getCommand('coder update codesess_abc123 --tags "urgent,frontend"'),
26
+ description: 'Update tags on a session',
27
+ },
28
+ {
29
+ command: getCommand(
30
+ 'coder update codesess_abc123 --workflow-mode loop --loop-goal "Build it" --loop-max-iterations 20'
31
+ ),
32
+ description: 'Switch a session to loop mode',
33
+ },
34
+ ],
35
+ schema: {
36
+ args: z.object({
37
+ sessionId: z.string().describe('Session ID to update'),
38
+ }),
39
+ options: z.object({
40
+ url: z.string().optional().describe('Coder API URL override'),
41
+ label: z.string().optional().describe('Updated session label'),
42
+ agent: z.string().optional().describe('Updated default agent role'),
43
+ visibility: z
44
+ .string()
45
+ .optional()
46
+ .describe('Updated visibility: private, org, or collaborate'),
47
+ workflowMode: z.string().optional().describe('Updated workflow mode: standard or loop'),
48
+ loopGoal: z.string().optional().describe('Goal for loop mode'),
49
+ loopMaxIterations: z.number().optional().describe('Maximum loop iterations'),
50
+ loopAutoContinue: z.boolean().optional().describe('Auto-continue loop'),
51
+ loopAllowDetached: z.boolean().optional().describe('Allow detached loop execution'),
52
+ tags: z.string().optional().describe('Comma-separated tags (replaces existing)'),
53
+ }),
54
+ },
55
+ async handler(ctx) {
56
+ const { args, opts, options } = ctx;
57
+ const client = new CoderClient({
58
+ apiKey: ctx.auth.apiKey,
59
+ url: opts?.url,
60
+ orgId: ctx.orgId,
61
+ });
62
+
63
+ const body: Record<string, unknown> = {};
64
+
65
+ if (opts?.label) body.label = opts.label;
66
+ if (opts?.agent) body.agent = opts.agent;
67
+ if (opts?.visibility) body.visibility = normalizeVisibility(opts.visibility);
68
+ if (opts?.workflowMode) body.workflowMode = opts.workflowMode;
69
+ if (opts?.tags) {
70
+ body.tags = opts.tags
71
+ .split(',')
72
+ .map((t) => t.trim())
73
+ .filter(Boolean);
74
+ }
75
+
76
+ if (
77
+ opts?.loopGoal ||
78
+ opts?.loopMaxIterations ||
79
+ opts?.loopAutoContinue !== undefined ||
80
+ opts?.loopAllowDetached !== undefined
81
+ ) {
82
+ const loop: Record<string, unknown> = {};
83
+ if (opts.loopGoal) loop.goal = opts.loopGoal;
84
+ if (opts.loopMaxIterations) loop.maxIterations = opts.loopMaxIterations;
85
+ if (opts.loopAutoContinue !== undefined) loop.autoContinue = opts.loopAutoContinue;
86
+ if (opts.loopAllowDetached !== undefined) loop.allowDetached = opts.loopAllowDetached;
87
+ body.loop = loop;
88
+ if (!body.workflowMode) body.workflowMode = 'loop';
89
+ }
90
+
91
+ if (Object.keys(body).length === 0) {
92
+ tui.fatal(
93
+ 'No update fields provided. Use --label, --visibility, --tags, --agent, --workflow-mode, or loop options.',
94
+ ErrorCode.VALIDATION_FAILED
95
+ );
96
+ }
97
+
98
+ try {
99
+ const updated = await client.updateSession(args.sessionId, body as any);
100
+
101
+ if (options.json) {
102
+ return updated;
103
+ }
104
+
105
+ tui.success(`Session ${args.sessionId} updated.`);
106
+
107
+ const fields: string[] = [];
108
+ if (opts?.label) fields.push(`Label: ${opts.label}`);
109
+ if (opts?.visibility) fields.push(`Visibility: ${body.visibility}`);
110
+ if (opts?.tags) fields.push(`Tags: ${(body.tags as string[]).join(', ')}`);
111
+ if (opts?.agent) fields.push(`Agent: ${opts.agent}`);
112
+ if (opts?.workflowMode || body.loop) fields.push(`Workflow: ${body.workflowMode}`);
113
+
114
+ for (const f of fields) {
115
+ tui.output(` ${f}`);
116
+ }
117
+
118
+ return updated;
119
+ } catch (err) {
120
+ if (err instanceof ValidationOutputError) {
121
+ ctx.logger.trace('Validation response URL: %s', err.url ?? 'unknown');
122
+ ctx.logger.trace('Validation issues: %s', JSON.stringify(err.issues, null, 2));
123
+ }
124
+ const msg = err instanceof Error ? err.message : String(err);
125
+ tui.fatal(`Failed to update session ${args.sessionId}: ${msg}`, ErrorCode.NETWORK_ERROR);
126
+ }
127
+ },
128
+ });
@@ -0,0 +1,101 @@
1
+ import { z } from 'zod';
2
+ import { CoderClient, type CoderUser } from '@agentuity/core/coder';
3
+ import { ValidationOutputError } from '@agentuity/core';
4
+ import { createSubcommand } from '../../types';
5
+ import * as tui from '../../tui';
6
+ import { getCommand } from '../../command-prefix';
7
+ import { ErrorCode } from '../../errors';
8
+
9
+ function formatRelativeTime(isoDate: string): string {
10
+ const parsed = new Date(isoDate).getTime();
11
+ if (Number.isNaN(parsed)) return 'unknown';
12
+ const diffMs = Math.max(0, Date.now() - parsed);
13
+ const seconds = Math.floor(diffMs / 1000);
14
+ if (seconds < 60) return `${seconds}s ago`;
15
+ const minutes = Math.floor(seconds / 60);
16
+ if (minutes < 60) return `${minutes}m ago`;
17
+ const hours = Math.floor(minutes / 60);
18
+ if (hours < 24) return `${hours}h ago`;
19
+ const days = Math.floor(hours / 24);
20
+ return `${days}d ago`;
21
+ }
22
+
23
+ export const usersSubcommand = createSubcommand({
24
+ name: 'users',
25
+ aliases: ['user'],
26
+ description: 'List known Coder Hub users',
27
+ tags: ['read-only', 'fast', 'requires-auth'],
28
+ idempotent: true,
29
+ requires: { auth: true, org: true },
30
+ examples: [
31
+ {
32
+ command: getCommand('coder users'),
33
+ description: 'List known users',
34
+ },
35
+ {
36
+ command: getCommand('coder users --search jenny'),
37
+ description: 'Filter users by display name',
38
+ },
39
+ {
40
+ command: getCommand('coder users --json'),
41
+ description: 'Return users as JSON',
42
+ },
43
+ ],
44
+ schema: {
45
+ options: z.object({
46
+ search: z.string().optional().describe('Filter users by display name'),
47
+ url: z.string().optional().describe('Coder API URL override'),
48
+ }),
49
+ },
50
+ async handler(ctx) {
51
+ const { options, opts } = ctx;
52
+ const client = new CoderClient({
53
+ apiKey: ctx.auth.apiKey,
54
+ url: opts?.url,
55
+ orgId: ctx.orgId,
56
+ });
57
+
58
+ let users: CoderUser[] = [];
59
+ try {
60
+ const response = await client.listUsers({ search: opts?.search });
61
+ users = response.users;
62
+ } catch (err) {
63
+ if (err instanceof ValidationOutputError) {
64
+ ctx.logger.trace('Validation response URL: %s', err.url ?? 'unknown');
65
+ ctx.logger.trace('Validation issues: %s', JSON.stringify(err.issues, null, 2));
66
+ }
67
+ const msg = err instanceof Error ? err.message : String(err);
68
+ tui.fatal(`Failed to list Coder users: ${msg}`, ErrorCode.NETWORK_ERROR);
69
+ }
70
+
71
+ if (options.json) {
72
+ return users;
73
+ }
74
+
75
+ if (users.length === 0) {
76
+ tui.info('No Coder users found.');
77
+ return [];
78
+ }
79
+
80
+ tui.table(
81
+ users.map((u) => ({
82
+ User: u.displayName,
83
+ Email: u.email,
84
+ Provider: u.provider,
85
+ 'Last Login': formatRelativeTime(u.lastLoginAt),
86
+ 'Last Seen': formatRelativeTime(u.lastSeenAt),
87
+ Joined: formatRelativeTime(u.createdAt),
88
+ })),
89
+ [
90
+ { name: 'User', alignment: 'left' },
91
+ { name: 'Email', alignment: 'left' },
92
+ { name: 'Provider', alignment: 'left' },
93
+ { name: 'Last Login', alignment: 'right' },
94
+ { name: 'Last Seen', alignment: 'right' },
95
+ { name: 'Joined', alignment: 'right' },
96
+ ]
97
+ );
98
+
99
+ return users;
100
+ },
101
+ });