@agentuity/cli 2.0.10 → 2.0.12

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 (233) hide show
  1. package/dist/cache/resource-region.d.ts.map +1 -1
  2. package/dist/cache/resource-region.js +48 -25
  3. package/dist/cache/resource-region.js.map +1 -1
  4. package/dist/cmd/build/vite/agent-discovery.js +4 -4
  5. package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
  6. package/dist/cmd/build/vite/bun-dev-server.d.ts +20 -0
  7. package/dist/cmd/build/vite/bun-dev-server.d.ts.map +1 -1
  8. package/dist/cmd/build/vite/bun-dev-server.js +62 -4
  9. package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
  10. package/dist/cmd/build/vite/index.d.ts +0 -1
  11. package/dist/cmd/build/vite/index.d.ts.map +1 -1
  12. package/dist/cmd/build/vite/index.js +0 -1
  13. package/dist/cmd/build/vite/index.js.map +1 -1
  14. package/dist/cmd/build/vite/static-renderer.d.ts +17 -0
  15. package/dist/cmd/build/vite/static-renderer.d.ts.map +1 -1
  16. package/dist/cmd/build/vite/static-renderer.js +18 -6
  17. package/dist/cmd/build/vite/static-renderer.js.map +1 -1
  18. package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
  19. package/dist/cmd/build/vite/vite-asset-server-config.js +34 -27
  20. package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
  21. package/dist/cmd/build/vite/vite-asset-server.d.ts +9 -0
  22. package/dist/cmd/build/vite/vite-asset-server.d.ts.map +1 -1
  23. package/dist/cmd/build/vite/vite-asset-server.js +5 -1
  24. package/dist/cmd/build/vite/vite-asset-server.js.map +1 -1
  25. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  26. package/dist/cmd/build/vite/vite-builder.js +12 -1
  27. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  28. package/dist/cmd/build/vite/ws-proxy.d.ts +15 -1
  29. package/dist/cmd/build/vite/ws-proxy.d.ts.map +1 -1
  30. package/dist/cmd/build/vite/ws-proxy.js +33 -0
  31. package/dist/cmd/build/vite/ws-proxy.js.map +1 -1
  32. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  33. package/dist/cmd/cloud/deploy.js +98 -39
  34. package/dist/cmd/cloud/deploy.js.map +1 -1
  35. package/dist/cmd/cloud/sandbox/checkpoint/create.d.ts.map +1 -1
  36. package/dist/cmd/cloud/sandbox/checkpoint/create.js +3 -4
  37. package/dist/cmd/cloud/sandbox/checkpoint/create.js.map +1 -1
  38. package/dist/cmd/cloud/sandbox/checkpoint/delete.d.ts.map +1 -1
  39. package/dist/cmd/cloud/sandbox/checkpoint/delete.js +3 -4
  40. package/dist/cmd/cloud/sandbox/checkpoint/delete.js.map +1 -1
  41. package/dist/cmd/cloud/sandbox/checkpoint/list.d.ts.map +1 -1
  42. package/dist/cmd/cloud/sandbox/checkpoint/list.js +3 -4
  43. package/dist/cmd/cloud/sandbox/checkpoint/list.js.map +1 -1
  44. package/dist/cmd/cloud/sandbox/checkpoint/restore.d.ts.map +1 -1
  45. package/dist/cmd/cloud/sandbox/checkpoint/restore.js +3 -4
  46. package/dist/cmd/cloud/sandbox/checkpoint/restore.js.map +1 -1
  47. package/dist/cmd/cloud/sandbox/create.d.ts.map +1 -1
  48. package/dist/cmd/cloud/sandbox/create.js +13 -4
  49. package/dist/cmd/cloud/sandbox/create.js.map +1 -1
  50. package/dist/cmd/cloud/sandbox/delete.d.ts.map +1 -1
  51. package/dist/cmd/cloud/sandbox/delete.js +3 -4
  52. package/dist/cmd/cloud/sandbox/delete.js.map +1 -1
  53. package/dist/cmd/cloud/sandbox/env.d.ts.map +1 -1
  54. package/dist/cmd/cloud/sandbox/env.js +3 -5
  55. package/dist/cmd/cloud/sandbox/env.js.map +1 -1
  56. package/dist/cmd/cloud/sandbox/exec.d.ts.map +1 -1
  57. package/dist/cmd/cloud/sandbox/exec.js +114 -41
  58. package/dist/cmd/cloud/sandbox/exec.js.map +1 -1
  59. package/dist/cmd/cloud/sandbox/execution/list.d.ts.map +1 -1
  60. package/dist/cmd/cloud/sandbox/execution/list.js +3 -5
  61. package/dist/cmd/cloud/sandbox/execution/list.js.map +1 -1
  62. package/dist/cmd/cloud/sandbox/fs/cp.d.ts.map +1 -1
  63. package/dist/cmd/cloud/sandbox/fs/cp.js +61 -113
  64. package/dist/cmd/cloud/sandbox/fs/cp.js.map +1 -1
  65. package/dist/cmd/cloud/sandbox/fs/download.d.ts.map +1 -1
  66. package/dist/cmd/cloud/sandbox/fs/download.js +11 -22
  67. package/dist/cmd/cloud/sandbox/fs/download.js.map +1 -1
  68. package/dist/cmd/cloud/sandbox/fs/ls.d.ts.map +1 -1
  69. package/dist/cmd/cloud/sandbox/fs/ls.js +3 -5
  70. package/dist/cmd/cloud/sandbox/fs/ls.js.map +1 -1
  71. package/dist/cmd/cloud/sandbox/fs/mkdir.d.ts.map +1 -1
  72. package/dist/cmd/cloud/sandbox/fs/mkdir.js +3 -5
  73. package/dist/cmd/cloud/sandbox/fs/mkdir.js.map +1 -1
  74. package/dist/cmd/cloud/sandbox/fs/rm.d.ts.map +1 -1
  75. package/dist/cmd/cloud/sandbox/fs/rm.js +3 -5
  76. package/dist/cmd/cloud/sandbox/fs/rm.js.map +1 -1
  77. package/dist/cmd/cloud/sandbox/fs/rmdir.d.ts.map +1 -1
  78. package/dist/cmd/cloud/sandbox/fs/rmdir.js +3 -5
  79. package/dist/cmd/cloud/sandbox/fs/rmdir.js.map +1 -1
  80. package/dist/cmd/cloud/sandbox/fs/upload.d.ts.map +1 -1
  81. package/dist/cmd/cloud/sandbox/fs/upload.js +7 -8
  82. package/dist/cmd/cloud/sandbox/fs/upload.js.map +1 -1
  83. package/dist/cmd/cloud/sandbox/get.d.ts.map +1 -1
  84. package/dist/cmd/cloud/sandbox/get.js +21 -7
  85. package/dist/cmd/cloud/sandbox/get.js.map +1 -1
  86. package/dist/cmd/cloud/sandbox/job/create.d.ts.map +1 -1
  87. package/dist/cmd/cloud/sandbox/job/create.js +3 -4
  88. package/dist/cmd/cloud/sandbox/job/create.js.map +1 -1
  89. package/dist/cmd/cloud/sandbox/job/destroy.d.ts.map +1 -1
  90. package/dist/cmd/cloud/sandbox/job/destroy.js +3 -4
  91. package/dist/cmd/cloud/sandbox/job/destroy.js.map +1 -1
  92. package/dist/cmd/cloud/sandbox/job/get.d.ts.map +1 -1
  93. package/dist/cmd/cloud/sandbox/job/get.js +3 -4
  94. package/dist/cmd/cloud/sandbox/job/get.js.map +1 -1
  95. package/dist/cmd/cloud/sandbox/job/list.d.ts.map +1 -1
  96. package/dist/cmd/cloud/sandbox/job/list.js +3 -4
  97. package/dist/cmd/cloud/sandbox/job/list.js.map +1 -1
  98. package/dist/cmd/cloud/sandbox/job/logs.d.ts.map +1 -1
  99. package/dist/cmd/cloud/sandbox/job/logs.js +4 -4
  100. package/dist/cmd/cloud/sandbox/job/logs.js.map +1 -1
  101. package/dist/cmd/cloud/sandbox/pause.d.ts.map +1 -1
  102. package/dist/cmd/cloud/sandbox/pause.js +21 -5
  103. package/dist/cmd/cloud/sandbox/pause.js.map +1 -1
  104. package/dist/cmd/cloud/sandbox/resume.d.ts.map +1 -1
  105. package/dist/cmd/cloud/sandbox/resume.js +3 -4
  106. package/dist/cmd/cloud/sandbox/resume.js.map +1 -1
  107. package/dist/cmd/cloud/sandbox/run.d.ts.map +1 -1
  108. package/dist/cmd/cloud/sandbox/run.js +36 -7
  109. package/dist/cmd/cloud/sandbox/run.js.map +1 -1
  110. package/dist/cmd/cloud/sandbox/util.d.ts +19 -0
  111. package/dist/cmd/cloud/sandbox/util.d.ts.map +1 -1
  112. package/dist/cmd/cloud/sandbox/util.js +40 -2
  113. package/dist/cmd/cloud/sandbox/util.js.map +1 -1
  114. package/dist/cmd/coder/create.d.ts.map +1 -1
  115. package/dist/cmd/coder/create.js +18 -0
  116. package/dist/cmd/coder/create.js.map +1 -1
  117. package/dist/cmd/coder/index.d.ts.map +1 -1
  118. package/dist/cmd/coder/index.js +4 -0
  119. package/dist/cmd/coder/index.js.map +1 -1
  120. package/dist/cmd/coder/start.d.ts.map +1 -1
  121. package/dist/cmd/coder/start.js +52 -1
  122. package/dist/cmd/coder/start.js.map +1 -1
  123. package/dist/cmd/coder/tui-init.js +1 -1
  124. package/dist/cmd/coder/tui-init.js.map +1 -1
  125. package/dist/cmd/coder/update.d.ts.map +1 -1
  126. package/dist/cmd/coder/update.js +21 -1
  127. package/dist/cmd/coder/update.js.map +1 -1
  128. package/dist/cmd/coder/workspace/create.d.ts.map +1 -1
  129. package/dist/cmd/coder/workspace/create.js +57 -13
  130. package/dist/cmd/coder/workspace/create.js.map +1 -1
  131. package/dist/cmd/coder/workspace/index.d.ts.map +1 -1
  132. package/dist/cmd/coder/workspace/index.js +1 -1
  133. package/dist/cmd/coder/workspace/index.js.map +1 -1
  134. package/dist/cmd/coder/workspace/list.js +2 -2
  135. package/dist/cmd/coder/workspace/list.js.map +1 -1
  136. package/dist/cmd/dev/dev-lock.d.ts.map +1 -1
  137. package/dist/cmd/dev/dev-lock.js +43 -17
  138. package/dist/cmd/dev/dev-lock.js.map +1 -1
  139. package/dist/cmd/dev/index.d.ts.map +1 -1
  140. package/dist/cmd/dev/index.js +211 -125
  141. package/dist/cmd/dev/index.js.map +1 -1
  142. package/dist/cmd/dev/process-manager.d.ts +41 -1
  143. package/dist/cmd/dev/process-manager.d.ts.map +1 -1
  144. package/dist/cmd/dev/process-manager.js +160 -31
  145. package/dist/cmd/dev/process-manager.js.map +1 -1
  146. package/dist/cmd/project/create.d.ts.map +1 -1
  147. package/dist/cmd/project/create.js +0 -2
  148. package/dist/cmd/project/create.js.map +1 -1
  149. package/dist/cmd/project/index.d.ts.map +1 -1
  150. package/dist/cmd/project/index.js +0 -3
  151. package/dist/cmd/project/index.js.map +1 -1
  152. package/dist/cmd/project/template-flow.d.ts +0 -1
  153. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  154. package/dist/cmd/project/template-flow.js +1 -124
  155. package/dist/cmd/project/template-flow.js.map +1 -1
  156. package/dist/types.d.ts +1 -1
  157. package/package.json +7 -7
  158. package/src/cache/resource-region.ts +68 -44
  159. package/src/cmd/ai/prompt/web.md +43 -17
  160. package/src/cmd/build/vite/agent-discovery.ts +4 -4
  161. package/src/cmd/build/vite/bun-dev-server.ts +92 -6
  162. package/src/cmd/build/vite/index.ts +0 -1
  163. package/src/cmd/build/vite/static-renderer.ts +18 -7
  164. package/src/cmd/build/vite/vite-asset-server-config.ts +37 -27
  165. package/src/cmd/build/vite/vite-asset-server.ts +5 -1
  166. package/src/cmd/build/vite/vite-builder.ts +12 -1
  167. package/src/cmd/build/vite/ws-proxy.ts +52 -3
  168. package/src/cmd/cloud/deploy.ts +117 -49
  169. package/src/cmd/cloud/sandbox/checkpoint/create.ts +10 -4
  170. package/src/cmd/cloud/sandbox/checkpoint/delete.ts +10 -4
  171. package/src/cmd/cloud/sandbox/checkpoint/list.ts +10 -4
  172. package/src/cmd/cloud/sandbox/checkpoint/restore.ts +10 -4
  173. package/src/cmd/cloud/sandbox/create.ts +14 -4
  174. package/src/cmd/cloud/sandbox/delete.ts +10 -4
  175. package/src/cmd/cloud/sandbox/env.ts +10 -5
  176. package/src/cmd/cloud/sandbox/exec.ts +157 -42
  177. package/src/cmd/cloud/sandbox/execution/list.ts +10 -5
  178. package/src/cmd/cloud/sandbox/fs/cp.ts +94 -126
  179. package/src/cmd/cloud/sandbox/fs/download.ts +18 -25
  180. package/src/cmd/cloud/sandbox/fs/ls.ts +10 -5
  181. package/src/cmd/cloud/sandbox/fs/mkdir.ts +10 -5
  182. package/src/cmd/cloud/sandbox/fs/rm.ts +10 -5
  183. package/src/cmd/cloud/sandbox/fs/rmdir.ts +10 -5
  184. package/src/cmd/cloud/sandbox/fs/upload.ts +14 -8
  185. package/src/cmd/cloud/sandbox/get.ts +28 -7
  186. package/src/cmd/cloud/sandbox/job/create.ts +10 -4
  187. package/src/cmd/cloud/sandbox/job/destroy.ts +10 -4
  188. package/src/cmd/cloud/sandbox/job/get.ts +10 -4
  189. package/src/cmd/cloud/sandbox/job/list.ts +10 -4
  190. package/src/cmd/cloud/sandbox/job/logs.ts +11 -4
  191. package/src/cmd/cloud/sandbox/pause.ts +31 -5
  192. package/src/cmd/cloud/sandbox/resume.ts +10 -4
  193. package/src/cmd/cloud/sandbox/run.ts +49 -11
  194. package/src/cmd/cloud/sandbox/util.ts +63 -2
  195. package/src/cmd/coder/create.ts +24 -1
  196. package/src/cmd/coder/index.ts +4 -0
  197. package/src/cmd/coder/start.ts +63 -1
  198. package/src/cmd/coder/tui-init.ts +1 -1
  199. package/src/cmd/coder/update.ts +18 -1
  200. package/src/cmd/coder/workspace/create.ts +84 -15
  201. package/src/cmd/coder/workspace/index.ts +3 -1
  202. package/src/cmd/coder/workspace/list.ts +2 -2
  203. package/src/cmd/dev/dev-lock.ts +50 -16
  204. package/src/cmd/dev/index.ts +249 -134
  205. package/src/cmd/dev/process-manager.ts +173 -33
  206. package/src/cmd/project/create.ts +0 -2
  207. package/src/cmd/project/index.ts +0 -3
  208. package/src/cmd/project/template-flow.ts +0 -147
  209. package/dist/cmd/build/vite/public-asset-path-plugin.d.ts +0 -45
  210. package/dist/cmd/build/vite/public-asset-path-plugin.d.ts.map +0 -1
  211. package/dist/cmd/build/vite/public-asset-path-plugin.js +0 -166
  212. package/dist/cmd/build/vite/public-asset-path-plugin.js.map +0 -1
  213. package/dist/cmd/project/auth/generate.d.ts +0 -5
  214. package/dist/cmd/project/auth/generate.d.ts.map +0 -1
  215. package/dist/cmd/project/auth/generate.js +0 -102
  216. package/dist/cmd/project/auth/generate.js.map +0 -1
  217. package/dist/cmd/project/auth/index.d.ts +0 -2
  218. package/dist/cmd/project/auth/index.d.ts.map +0 -1
  219. package/dist/cmd/project/auth/index.js +0 -21
  220. package/dist/cmd/project/auth/index.js.map +0 -1
  221. package/dist/cmd/project/auth/init.d.ts +0 -2
  222. package/dist/cmd/project/auth/init.d.ts.map +0 -1
  223. package/dist/cmd/project/auth/init.js +0 -213
  224. package/dist/cmd/project/auth/init.js.map +0 -1
  225. package/dist/cmd/project/auth/shared.d.ts +0 -93
  226. package/dist/cmd/project/auth/shared.d.ts.map +0 -1
  227. package/dist/cmd/project/auth/shared.js +0 -475
  228. package/dist/cmd/project/auth/shared.js.map +0 -1
  229. package/src/cmd/build/vite/public-asset-path-plugin.ts +0 -209
  230. package/src/cmd/project/auth/generate.ts +0 -116
  231. package/src/cmd/project/auth/index.ts +0 -21
  232. package/src/cmd/project/auth/init.ts +0 -256
  233. package/src/cmd/project/auth/shared.ts +0 -591
@@ -1,17 +1,69 @@
1
- import { readFileSync } from 'node:fs';
1
+ import { fstatSync, readFileSync, statSync } from 'node:fs';
2
2
  import { resolve } from 'node:path';
3
3
  import type { FileToWrite, Logger } from '@agentuity/core';
4
- import { APIClient, getServiceUrls, sandboxGet } from '@agentuity/server';
4
+ import { APIClient, getServiceUrls, sandboxGet, sandboxResolve } from '@agentuity/server';
5
5
  import { deleteResourceRegion, getResourceInfo, setResourceInfo } from '../../../cache';
6
6
  import { getGlobalCatalystAPIClient } from '../../../config';
7
7
  import { ErrorCode } from '../../../errors';
8
8
  import * as tui from '../../../tui';
9
9
  import type { AuthData, Config } from '../../../types';
10
10
 
11
+ /**
12
+ * Detect if a file descriptor is redirected to /dev/null (or NUL on Windows).
13
+ * Used to optimize stream creation - when output goes to /dev/null, we can
14
+ * skip creating the stream entirely on the server.
15
+ *
16
+ * @param fd - File descriptor (1 for stdout, 2 for stderr)
17
+ * @returns true if the fd points to /dev/null
18
+ */
19
+ export function detectNullStream(fd: number): boolean {
20
+ try {
21
+ const fdStat = fstatSync(fd);
22
+ const nullPath = process.platform === 'win32' ? 'NUL' : '/dev/null';
23
+ const nullStat = statSync(nullPath);
24
+ return fdStat.dev === nullStat.dev && fdStat.ino === nullStat.ino;
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+
11
30
  export function createSandboxClient(logger: Logger, auth: AuthData, region: string): APIClient {
12
31
  return new APIClient(getServiceUrls(region).catalyst, logger, auth.apiKey);
13
32
  }
14
33
 
34
+ export interface ResolvedSandboxTarget {
35
+ region: string;
36
+ orgId: string;
37
+ }
38
+
39
+ /**
40
+ * Resolve sandbox routing context using cache-first lookup.
41
+ * Falls back to the CLI resolve endpoint on cache miss or partial cache.
42
+ */
43
+ export async function resolveSandboxTarget(
44
+ logger: Logger,
45
+ auth: AuthData,
46
+ apiClient: APIClient | null = null,
47
+ sandboxId: string,
48
+ profileName = 'production',
49
+ config?: Config | null
50
+ ): Promise<ResolvedSandboxTarget> {
51
+ const cachedInfo = await getResourceInfo('sandbox', profileName, sandboxId);
52
+ if (cachedInfo?.region && cachedInfo?.orgId) {
53
+ logger.trace(
54
+ `[sandbox] Found cached target for ${sandboxId}: ${cachedInfo.region}/${cachedInfo.orgId}`
55
+ );
56
+ return { region: cachedInfo.region, orgId: cachedInfo.orgId };
57
+ }
58
+
59
+ logger.trace(`[sandbox] Cache miss for target ${sandboxId}, resolving via CLI API`);
60
+ const globalClient =
61
+ apiClient ?? (await getGlobalCatalystAPIClient(logger, auth, profileName, undefined, config));
62
+ const sandbox = await sandboxResolve(globalClient, sandboxId);
63
+ await setResourceInfo('sandbox', profileName, sandboxId, sandbox.region, sandbox.orgId);
64
+ return { region: sandbox.region, orgId: sandbox.orgId };
65
+ }
66
+
15
67
  /**
16
68
  * Look up the region for a sandbox, using cache-first strategy.
17
69
  * Falls back to API lookup if not in cache.
@@ -64,6 +116,15 @@ export async function cacheSandboxRegion(
64
116
  await setResourceInfo('sandbox', profileName, sandboxId, region);
65
117
  }
66
118
 
119
+ export async function cacheSandboxTarget(
120
+ profileName = 'production',
121
+ sandboxId: string,
122
+ region: string,
123
+ orgId?: string
124
+ ): Promise<void> {
125
+ await setResourceInfo('sandbox', profileName, sandboxId, region, orgId);
126
+ }
127
+
67
128
  /**
68
129
  * Clear cached region for a sandbox after delete
69
130
  */
@@ -44,6 +44,12 @@ export const createCoderSubcommand = createSubcommand({
44
44
  ),
45
45
  description: 'Create with label and tags, return JSON',
46
46
  },
47
+ {
48
+ command: getCommand(
49
+ 'coder create "Review this change" --default-agent code-review --enabled-agents code-review'
50
+ ),
51
+ description: 'Create with a selected agent roster and a custom default route target',
52
+ },
47
53
  ],
48
54
  schema: {
49
55
  args: z.object({
@@ -61,6 +67,10 @@ export const createCoderSubcommand = createSubcommand({
61
67
  // Session config
62
68
  label: z.string().optional().describe('Human-readable session label'),
63
69
  agent: z.string().optional().describe('Default agent role (e.g. lead, scout)'),
70
+ defaultAgent: z
71
+ .string()
72
+ .optional()
73
+ .describe('Preferred default agent slug or built-in route target'),
64
74
  visibility: z
65
75
  .string()
66
76
  .optional()
@@ -86,6 +96,10 @@ export const createCoderSubcommand = createSubcommand({
86
96
  // Resources
87
97
  workspaceId: z.string().optional().describe('Workspace ID to use'),
88
98
  tags: z.string().optional().describe('Comma-separated tags'),
99
+ enabledAgents: z
100
+ .string()
101
+ .optional()
102
+ .describe('Comma-separated built-in/custom agents to include'),
89
103
  env: z
90
104
  .string()
91
105
  .optional()
@@ -105,10 +119,14 @@ export const createCoderSubcommand = createSubcommand({
105
119
  });
106
120
 
107
121
  // Build the create session request body from flags
108
- const body: CoderCreateSessionRequest = {
122
+ const body: CoderCreateSessionRequest & {
123
+ defaultAgent?: string;
124
+ enabledAgents?: string[];
125
+ } = {
109
126
  task: args.task,
110
127
  ...(opts?.label && { label: opts.label }),
111
128
  ...(opts?.agent && { agent: opts.agent }),
129
+ ...(opts?.defaultAgent && { defaultAgent: opts.defaultAgent }),
112
130
  ...(opts?.visibility && { visibility: normalizeVisibility(opts.visibility) }),
113
131
  ...(opts?.workflowMode && { workflowMode: opts.workflowMode as 'standard' | 'loop' }),
114
132
  };
@@ -149,6 +167,11 @@ export const createCoderSubcommand = createSubcommand({
149
167
  .split(',')
150
168
  .map((t) => t.trim())
151
169
  .filter(Boolean);
170
+ if (opts?.enabledAgents)
171
+ body.enabledAgents = opts.enabledAgents
172
+ .split(',')
173
+ .map((name) => name.trim())
174
+ .filter(Boolean);
152
175
  if (opts?.savedSkillIds)
153
176
  body.savedSkillIds = opts.savedSkillIds
154
177
  .split(',')
@@ -24,6 +24,10 @@ export const command = createCommand({
24
24
  command: getCommand('coder start'),
25
25
  description: 'Start a coding session connected to Coder',
26
26
  },
27
+ {
28
+ command: getCommand('coder start --dir ~/path/to/my/project'),
29
+ description: 'Start a coding session from a specific local project directory',
30
+ },
27
31
  {
28
32
  command: getCommand('coder create "Build a REST API"'),
29
33
  description: 'Create a new Coder session with a task',
@@ -1,4 +1,6 @@
1
1
  import { dirname, resolve } from 'node:path';
2
+ import { stat } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
2
4
  import { z } from 'zod';
3
5
  import { CoderClient, type CoderSessionListItem } from '@agentuity/core/coder';
4
6
  import { ValidationOutputError } from '@agentuity/core';
@@ -64,6 +66,10 @@ export const startSubcommand = createSubcommand({
64
66
  command: getCommand('coder start'),
65
67
  description: 'Start Pi with auto-detected Hub and extension',
66
68
  },
69
+ {
70
+ command: getCommand('coder start --dir ~/path/to/my/project'),
71
+ description: 'Start from a specific local project directory',
72
+ },
67
73
  {
68
74
  command: getCommand('coder start --url ws://127.0.0.1:3500/api/ws'),
69
75
  description: 'Start with explicit Coder URL',
@@ -97,6 +103,7 @@ export const startSubcommand = createSubcommand({
97
103
  ],
98
104
  schema: {
99
105
  options: z.object({
106
+ dir: z.string().optional().describe('Local project directory to start from'),
100
107
  url: z.string().optional().describe('Coder API URL override'),
101
108
  extension: z.string().optional().describe('Coder extension path override'),
102
109
  pi: z.string().optional().describe('Path to pi binary'),
@@ -121,6 +128,32 @@ export const startSubcommand = createSubcommand({
121
128
  },
122
129
  async handler(ctx) {
123
130
  const { opts, options } = ctx;
131
+ // Keep Pi's interactive install telemetry disabled for Agentuity CLI sessions.
132
+ process.env.PI_TELEMETRY = '0';
133
+
134
+ // Resolve working directory from optional --dir option
135
+ let cwd = process.cwd();
136
+ if (opts?.dir) {
137
+ // Warn if --dir is provided with --remote or --sandbox (dir is ignored in those modes)
138
+ if (opts?.remote !== undefined || opts?.sandbox !== undefined) {
139
+ tui.warning('--dir is ignored in remote/sandbox mode');
140
+ } else {
141
+ const raw = opts.dir.trim();
142
+ cwd =
143
+ raw === '~' || raw.startsWith('~/')
144
+ ? resolve(homedir(), raw.slice(2))
145
+ : resolve(raw);
146
+
147
+ const st = await stat(cwd).catch(() => null);
148
+ if (!st?.isDirectory()) {
149
+ tui.fatal(
150
+ `The specified path is not a valid directory: ${cwd}`,
151
+ ErrorCode.CONFIG_INVALID
152
+ );
153
+ return;
154
+ }
155
+ }
156
+ }
124
157
  const client = new CoderClient({
125
158
  apiKey: ctx.auth.apiKey,
126
159
  url: opts?.url,
@@ -216,6 +249,33 @@ export const startSubcommand = createSubcommand({
216
249
  // with the coder extension loaded for Hub UI (footer, /hub, commands).
217
250
  // Agent.emit() drives native rendering — no [remote_message] blocks.
218
251
  if (remoteSessionId) {
252
+ try {
253
+ const session = await tui.spinner({
254
+ message: 'Preparing remote session…',
255
+ callback: async () => client.prepareSessionForRemoteAttach(remoteSessionId!),
256
+ });
257
+
258
+ if (session.historyOnly === true) {
259
+ tui.fatal(
260
+ `Session ${remoteSessionId} is history-only and cannot be attached remotely.`,
261
+ ErrorCode.CONFIG_INVALID
262
+ );
263
+ return;
264
+ }
265
+
266
+ if (session.runtimeAvailable === false) {
267
+ tui.fatal(
268
+ `Session ${remoteSessionId} is offline and could not be resumed for remote attach.`,
269
+ ErrorCode.NETWORK_ERROR
270
+ );
271
+ return;
272
+ }
273
+ } catch (err) {
274
+ const msg = err instanceof Error ? err.message : String(err);
275
+ tui.fatal(`Failed to prepare remote session: ${msg}`, ErrorCode.NETWORK_ERROR);
276
+ return;
277
+ }
278
+
219
279
  if (!options.json) {
220
280
  tui.newline();
221
281
  tui.output(` Hub: ${tui.bold(hubWsUrl)}`);
@@ -330,6 +390,7 @@ export const startSubcommand = createSubcommand({
330
390
  AGENTUITY_CODER_HUB_URL: hubWsUrl,
331
391
  };
332
392
  env.AGENTUITY_CODER_API_KEY = ctx.auth.apiKey;
393
+ env.AGENTUITY_ORGID = ctx.orgId;
333
394
 
334
395
  if (opts?.agent) {
335
396
  env.AGENTUITY_CODER_AGENT = opts.agent;
@@ -343,6 +404,7 @@ export const startSubcommand = createSubcommand({
343
404
  tui.output(` Hub: ${tui.bold(hubWsUrl)}`);
344
405
  tui.output(` Extension: ${tui.bold(extensionPath)}`);
345
406
  tui.output(` Pi: ${tui.bold(piBinary)}`);
407
+ if (opts?.dir) tui.output(` Dir: ${tui.bold(cwd)}`);
346
408
  if (opts?.agent) tui.output(` Agent: ${tui.bold(opts.agent)}`);
347
409
  tui.newline();
348
410
  }
@@ -351,7 +413,7 @@ export const startSubcommand = createSubcommand({
351
413
  try {
352
414
  const proc = Bun.spawn([piBinary, ...piArgs], {
353
415
  env,
354
- cwd: process.cwd(),
416
+ cwd,
355
417
  stdin: 'inherit',
356
418
  stdout: 'inherit',
357
419
  stderr: 'inherit',
@@ -43,7 +43,7 @@ export async function probeHubInitAccess(
43
43
  try {
44
44
  const response = await fetchImpl(`${hubHttpUrl}/api/hub/init`, {
45
45
  headers,
46
- signal: AbortSignal.timeout(5_000),
46
+ signal: AbortSignal.timeout(10_000),
47
47
  });
48
48
 
49
49
  let payload: unknown;
@@ -40,6 +40,10 @@ export const updateSubcommand = createSubcommand({
40
40
  url: z.string().optional().describe('Coder API URL override'),
41
41
  label: z.string().optional().describe('Updated session label'),
42
42
  agent: z.string().optional().describe('Updated default agent role'),
43
+ defaultAgent: z
44
+ .string()
45
+ .optional()
46
+ .describe('Updated preferred default agent slug or built-in route target'),
43
47
  visibility: z
44
48
  .string()
45
49
  .optional()
@@ -50,6 +54,10 @@ export const updateSubcommand = createSubcommand({
50
54
  loopAutoContinue: z.boolean().optional().describe('Auto-continue loop'),
51
55
  loopAllowDetached: z.boolean().optional().describe('Allow detached loop execution'),
52
56
  tags: z.string().optional().describe('Comma-separated tags (replaces existing)'),
57
+ enabledAgents: z
58
+ .string()
59
+ .optional()
60
+ .describe('Comma-separated built-in/custom agents (replaces existing)'),
53
61
  }),
54
62
  },
55
63
  async handler(ctx) {
@@ -64,6 +72,7 @@ export const updateSubcommand = createSubcommand({
64
72
 
65
73
  if (opts?.label) body.label = opts.label;
66
74
  if (opts?.agent) body.agent = opts.agent;
75
+ if (opts?.defaultAgent) body.defaultAgent = opts.defaultAgent;
67
76
  if (opts?.visibility) body.visibility = normalizeVisibility(opts.visibility);
68
77
  if (opts?.workflowMode) body.workflowMode = opts.workflowMode;
69
78
  if (opts?.tags) {
@@ -72,6 +81,12 @@ export const updateSubcommand = createSubcommand({
72
81
  .map((t) => t.trim())
73
82
  .filter(Boolean);
74
83
  }
84
+ if (opts?.enabledAgents) {
85
+ body.enabledAgents = opts.enabledAgents
86
+ .split(',')
87
+ .map((name) => name.trim())
88
+ .filter(Boolean);
89
+ }
75
90
 
76
91
  if (
77
92
  opts?.loopGoal ||
@@ -90,7 +105,7 @@ export const updateSubcommand = createSubcommand({
90
105
 
91
106
  if (Object.keys(body).length === 0) {
92
107
  tui.fatal(
93
- 'No update fields provided. Use --label, --visibility, --tags, --agent, --workflow-mode, or loop options.',
108
+ 'No update fields provided. Use --label, --visibility, --tags, --agent, --default-agent, --enabled-agents, --workflow-mode, or loop options.',
94
109
  ErrorCode.VALIDATION_FAILED
95
110
  );
96
111
  }
@@ -109,6 +124,8 @@ export const updateSubcommand = createSubcommand({
109
124
  if (opts?.visibility) fields.push(`Visibility: ${body.visibility}`);
110
125
  if (opts?.tags) fields.push(`Tags: ${(body.tags as string[]).join(', ')}`);
111
126
  if (opts?.agent) fields.push(`Agent: ${opts.agent}`);
127
+ if (opts?.defaultAgent) fields.push(`Default agent: ${opts.defaultAgent}`);
128
+ if (opts?.enabledAgents) fields.push(`Enabled agents: ${opts.enabledAgents}`);
112
129
  if (opts?.workflowMode || body.loop) fields.push(`Workflow: ${body.workflowMode}`);
113
130
 
114
131
  for (const f of fields) {
@@ -1,37 +1,66 @@
1
1
  import { z } from 'zod';
2
- import { CoderClient, type CoderCreateWorkspaceRequest } from '@agentuity/core/coder';
3
- import { ValidationOutputError } from '@agentuity/core';
2
+ import { APIError, ValidationInputError, ValidationOutputError } from '@agentuity/core';
3
+ import {
4
+ CoderClient,
5
+ CoderCreateWorkspaceRequestSchema,
6
+ type CoderCreateWorkspaceRequest,
7
+ } from '@agentuity/core/coder';
4
8
  import { createSubcommand } from '../../../types';
5
9
  import * as tui from '../../../tui';
6
10
  import { getCommand } from '../../../command-prefix';
7
11
  import { ErrorCode } from '../../../errors';
8
12
  import { resolveGitHubRepo } from '../resolve-repo';
9
13
 
14
+ const EMPTY_WORKSPACE_ERROR =
15
+ 'A workspace needs at least one repo, saved skill, skill bucket, or agent';
16
+
17
+ function hasWorkspaceSelections(input: CoderCreateWorkspaceRequest): boolean {
18
+ return (
19
+ (input.repos?.length ?? 0) > 0 ||
20
+ (input.savedSkillIds?.length ?? 0) > 0 ||
21
+ (input.skillBucketIds?.length ?? 0) > 0 ||
22
+ (input.enabledAgents?.length ?? 0) > 0
23
+ );
24
+ }
25
+
26
+ function formatWorkspaceValidationMessage(issues: Array<{ message: string }>): string {
27
+ const messages = [...new Set(issues.map((issue) => issue.message).filter(Boolean))];
28
+ if (messages.length === 0) {
29
+ return 'Invalid workspace configuration';
30
+ }
31
+ if (messages.includes(EMPTY_WORKSPACE_ERROR)) {
32
+ return `${EMPTY_WORKSPACE_ERROR}. Use --repo or --enabled-agents.`;
33
+ }
34
+ return messages.join('; ');
35
+ }
36
+
10
37
  export const createWorkspaceSubcommand = createSubcommand({
11
38
  name: 'create',
12
39
  aliases: ['new', 'add'],
13
- description: 'Create a new Coder workspace',
40
+ description: 'Create a new Coder workspace with at least one repo or agent',
14
41
  tags: ['mutating', 'requires-auth'],
15
42
  requires: { auth: true, org: true },
16
43
  examples: [
17
- {
18
- command: getCommand('coder workspace create "My Workspace"'),
19
- description: 'Create a workspace with a name',
20
- },
21
44
  {
22
45
  command: getCommand(
23
- 'coder workspace create "My Workspace" --description "For frontend work" --scope org'
46
+ 'coder workspace create "My Workspace" --repo https://github.com/org/repo --repo-branch main'
24
47
  ),
25
- description: 'Create an org-scoped workspace with description',
48
+ description: 'Create a workspace with a repository',
26
49
  },
27
50
  {
28
51
  command: getCommand(
29
- 'coder workspace create "My Workspace" --repo https://github.com/org/repo --repo-branch main'
52
+ 'coder workspace create "My Workspace" --enabled-agents code-review --description "For frontend work" --scope org'
30
53
  ),
31
- description: 'Create a workspace with a repository',
54
+ description: 'Create an org-scoped workspace with description and agents',
32
55
  },
33
56
  {
34
- command: getCommand('coder workspace create "My Workspace" --json'),
57
+ command: getCommand('coder workspace create "My Workspace" --enabled-agents code-review'),
58
+ description: 'Create a workspace with an agent roster',
59
+ },
60
+ {
61
+ command: getCommand(
62
+ 'coder workspace create "My Workspace" --enabled-agents code-review --json'
63
+ ),
35
64
  description: 'Create a workspace and return JSON output',
36
65
  },
37
66
  ],
@@ -45,6 +74,10 @@ export const createWorkspaceSubcommand = createSubcommand({
45
74
  scope: z.string().optional().describe('Workspace scope: user or org'),
46
75
  repo: z.string().optional().describe('Repository URL to add'),
47
76
  repoBranch: z.string().optional().describe('Branch for the repository'),
77
+ enabledAgents: z
78
+ .string()
79
+ .optional()
80
+ .describe('Comma-separated built-in/custom agents to include'),
48
81
  }),
49
82
  },
50
83
  async handler(ctx) {
@@ -72,9 +105,36 @@ export const createWorkspaceSubcommand = createSubcommand({
72
105
  return;
73
106
  }
74
107
  }
108
+ if (opts?.enabledAgents) {
109
+ body.enabledAgents = opts.enabledAgents
110
+ .split(',')
111
+ .map((name) => name.trim())
112
+ .filter(Boolean);
113
+ }
114
+ if (!hasWorkspaceSelections(body)) {
115
+ tui.fatal(
116
+ `Failed to create workspace: ${EMPTY_WORKSPACE_ERROR}. Use --repo or --enabled-agents.`,
117
+ ErrorCode.VALIDATION_FAILED
118
+ );
119
+ }
120
+
121
+ const validationResult = CoderCreateWorkspaceRequestSchema.safeParse(body);
122
+ if (!validationResult.success) {
123
+ ctx.logger.trace(
124
+ 'Validation issues: %s',
125
+ JSON.stringify(validationResult.error.issues, null, 2)
126
+ );
127
+ tui.fatal(
128
+ `Failed to create workspace: ${formatWorkspaceValidationMessage(validationResult.error.issues)}`,
129
+ ErrorCode.VALIDATION_FAILED
130
+ );
131
+ }
75
132
 
76
133
  try {
77
- const created = await client.createWorkspace(body);
134
+ const created = await client.createWorkspace(validationResult.data);
135
+ const createdEnabledAgents = Array.isArray(created.enabledAgents)
136
+ ? created.enabledAgents.filter((name): name is string => typeof name === 'string')
137
+ : [];
78
138
 
79
139
  if (options.json) {
80
140
  return created;
@@ -88,13 +148,22 @@ export const createWorkspaceSubcommand = createSubcommand({
88
148
  }
89
149
  tui.output(` Scope: ${created.scope}`);
90
150
  tui.output(` Repos: ${created.repoCount}`);
91
- tui.output(` Skills: ${created.selectionCount}`);
151
+ tui.output(` Selections: ${created.selectionCount}`);
152
+ if (createdEnabledAgents.length > 0) {
153
+ tui.output(` Agents: ${createdEnabledAgents.join(', ')}`);
154
+ }
92
155
 
93
156
  return created;
94
157
  } catch (err) {
95
- if (err instanceof ValidationOutputError) {
158
+ if (err instanceof ValidationInputError || err instanceof ValidationOutputError) {
96
159
  ctx.logger.trace('Validation response URL: %s', err.url ?? 'unknown');
97
160
  ctx.logger.trace('Validation issues: %s', JSON.stringify(err.issues, null, 2));
161
+ tui.fatal(
162
+ `Failed to create workspace: ${formatWorkspaceValidationMessage(err.issues)}`,
163
+ ErrorCode.VALIDATION_FAILED
164
+ );
165
+ }
166
+ if (err instanceof APIError && err.status >= 400 && err.status < 500) {
98
167
  tui.fatal(`Failed to create workspace: ${err.message}`, ErrorCode.VALIDATION_FAILED);
99
168
  }
100
169
  const msg = err instanceof Error ? err.message : String(err);
@@ -17,7 +17,9 @@ export const workspaceCommand = createCommand({
17
17
  description: 'List all workspaces',
18
18
  },
19
19
  {
20
- command: getCommand('coder workspace create "My Workspace"'),
20
+ command: getCommand(
21
+ 'coder workspace create "My Workspace" --repo https://github.com/org/repo'
22
+ ),
21
23
  description: 'Create a new workspace',
22
24
  },
23
25
  {
@@ -83,7 +83,7 @@ export const listSubcommand = createSubcommand({
83
83
  Name: w.name,
84
84
  Scope: w.scope,
85
85
  Repos: String(w.repoCount),
86
- Skills: String(w.selectionCount),
86
+ Selections: String(w.selectionCount),
87
87
  Created: formatRelativeTime(w.createdAt),
88
88
  })),
89
89
  [
@@ -91,7 +91,7 @@ export const listSubcommand = createSubcommand({
91
91
  { name: 'Name', alignment: 'left' },
92
92
  { name: 'Scope', alignment: 'center' },
93
93
  { name: 'Repos', alignment: 'right' },
94
- { name: 'Skills', alignment: 'right' },
94
+ { name: 'Selections', alignment: 'right' },
95
95
  { name: 'Created', alignment: 'right' },
96
96
  ]
97
97
  );
@@ -71,35 +71,69 @@ function pidExists(pid: number): boolean {
71
71
  }
72
72
 
73
73
  /**
74
- * Kill a process by PID with SIGTERM, then SIGKILL if still alive
74
+ * Send a signal to a process group (negative PID) with fallback to direct PID.
75
+ * Returns true if the signal was sent successfully.
75
76
  */
76
- async function killPid(pid: number, logger: LoggerLike): Promise<void> {
77
- if (!pidExists(pid)) return;
77
+ function killProcessTree(pid: number, signal: NodeJS.Signals, logger: LoggerLike): boolean {
78
+ // Safety: never send signals to PID 0 (own process group), PID 1 (init/systemd),
79
+ // or other dangerously low PIDs. process.kill(-1) would signal every process
80
+ // the user owns, which would crash the entire desktop session.
81
+ if (pid <= 1) {
82
+ logger.debug('Refusing to kill dangerous pid %d, skipping process tree kill', pid);
83
+ return false;
84
+ }
78
85
 
86
+ // Try process group kill first (kills all children too)
79
87
  try {
80
- process.kill(pid, 'SIGTERM');
81
- logger.debug('Sent SIGTERM to pid %d', pid);
88
+ process.kill(-pid, signal);
89
+ logger.debug('Sent %s to process group -%d', signal, pid);
90
+ return true;
82
91
  } catch (err: unknown) {
83
92
  const error = err as NodeJS.ErrnoException;
84
- if (error.code === 'ESRCH') return;
85
- logger.debug('Error sending SIGTERM to pid %d: %s', pid, error.message);
93
+ if (error.code !== 'ESRCH') {
94
+ logger.debug(
95
+ 'Process group kill failed for pid %d (%s), falling back to direct',
96
+ pid,
97
+ error.code
98
+ );
99
+ }
86
100
  }
87
101
 
88
- // Give it a moment to exit gracefully
89
- await new Promise((r) => setTimeout(r, 500));
90
-
91
- if (!pidExists(pid)) return;
92
-
93
- // Force kill
102
+ // Fall back to direct PID kill
94
103
  try {
95
- process.kill(pid, 'SIGKILL');
96
- logger.debug('Sent SIGKILL to pid %d', pid);
104
+ process.kill(pid, signal);
105
+ logger.debug('Sent %s to pid %d (direct)', signal, pid);
106
+ return true;
97
107
  } catch (err: unknown) {
98
108
  const error = err as NodeJS.ErrnoException;
99
109
  if (error.code !== 'ESRCH') {
100
- logger.debug('Error sending SIGKILL to pid %d: %s', pid, error.message);
110
+ logger.debug('Direct kill failed for pid %d: %s', pid, error.code);
101
111
  }
112
+ return false;
102
113
  }
114
+ }
115
+
116
+ /**
117
+ * Kill a process by PID with SIGTERM, then SIGKILL if still alive.
118
+ * Targets the entire process tree to prevent orphaned child processes.
119
+ */
120
+ async function killPid(pid: number, logger: LoggerLike): Promise<void> {
121
+ if (!pidExists(pid)) return;
122
+
123
+ // Send SIGTERM to process tree
124
+ const sent = killProcessTree(pid, 'SIGTERM', logger);
125
+ if (!sent) return;
126
+
127
+ // Give it a moment to exit gracefully
128
+ await new Promise((r) => setTimeout(r, 500));
129
+
130
+ // Always attempt SIGKILL on the process tree even if the leader has exited.
131
+ // On Unix, process groups persist after the leader exits and signaling via
132
+ // negative PGID still reaches remaining members. killProcessTree() handles
133
+ // ESRCH gracefully if the group no longer exists.
134
+
135
+ // Force kill the entire process tree
136
+ killProcessTree(pid, 'SIGKILL', logger);
103
137
 
104
138
  // Wait for process to fully terminate
105
139
  await new Promise((r) => setTimeout(r, 100));