@agentuity/cli 2.0.14 → 2.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-detection.d.ts.map +1 -1
- package/dist/agent-detection.js +3 -6
- package/dist/agent-detection.js.map +1 -1
- package/dist/ai-help.js +10 -10
- package/dist/ai-help.js.map +1 -1
- package/dist/cmd/ai/capabilities/show.d.ts.map +1 -1
- package/dist/cmd/ai/capabilities/show.js +6 -0
- package/dist/cmd/ai/capabilities/show.js.map +1 -1
- package/dist/cmd/ai/intro.d.ts.map +1 -1
- package/dist/cmd/ai/intro.js +1 -0
- package/dist/cmd/ai/intro.js.map +1 -1
- package/dist/cmd/cloud/aigateway/complete.d.ts +7 -0
- package/dist/cmd/cloud/aigateway/complete.d.ts.map +1 -0
- package/dist/cmd/cloud/aigateway/complete.js +386 -0
- package/dist/cmd/cloud/aigateway/complete.js.map +1 -0
- package/dist/cmd/cloud/aigateway/index.d.ts +3 -0
- package/dist/cmd/cloud/aigateway/index.d.ts.map +1 -0
- package/dist/cmd/cloud/aigateway/index.js +20 -0
- package/dist/cmd/cloud/aigateway/index.js.map +1 -0
- package/dist/cmd/cloud/aigateway/model-cache.d.ts +4 -0
- package/dist/cmd/cloud/aigateway/model-cache.d.ts.map +1 -0
- package/dist/cmd/cloud/aigateway/model-cache.js +72 -0
- package/dist/cmd/cloud/aigateway/model-cache.js.map +1 -0
- package/dist/cmd/cloud/aigateway/models.d.ts +2 -0
- package/dist/cmd/cloud/aigateway/models.d.ts.map +1 -0
- package/dist/cmd/cloud/aigateway/models.js +193 -0
- package/dist/cmd/cloud/aigateway/models.js.map +1 -0
- package/dist/cmd/cloud/aigateway/util.d.ts +20 -0
- package/dist/cmd/cloud/aigateway/util.d.ts.map +1 -0
- package/dist/cmd/cloud/aigateway/util.js +58 -0
- package/dist/cmd/cloud/aigateway/util.js.map +1 -0
- package/dist/cmd/cloud/index.d.ts.map +1 -1
- package/dist/cmd/cloud/index.js +2 -0
- package/dist/cmd/cloud/index.js.map +1 -1
- package/dist/cmd/coder/skill/create.d.ts +2 -0
- package/dist/cmd/coder/skill/create.d.ts.map +1 -0
- package/dist/cmd/coder/skill/create.js +104 -0
- package/dist/cmd/coder/skill/create.js.map +1 -0
- package/dist/cmd/coder/skill/index.d.ts.map +1 -1
- package/dist/cmd/coder/skill/index.js +12 -1
- package/dist/cmd/coder/skill/index.js.map +1 -1
- package/dist/cmd/coder/workspace/common.d.ts +22 -2
- package/dist/cmd/coder/workspace/common.d.ts.map +1 -1
- package/dist/cmd/coder/workspace/common.js +38 -2
- package/dist/cmd/coder/workspace/common.js.map +1 -1
- package/dist/cmd/coder/workspace/create.d.ts.map +1 -1
- package/dist/cmd/coder/workspace/create.js +34 -2
- package/dist/cmd/coder/workspace/create.js.map +1 -1
- package/dist/cmd/coder/workspace/update.d.ts.map +1 -1
- package/dist/cmd/coder/workspace/update.js +33 -1
- package/dist/cmd/coder/workspace/update.js.map +1 -1
- package/dist/cmd/dev/download.d.ts +8 -0
- package/dist/cmd/dev/download.d.ts.map +1 -1
- package/dist/cmd/dev/download.js +27 -1
- package/dist/cmd/dev/download.js.map +1 -1
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/index.js +18 -7
- package/dist/cmd/dev/index.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/types.d.ts +3 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/package.json +7 -7
- package/src/agent-detection.ts +3 -6
- package/src/ai-help.ts +10 -10
- package/src/cmd/ai/capabilities/show.ts +6 -0
- package/src/cmd/ai/intro.ts +1 -0
- package/src/cmd/cloud/aigateway/complete.ts +461 -0
- package/src/cmd/cloud/aigateway/index.ts +21 -0
- package/src/cmd/cloud/aigateway/model-cache.ts +89 -0
- package/src/cmd/cloud/aigateway/models.ts +219 -0
- package/src/cmd/cloud/aigateway/util.ts +86 -0
- package/src/cmd/cloud/index.ts +2 -0
- package/src/cmd/coder/skill/create.ts +122 -0
- package/src/cmd/coder/skill/index.ts +14 -1
- package/src/cmd/coder/workspace/common.ts +46 -2
- package/src/cmd/coder/workspace/create.ts +34 -1
- package/src/cmd/coder/workspace/update.ts +33 -0
- package/src/cmd/dev/download.ts +32 -1
- package/src/cmd/dev/index.ts +24 -8
- package/src/config.ts +3 -0
- package/src/types.ts +1 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createCommand } from '../../../types';
|
|
3
|
+
import * as tui from '../../../tui';
|
|
4
|
+
import { getCommand } from '../../../command-prefix';
|
|
5
|
+
import { getExecutingAgent } from '../../../agent-detection';
|
|
6
|
+
import { createPublicAIGatewayService, getAIGatewayUrl } from './util';
|
|
7
|
+
import { getCachedAIGatewayModels, setCachedAIGatewayModels } from './model-cache';
|
|
8
|
+
|
|
9
|
+
const ModelRowSchema = z.object({
|
|
10
|
+
provider: z.string(),
|
|
11
|
+
id: z.string(),
|
|
12
|
+
name: z.string(),
|
|
13
|
+
api: z.string().optional(),
|
|
14
|
+
reasoning: z.boolean().optional(),
|
|
15
|
+
contextWindow: z.number().optional(),
|
|
16
|
+
maxOutputTokens: z.number().optional(),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const ModelsResponseSchema = z.object({
|
|
20
|
+
models: z.array(ModelRowSchema),
|
|
21
|
+
count: z.number(),
|
|
22
|
+
model: ModelRowSchema.nullable().optional(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const recommendedModels = [
|
|
26
|
+
{ use: 'fast', candidates: ['openai/gpt-4o-mini', 'openai/gpt-4.1-mini'] },
|
|
27
|
+
{ use: 'reasoning', candidates: ['openai/gpt-5-mini', 'openai/o4-mini'] },
|
|
28
|
+
{ use: 'coding', candidates: ['anthropic/claude-opus-4-7', 'openai/gpt-5-codex'] },
|
|
29
|
+
{ use: 'cheap', candidates: ['openai/gpt-4.1-nano', 'openai/gpt-5-nano'] },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
function isAgentOutputMode(): boolean {
|
|
33
|
+
return Boolean(getExecutingAgent()) && process.env.AGENTUITY_AIGATEWAY_AGENT_OUTPUT !== 'false';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getRecommendations(rows: z.infer<typeof ModelRowSchema>[]) {
|
|
37
|
+
const byId = new Map(rows.map((row) => [normalizeModelId(row.id), row]));
|
|
38
|
+
return recommendedModels
|
|
39
|
+
.map((rec) => {
|
|
40
|
+
const model = rec.candidates.map((id) => byId.get(normalizeModelId(id))).find(Boolean);
|
|
41
|
+
return model ? { use: rec.use, model: model.id, name: model.name } : undefined;
|
|
42
|
+
})
|
|
43
|
+
.filter((row): row is { use: string; model: string; name: string } => Boolean(row));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizeModelId(id: string): string {
|
|
47
|
+
const normalized = id.toLowerCase();
|
|
48
|
+
const parts = normalized.split('/');
|
|
49
|
+
return parts.length > 1 ? (parts.at(-1) ?? normalized) : normalized;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function matchesProviderFilter(
|
|
53
|
+
provider: string,
|
|
54
|
+
modelId: string,
|
|
55
|
+
providerFilter?: string
|
|
56
|
+
): boolean {
|
|
57
|
+
if (!providerFilter) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
return provider === providerFilter || modelId.startsWith(`${providerFilter}/`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function matchesModelFilter(provider: string, modelId: string, modelFilter?: string): boolean {
|
|
64
|
+
if (!modelFilter) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
return modelId === modelFilter || `${provider}/${modelId}` === modelFilter;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function matchesNameFilter(modelId: string, modelName: string, nameFilter?: string): boolean {
|
|
71
|
+
if (!nameFilter) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
const normalized = nameFilter.toLowerCase();
|
|
75
|
+
return (
|
|
76
|
+
modelId.toLowerCase() === normalized ||
|
|
77
|
+
modelId.split('/').pop()?.toLowerCase() === normalized ||
|
|
78
|
+
modelName.toLowerCase() === normalized
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const modelsSubcommand = createCommand({
|
|
83
|
+
name: 'models',
|
|
84
|
+
aliases: ['list', 'ls'],
|
|
85
|
+
description: 'List AI Gateway models',
|
|
86
|
+
tags: ['read-only', 'fast'],
|
|
87
|
+
idempotent: true,
|
|
88
|
+
examples: [
|
|
89
|
+
{ command: getCommand('cloud aigateway models'), description: 'List all models' },
|
|
90
|
+
{
|
|
91
|
+
command: getCommand('cloud aigateway models --provider openai'),
|
|
92
|
+
description: 'List OpenAI models',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
command: getCommand('cloud aigateway models --model anthropic/claude-opus-4-7'),
|
|
96
|
+
description: 'Show one model by id',
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
schema: {
|
|
100
|
+
options: z.object({
|
|
101
|
+
model: z.string().optional().describe('show one model by full provider/id'),
|
|
102
|
+
provider: z.string().optional().describe('filter by provider'),
|
|
103
|
+
name: z
|
|
104
|
+
.string()
|
|
105
|
+
.optional()
|
|
106
|
+
.describe('show one model by id or display name with --provider'),
|
|
107
|
+
reasoning: z.boolean().optional().describe('only show reasoning models'),
|
|
108
|
+
input: z.string().optional().describe('filter by input modality, such as text or image'),
|
|
109
|
+
output: z.string().optional().describe('filter by output modality, such as text or image'),
|
|
110
|
+
ids: z.boolean().optional().describe('only print model ids'),
|
|
111
|
+
simple: z.boolean().optional().describe('print a compact model list'),
|
|
112
|
+
recommended: z.boolean().optional().describe('show recommended models for common uses'),
|
|
113
|
+
refreshModels: z
|
|
114
|
+
.boolean()
|
|
115
|
+
.optional()
|
|
116
|
+
.describe('refresh the cached AI Gateway model catalog'),
|
|
117
|
+
}),
|
|
118
|
+
response: ModelsResponseSchema,
|
|
119
|
+
},
|
|
120
|
+
async handler(ctx) {
|
|
121
|
+
const service = createPublicAIGatewayService(ctx);
|
|
122
|
+
const profile = ctx.config?.name ?? 'default';
|
|
123
|
+
const cacheKey = getAIGatewayUrl(ctx.region, ctx.config?.overrides);
|
|
124
|
+
const cached = ctx.opts.refreshModels
|
|
125
|
+
? null
|
|
126
|
+
: await getCachedAIGatewayModels(profile, cacheKey);
|
|
127
|
+
const catalog = cached ?? (await service.listModels());
|
|
128
|
+
if (!cached) {
|
|
129
|
+
await setCachedAIGatewayModels(profile, cacheKey, catalog);
|
|
130
|
+
}
|
|
131
|
+
const rows = Object.entries(catalog).flatMap(([provider, models]) =>
|
|
132
|
+
models
|
|
133
|
+
.filter((model) => matchesProviderFilter(provider, model.id, ctx.opts.provider))
|
|
134
|
+
.filter((model) => matchesModelFilter(provider, model.id, ctx.opts.model))
|
|
135
|
+
.filter((model) => matchesNameFilter(model.id, model.name, ctx.opts.name))
|
|
136
|
+
.filter((model) => !ctx.opts.reasoning || model.reasoning)
|
|
137
|
+
.filter((model) => !ctx.opts.input || model.input_modalities?.includes(ctx.opts.input))
|
|
138
|
+
.filter(
|
|
139
|
+
(model) => !ctx.opts.output || model.output_modalities?.includes(ctx.opts.output)
|
|
140
|
+
)
|
|
141
|
+
.map((model) => ({
|
|
142
|
+
provider,
|
|
143
|
+
id: model.id,
|
|
144
|
+
name: model.name,
|
|
145
|
+
api: model.api,
|
|
146
|
+
reasoning: model.reasoning,
|
|
147
|
+
contextWindow: model.context_window,
|
|
148
|
+
maxOutputTokens: model.max_output_tokens,
|
|
149
|
+
}))
|
|
150
|
+
);
|
|
151
|
+
const singleLookup = Boolean(ctx.opts.model || ctx.opts.name);
|
|
152
|
+
const selectedModel = singleLookup ? (rows[0] ?? null) : undefined;
|
|
153
|
+
|
|
154
|
+
const agentOutput = isAgentOutputMode();
|
|
155
|
+
if (ctx.options.json || agentOutput) {
|
|
156
|
+
if (agentOutput && !ctx.options.json) {
|
|
157
|
+
if (ctx.opts.ids) {
|
|
158
|
+
console.log(
|
|
159
|
+
JSON.stringify({ ids: rows.map((row) => row.id), count: rows.length }, null, 2)
|
|
160
|
+
);
|
|
161
|
+
} else if (ctx.opts.recommended) {
|
|
162
|
+
console.log(JSON.stringify({ recommendations: getRecommendations(rows) }, null, 2));
|
|
163
|
+
} else if (singleLookup) {
|
|
164
|
+
console.log(
|
|
165
|
+
JSON.stringify(
|
|
166
|
+
{ model: selectedModel, models: rows, count: rows.length },
|
|
167
|
+
null,
|
|
168
|
+
2
|
|
169
|
+
)
|
|
170
|
+
);
|
|
171
|
+
} else {
|
|
172
|
+
console.log(JSON.stringify({ models: rows, count: rows.length }, null, 2));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
if (rows.length === 0) {
|
|
177
|
+
tui.info('No AI Gateway models found');
|
|
178
|
+
} else if (ctx.opts.ids) {
|
|
179
|
+
for (const row of rows) {
|
|
180
|
+
console.log(row.id);
|
|
181
|
+
}
|
|
182
|
+
} else if (ctx.opts.recommended) {
|
|
183
|
+
const recommendations = getRecommendations(rows).map((row) => ({
|
|
184
|
+
Use: row.use,
|
|
185
|
+
Model: row.model,
|
|
186
|
+
Name: row.name,
|
|
187
|
+
}));
|
|
188
|
+
if (recommendations.length === 0) {
|
|
189
|
+
tui.info('No recommended AI Gateway models found');
|
|
190
|
+
} else {
|
|
191
|
+
tui.table(recommendations, ['Use', 'Model', 'Name']);
|
|
192
|
+
}
|
|
193
|
+
} else if (ctx.opts.simple) {
|
|
194
|
+
tui.table(
|
|
195
|
+
rows.map((row) => ({
|
|
196
|
+
Model: row.id,
|
|
197
|
+
Name: row.name,
|
|
198
|
+
})),
|
|
199
|
+
['Model', 'Name']
|
|
200
|
+
);
|
|
201
|
+
} else {
|
|
202
|
+
tui.info(`Found ${rows.length} AI Gateway model(s):`);
|
|
203
|
+
tui.table(
|
|
204
|
+
rows.map((row) => ({
|
|
205
|
+
Provider: row.provider,
|
|
206
|
+
Model: row.id,
|
|
207
|
+
Name: row.name,
|
|
208
|
+
API: row.api ?? '-',
|
|
209
|
+
Reasoning: row.reasoning ? 'yes' : 'no',
|
|
210
|
+
Context: row.contextWindow ?? '-',
|
|
211
|
+
})),
|
|
212
|
+
['Provider', 'Model', 'Name', 'API', 'Reasoning', 'Context']
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return { models: rows, count: rows.length, model: selectedModel };
|
|
218
|
+
},
|
|
219
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { AIGatewayService, type Logger } from '@agentuity/core';
|
|
2
|
+
import { createServerFetchAdapter, getServiceUrls } from '@agentuity/server';
|
|
3
|
+
import * as tui from '../../../tui';
|
|
4
|
+
import type { AuthData, Config, GlobalOptions, ProjectConfig } from '../../../types';
|
|
5
|
+
|
|
6
|
+
const defaultAIGatewayRegion = 'usc';
|
|
7
|
+
|
|
8
|
+
export function getAIGatewayUrl(
|
|
9
|
+
region?: string,
|
|
10
|
+
overrides?: { aigateway_url?: string } | null
|
|
11
|
+
): string {
|
|
12
|
+
if (process.env.AGENTUITY_AIGATEWAY_URL) {
|
|
13
|
+
return process.env.AGENTUITY_AIGATEWAY_URL;
|
|
14
|
+
}
|
|
15
|
+
if (overrides?.aigateway_url) {
|
|
16
|
+
return overrides.aigateway_url;
|
|
17
|
+
}
|
|
18
|
+
return getServiceUrls(region || process.env.AGENTUITY_REGION || defaultAIGatewayRegion)
|
|
19
|
+
.aigateway;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createAIGatewayService(ctx: {
|
|
23
|
+
logger: Logger;
|
|
24
|
+
auth: AuthData;
|
|
25
|
+
region?: string;
|
|
26
|
+
project?: ProjectConfig;
|
|
27
|
+
config: Config | null;
|
|
28
|
+
options: GlobalOptions;
|
|
29
|
+
}) {
|
|
30
|
+
const orgId =
|
|
31
|
+
ctx.project?.orgId ??
|
|
32
|
+
ctx.options.orgId ??
|
|
33
|
+
(process.env.AGENTUITY_CLOUD_ORG_ID || ctx.config?.preferences?.orgId);
|
|
34
|
+
if (!orgId) {
|
|
35
|
+
tui.fatal(
|
|
36
|
+
'Organization ID is required. Either run from a project directory or use --org-id flag.'
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const adapter = createServerFetchAdapter(
|
|
41
|
+
{
|
|
42
|
+
headers: {
|
|
43
|
+
Authorization: `Bearer ${ctx.auth.apiKey}`,
|
|
44
|
+
'x-agentuity-orgid': orgId,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
ctx.logger
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
return new AIGatewayService(getAIGatewayUrl(ctx.region, ctx.config?.overrides), adapter);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function createPublicAIGatewayService(ctx: {
|
|
54
|
+
logger: Logger;
|
|
55
|
+
region?: string;
|
|
56
|
+
config: Config | null;
|
|
57
|
+
}) {
|
|
58
|
+
const adapter = createServerFetchAdapter({ headers: {} }, ctx.logger);
|
|
59
|
+
return new AIGatewayService(getAIGatewayUrl(ctx.region, ctx.config?.overrides), adapter);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function getCompletionText(response: unknown): string {
|
|
63
|
+
const choices = (response as { choices?: unknown }).choices;
|
|
64
|
+
const first =
|
|
65
|
+
Array.isArray(choices) && choices.length > 0
|
|
66
|
+
? (choices[0] as { message?: { content?: unknown }; text?: unknown; delta?: unknown })
|
|
67
|
+
: undefined;
|
|
68
|
+
const content =
|
|
69
|
+
first?.message?.content ?? first?.text ?? (response as { content?: unknown }).content;
|
|
70
|
+
if (typeof content === 'string') {
|
|
71
|
+
return content;
|
|
72
|
+
}
|
|
73
|
+
if (Array.isArray(content)) {
|
|
74
|
+
return content
|
|
75
|
+
.map((part) => {
|
|
76
|
+
if (typeof part === 'string') return part;
|
|
77
|
+
if (part && typeof part === 'object' && 'text' in part) {
|
|
78
|
+
const text = (part as { text?: unknown }).text;
|
|
79
|
+
return typeof text === 'string' ? text : '';
|
|
80
|
+
}
|
|
81
|
+
return '';
|
|
82
|
+
})
|
|
83
|
+
.join('');
|
|
84
|
+
}
|
|
85
|
+
return '';
|
|
86
|
+
}
|
package/src/cmd/cloud/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import webhookCommand from './webhook';
|
|
|
14
14
|
import { agentCommand } from './agent';
|
|
15
15
|
import envCommand from './env';
|
|
16
16
|
import apikeyCommand from './apikey';
|
|
17
|
+
import { aigatewayCommand } from './aigateway';
|
|
17
18
|
import oidcCommand from './oidc';
|
|
18
19
|
import streamCommand from './stream';
|
|
19
20
|
import vectorCommand from './vector';
|
|
@@ -41,6 +42,7 @@ export const command = createCommand({
|
|
|
41
42
|
],
|
|
42
43
|
subcommands: [
|
|
43
44
|
apikeyCommand,
|
|
45
|
+
aigatewayCommand,
|
|
44
46
|
oidcCommand,
|
|
45
47
|
keyvalueCommand,
|
|
46
48
|
queueCommand,
|
|
@@ -0,0 +1,122 @@
|
|
|
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
|
+
async function readSkillContent(input: {
|
|
10
|
+
content?: string;
|
|
11
|
+
contentFile?: string;
|
|
12
|
+
}): Promise<string> {
|
|
13
|
+
if (input.content !== undefined && input.contentFile) {
|
|
14
|
+
throw new Error('Use either --content or --content-file, not both.');
|
|
15
|
+
}
|
|
16
|
+
if (input.content !== undefined) return input.content;
|
|
17
|
+
if (!input.contentFile) {
|
|
18
|
+
throw new Error('Provide --content or --content-file.');
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
return await Bun.file(input.contentFile).text();
|
|
22
|
+
} catch (error) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`Failed to read content file "${input.contentFile}": ${
|
|
25
|
+
error instanceof Error ? error.message : String(error)
|
|
26
|
+
}`,
|
|
27
|
+
{ cause: error }
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const createCustomSkillSubcommand = createSubcommand({
|
|
33
|
+
name: 'create',
|
|
34
|
+
aliases: ['new'],
|
|
35
|
+
description: 'Create a custom SKILL.md-backed skill',
|
|
36
|
+
tags: ['mutating', 'requires-auth'],
|
|
37
|
+
requires: { auth: true, org: true },
|
|
38
|
+
examples: [
|
|
39
|
+
{
|
|
40
|
+
command: getCommand(
|
|
41
|
+
'coder skill create --skill-id release-checklist --name "Release checklist" --content-file ./SKILL.md'
|
|
42
|
+
),
|
|
43
|
+
description: 'Create a custom skill from a SKILL.md file',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
command: getCommand(
|
|
47
|
+
'coder skill create --skill-id release-checklist --name "Release checklist" --content "# Release checklist" --json'
|
|
48
|
+
),
|
|
49
|
+
description: 'Create a custom skill from inline content and return JSON',
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
schema: {
|
|
53
|
+
options: z.object({
|
|
54
|
+
url: z.string().optional().describe('Coder API URL override'),
|
|
55
|
+
skillId: z.string().describe('Skill identifier'),
|
|
56
|
+
name: z.string().describe('Skill name'),
|
|
57
|
+
description: z.string().optional().describe('Skill description'),
|
|
58
|
+
content: z.string().optional().describe('Inline SKILL.md content'),
|
|
59
|
+
contentFile: z.string().optional().describe('Path to a SKILL.md file'),
|
|
60
|
+
}),
|
|
61
|
+
},
|
|
62
|
+
async handler(ctx) {
|
|
63
|
+
const { opts, options } = ctx;
|
|
64
|
+
const client = new CoderClient({
|
|
65
|
+
apiKey: ctx.auth.apiKey,
|
|
66
|
+
url: opts?.url,
|
|
67
|
+
orgId: ctx.orgId,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
let content: string;
|
|
71
|
+
try {
|
|
72
|
+
content = await readSkillContent({
|
|
73
|
+
content: opts?.content,
|
|
74
|
+
contentFile: opts?.contentFile,
|
|
75
|
+
});
|
|
76
|
+
} catch (err) {
|
|
77
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
78
|
+
tui.fatal(`Failed to create custom skill: ${msg}`, ErrorCode.VALIDATION_FAILED);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!content.trim()) {
|
|
83
|
+
tui.fatal(
|
|
84
|
+
'Failed to create custom skill: SKILL.md content cannot be empty.',
|
|
85
|
+
ErrorCode.VALIDATION_FAILED
|
|
86
|
+
);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const saved = await client.createCustomSkill({
|
|
92
|
+
skillId: opts.skillId,
|
|
93
|
+
name: opts.name,
|
|
94
|
+
...(opts?.description !== undefined ? { description: opts.description } : {}),
|
|
95
|
+
content,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (options.json) {
|
|
99
|
+
return saved;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
tui.success(`Custom skill ${saved.id} created.`);
|
|
103
|
+
tui.newline();
|
|
104
|
+
tui.output(` Name: ${tui.bold(saved.name)}`);
|
|
105
|
+
tui.output(` Skill ID: ${saved.skillId}`);
|
|
106
|
+
tui.output(` Source: ${saved.source}`);
|
|
107
|
+
if (saved.description) {
|
|
108
|
+
tui.output(` Desc: ${saved.description}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return saved;
|
|
112
|
+
} catch (err) {
|
|
113
|
+
if (err instanceof ValidationOutputError) {
|
|
114
|
+
ctx.logger.trace('Validation response URL: %s', err.url ?? 'unknown');
|
|
115
|
+
ctx.logger.trace('Validation issues: %s', JSON.stringify(err.issues, null, 2));
|
|
116
|
+
tui.fatal(`Failed to create custom skill: ${err.message}`, ErrorCode.VALIDATION_FAILED);
|
|
117
|
+
}
|
|
118
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
119
|
+
tui.fatal(`Failed to create custom skill: ${msg}`, ErrorCode.NETWORK_ERROR);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createCommand } from '../../../types';
|
|
2
2
|
import { listSubcommand } from './list';
|
|
3
|
+
import { createCustomSkillSubcommand } from './create';
|
|
3
4
|
import { saveSkillSubcommand } from './save';
|
|
4
5
|
import { deleteSkillSubcommand } from './delete';
|
|
5
6
|
import { bucketsSubcommand } from './buckets';
|
|
@@ -16,6 +17,12 @@ export const skillCommand = createCommand({
|
|
|
16
17
|
command: getCommand('coder skill list'),
|
|
17
18
|
description: 'List saved skills',
|
|
18
19
|
},
|
|
20
|
+
{
|
|
21
|
+
command: getCommand(
|
|
22
|
+
'coder skill create --skill-id release-checklist --name "Release checklist" --content-file ./SKILL.md'
|
|
23
|
+
),
|
|
24
|
+
description: 'Create a custom skill',
|
|
25
|
+
},
|
|
19
26
|
{
|
|
20
27
|
command: getCommand(
|
|
21
28
|
'coder skill save --repo org/repo --skill-id sk_abc --name "My Skill"'
|
|
@@ -31,5 +38,11 @@ export const skillCommand = createCommand({
|
|
|
31
38
|
description: 'List skill buckets',
|
|
32
39
|
},
|
|
33
40
|
],
|
|
34
|
-
subcommands: [
|
|
41
|
+
subcommands: [
|
|
42
|
+
listSubcommand,
|
|
43
|
+
createCustomSkillSubcommand,
|
|
44
|
+
saveSkillSubcommand,
|
|
45
|
+
deleteSkillSubcommand,
|
|
46
|
+
bucketsSubcommand,
|
|
47
|
+
],
|
|
35
48
|
});
|
|
@@ -2,16 +2,32 @@ import {
|
|
|
2
2
|
type CoderCreateWorkspaceRequest,
|
|
3
3
|
type CoderUpdateWorkspaceRequest,
|
|
4
4
|
type CoderWorkspaceDetail,
|
|
5
|
+
type CoderWorkspaceSystemPromptMode,
|
|
5
6
|
} from '@agentuity/core/coder';
|
|
6
7
|
import { StructuredError } from '@agentuity/core';
|
|
7
8
|
import * as tui from '../../../tui';
|
|
8
9
|
|
|
9
10
|
export const EMPTY_WORKSPACE_ERROR =
|
|
10
|
-
'A workspace needs at least one repo, dependency, setup script, saved skill, skill bucket, or agent';
|
|
11
|
+
'A workspace needs at least one repo, dependency, setup script, system prompt, saved skill, skill bucket, or agent';
|
|
11
12
|
export const SetupScriptValidationError = StructuredError('SetupScriptValidationError')<{
|
|
12
13
|
message: string;
|
|
13
14
|
path?: string;
|
|
14
15
|
}>();
|
|
16
|
+
export const SystemPromptValidationError = StructuredError('SystemPromptValidationError')<{
|
|
17
|
+
message: string;
|
|
18
|
+
path?: string;
|
|
19
|
+
}>();
|
|
20
|
+
|
|
21
|
+
export function normalizeSystemPromptMode(
|
|
22
|
+
value?: string
|
|
23
|
+
): CoderWorkspaceSystemPromptMode | undefined {
|
|
24
|
+
if (value === undefined) return undefined;
|
|
25
|
+
const normalized = value.trim().toLowerCase();
|
|
26
|
+
if (normalized === 'append' || normalized === 'overwrite') return normalized;
|
|
27
|
+
throw new SystemPromptValidationError({
|
|
28
|
+
message: 'Use --system-prompt-mode append or --system-prompt-mode overwrite.',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
15
31
|
|
|
16
32
|
export function parseCommaList(value?: string): string[] {
|
|
17
33
|
return value
|
|
@@ -46,11 +62,36 @@ export async function readSetupScript(input: {
|
|
|
46
62
|
}
|
|
47
63
|
}
|
|
48
64
|
|
|
65
|
+
export async function readSystemPrompt(input: {
|
|
66
|
+
systemPrompt?: string;
|
|
67
|
+
systemPromptFile?: string;
|
|
68
|
+
}): Promise<string | undefined> {
|
|
69
|
+
if (input.systemPrompt !== undefined && input.systemPromptFile) {
|
|
70
|
+
throw new SystemPromptValidationError({
|
|
71
|
+
message: 'Use either --system-prompt or --system-prompt-file, not both.',
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
if (input.systemPrompt !== undefined) return input.systemPrompt;
|
|
75
|
+
if (!input.systemPromptFile) return undefined;
|
|
76
|
+
try {
|
|
77
|
+
return await Bun.file(input.systemPromptFile).text();
|
|
78
|
+
} catch (error) {
|
|
79
|
+
throw new SystemPromptValidationError({
|
|
80
|
+
message: `Failed to read system prompt file "${input.systemPromptFile}": ${
|
|
81
|
+
error instanceof Error ? error.message : String(error)
|
|
82
|
+
}`,
|
|
83
|
+
path: input.systemPromptFile,
|
|
84
|
+
cause: error,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
49
89
|
export function hasWorkspaceSelections(input: CoderCreateWorkspaceRequest): boolean {
|
|
50
90
|
return (
|
|
51
91
|
(input.repos?.length ?? 0) > 0 ||
|
|
52
92
|
(input.dependencies?.length ?? 0) > 0 ||
|
|
53
93
|
Boolean(input.setupScript?.trim()) ||
|
|
94
|
+
Boolean(input.systemPrompt?.trim()) ||
|
|
54
95
|
(input.savedSkillIds?.length ?? 0) > 0 ||
|
|
55
96
|
(input.skillBucketIds?.length ?? 0) > 0 ||
|
|
56
97
|
(input.enabledAgents?.length ?? 0) > 0
|
|
@@ -67,7 +108,7 @@ export function formatWorkspaceValidationMessage(issues: Array<{ message: string
|
|
|
67
108
|
return 'Invalid workspace configuration';
|
|
68
109
|
}
|
|
69
110
|
if (messages.includes(EMPTY_WORKSPACE_ERROR)) {
|
|
70
|
-
return `${EMPTY_WORKSPACE_ERROR}. Use --repo, --dependency, --setup-script, or --enabled-agents.`;
|
|
111
|
+
return `${EMPTY_WORKSPACE_ERROR}. Use --repo, --dependency, --setup-script, --system-prompt, or --enabled-agents.`;
|
|
71
112
|
}
|
|
72
113
|
return messages.join('; ');
|
|
73
114
|
}
|
|
@@ -94,6 +135,9 @@ export function printWorkspaceSummary(workspace: CoderWorkspaceDetail): void {
|
|
|
94
135
|
if (workspace.setupScript) {
|
|
95
136
|
tui.output(' Setup: configured');
|
|
96
137
|
}
|
|
138
|
+
if (workspace.systemPrompt) {
|
|
139
|
+
tui.output(` Prompt: configured (${workspace.systemPromptMode})`);
|
|
140
|
+
}
|
|
97
141
|
if (workspace.snapshot?.status) {
|
|
98
142
|
tui.output(` Snapshot: ${workspace.snapshot.status}`);
|
|
99
143
|
}
|
|
@@ -14,8 +14,10 @@ import {
|
|
|
14
14
|
EMPTY_WORKSPACE_ERROR,
|
|
15
15
|
formatWorkspaceValidationMessage,
|
|
16
16
|
hasWorkspaceSelections,
|
|
17
|
+
normalizeSystemPromptMode,
|
|
17
18
|
parseCommaList,
|
|
18
19
|
printWorkspaceSummary,
|
|
20
|
+
readSystemPrompt,
|
|
19
21
|
readSetupScript,
|
|
20
22
|
} from './common';
|
|
21
23
|
|
|
@@ -38,6 +40,12 @@ export const createWorkspaceSubcommand = createSubcommand({
|
|
|
38
40
|
),
|
|
39
41
|
description: 'Create an org-scoped workspace with dependencies and a setup script',
|
|
40
42
|
},
|
|
43
|
+
{
|
|
44
|
+
command: getCommand(
|
|
45
|
+
'coder workspace create "My Workspace" --system-prompt-file ./WORKSPACE_PROMPT.md --system-prompt-mode overwrite'
|
|
46
|
+
),
|
|
47
|
+
description: 'Create a workspace with Lead system prompt instructions',
|
|
48
|
+
},
|
|
41
49
|
{
|
|
42
50
|
command: getCommand('coder workspace create "My Workspace" --enabled-agents code-review'),
|
|
43
51
|
description: 'Create a workspace with an agent roster',
|
|
@@ -71,6 +79,18 @@ export const createWorkspaceSubcommand = createSubcommand({
|
|
|
71
79
|
.string()
|
|
72
80
|
.optional()
|
|
73
81
|
.describe('Path to a shell script to run while preparing workspace snapshots'),
|
|
82
|
+
systemPrompt: z
|
|
83
|
+
.string()
|
|
84
|
+
.optional()
|
|
85
|
+
.describe('Inline Lead system prompt to apply to sessions created from this workspace'),
|
|
86
|
+
systemPromptFile: z
|
|
87
|
+
.string()
|
|
88
|
+
.optional()
|
|
89
|
+
.describe('Path to a file containing the workspace Lead system prompt'),
|
|
90
|
+
systemPromptMode: z
|
|
91
|
+
.string()
|
|
92
|
+
.optional()
|
|
93
|
+
.describe('How to apply the system prompt: append or overwrite'),
|
|
74
94
|
enabledAgents: z
|
|
75
95
|
.string()
|
|
76
96
|
.optional()
|
|
@@ -116,12 +136,25 @@ export const createWorkspaceSubcommand = createSubcommand({
|
|
|
116
136
|
tui.fatal(`Failed to read setup script: ${msg}`, ErrorCode.VALIDATION_FAILED);
|
|
117
137
|
return;
|
|
118
138
|
}
|
|
139
|
+
try {
|
|
140
|
+
const systemPrompt = await readSystemPrompt({
|
|
141
|
+
systemPrompt: opts?.systemPrompt,
|
|
142
|
+
systemPromptFile: opts?.systemPromptFile,
|
|
143
|
+
});
|
|
144
|
+
if (systemPrompt !== undefined) body.systemPrompt = systemPrompt;
|
|
145
|
+
const systemPromptMode = normalizeSystemPromptMode(opts?.systemPromptMode);
|
|
146
|
+
if (systemPromptMode !== undefined) body.systemPromptMode = systemPromptMode;
|
|
147
|
+
} catch (err) {
|
|
148
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
149
|
+
tui.fatal(`Failed to read system prompt: ${msg}`, ErrorCode.VALIDATION_FAILED);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
119
152
|
if (opts?.enabledAgents) {
|
|
120
153
|
body.enabledAgents = parseCommaList(opts.enabledAgents);
|
|
121
154
|
}
|
|
122
155
|
if (!hasWorkspaceSelections(body)) {
|
|
123
156
|
tui.fatal(
|
|
124
|
-
`Failed to create workspace: ${EMPTY_WORKSPACE_ERROR}. Use --repo, --dependency, --setup-script, or --enabled-agents.`,
|
|
157
|
+
`Failed to create workspace: ${EMPTY_WORKSPACE_ERROR}. Use --repo, --dependency, --setup-script, --system-prompt, or --enabled-agents.`,
|
|
125
158
|
ErrorCode.VALIDATION_FAILED
|
|
126
159
|
);
|
|
127
160
|
}
|
|
@@ -13,8 +13,10 @@ import { resolveGitHubRepo } from '../resolve-repo';
|
|
|
13
13
|
import {
|
|
14
14
|
formatWorkspaceValidationMessage,
|
|
15
15
|
hasWorkspaceUpdate,
|
|
16
|
+
normalizeSystemPromptMode,
|
|
16
17
|
parseCommaList,
|
|
17
18
|
printWorkspaceSummary,
|
|
19
|
+
readSystemPrompt,
|
|
18
20
|
readSetupScript,
|
|
19
21
|
} from './common';
|
|
20
22
|
|
|
@@ -33,6 +35,12 @@ export const updateWorkspaceSubcommand = createSubcommand({
|
|
|
33
35
|
command: getCommand('coder workspace update ws_abc123 --setup-script-file ./setup.sh'),
|
|
34
36
|
description: 'Update the workspace setup script',
|
|
35
37
|
},
|
|
38
|
+
{
|
|
39
|
+
command: getCommand(
|
|
40
|
+
'coder workspace update ws_abc123 --system-prompt-file ./WORKSPACE_PROMPT.md --system-prompt-mode append'
|
|
41
|
+
),
|
|
42
|
+
description: 'Update the workspace Lead system prompt',
|
|
43
|
+
},
|
|
36
44
|
],
|
|
37
45
|
schema: {
|
|
38
46
|
args: z.object({
|
|
@@ -57,6 +65,18 @@ export const updateWorkspaceSubcommand = createSubcommand({
|
|
|
57
65
|
.string()
|
|
58
66
|
.optional()
|
|
59
67
|
.describe('Path to a shell script to run while preparing workspace snapshots'),
|
|
68
|
+
systemPrompt: z
|
|
69
|
+
.string()
|
|
70
|
+
.optional()
|
|
71
|
+
.describe('Inline Lead system prompt to apply to sessions created from this workspace'),
|
|
72
|
+
systemPromptFile: z
|
|
73
|
+
.string()
|
|
74
|
+
.optional()
|
|
75
|
+
.describe('Path to a file containing the workspace Lead system prompt'),
|
|
76
|
+
systemPromptMode: z
|
|
77
|
+
.string()
|
|
78
|
+
.optional()
|
|
79
|
+
.describe('How to apply the system prompt: append or overwrite'),
|
|
60
80
|
enabledAgents: z
|
|
61
81
|
.string()
|
|
62
82
|
.optional()
|
|
@@ -102,6 +122,19 @@ export const updateWorkspaceSubcommand = createSubcommand({
|
|
|
102
122
|
tui.fatal(`Failed to read setup script: ${msg}`, ErrorCode.VALIDATION_FAILED);
|
|
103
123
|
return;
|
|
104
124
|
}
|
|
125
|
+
try {
|
|
126
|
+
const systemPrompt = await readSystemPrompt({
|
|
127
|
+
systemPrompt: opts?.systemPrompt,
|
|
128
|
+
systemPromptFile: opts?.systemPromptFile,
|
|
129
|
+
});
|
|
130
|
+
if (systemPrompt !== undefined) body.systemPrompt = systemPrompt;
|
|
131
|
+
const systemPromptMode = normalizeSystemPromptMode(opts?.systemPromptMode);
|
|
132
|
+
if (systemPromptMode !== undefined) body.systemPromptMode = systemPromptMode;
|
|
133
|
+
} catch (err) {
|
|
134
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
135
|
+
tui.fatal(`Failed to read system prompt: ${msg}`, ErrorCode.VALIDATION_FAILED);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
105
138
|
if (opts?.enabledAgents) {
|
|
106
139
|
body.enabledAgents = parseCommaList(opts.enabledAgents);
|
|
107
140
|
}
|