@agentuity/cli 2.0.7 → 2.0.9

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 (176) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +4 -2
  3. package/dist/cli.js.map +1 -1
  4. package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
  5. package/dist/cmd/build/vite/route-discovery.js +6 -0
  6. package/dist/cmd/build/vite/route-discovery.js.map +1 -1
  7. package/dist/cmd/cloud/sandbox/fs/rm.d.ts.map +1 -1
  8. package/dist/cmd/cloud/sandbox/fs/rm.js +9 -3
  9. package/dist/cmd/cloud/sandbox/fs/rm.js.map +1 -1
  10. package/dist/cmd/cloud/sandbox/fs/rmdir.d.ts.map +1 -1
  11. package/dist/cmd/cloud/sandbox/fs/rmdir.js +9 -3
  12. package/dist/cmd/cloud/sandbox/fs/rmdir.js.map +1 -1
  13. package/dist/cmd/coder/archive.d.ts +2 -0
  14. package/dist/cmd/coder/archive.d.ts.map +1 -0
  15. package/dist/cmd/coder/archive.js +57 -0
  16. package/dist/cmd/coder/archive.js.map +1 -0
  17. package/dist/cmd/coder/create.d.ts +2 -0
  18. package/dist/cmd/coder/create.d.ts.map +1 -0
  19. package/dist/cmd/coder/create.js +245 -0
  20. package/dist/cmd/coder/create.js.map +1 -0
  21. package/dist/cmd/coder/delete.d.ts +2 -0
  22. package/dist/cmd/coder/delete.d.ts.map +1 -0
  23. package/dist/cmd/coder/delete.js +64 -0
  24. package/dist/cmd/coder/delete.js.map +1 -0
  25. package/dist/cmd/coder/events.d.ts +2 -0
  26. package/dist/cmd/coder/events.d.ts.map +1 -0
  27. package/dist/cmd/coder/events.js +99 -0
  28. package/dist/cmd/coder/events.js.map +1 -0
  29. package/dist/cmd/coder/extension-path.d.ts +8 -0
  30. package/dist/cmd/coder/extension-path.d.ts.map +1 -0
  31. package/dist/cmd/coder/extension-path.js +59 -0
  32. package/dist/cmd/coder/extension-path.js.map +1 -0
  33. package/dist/cmd/coder/get.d.ts +2 -0
  34. package/dist/cmd/coder/get.d.ts.map +1 -0
  35. package/dist/cmd/coder/{inspect.js → get.js} +38 -42
  36. package/dist/cmd/coder/get.js.map +1 -0
  37. package/dist/cmd/coder/index.d.ts.map +1 -1
  38. package/dist/cmd/coder/index.js +52 -7
  39. package/dist/cmd/coder/index.js.map +1 -1
  40. package/dist/cmd/coder/list.d.ts.map +1 -1
  41. package/dist/cmd/coder/list.js +26 -42
  42. package/dist/cmd/coder/list.js.map +1 -1
  43. package/dist/cmd/coder/loop.d.ts +2 -0
  44. package/dist/cmd/coder/loop.d.ts.map +1 -0
  45. package/dist/cmd/coder/loop.js +78 -0
  46. package/dist/cmd/coder/loop.js.map +1 -0
  47. package/dist/cmd/coder/participants.d.ts +2 -0
  48. package/dist/cmd/coder/participants.d.ts.map +1 -0
  49. package/dist/cmd/coder/participants.js +93 -0
  50. package/dist/cmd/coder/participants.js.map +1 -0
  51. package/dist/cmd/coder/replay.d.ts +2 -0
  52. package/dist/cmd/coder/replay.d.ts.map +1 -0
  53. package/dist/cmd/coder/replay.js +53 -0
  54. package/dist/cmd/coder/replay.js.map +1 -0
  55. package/dist/cmd/coder/resolve-repo.d.ts +27 -0
  56. package/dist/cmd/coder/resolve-repo.d.ts.map +1 -0
  57. package/dist/cmd/coder/resolve-repo.js +97 -0
  58. package/dist/cmd/coder/resolve-repo.js.map +1 -0
  59. package/dist/cmd/coder/skill/buckets.d.ts +2 -0
  60. package/dist/cmd/coder/skill/buckets.d.ts.map +1 -0
  61. package/dist/cmd/coder/skill/buckets.js +174 -0
  62. package/dist/cmd/coder/skill/buckets.js.map +1 -0
  63. package/dist/cmd/coder/skill/delete.d.ts +2 -0
  64. package/dist/cmd/coder/skill/delete.d.ts.map +1 -0
  65. package/dist/cmd/coder/skill/delete.js +64 -0
  66. package/dist/cmd/coder/skill/delete.js.map +1 -0
  67. package/dist/cmd/coder/skill/index.d.ts +2 -0
  68. package/dist/cmd/coder/skill/index.d.ts.map +1 -0
  69. package/dist/cmd/coder/skill/index.js +33 -0
  70. package/dist/cmd/coder/skill/index.js.map +1 -0
  71. package/dist/cmd/coder/skill/list.d.ts +2 -0
  72. package/dist/cmd/coder/skill/list.d.ts.map +1 -0
  73. package/dist/cmd/coder/skill/list.js +93 -0
  74. package/dist/cmd/coder/skill/list.js.map +1 -0
  75. package/dist/cmd/coder/skill/save.d.ts +2 -0
  76. package/dist/cmd/coder/skill/save.d.ts.map +1 -0
  77. package/dist/cmd/coder/skill/save.js +77 -0
  78. package/dist/cmd/coder/skill/save.js.map +1 -0
  79. package/dist/cmd/coder/start.d.ts.map +1 -1
  80. package/dist/cmd/coder/start.js +87 -131
  81. package/dist/cmd/coder/start.js.map +1 -1
  82. package/dist/cmd/coder/tui-init.d.ts.map +1 -1
  83. package/dist/cmd/coder/tui-init.js +7 -2
  84. package/dist/cmd/coder/tui-init.js.map +1 -1
  85. package/dist/cmd/coder/update.d.ts +2 -0
  86. package/dist/cmd/coder/update.d.ts.map +1 -0
  87. package/dist/cmd/coder/update.js +126 -0
  88. package/dist/cmd/coder/update.js.map +1 -0
  89. package/dist/cmd/coder/users.d.ts +2 -0
  90. package/dist/cmd/coder/users.d.ts.map +1 -0
  91. package/dist/cmd/coder/users.js +97 -0
  92. package/dist/cmd/coder/users.js.map +1 -0
  93. package/dist/cmd/coder/workspace/create.d.ts +2 -0
  94. package/dist/cmd/coder/workspace/create.d.ts.map +1 -0
  95. package/dist/cmd/coder/workspace/create.js +97 -0
  96. package/dist/cmd/coder/workspace/create.js.map +1 -0
  97. package/dist/cmd/coder/workspace/delete.d.ts +2 -0
  98. package/dist/cmd/coder/workspace/delete.d.ts.map +1 -0
  99. package/dist/cmd/coder/workspace/delete.js +64 -0
  100. package/dist/cmd/coder/workspace/delete.js.map +1 -0
  101. package/dist/cmd/coder/workspace/get.d.ts +2 -0
  102. package/dist/cmd/coder/workspace/get.d.ts.map +1 -0
  103. package/dist/cmd/coder/workspace/get.js +109 -0
  104. package/dist/cmd/coder/workspace/get.js.map +1 -0
  105. package/dist/cmd/coder/workspace/index.d.ts +2 -0
  106. package/dist/cmd/coder/workspace/index.d.ts.map +1 -0
  107. package/dist/cmd/coder/workspace/index.js +38 -0
  108. package/dist/cmd/coder/workspace/index.js.map +1 -0
  109. package/dist/cmd/coder/workspace/list.d.ts +2 -0
  110. package/dist/cmd/coder/workspace/list.d.ts.map +1 -0
  111. package/dist/cmd/coder/workspace/list.js +93 -0
  112. package/dist/cmd/coder/workspace/list.js.map +1 -0
  113. package/dist/config.d.ts.map +1 -1
  114. package/dist/config.js +3 -3
  115. package/dist/config.js.map +1 -1
  116. package/dist/types.d.ts +1 -5
  117. package/dist/types.d.ts.map +1 -1
  118. package/dist/types.js +0 -10
  119. package/dist/types.js.map +1 -1
  120. package/package.json +7 -6
  121. package/src/cli.ts +4 -2
  122. package/src/cmd/ai/prompt/agent.md +6 -6
  123. package/src/cmd/build/vite/route-discovery.ts +8 -0
  124. package/src/cmd/cloud/sandbox/fs/rm.ts +8 -3
  125. package/src/cmd/cloud/sandbox/fs/rmdir.ts +8 -3
  126. package/src/cmd/coder/archive.ts +59 -0
  127. package/src/cmd/coder/create.ts +268 -0
  128. package/src/cmd/coder/delete.ts +67 -0
  129. package/src/cmd/coder/events.ts +106 -0
  130. package/src/cmd/coder/extension-path.ts +71 -0
  131. package/src/cmd/coder/{inspect.ts → get.ts} +45 -69
  132. package/src/cmd/coder/index.ts +52 -7
  133. package/src/cmd/coder/list.ts +29 -89
  134. package/src/cmd/coder/loop.ts +85 -0
  135. package/src/cmd/coder/participants.ts +100 -0
  136. package/src/cmd/coder/replay.ts +58 -0
  137. package/src/cmd/coder/resolve-repo.ts +119 -0
  138. package/src/cmd/coder/skill/buckets.ts +191 -0
  139. package/src/cmd/coder/skill/delete.ts +67 -0
  140. package/src/cmd/coder/skill/index.ts +35 -0
  141. package/src/cmd/coder/skill/list.ts +97 -0
  142. package/src/cmd/coder/skill/save.ts +84 -0
  143. package/src/cmd/coder/start.ts +101 -174
  144. package/src/cmd/coder/tui-init.ts +7 -3
  145. package/src/cmd/coder/update.ts +128 -0
  146. package/src/cmd/coder/users.ts +101 -0
  147. package/src/cmd/coder/workspace/create.ts +104 -0
  148. package/src/cmd/coder/workspace/delete.ts +70 -0
  149. package/src/cmd/coder/workspace/get.ts +112 -0
  150. package/src/cmd/coder/workspace/index.ts +38 -0
  151. package/src/cmd/coder/workspace/list.ts +101 -0
  152. package/src/config.ts +4 -3
  153. package/src/types.ts +0 -10
  154. package/dist/cmd/coder/config/index.d.ts +0 -2
  155. package/dist/cmd/coder/config/index.d.ts.map +0 -1
  156. package/dist/cmd/coder/config/index.js +0 -20
  157. package/dist/cmd/coder/config/index.js.map +0 -1
  158. package/dist/cmd/coder/config/set.d.ts +0 -2
  159. package/dist/cmd/coder/config/set.d.ts.map +0 -1
  160. package/dist/cmd/coder/config/set.js +0 -100
  161. package/dist/cmd/coder/config/set.js.map +0 -1
  162. package/dist/cmd/coder/hub-url.d.ts +0 -47
  163. package/dist/cmd/coder/hub-url.d.ts.map +0 -1
  164. package/dist/cmd/coder/hub-url.js +0 -148
  165. package/dist/cmd/coder/hub-url.js.map +0 -1
  166. package/dist/cmd/coder/inspect.d.ts +0 -2
  167. package/dist/cmd/coder/inspect.d.ts.map +0 -1
  168. package/dist/cmd/coder/inspect.js.map +0 -1
  169. package/dist/coder-config.d.ts +0 -14
  170. package/dist/coder-config.d.ts.map +0 -1
  171. package/dist/coder-config.js +0 -119
  172. package/dist/coder-config.js.map +0 -1
  173. package/src/cmd/coder/config/index.ts +0 -20
  174. package/src/cmd/coder/config/set.ts +0 -112
  175. package/src/cmd/coder/hub-url.ts +0 -205
  176. package/src/coder-config.ts +0 -141
@@ -0,0 +1,58 @@
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 replaySubcommand = createSubcommand({
10
+ name: 'replay',
11
+ description: 'Get replay data for a Coder Hub session',
12
+ tags: ['read-only', 'requires-auth'],
13
+ idempotent: true,
14
+ requires: { auth: true, org: true },
15
+ examples: [
16
+ {
17
+ command: getCommand('coder replay codesess_abc123 --json'),
18
+ description: 'Get replay data as JSON',
19
+ },
20
+ ],
21
+ schema: {
22
+ args: z.object({
23
+ sessionId: z.string().describe('Session ID to get replay data for'),
24
+ }),
25
+ options: z.object({
26
+ url: z.string().optional().describe('Coder API URL override'),
27
+ }),
28
+ },
29
+ async handler(ctx) {
30
+ const { args, opts, options } = ctx;
31
+ const client = new CoderClient({
32
+ apiKey: ctx.auth.apiKey,
33
+ url: opts?.url,
34
+ orgId: ctx.orgId,
35
+ });
36
+
37
+ try {
38
+ const replay = await client.getReplay(args.sessionId);
39
+
40
+ if (!options.json) {
41
+ tui.info('Replay data is shown as JSON because it is a complex payload.');
42
+ tui.output(JSON.stringify(replay, null, 2));
43
+ }
44
+
45
+ return replay;
46
+ } catch (err) {
47
+ if (err instanceof ValidationOutputError) {
48
+ ctx.logger.trace('Validation response URL: %s', err.url ?? 'unknown');
49
+ ctx.logger.trace('Validation issues: %s', JSON.stringify(err.issues, null, 2));
50
+ }
51
+ const msg = err instanceof Error ? err.message : String(err);
52
+ tui.fatal(
53
+ `Failed to get replay for session ${args.sessionId}: ${msg}`,
54
+ ErrorCode.NETWORK_ERROR
55
+ );
56
+ }
57
+ },
58
+ });
@@ -0,0 +1,119 @@
1
+ import type { CoderClient, CoderSessionRepositoryRef } from '@agentuity/core/coder';
2
+
3
+ /**
4
+ * Parse a GitHub reference string into owner/repo/branch.
5
+ * Accepts:
6
+ * - github.com/owner/repo
7
+ * - https://github.com/owner/repo
8
+ * - https://github.com/owner/repo.git
9
+ * - owner/repo
10
+ * - owner/repo#branch
11
+ * - https://github.com/owner/repo/tree/branch
12
+ */
13
+ export function parseGitHubRef(
14
+ input: string
15
+ ): { owner: string; repo: string; branch?: string } | null {
16
+ let clean = input.trim();
17
+
18
+ // Handle owner/repo#branch
19
+ let branch: string | undefined;
20
+ const hashIdx = clean.indexOf('#');
21
+ if (hashIdx > 0) {
22
+ branch = clean.slice(hashIdx + 1);
23
+ clean = clean.slice(0, hashIdx);
24
+ }
25
+
26
+ // Handle URLs
27
+ if (clean.includes('github.com')) {
28
+ try {
29
+ const url = new URL(clean.startsWith('http') ? clean : `https://${clean}`);
30
+ const parts = url.pathname
31
+ .replace(/^\//, '')
32
+ .replace(/\.git$/, '')
33
+ .split('/');
34
+ const owner = parts[0];
35
+ const repo = parts[1];
36
+ if (parts.length >= 2 && owner && repo) {
37
+ // Handle /tree/branch paths
38
+ if (parts.length >= 4 && parts[2] === 'tree') {
39
+ branch = branch || parts.slice(3).join('/');
40
+ }
41
+ return { owner, repo, branch };
42
+ }
43
+ } catch {
44
+ // fall through
45
+ }
46
+ }
47
+
48
+ // Handle owner/repo
49
+ const slashParts = clean.replace(/\.git$/, '').split('/');
50
+ if (slashParts.length === 2 && slashParts[0] && slashParts[1]) {
51
+ return { owner: slashParts[0], repo: slashParts[1], branch };
52
+ }
53
+
54
+ return null;
55
+ }
56
+
57
+ /**
58
+ * Resolve a GitHub reference to a full repository ref by calling the Hub's GitHub APIs.
59
+ *
60
+ * Flow:
61
+ * 1. Parse the input to get owner/repo
62
+ * 2. List GitHub accounts → find the one matching the owner
63
+ * 3. List repos for that account → find the matching repo
64
+ * 4. Build full CoderSessionRepositoryRef with all metadata
65
+ */
66
+ export async function resolveGitHubRepo(
67
+ client: CoderClient,
68
+ input: string,
69
+ branchOverride?: string
70
+ ): Promise<CoderSessionRepositoryRef> {
71
+ const parsed = parseGitHubRef(input);
72
+ if (!parsed) {
73
+ throw new Error(`Could not parse GitHub reference: ${input}`);
74
+ }
75
+
76
+ const { owner, repo, branch: parsedBranch } = parsed;
77
+ const branch = branchOverride || parsedBranch;
78
+
79
+ // Step 1: List GitHub accounts
80
+ const accountsResponse = await client.listGitHubAccounts();
81
+ if (!accountsResponse.connected) {
82
+ throw new Error('GitHub is not connected. Please connect GitHub via the Coder web UI first.');
83
+ }
84
+
85
+ const account = accountsResponse.accounts.find(
86
+ (a) => a.accountName.toLowerCase() === owner.toLowerCase()
87
+ );
88
+ if (!account) {
89
+ const available = accountsResponse.accounts.map((a) => a.accountName).join(', ');
90
+ throw new Error(
91
+ `GitHub account "${owner}" not found. Available accounts: ${available || 'none'}`
92
+ );
93
+ }
94
+
95
+ // Step 2: List repos for that account
96
+ const reposResponse = await client.listGitHubRepos(account.accountId);
97
+ const repoMatch = reposResponse.repositories.find(
98
+ (r) => r.name.toLowerCase() === repo.toLowerCase()
99
+ );
100
+ if (!repoMatch) {
101
+ throw new Error(`Repository "${repo}" not found in GitHub account "${owner}".`);
102
+ }
103
+
104
+ // Step 3: Build full repo ref
105
+ return {
106
+ url: repoMatch.cloneUrl,
107
+ branch: branch || repoMatch.defaultBranch,
108
+ provider: 'github',
109
+ name: repoMatch.name,
110
+ fullName: repoMatch.fullName,
111
+ defaultBranch: repoMatch.defaultBranch,
112
+ private: repoMatch.private,
113
+ htmlUrl: repoMatch.htmlUrl,
114
+ accountId: account.accountId,
115
+ accountName: account.accountName,
116
+ accountType: account.accountType,
117
+ accountAvatarUrl: account.avatarUrl,
118
+ } as CoderSessionRepositoryRef;
119
+ }
@@ -0,0 +1,191 @@
1
+ import { z } from 'zod';
2
+ import { CoderClient, type CoderSkillBucket } 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 bucketsSubcommand = createSubcommand({
24
+ name: 'buckets',
25
+ aliases: ['bucket'],
26
+ description: 'List, create, or delete skill buckets',
27
+ tags: ['requires-auth'],
28
+ requires: { auth: true, org: true },
29
+ examples: [
30
+ {
31
+ command: getCommand('coder skill buckets'),
32
+ description: 'List skill buckets',
33
+ },
34
+ {
35
+ command: getCommand('coder skill buckets --create "My Bucket"'),
36
+ description: 'Create a new skill bucket',
37
+ },
38
+ {
39
+ command: getCommand(
40
+ 'coder skill buckets --create "My Bucket" --description "Frontend skills"'
41
+ ),
42
+ description: 'Create a skill bucket with description',
43
+ },
44
+ {
45
+ command: getCommand('coder skill buckets --delete bucket_abc123'),
46
+ description: 'Delete a skill bucket',
47
+ },
48
+ {
49
+ command: getCommand('coder skill buckets --json'),
50
+ description: 'List skill buckets as JSON',
51
+ },
52
+ ],
53
+ schema: {
54
+ options: z.object({
55
+ url: z.string().optional().describe('Coder API URL override'),
56
+ create: z.string().optional().describe('Create a new bucket with this name'),
57
+ delete: z.string().optional().describe('Delete bucket by ID'),
58
+ description: z
59
+ .string()
60
+ .optional()
61
+ .describe('Description for new bucket (used with --create)'),
62
+ }),
63
+ },
64
+ async handler(ctx) {
65
+ const { opts, options } = ctx;
66
+
67
+ // Validate mutually exclusive actions
68
+ if (opts?.create && opts?.delete) {
69
+ tui.fatal('Cannot use --create and --delete together.', ErrorCode.VALIDATION_FAILED);
70
+ }
71
+ if (opts?.create && !opts.create.trim()) {
72
+ tui.fatal('--create requires a non-empty bucket name.', ErrorCode.VALIDATION_FAILED);
73
+ }
74
+ if (opts?.delete && !opts.delete.trim()) {
75
+ tui.fatal('--delete requires a non-empty bucket ID.', ErrorCode.VALIDATION_FAILED);
76
+ }
77
+ if (opts?.description && !opts?.create) {
78
+ tui.fatal('--description can only be used with --create.', ErrorCode.VALIDATION_FAILED);
79
+ }
80
+
81
+ const client = new CoderClient({
82
+ apiKey: ctx.auth.apiKey,
83
+ url: opts?.url,
84
+ orgId: ctx.orgId,
85
+ });
86
+
87
+ // Delete a bucket
88
+ if (opts?.delete) {
89
+ if (!options.json) {
90
+ const confirmed = await tui.confirm(`Delete skill bucket ${opts.delete}?`, false);
91
+ if (!confirmed) {
92
+ tui.info('Cancelled.');
93
+ return { deleted: false, id: opts.delete };
94
+ }
95
+ }
96
+
97
+ try {
98
+ await client.deleteSkillBucket(opts.delete);
99
+ } catch (err) {
100
+ if (err instanceof ValidationOutputError) {
101
+ ctx.logger.trace('Validation response URL: %s', err.url ?? 'unknown');
102
+ ctx.logger.trace('Validation issues: %s', JSON.stringify(err.issues, null, 2));
103
+ }
104
+ const msg = err instanceof Error ? err.message : String(err);
105
+ tui.fatal(
106
+ `Failed to delete skill bucket ${opts.delete}: ${msg}`,
107
+ ErrorCode.NETWORK_ERROR
108
+ );
109
+ }
110
+
111
+ if (options.json) {
112
+ return { deleted: true, id: opts.delete };
113
+ }
114
+
115
+ tui.success(`Skill bucket ${opts.delete} deleted.`);
116
+ return { deleted: true, id: opts.delete };
117
+ }
118
+
119
+ // Create a bucket
120
+ if (opts?.create) {
121
+ try {
122
+ const created = await client.createSkillBucket({
123
+ name: opts.create,
124
+ ...(opts?.description && { description: opts.description }),
125
+ });
126
+
127
+ if (options.json) {
128
+ return created;
129
+ }
130
+
131
+ tui.success(`Skill bucket ${created.id} created.`);
132
+ tui.newline();
133
+ tui.output(` Name: ${tui.bold(created.name)}`);
134
+ if (created.description) {
135
+ tui.output(` Desc: ${created.description}`);
136
+ }
137
+ tui.output(` Skills: ${created.skillCount}`);
138
+
139
+ return created;
140
+ } catch (err) {
141
+ if (err instanceof ValidationOutputError) {
142
+ ctx.logger.trace('Validation response URL: %s', err.url ?? 'unknown');
143
+ ctx.logger.trace('Validation issues: %s', JSON.stringify(err.issues, null, 2));
144
+ }
145
+ const msg = err instanceof Error ? err.message : String(err);
146
+ tui.fatal(`Failed to create skill bucket: ${msg}`, ErrorCode.NETWORK_ERROR);
147
+ return;
148
+ }
149
+ }
150
+
151
+ // Default: list buckets
152
+ let buckets: CoderSkillBucket[] = [];
153
+ try {
154
+ const response = await client.listSkillBuckets();
155
+ buckets = response.buckets;
156
+ } catch (err) {
157
+ if (err instanceof ValidationOutputError) {
158
+ ctx.logger.trace('Validation response URL: %s', err.url ?? 'unknown');
159
+ ctx.logger.trace('Validation issues: %s', JSON.stringify(err.issues, null, 2));
160
+ }
161
+ const msg = err instanceof Error ? err.message : String(err);
162
+ tui.fatal(`Failed to list Coder skill buckets: ${msg}`, ErrorCode.NETWORK_ERROR);
163
+ }
164
+
165
+ if (options.json) {
166
+ return buckets;
167
+ }
168
+
169
+ if (buckets.length === 0) {
170
+ tui.info('No Coder skill buckets found.');
171
+ return [];
172
+ }
173
+
174
+ tui.table(
175
+ buckets.map((b) => ({
176
+ ID: b.id,
177
+ Name: b.name,
178
+ Skills: String(b.skillCount),
179
+ Created: formatRelativeTime(b.createdAt),
180
+ })),
181
+ [
182
+ { name: 'ID', alignment: 'left' },
183
+ { name: 'Name', alignment: 'left' },
184
+ { name: 'Skills', alignment: 'right' },
185
+ { name: 'Created', alignment: 'right' },
186
+ ]
187
+ );
188
+
189
+ return buckets;
190
+ },
191
+ });
@@ -0,0 +1,67 @@
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 deleteSkillSubcommand = createSubcommand({
10
+ name: 'delete',
11
+ aliases: ['rm', 'del', 'remove'],
12
+ description: 'Delete a saved skill from your library',
13
+ tags: ['destructive', 'deletes-resource', 'requires-auth'],
14
+ requires: { auth: true, org: true },
15
+ examples: [
16
+ {
17
+ command: getCommand('coder skill delete sk_abc123'),
18
+ description: 'Delete a saved skill',
19
+ },
20
+ {
21
+ command: getCommand('coder skill delete sk_abc123 --json'),
22
+ description: 'Delete a saved skill and return JSON output',
23
+ },
24
+ ],
25
+ schema: {
26
+ args: z.object({
27
+ id: z.string().describe('Saved skill 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 saved skill ${args.id}?`, false);
43
+ if (!confirmed) {
44
+ tui.info('Cancelled.');
45
+ return { deleted: false, id: args.id };
46
+ }
47
+ }
48
+
49
+ try {
50
+ await client.deleteSavedSkill(args.id);
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(`Failed to delete saved skill ${args.id}: ${msg}`, ErrorCode.NETWORK_ERROR);
58
+ }
59
+
60
+ if (options.json) {
61
+ return { deleted: true, id: args.id };
62
+ }
63
+
64
+ tui.success(`Saved skill ${args.id} deleted.`);
65
+ return { deleted: true, id: args.id };
66
+ },
67
+ });
@@ -0,0 +1,35 @@
1
+ import { createCommand } from '../../../types';
2
+ import { listSubcommand } from './list';
3
+ import { saveSkillSubcommand } from './save';
4
+ import { deleteSkillSubcommand } from './delete';
5
+ import { bucketsSubcommand } from './buckets';
6
+ import { getCommand } from '../../../command-prefix';
7
+
8
+ export const skillCommand = createCommand({
9
+ name: 'skill',
10
+ aliases: ['skills'],
11
+ description: 'Manage Coder saved skills and skill buckets',
12
+ tags: ['requires-auth'],
13
+ requires: { auth: true, org: true },
14
+ examples: [
15
+ {
16
+ command: getCommand('coder skill list'),
17
+ description: 'List saved skills',
18
+ },
19
+ {
20
+ command: getCommand(
21
+ 'coder skill save --repo org/repo --skill-id sk_abc --name "My Skill"'
22
+ ),
23
+ description: 'Save a skill to your library',
24
+ },
25
+ {
26
+ command: getCommand('coder skill delete sk_abc123'),
27
+ description: 'Delete a saved skill',
28
+ },
29
+ {
30
+ command: getCommand('coder skill buckets'),
31
+ description: 'List skill buckets',
32
+ },
33
+ ],
34
+ subcommands: [listSubcommand, saveSkillSubcommand, deleteSkillSubcommand, bucketsSubcommand],
35
+ });
@@ -0,0 +1,97 @@
1
+ import { z } from 'zod';
2
+ import { CoderClient, type CoderSavedSkill, CoderSavedSkillSchema } 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 listSubcommand = createSubcommand({
24
+ name: 'list',
25
+ aliases: ['ls'],
26
+ description: 'List saved skills',
27
+ tags: ['read-only', 'fast', 'requires-auth'],
28
+ idempotent: true,
29
+ requires: { auth: true, org: true },
30
+ examples: [
31
+ {
32
+ command: getCommand('coder skill list'),
33
+ description: 'List saved skills',
34
+ },
35
+ {
36
+ command: getCommand('coder skill list --json'),
37
+ description: 'Return saved skills as JSON',
38
+ },
39
+ ],
40
+ schema: {
41
+ options: z.object({
42
+ url: z.string().optional().describe('Coder API URL override'),
43
+ }),
44
+ response: z.array(CoderSavedSkillSchema),
45
+ },
46
+ async handler(ctx) {
47
+ const { options, opts } = ctx;
48
+ const client = new CoderClient({
49
+ apiKey: ctx.auth.apiKey,
50
+ url: opts?.url,
51
+ orgId: ctx.orgId,
52
+ });
53
+
54
+ let skills: CoderSavedSkill[] = [];
55
+ try {
56
+ const response = await client.listSavedSkills();
57
+ skills = response.skills;
58
+ } catch (err) {
59
+ if (err instanceof ValidationOutputError) {
60
+ ctx.logger.trace('Validation response URL: %s', err.url ?? 'unknown');
61
+ ctx.logger.trace('Validation issues: %s', JSON.stringify(err.issues, null, 2));
62
+ }
63
+ const msg = err instanceof Error ? err.message : String(err);
64
+ tui.fatal(`Failed to list Coder saved skills: ${msg}`, ErrorCode.NETWORK_ERROR);
65
+ }
66
+
67
+ if (options.json) {
68
+ return skills;
69
+ }
70
+
71
+ if (skills.length === 0) {
72
+ tui.info('No Coder saved skills found.');
73
+ return [];
74
+ }
75
+
76
+ tui.table(
77
+ skills.map((s) => ({
78
+ ID: s.id,
79
+ Name: s.name,
80
+ Repo: s.repo,
81
+ Source: s.source,
82
+ Installs: s.installs !== undefined ? String(s.installs) : '-',
83
+ Created: formatRelativeTime(s.createdAt),
84
+ })),
85
+ [
86
+ { name: 'ID', alignment: 'left' },
87
+ { name: 'Name', alignment: 'left' },
88
+ { name: 'Repo', alignment: 'left' },
89
+ { name: 'Source', alignment: 'left' },
90
+ { name: 'Installs', alignment: 'right' },
91
+ { name: 'Created', alignment: 'right' },
92
+ ]
93
+ );
94
+
95
+ return skills;
96
+ },
97
+ });
@@ -0,0 +1,84 @@
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 saveSkillSubcommand = createSubcommand({
10
+ name: 'save',
11
+ aliases: ['add', 'upsert'],
12
+ description: 'Save a skill to your library',
13
+ tags: ['mutating', 'requires-auth'],
14
+ requires: { auth: true, org: true },
15
+ examples: [
16
+ {
17
+ command: getCommand(
18
+ 'coder skill save --repo my-org/my-repo --skill-id sk_abc123 --name "My Skill"'
19
+ ),
20
+ description: 'Save a skill to your library',
21
+ },
22
+ {
23
+ command: getCommand(
24
+ 'coder skill save --repo my-org/my-repo --skill-id sk_abc123 --name "My Skill" --description "Useful skill" --skill-url https://example.com --json'
25
+ ),
26
+ description: 'Save a skill with all options and return JSON',
27
+ },
28
+ ],
29
+ schema: {
30
+ options: z.object({
31
+ url: z.string().optional().describe('Coder API URL override'),
32
+ repo: z.string().describe('Repository identifier for the skill'),
33
+ skillId: z.string().describe('Skill identifier'),
34
+ name: z.string().describe('Skill name'),
35
+ description: z.string().optional().describe('Skill description'),
36
+ skillUrl: z.string().optional().describe('Skill URL'),
37
+ source: z.string().optional().describe('Skill source (default: registry)'),
38
+ content: z.string().optional().describe('Skill content'),
39
+ }),
40
+ },
41
+ async handler(ctx) {
42
+ const { opts, options } = ctx;
43
+ const client = new CoderClient({
44
+ apiKey: ctx.auth.apiKey,
45
+ url: opts?.url,
46
+ orgId: ctx.orgId,
47
+ });
48
+
49
+ try {
50
+ const saved = await client.saveSkill({
51
+ repo: opts.repo,
52
+ skillId: opts.skillId,
53
+ name: opts.name,
54
+ ...(opts?.description !== undefined && { description: opts.description }),
55
+ ...(opts?.skillUrl !== undefined && { url: opts.skillUrl }),
56
+ ...(opts?.source !== undefined && { source: opts.source }),
57
+ ...(opts?.content !== undefined && { content: opts.content }),
58
+ });
59
+
60
+ if (options.json) {
61
+ return saved;
62
+ }
63
+
64
+ tui.success(`Skill ${saved.id} saved.`);
65
+ tui.newline();
66
+ tui.output(` Name: ${tui.bold(saved.name)}`);
67
+ tui.output(` Repo: ${saved.repo}`);
68
+ tui.output(` Source: ${saved.source}`);
69
+ if (saved.description) {
70
+ tui.output(` Desc: ${saved.description}`);
71
+ }
72
+
73
+ return saved;
74
+ } catch (err) {
75
+ if (err instanceof ValidationOutputError) {
76
+ ctx.logger.trace('Validation response URL: %s', err.url ?? 'unknown');
77
+ ctx.logger.trace('Validation issues: %s', JSON.stringify(err.issues, null, 2));
78
+ tui.fatal(`Failed to save skill: ${err.message}`, ErrorCode.VALIDATION_FAILED);
79
+ }
80
+ const msg = err instanceof Error ? err.message : String(err);
81
+ tui.fatal(`Failed to save skill: ${msg}`, ErrorCode.NETWORK_ERROR);
82
+ }
83
+ },
84
+ });