@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
@@ -0,0 +1,104 @@
1
+ import { z } from 'zod';
2
+ import { CoderClient, type CoderCreateWorkspaceRequest } 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
+ import { resolveGitHubRepo } from '../resolve-repo';
9
+
10
+ export const createWorkspaceSubcommand = createSubcommand({
11
+ name: 'create',
12
+ aliases: ['new', 'add'],
13
+ description: 'Create a new Coder workspace',
14
+ tags: ['mutating', 'requires-auth'],
15
+ requires: { auth: true, org: true },
16
+ examples: [
17
+ {
18
+ command: getCommand('coder workspace create "My Workspace"'),
19
+ description: 'Create a workspace with a name',
20
+ },
21
+ {
22
+ command: getCommand(
23
+ 'coder workspace create "My Workspace" --description "For frontend work" --scope org'
24
+ ),
25
+ description: 'Create an org-scoped workspace with description',
26
+ },
27
+ {
28
+ command: getCommand(
29
+ 'coder workspace create "My Workspace" --repo https://github.com/org/repo --repo-branch main'
30
+ ),
31
+ description: 'Create a workspace with a repository',
32
+ },
33
+ {
34
+ command: getCommand('coder workspace create "My Workspace" --json'),
35
+ description: 'Create a workspace and return JSON output',
36
+ },
37
+ ],
38
+ schema: {
39
+ args: z.object({
40
+ name: z.string().describe('Workspace name'),
41
+ }),
42
+ options: z.object({
43
+ url: z.string().optional().describe('Coder API URL override'),
44
+ description: z.string().optional().describe('Workspace description'),
45
+ scope: z.string().optional().describe('Workspace scope: user or org'),
46
+ repo: z.string().optional().describe('Repository URL to add'),
47
+ repoBranch: z.string().optional().describe('Branch for the repository'),
48
+ }),
49
+ },
50
+ async handler(ctx) {
51
+ const { args, opts, options } = ctx;
52
+ const client = new CoderClient({
53
+ apiKey: ctx.auth.apiKey,
54
+ url: opts?.url,
55
+ orgId: ctx.orgId,
56
+ });
57
+
58
+ const body: CoderCreateWorkspaceRequest = {
59
+ name: args.name,
60
+ ...(opts?.description && { description: opts.description }),
61
+ ...(opts?.scope && { scope: opts.scope as 'user' | 'org' }),
62
+ };
63
+
64
+ if (opts?.repo) {
65
+ if (!options.json) tui.output('Resolving repository...');
66
+ try {
67
+ const resolved = await resolveGitHubRepo(client, opts.repo, opts?.repoBranch);
68
+ body.repos = [resolved];
69
+ } catch (err) {
70
+ const msg = err instanceof Error ? err.message : String(err);
71
+ tui.fatal(`Failed to resolve repository: ${msg}`, ErrorCode.VALIDATION_FAILED);
72
+ return;
73
+ }
74
+ }
75
+
76
+ try {
77
+ const created = await client.createWorkspace(body);
78
+
79
+ if (options.json) {
80
+ return created;
81
+ }
82
+
83
+ tui.success(`Workspace ${created.id} created.`);
84
+ tui.newline();
85
+ tui.output(` Name: ${tui.bold(created.name)}`);
86
+ if (created.description) {
87
+ tui.output(` Description: ${created.description}`);
88
+ }
89
+ tui.output(` Scope: ${created.scope}`);
90
+ tui.output(` Repos: ${created.repoCount}`);
91
+ tui.output(` Skills: ${created.selectionCount}`);
92
+
93
+ return created;
94
+ } catch (err) {
95
+ if (err instanceof ValidationOutputError) {
96
+ ctx.logger.trace('Validation response URL: %s', err.url ?? 'unknown');
97
+ ctx.logger.trace('Validation issues: %s', JSON.stringify(err.issues, null, 2));
98
+ tui.fatal(`Failed to create workspace: ${err.message}`, ErrorCode.VALIDATION_FAILED);
99
+ }
100
+ const msg = err instanceof Error ? err.message : String(err);
101
+ tui.fatal(`Failed to create workspace: ${msg}`, ErrorCode.NETWORK_ERROR);
102
+ }
103
+ },
104
+ });
@@ -0,0 +1,70 @@
1
+ import { z } from 'zod';
2
+ import { CoderClient } 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 deleteWorkspaceSubcommand = createSubcommand({
10
+ name: 'delete',
11
+ aliases: ['rm', 'del', 'remove'],
12
+ description: 'Delete a Coder workspace',
13
+ tags: ['destructive', 'deletes-resource', 'requires-auth'],
14
+ requires: { auth: true, org: true },
15
+ examples: [
16
+ {
17
+ command: getCommand('coder workspace delete ws_abc123'),
18
+ description: 'Delete a workspace',
19
+ },
20
+ {
21
+ command: getCommand('coder workspace delete ws_abc123 --json'),
22
+ description: 'Delete a workspace and return JSON output',
23
+ },
24
+ ],
25
+ schema: {
26
+ args: z.object({
27
+ workspaceId: z.string().describe('Workspace ID to delete'),
28
+ }),
29
+ options: z.object({
30
+ url: z.string().optional().describe('Coder API URL override'),
31
+ }),
32
+ },
33
+ async handler(ctx) {
34
+ const { args, opts, options } = ctx;
35
+ const client = new CoderClient({
36
+ apiKey: ctx.auth.apiKey,
37
+ url: opts?.url,
38
+ orgId: ctx.orgId,
39
+ });
40
+
41
+ if (!options.json) {
42
+ const confirmed = await tui.confirm(`Delete workspace ${args.workspaceId}?`, false);
43
+ if (!confirmed) {
44
+ tui.info('Cancelled.');
45
+ return { deleted: false, workspaceId: args.workspaceId };
46
+ }
47
+ }
48
+
49
+ try {
50
+ await client.deleteWorkspace(args.workspaceId);
51
+ } catch (err) {
52
+ if (err instanceof ValidationOutputError) {
53
+ ctx.logger.trace('Validation response URL: %s', err.url ?? 'unknown');
54
+ ctx.logger.trace('Validation issues: %s', JSON.stringify(err.issues, null, 2));
55
+ }
56
+ const msg = err instanceof Error ? err.message : String(err);
57
+ tui.fatal(
58
+ `Failed to delete workspace ${args.workspaceId}: ${msg}`,
59
+ ErrorCode.NETWORK_ERROR
60
+ );
61
+ }
62
+
63
+ if (options.json) {
64
+ return { deleted: true, workspaceId: args.workspaceId };
65
+ }
66
+
67
+ tui.success(`Workspace ${args.workspaceId} deleted.`);
68
+ return { deleted: true, workspaceId: args.workspaceId };
69
+ },
70
+ });
@@ -0,0 +1,112 @@
1
+ import { z } from 'zod';
2
+ import { CoderClient } 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 getWorkspaceSubcommand = createSubcommand({
24
+ name: 'get',
25
+ aliases: ['show', 'inspect'],
26
+ description: 'Show detailed information about a Coder workspace',
27
+ tags: ['read-only', 'fast', 'requires-auth'],
28
+ idempotent: true,
29
+ requires: { auth: true, org: true },
30
+ examples: [
31
+ {
32
+ command: getCommand('coder workspace get ws_abc123'),
33
+ description: 'Get a workspace by ID',
34
+ },
35
+ {
36
+ command: getCommand('coder workspace get ws_abc123 --json'),
37
+ description: 'Get workspace details as JSON',
38
+ },
39
+ ],
40
+ schema: {
41
+ args: z.object({
42
+ workspaceId: z.string().describe('Workspace ID to inspect'),
43
+ }),
44
+ options: z.object({
45
+ url: z.string().optional().describe('Coder API URL override'),
46
+ }),
47
+ },
48
+ async handler(ctx) {
49
+ const { args, opts, options } = ctx;
50
+ const client = new CoderClient({
51
+ apiKey: ctx.auth.apiKey,
52
+ url: opts?.url,
53
+ orgId: ctx.orgId,
54
+ });
55
+
56
+ try {
57
+ const workspace = await client.getWorkspace(args.workspaceId);
58
+
59
+ if (options.json) {
60
+ return workspace;
61
+ }
62
+
63
+ tui.header(`Workspace: ${workspace.name}`);
64
+ tui.newline();
65
+ tui.output(` ID: ${workspace.id}`);
66
+ tui.output(` Name: ${tui.bold(workspace.name)}`);
67
+ if (workspace.description) {
68
+ tui.output(` Description: ${workspace.description}`);
69
+ }
70
+ tui.output(` Scope: ${workspace.scope}`);
71
+ tui.output(` Owner: ${workspace.ownerUserId}`);
72
+ tui.output(` Created: ${formatRelativeTime(workspace.createdAt)}`);
73
+ tui.output(` Updated: ${formatRelativeTime(workspace.updatedAt)}`);
74
+ tui.newline();
75
+
76
+ if (workspace.repos.length > 0) {
77
+ tui.output(` Repositories (${workspace.repoCount}):`);
78
+ for (const repo of workspace.repos) {
79
+ const name = repo.fullName || repo.name || repo.url || repo.cloneUrl || 'unknown';
80
+ const branch = repo.branch || repo.defaultBranch || '';
81
+ tui.output(` - ${name}${branch ? ` (${branch})` : ''}`);
82
+ }
83
+ tui.newline();
84
+ }
85
+
86
+ if (workspace.savedSkillIds.length > 0) {
87
+ tui.output(` Saved Skill IDs (${workspace.savedSkillIds.length}):`);
88
+ for (const id of workspace.savedSkillIds) {
89
+ tui.output(` - ${id}`);
90
+ }
91
+ tui.newline();
92
+ }
93
+
94
+ if (workspace.skillBucketIds.length > 0) {
95
+ tui.output(` Skill Bucket IDs (${workspace.skillBucketIds.length}):`);
96
+ for (const id of workspace.skillBucketIds) {
97
+ tui.output(` - ${id}`);
98
+ }
99
+ tui.newline();
100
+ }
101
+
102
+ return workspace;
103
+ } catch (err) {
104
+ if (err instanceof ValidationOutputError) {
105
+ ctx.logger.trace('Validation response URL: %s', err.url ?? 'unknown');
106
+ ctx.logger.trace('Validation issues: %s', JSON.stringify(err.issues, null, 2));
107
+ }
108
+ const msg = err instanceof Error ? err.message : String(err);
109
+ tui.fatal(`Failed to get workspace ${args.workspaceId}: ${msg}`, ErrorCode.NETWORK_ERROR);
110
+ }
111
+ },
112
+ });
@@ -0,0 +1,38 @@
1
+ import { createCommand } from '../../../types';
2
+ import { listSubcommand } from './list';
3
+ import { createWorkspaceSubcommand } from './create';
4
+ import { getWorkspaceSubcommand } from './get';
5
+ import { deleteWorkspaceSubcommand } from './delete';
6
+ import { getCommand } from '../../../command-prefix';
7
+
8
+ export const workspaceCommand = createCommand({
9
+ name: 'workspace',
10
+ aliases: ['workspaces', 'ws'],
11
+ description: 'Manage Coder workspaces',
12
+ tags: ['requires-auth'],
13
+ requires: { auth: true, org: true },
14
+ examples: [
15
+ {
16
+ command: getCommand('coder workspace list'),
17
+ description: 'List all workspaces',
18
+ },
19
+ {
20
+ command: getCommand('coder workspace create "My Workspace"'),
21
+ description: 'Create a new workspace',
22
+ },
23
+ {
24
+ command: getCommand('coder workspace get ws_abc123'),
25
+ description: 'Show workspace details',
26
+ },
27
+ {
28
+ command: getCommand('coder workspace delete ws_abc123'),
29
+ description: 'Delete a workspace',
30
+ },
31
+ ],
32
+ subcommands: [
33
+ listSubcommand,
34
+ createWorkspaceSubcommand,
35
+ getWorkspaceSubcommand,
36
+ deleteWorkspaceSubcommand,
37
+ ],
38
+ });
@@ -0,0 +1,101 @@
1
+ import { z } from 'zod';
2
+ import {
3
+ CoderClient,
4
+ type CoderWorkspaceDetail,
5
+ CoderWorkspaceDetailSchema,
6
+ } from '@agentuity/core/coder';
7
+ import { ValidationOutputError } from '@agentuity/core';
8
+ import { createSubcommand } from '../../../types';
9
+ import * as tui from '../../../tui';
10
+ import { getCommand } from '../../../command-prefix';
11
+ import { ErrorCode } from '../../../errors';
12
+
13
+ function formatRelativeTime(isoDate: string): string {
14
+ const parsed = new Date(isoDate).getTime();
15
+ if (Number.isNaN(parsed)) return 'unknown';
16
+ const diffMs = Math.max(0, Date.now() - parsed);
17
+ const seconds = Math.floor(diffMs / 1000);
18
+ if (seconds < 60) return `${seconds}s ago`;
19
+ const minutes = Math.floor(seconds / 60);
20
+ if (minutes < 60) return `${minutes}m ago`;
21
+ const hours = Math.floor(minutes / 60);
22
+ if (hours < 24) return `${hours}h ago`;
23
+ const days = Math.floor(hours / 24);
24
+ return `${days}d ago`;
25
+ }
26
+
27
+ export const listSubcommand = createSubcommand({
28
+ name: 'list',
29
+ aliases: ['ls'],
30
+ description: 'List Coder workspaces',
31
+ tags: ['read-only', 'fast', 'requires-auth'],
32
+ idempotent: true,
33
+ requires: { auth: true, org: true },
34
+ examples: [
35
+ {
36
+ command: getCommand('coder workspace list'),
37
+ description: 'List Coder workspaces',
38
+ },
39
+ {
40
+ command: getCommand('coder workspace list --json'),
41
+ description: 'Return workspaces as JSON',
42
+ },
43
+ ],
44
+ schema: {
45
+ options: z.object({
46
+ url: z.string().optional().describe('Coder API URL override'),
47
+ }),
48
+ response: z.array(CoderWorkspaceDetailSchema),
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 workspaces: CoderWorkspaceDetail[] = [];
59
+ try {
60
+ const response = await client.listWorkspaces();
61
+ workspaces = response.workspaces;
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 workspaces: ${msg}`, ErrorCode.NETWORK_ERROR);
69
+ }
70
+
71
+ if (options.json) {
72
+ return workspaces;
73
+ }
74
+
75
+ if (workspaces.length === 0) {
76
+ tui.info('No Coder workspaces found.');
77
+ return [];
78
+ }
79
+
80
+ tui.table(
81
+ workspaces.map((w) => ({
82
+ ID: w.id,
83
+ Name: w.name,
84
+ Scope: w.scope,
85
+ Repos: String(w.repoCount),
86
+ Skills: String(w.selectionCount),
87
+ Created: formatRelativeTime(w.createdAt),
88
+ })),
89
+ [
90
+ { name: 'ID', alignment: 'left' },
91
+ { name: 'Name', alignment: 'left' },
92
+ { name: 'Scope', alignment: 'center' },
93
+ { name: 'Repos', alignment: 'right' },
94
+ { name: 'Skills', alignment: 'right' },
95
+ { name: 'Created', alignment: 'right' },
96
+ ]
97
+ );
98
+
99
+ return workspaces;
100
+ },
101
+ });
@@ -169,7 +169,7 @@ class DevmodeSyncService implements IDevmodeSyncService {
169
169
  }
170
170
  }
171
171
  }
172
- this.logger.debug('Previous metadata found with %d eval(s)', prevEvalCount);
172
+ this.logger.debug('Previous metadata found with %d evaluations', prevEvalCount);
173
173
  } else {
174
174
  this.logger.debug('No previous metadata, all evals will be treated as new');
175
175
  }
@@ -182,7 +182,7 @@ class DevmodeSyncService implements IDevmodeSyncService {
182
182
  if (agent.evals) {
183
183
  currentEvalCount += agent.evals.length;
184
184
  this.logger.debug(
185
- '[CLI EVAL SYNC] Agent "%s" has %d eval(s)',
185
+ '[CLI EVAL SYNC] Agent "%s" has %d evaluations',
186
186
  agent.name,
187
187
  agent.evals.length
188
188
  );
@@ -196,7 +196,7 @@ class DevmodeSyncService implements IDevmodeSyncService {
196
196
  }
197
197
  }
198
198
  }
199
- this.logger.debug('[CLI EVAL SYNC] Total current eval(s): %d', currentEvalCount);
199
+ this.logger.debug('[CLI EVAL SYNC] Total current evaluations: %d', currentEvalCount);
200
200
 
201
201
  // Get agents and evals to sync using shared diff logic
202
202
  const { create: agentsToCreate, delete: agentsToDelete } = getAgentsToSync(
@@ -241,7 +241,7 @@ class DevmodeSyncService implements IDevmodeSyncService {
241
241
  }
242
242
  if (evalsToCreate.length > 0 || evalsToDelete.length > 0) {
243
243
  this.logger.debug(
244
- 'Successfully bulk synced %d eval(s) to create, %d eval(s) to delete',
244
+ 'Successfully bulk synced %d evaluations to create, %d evaluations to delete',
245
245
  evalsToCreate.length,
246
246
  evalsToDelete.length
247
247
  );
@@ -369,7 +369,7 @@ class MockDevmodeSyncService implements IDevmodeSyncService {
369
369
 
370
370
  if (evalsToCreate.length > 0 || evalsToDelete.length > 0) {
371
371
  this.logger.debug(
372
- '[MOCK] Would make request: POST /cli/devmode/eval with %d eval(s) to create, %d eval(s) to delete',
372
+ '[MOCK] Would make request: POST /cli/devmode/eval with %d evaluations to create, %d evaluations to delete',
373
373
  evalsToCreate.length,
374
374
  evalsToDelete.length
375
375
  );
@@ -0,0 +1,32 @@
1
+ export function normalizeCoderHubHttpUrl(url: string): string {
2
+ let normalized = url.trim().replace(/\/+$/, '');
3
+
4
+ if (normalized.startsWith('ws://')) normalized = 'http://' + normalized.slice(5);
5
+ else if (normalized.startsWith('wss://')) normalized = 'https://' + normalized.slice(6);
6
+
7
+ normalized = normalized.replace(/\/api\/ws\b.*$/, '');
8
+ normalized = normalized.replace(/\/ws\b.*$/, '');
9
+ normalized = normalized.replace(/\/api\/hub\b.*$/, '');
10
+
11
+ return normalized.replace(/\/+$/, '');
12
+ }
13
+
14
+ export function toCoderHubWsUrl(hubHttpUrl: string): string {
15
+ let wsUrl = hubHttpUrl;
16
+ if (wsUrl.startsWith('http://')) wsUrl = 'ws://' + wsUrl.slice(7);
17
+ else if (wsUrl.startsWith('https://')) wsUrl = 'wss://' + wsUrl.slice(8);
18
+
19
+ try {
20
+ const parsed = new URL(wsUrl);
21
+ if (parsed.pathname !== '/api/ws') {
22
+ parsed.pathname = '/api/ws';
23
+ wsUrl = parsed.toString().replace(/\/$/, '');
24
+ }
25
+ } catch {
26
+ if (!wsUrl.endsWith('/api/ws')) {
27
+ wsUrl = wsUrl.replace(/\/?$/, '/api/ws');
28
+ }
29
+ }
30
+
31
+ return wsUrl;
32
+ }
package/src/config.ts CHANGED
@@ -28,6 +28,14 @@ import { ConfigSchema, ProjectSchema } from './types';
28
28
  export const defaultProfileName = 'production';
29
29
 
30
30
  export function getDefaultConfigDir(): string {
31
+ const configDirOverride = process.env.AGENTUITY_CONFIG_DIR?.trim();
32
+ if (configDirOverride) {
33
+ if (configDirOverride.startsWith('~/')) {
34
+ return resolve(join(homedir(), configDirOverride.slice(2)));
35
+ }
36
+ return resolve(configDirOverride);
37
+ }
38
+
31
39
  return join(homedir(), '.config', 'agentuity');
32
40
  }
33
41
 
@@ -144,16 +152,22 @@ let cachedConfig: Config | null | undefined;
144
152
  // Track the resolved config path so saveConfig writes back to the same file
145
153
  let cachedConfigPath: string | undefined;
146
154
 
155
+ export function resetConfigCache(): void {
156
+ cachedConfig = undefined;
157
+ cachedConfigPath = undefined;
158
+ }
159
+
147
160
  export async function loadConfig(
148
161
  customPath?: string,
149
162
  skipCache = false,
150
163
  profileFromFlag?: string
151
164
  ): Promise<Config | null> {
152
- // Use cache if available and not skipped
153
- if (!skipCache && cachedConfig !== undefined) {
165
+ const configPath = customPath ? expandTilde(customPath) : await getProfile(profileFromFlag);
166
+
167
+ // Use cache if available and not skipped, but only if the path matches
168
+ if (!skipCache && cachedConfig !== undefined && cachedConfigPath === configPath) {
154
169
  return cachedConfig;
155
170
  }
156
- const configPath = customPath ? expandTilde(customPath) : await getProfile(profileFromFlag);
157
171
 
158
172
  try {
159
173
  const file = Bun.file(configPath);
@@ -43,6 +43,16 @@ const SENSITIVE_ENV_PATTERNS = [
43
43
  /AUTH/i,
44
44
  ];
45
45
 
46
+ const MASKED_ARG_VALUE = '***MASKED***';
47
+ const SENSITIVE_ARG_PATTERNS = [
48
+ /^--?api[-_]?key$/i,
49
+ /^--?token$/i,
50
+ /^--?secret$/i,
51
+ /^--?password$/i,
52
+ /^--?client[-_]?secret$/i,
53
+ /^--?auth[-_]?value$/i,
54
+ ];
55
+
46
56
  interface SessionMetadata {
47
57
  sessionId: string;
48
58
  bucket: number;
@@ -102,6 +112,75 @@ function maskEnvironment(): Record<string, string> {
102
112
  return masked;
103
113
  }
104
114
 
115
+ function isSensitiveArgFlag(arg: string): boolean {
116
+ return SENSITIVE_ARG_PATTERNS.some((pattern) => pattern.test(arg));
117
+ }
118
+
119
+ function sanitizeArgsForLogging(args: string[]): string[] {
120
+ const sanitized: string[] = [];
121
+ let maskNextValue = false;
122
+
123
+ for (let i = 0; i < args.length; i += 1) {
124
+ const arg = args[i]!;
125
+
126
+ if (maskNextValue) {
127
+ if (!arg.startsWith('-')) {
128
+ sanitized.push(MASKED_ARG_VALUE);
129
+ maskNextValue = false;
130
+ continue;
131
+ }
132
+
133
+ sanitized.push(MASKED_ARG_VALUE);
134
+ maskNextValue = false;
135
+ i -= 1;
136
+ continue;
137
+ }
138
+
139
+ if (!arg.startsWith('-')) {
140
+ sanitized.push(arg);
141
+ continue;
142
+ }
143
+
144
+ const equalsIndex = arg.indexOf('=');
145
+ if (equalsIndex > 0) {
146
+ const flag = arg.slice(0, equalsIndex);
147
+ if (isSensitiveArgFlag(flag)) {
148
+ sanitized.push(`${flag}=${MASKED_ARG_VALUE}`);
149
+ continue;
150
+ }
151
+ }
152
+
153
+ sanitized.push(arg);
154
+ if (isSensitiveArgFlag(arg)) {
155
+ maskNextValue = true;
156
+ }
157
+ }
158
+
159
+ return sanitized;
160
+ }
161
+
162
+ export function sanitizeCliCommandForLogging(
163
+ command: string,
164
+ args: string[]
165
+ ): { command: string; args: string[] } {
166
+ const commandTokens = command ? command.split(' ') : [];
167
+
168
+ if (
169
+ commandTokens.length >= 5 &&
170
+ commandTokens[0] === 'coder' &&
171
+ commandTokens[1] === 'config' &&
172
+ commandTokens[2] === 'set' &&
173
+ commandTokens[3] === 'apikey'
174
+ ) {
175
+ commandTokens[4] = MASKED_ARG_VALUE;
176
+ }
177
+
178
+ return {
179
+ command: commandTokens.join(' '),
180
+ args: sanitizeArgsForLogging(args),
181
+ };
182
+ }
183
+
105
184
  /**
106
185
  * Get the logs directory path
107
186
  */
@@ -266,14 +345,16 @@ export class InternalLogger implements Logger {
266
345
  // Use workingDir as cwd in session metadata
267
346
  const cwd = workingDir;
268
347
 
348
+ const sanitizedInvocation = sanitizeCliCommandForLogging(command, args);
349
+
269
350
  // Gather session metadata
270
351
  const sessionMetadata: SessionMetadata = {
271
352
  sessionId: this.sessionId,
272
353
  bucket: this.bucket,
273
354
  pid: process.pid,
274
355
  ppid: process.ppid,
275
- command,
276
- args,
356
+ command: sanitizedInvocation.command,
357
+ args: sanitizedInvocation.args,
277
358
  timestamp: new Date().toISOString(),
278
359
  cli: {
279
360
  version: this.cliVersion,