@agentuity/cli 2.0.5 → 2.0.7
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/README.md +11 -0
- package/dist/cmd/build/patch/otel-llm.js +2 -2
- package/dist/cmd/build/patch/otel-llm.js.map +1 -1
- package/dist/cmd/build/vite/bun-dev-server.d.ts.map +1 -1
- package/dist/cmd/build/vite/bun-dev-server.js +1 -0
- package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
- package/dist/cmd/build/vite/index.d.ts +0 -28
- package/dist/cmd/build/vite/index.d.ts.map +1 -1
- package/dist/cmd/build/vite/index.js +1 -104
- package/dist/cmd/build/vite/index.js.map +1 -1
- package/dist/cmd/build/vite/metadata-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/metadata-generator.js +8 -2
- package/dist/cmd/build/vite/metadata-generator.js.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server-config.d.ts +2 -0
- package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server-config.js +5 -1
- package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server.d.ts +2 -0
- package/dist/cmd/build/vite/vite-asset-server.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server.js +2 -1
- package/dist/cmd/build/vite/vite-asset-server.js.map +1 -1
- package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-builder.js +143 -2
- package/dist/cmd/build/vite/vite-builder.js.map +1 -1
- package/dist/cmd/cloud/task/close.d.ts +3 -0
- package/dist/cmd/cloud/task/close.d.ts.map +1 -0
- package/dist/cmd/cloud/task/close.js +286 -0
- package/dist/cmd/cloud/task/close.js.map +1 -0
- package/dist/cmd/cloud/task/delete.d.ts +1 -5
- package/dist/cmd/cloud/task/delete.d.ts.map +1 -1
- package/dist/cmd/cloud/task/delete.js +15 -38
- package/dist/cmd/cloud/task/delete.js.map +1 -1
- package/dist/cmd/cloud/task/index.d.ts.map +1 -1
- package/dist/cmd/cloud/task/index.js +10 -0
- package/dist/cmd/cloud/task/index.js.map +1 -1
- package/dist/cmd/cloud/task/list.d.ts.map +1 -1
- package/dist/cmd/cloud/task/list.js +97 -3
- package/dist/cmd/cloud/task/list.js.map +1 -1
- package/dist/cmd/cloud/task/util.d.ts +10 -0
- package/dist/cmd/cloud/task/util.d.ts.map +1 -1
- package/dist/cmd/cloud/task/util.js +47 -3
- package/dist/cmd/cloud/task/util.js.map +1 -1
- package/dist/cmd/coder/config/index.d.ts +2 -0
- package/dist/cmd/coder/config/index.d.ts.map +1 -0
- package/dist/cmd/coder/config/index.js +20 -0
- package/dist/cmd/coder/config/index.js.map +1 -0
- package/dist/cmd/coder/config/set.d.ts +2 -0
- package/dist/cmd/coder/config/set.d.ts.map +1 -0
- package/dist/cmd/coder/config/set.js +100 -0
- package/dist/cmd/coder/config/set.js.map +1 -0
- package/dist/cmd/coder/hub-url.d.ts +21 -10
- package/dist/cmd/coder/hub-url.d.ts.map +1 -1
- package/dist/cmd/coder/hub-url.js +97 -55
- package/dist/cmd/coder/hub-url.js.map +1 -1
- package/dist/cmd/coder/index.d.ts.map +1 -1
- package/dist/cmd/coder/index.js +6 -1
- package/dist/cmd/coder/index.js.map +1 -1
- package/dist/cmd/coder/inspect.d.ts.map +1 -1
- package/dist/cmd/coder/inspect.js +15 -7
- package/dist/cmd/coder/inspect.js.map +1 -1
- package/dist/cmd/coder/list.d.ts.map +1 -1
- package/dist/cmd/coder/list.js +14 -7
- package/dist/cmd/coder/list.js.map +1 -1
- package/dist/cmd/coder/start.d.ts.map +1 -1
- package/dist/cmd/coder/start.js +38 -23
- package/dist/cmd/coder/start.js.map +1 -1
- package/dist/cmd/coder/tui-init.d.ts +4 -1
- package/dist/cmd/coder/tui-init.d.ts.map +1 -1
- package/dist/cmd/coder/tui-init.js +3 -2
- package/dist/cmd/coder/tui-init.js.map +1 -1
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/index.js +1 -0
- package/dist/cmd/dev/index.js.map +1 -1
- package/dist/cmd/dev/sync.js +5 -5
- package/dist/cmd/dev/sync.js.map +1 -1
- package/dist/coder-config.d.ts +14 -0
- package/dist/coder-config.d.ts.map +1 -0
- package/dist/coder-config.js +119 -0
- package/dist/coder-config.js.map +1 -0
- package/dist/coder-hub-url.d.ts +3 -0
- package/dist/coder-hub-url.d.ts.map +1 -0
- package/dist/coder-hub-url.js +32 -0
- package/dist/coder-hub-url.js.map +1 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +11 -0
- package/dist/config.js.map +1 -1
- package/dist/internal-logger.d.ts +4 -0
- package/dist/internal-logger.d.ts.map +1 -1
- package/dist/internal-logger.js +64 -2
- package/dist/internal-logger.js.map +1 -1
- package/dist/keychain.d.ts +3 -0
- package/dist/keychain.d.ts.map +1 -1
- package/dist/keychain.js +47 -28
- package/dist/keychain.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -1
- package/package.json +6 -6
- package/src/cmd/build/patch/otel-llm.ts +2 -2
- package/src/cmd/build/vite/bun-dev-server.ts +1 -0
- package/src/cmd/build/vite/index.ts +1 -148
- package/src/cmd/build/vite/metadata-generator.ts +8 -2
- package/src/cmd/build/vite/vite-asset-server-config.ts +16 -1
- package/src/cmd/build/vite/vite-asset-server.ts +4 -0
- package/src/cmd/build/vite/vite-builder.ts +171 -9
- package/src/cmd/cloud/task/close.ts +319 -0
- package/src/cmd/cloud/task/delete.ts +15 -43
- package/src/cmd/cloud/task/index.ts +10 -0
- package/src/cmd/cloud/task/list.ts +111 -4
- package/src/cmd/cloud/task/util.ts +59 -5
- package/src/cmd/coder/config/index.ts +20 -0
- package/src/cmd/coder/config/set.ts +112 -0
- package/src/cmd/coder/hub-url.ts +147 -53
- package/src/cmd/coder/index.ts +6 -1
- package/src/cmd/coder/inspect.ts +33 -10
- package/src/cmd/coder/list.ts +33 -10
- package/src/cmd/coder/start.ts +62 -26
- package/src/cmd/coder/tui-init.ts +7 -2
- package/src/cmd/dev/index.ts +1 -0
- package/src/cmd/dev/sync.ts +5 -5
- package/src/coder-config.ts +141 -0
- package/src/coder-hub-url.ts +32 -0
- package/src/config.ts +13 -0
- package/src/internal-logger.ts +83 -2
- package/src/keychain.ts +68 -39
- package/src/types.ts +10 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { createCommand } from '../../../types';
|
|
3
3
|
import * as tui from '../../../tui';
|
|
4
|
-
import { createStorageAdapter } from './util';
|
|
4
|
+
import { createStorageAdapter, resolveMeId } from './util';
|
|
5
5
|
import { getCommand } from '../../../command-prefix';
|
|
6
|
-
import type { TaskPriority, TaskStatus, TaskType, Task } from '@agentuity/core';
|
|
6
|
+
import type { TaskPriority, TaskStatus, TaskType, Task, TaskIncludeField } from '@agentuity/core';
|
|
7
7
|
|
|
8
8
|
const TaskListResponseSchema = z.object({
|
|
9
9
|
success: z.boolean().describe('Whether the operation succeeded'),
|
|
@@ -14,6 +14,12 @@ const TaskListResponseSchema = z.object({
|
|
|
14
14
|
type: z.string(),
|
|
15
15
|
status: z.string(),
|
|
16
16
|
priority: z.string(),
|
|
17
|
+
description: z.string().optional(),
|
|
18
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
19
|
+
tags: z.array(z.object({ id: z.string(), name: z.string() })).optional(),
|
|
20
|
+
subtask_count: z.number().optional(),
|
|
21
|
+
created_id: z.string().optional(),
|
|
22
|
+
deleted: z.boolean().optional(),
|
|
17
23
|
creator: z
|
|
18
24
|
.object({
|
|
19
25
|
id: z.string(),
|
|
@@ -73,6 +79,38 @@ function truncate(s: string, max: number): string {
|
|
|
73
79
|
return `${s.slice(0, max - 1)}…`;
|
|
74
80
|
}
|
|
75
81
|
|
|
82
|
+
const VALID_INCLUDE_FIELDS = new Set<TaskIncludeField>([
|
|
83
|
+
'description',
|
|
84
|
+
'metadata',
|
|
85
|
+
'tags',
|
|
86
|
+
'subtask_count',
|
|
87
|
+
'created_id',
|
|
88
|
+
'deleted',
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
function parseIncludeParam(include: string | undefined): TaskIncludeField[] | undefined {
|
|
92
|
+
if (!include) return undefined;
|
|
93
|
+
const fields: TaskIncludeField[] = [];
|
|
94
|
+
for (const f of include.split(',')) {
|
|
95
|
+
const trimmed = f.trim() as TaskIncludeField;
|
|
96
|
+
if (VALID_INCLUDE_FIELDS.has(trimmed)) {
|
|
97
|
+
fields.push(trimmed);
|
|
98
|
+
} else {
|
|
99
|
+
tui.fatal(
|
|
100
|
+
`Invalid include field: "${trimmed}". Valid fields are: ${[...VALID_INCLUDE_FIELDS].join(', ')}`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return fields.length > 0 ? fields : undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function hasIncludeField(
|
|
108
|
+
include: TaskIncludeField[] | undefined,
|
|
109
|
+
field: TaskIncludeField
|
|
110
|
+
): boolean {
|
|
111
|
+
return include?.includes(field) ?? false;
|
|
112
|
+
}
|
|
113
|
+
|
|
76
114
|
export const listSubcommand = createCommand({
|
|
77
115
|
name: 'list',
|
|
78
116
|
aliases: ['ls'],
|
|
@@ -103,6 +141,14 @@ export const listSubcommand = createCommand({
|
|
|
103
141
|
command: getCommand('cloud task list --assigned-id agent_001 --limit 10'),
|
|
104
142
|
description: 'List first 10 tasks assigned to an agent',
|
|
105
143
|
},
|
|
144
|
+
{
|
|
145
|
+
command: getCommand('cloud task list --created-id me --include description,metadata,tags'),
|
|
146
|
+
description: 'List tasks created by me with full details',
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
command: getCommand('cloud task list --project-id proj_abc123'),
|
|
150
|
+
description: 'List tasks for a specific project',
|
|
151
|
+
},
|
|
106
152
|
],
|
|
107
153
|
schema: {
|
|
108
154
|
options: z.object({
|
|
@@ -118,8 +164,24 @@ export const listSubcommand = createCommand({
|
|
|
118
164
|
.enum(['high', 'medium', 'low', 'none'])
|
|
119
165
|
.optional()
|
|
120
166
|
.describe('filter by priority'),
|
|
121
|
-
assignedId: z
|
|
167
|
+
assignedId: z
|
|
168
|
+
.string()
|
|
169
|
+
.optional()
|
|
170
|
+
.describe('filter by assigned agent or user ID (use "me" for current user)'),
|
|
171
|
+
createdId: z
|
|
172
|
+
.string()
|
|
173
|
+
.optional()
|
|
174
|
+
.describe('filter by creator ID (use "me" for current user)'),
|
|
122
175
|
parentId: z.string().optional().describe('filter by parent task ID'),
|
|
176
|
+
projectId: z.string().optional().describe('filter by project ID'),
|
|
177
|
+
tagId: z.string().optional().describe('filter by tag ID'),
|
|
178
|
+
deleted: z.boolean().optional().describe('include soft-deleted tasks'),
|
|
179
|
+
include: z
|
|
180
|
+
.string()
|
|
181
|
+
.optional()
|
|
182
|
+
.describe(
|
|
183
|
+
'comma-separated fields to include: description,metadata,tags,subtask_count,created_id,deleted'
|
|
184
|
+
),
|
|
123
185
|
sort: z
|
|
124
186
|
.enum(['created_at', 'updated_at', 'priority'])
|
|
125
187
|
.optional()
|
|
@@ -127,6 +189,7 @@ export const listSubcommand = createCommand({
|
|
|
127
189
|
order: z.enum(['asc', 'desc']).optional().describe('sort order (default: desc)'),
|
|
128
190
|
limit: z.coerce.number().optional().describe('max results to return (default: 50)'),
|
|
129
191
|
offset: z.coerce.number().optional().describe('offset for pagination'),
|
|
192
|
+
orgId: z.string().optional().describe('organization ID (uses default if not specified)'),
|
|
130
193
|
}),
|
|
131
194
|
response: TaskListResponseSchema,
|
|
132
195
|
},
|
|
@@ -136,12 +199,22 @@ export const listSubcommand = createCommand({
|
|
|
136
199
|
const started = Date.now();
|
|
137
200
|
const storage = await createStorageAdapter(ctx);
|
|
138
201
|
|
|
202
|
+
const createdId = resolveMeId(opts.createdId, ctx);
|
|
203
|
+
const assignedId = resolveMeId(opts.assignedId, ctx);
|
|
204
|
+
|
|
205
|
+
const includeFields = parseIncludeParam(opts.include);
|
|
206
|
+
|
|
139
207
|
const result = await storage.list({
|
|
140
208
|
status: opts.status as TaskStatus | undefined,
|
|
141
209
|
type: opts.type as TaskType | undefined,
|
|
142
210
|
priority: opts.priority as TaskPriority | undefined,
|
|
143
|
-
assigned_id:
|
|
211
|
+
assigned_id: assignedId,
|
|
212
|
+
created_id: createdId,
|
|
144
213
|
parent_id: opts.parentId,
|
|
214
|
+
project_id: opts.projectId,
|
|
215
|
+
tag_id: opts.tagId,
|
|
216
|
+
deleted: opts.deleted,
|
|
217
|
+
include: includeFields,
|
|
145
218
|
sort: opts.sort,
|
|
146
219
|
order: opts.order,
|
|
147
220
|
limit: opts.limit,
|
|
@@ -154,6 +227,10 @@ export const listSubcommand = createCommand({
|
|
|
154
227
|
if (result.tasks.length === 0) {
|
|
155
228
|
tui.info('No tasks found');
|
|
156
229
|
} else {
|
|
230
|
+
const showDescription = hasIncludeField(includeFields, 'description');
|
|
231
|
+
const showTags = hasIncludeField(includeFields, 'tags');
|
|
232
|
+
const showMetadata = hasIncludeField(includeFields, 'metadata');
|
|
233
|
+
|
|
157
234
|
const tableData = result.tasks.map((task: Task) => ({
|
|
158
235
|
ID: tui.muted(truncate(task.id, 28)),
|
|
159
236
|
Title: truncate(task.title, 40),
|
|
@@ -176,6 +253,30 @@ export const listSubcommand = createCommand({
|
|
|
176
253
|
{ name: 'Updated', alignment: 'left' },
|
|
177
254
|
]);
|
|
178
255
|
|
|
256
|
+
// Show extra details for each task if included
|
|
257
|
+
if (showDescription || showTags || showMetadata) {
|
|
258
|
+
for (const task of result.tasks) {
|
|
259
|
+
const extras: string[] = [];
|
|
260
|
+
if (showDescription && task.description) {
|
|
261
|
+
extras.push(`${tui.muted('Desc:')} ${truncate(task.description, 80)}`);
|
|
262
|
+
}
|
|
263
|
+
if (showTags && task.tags && task.tags.length > 0) {
|
|
264
|
+
const tagList = task.tags.map((t) => t.name).join(', ');
|
|
265
|
+
extras.push(`${tui.muted('Tags:')} ${tagList}`);
|
|
266
|
+
}
|
|
267
|
+
if (showMetadata && task.metadata) {
|
|
268
|
+
const metaStr =
|
|
269
|
+
typeof task.metadata === 'object'
|
|
270
|
+
? JSON.stringify(task.metadata)
|
|
271
|
+
: String(task.metadata);
|
|
272
|
+
extras.push(`${tui.muted('Meta:')} ${truncate(metaStr, 80)}`);
|
|
273
|
+
}
|
|
274
|
+
if (extras.length > 0) {
|
|
275
|
+
tui.output(` ${tui.muted(truncate(task.id, 28))} → ${extras.join(' | ')}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
179
280
|
tui.info(
|
|
180
281
|
`Showing ${result.tasks.length} of ${result.total} ${tui.plural(result.total, 'task', 'tasks')} (${durationMs.toFixed(1)}ms)`
|
|
181
282
|
);
|
|
@@ -190,6 +291,12 @@ export const listSubcommand = createCommand({
|
|
|
190
291
|
type: task.type,
|
|
191
292
|
status: task.status,
|
|
192
293
|
priority: task.priority,
|
|
294
|
+
description: task.description,
|
|
295
|
+
metadata: task.metadata,
|
|
296
|
+
tags: task.tags,
|
|
297
|
+
subtask_count: task.subtask_count,
|
|
298
|
+
created_id: task.created_id,
|
|
299
|
+
deleted: task.deleted,
|
|
193
300
|
creator: task.creator,
|
|
194
301
|
assignee: task.assignee,
|
|
195
302
|
project: task.project,
|
|
@@ -11,11 +11,14 @@ export interface TaskContext {
|
|
|
11
11
|
auth: AuthData;
|
|
12
12
|
config: Config | null;
|
|
13
13
|
options: GlobalOptions;
|
|
14
|
+
project?: {
|
|
15
|
+
projectId: string;
|
|
16
|
+
orgId: string;
|
|
17
|
+
};
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
export async function createStorageAdapter(ctx: TaskContext) {
|
|
17
|
-
const orgId =
|
|
18
|
-
ctx.options.orgId ?? (process.env.AGENTUITY_CLOUD_ORG_ID || ctx.config?.preferences?.orgId);
|
|
21
|
+
const orgId = resolveOrgId(ctx);
|
|
19
22
|
if (!orgId) {
|
|
20
23
|
tui.fatal('Organization ID is required. Use --org-id flag or set AGENTUITY_CLOUD_ORG_ID.');
|
|
21
24
|
}
|
|
@@ -37,8 +40,7 @@ export async function createStorageAdapter(ctx: TaskContext) {
|
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
export async function createStorageAdapterOptionalOrg(ctx: TaskContext) {
|
|
40
|
-
const orgId =
|
|
41
|
-
ctx.options.orgId ?? (process.env.AGENTUITY_CLOUD_ORG_ID || ctx.config?.preferences?.orgId);
|
|
43
|
+
const orgId = resolveOrgId(ctx);
|
|
42
44
|
|
|
43
45
|
const headers: Record<string, string> = {
|
|
44
46
|
Authorization: `Bearer ${ctx.auth.apiKey}`,
|
|
@@ -54,17 +56,30 @@ export async function createStorageAdapterOptionalOrg(ctx: TaskContext) {
|
|
|
54
56
|
return new TaskStorageService(baseUrl, adapter);
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
function resolveOrgId(ctx: TaskContext): string | undefined {
|
|
60
|
+
return (
|
|
61
|
+
ctx.options.orgId ??
|
|
62
|
+
process.env.AGENTUITY_CLOUD_ORG_ID ??
|
|
63
|
+
ctx.project?.orgId ??
|
|
64
|
+
ctx.config?.preferences?.orgId
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
57
68
|
export async function cacheTaskId(
|
|
58
69
|
ctx: {
|
|
59
70
|
config: Config | null;
|
|
60
71
|
options: GlobalOptions;
|
|
72
|
+
project?: { orgId: string };
|
|
61
73
|
},
|
|
62
74
|
taskId: string
|
|
63
75
|
) {
|
|
64
76
|
const profileName = ctx.config?.name ?? defaultProfileName;
|
|
65
77
|
const region = await getDefaultRegion(profileName, ctx.config);
|
|
66
78
|
const orgId =
|
|
67
|
-
ctx.options.orgId ??
|
|
79
|
+
ctx.options.orgId ??
|
|
80
|
+
process.env.AGENTUITY_CLOUD_ORG_ID ??
|
|
81
|
+
ctx.project?.orgId ??
|
|
82
|
+
ctx.config?.preferences?.orgId;
|
|
68
83
|
await setResourceInfo('task', profileName, taskId, region, orgId);
|
|
69
84
|
}
|
|
70
85
|
|
|
@@ -76,3 +91,42 @@ export function parseMetadataFlag(raw: string | undefined): Record<string, unkno
|
|
|
76
91
|
tui.fatal('Invalid JSON for --metadata flag');
|
|
77
92
|
}
|
|
78
93
|
}
|
|
94
|
+
|
|
95
|
+
const DURATION_UNITS: Record<string, number> = {
|
|
96
|
+
s: 1000,
|
|
97
|
+
m: 60 * 1000,
|
|
98
|
+
h: 60 * 60 * 1000,
|
|
99
|
+
d: 24 * 60 * 60 * 1000,
|
|
100
|
+
w: 7 * 24 * 60 * 60 * 1000,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export function parseDuration(duration: string): number {
|
|
104
|
+
const match = duration.match(/^(\d+)([smhdw])$/);
|
|
105
|
+
if (!match) {
|
|
106
|
+
tui.fatal(
|
|
107
|
+
`Invalid duration format: "${duration}". Use a number followed by s (seconds), m (minutes), h (hours), d (days), or w (weeks). Examples: 30s, 30m, 24h, 7d, 2w`
|
|
108
|
+
);
|
|
109
|
+
throw new Error('unreachable');
|
|
110
|
+
}
|
|
111
|
+
const value = parseInt(match[1]!, 10);
|
|
112
|
+
const unit = match[2]!;
|
|
113
|
+
const ms = DURATION_UNITS[unit];
|
|
114
|
+
if (!ms) {
|
|
115
|
+
tui.fatal(`Unknown duration unit: "${unit}"`);
|
|
116
|
+
throw new Error('unreachable');
|
|
117
|
+
}
|
|
118
|
+
return value * ms;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function truncate(s: string, max: number): string {
|
|
122
|
+
if (s.length <= max) return s;
|
|
123
|
+
return `${s.slice(0, max - 1)}…`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function resolveMeId(id: string | undefined, ctx: TaskContext): string | undefined {
|
|
127
|
+
if (!id) return undefined;
|
|
128
|
+
if (id === 'me') {
|
|
129
|
+
return ctx.auth.userId;
|
|
130
|
+
}
|
|
131
|
+
return id;
|
|
132
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createCommand } from '../../../types';
|
|
2
|
+
import { getCommand } from '../../../command-prefix';
|
|
3
|
+
import { setSubcommand } from './set';
|
|
4
|
+
|
|
5
|
+
export const configSubcommand = createCommand({
|
|
6
|
+
name: 'config',
|
|
7
|
+
description: 'Manage stored Coder Hub configuration',
|
|
8
|
+
tags: ['fast'],
|
|
9
|
+
examples: [
|
|
10
|
+
{
|
|
11
|
+
command: getCommand('coder config set url https://hub.example.com'),
|
|
12
|
+
description: 'Set the default Coder Hub URL for the active profile',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
command: getCommand('coder config set apikey agc_...'),
|
|
16
|
+
description: 'Set the default Coder Hub API key for the active profile',
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
subcommands: [setSubcommand],
|
|
20
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { saveCoderApiKey, saveCoderHubUrl } from '../../../coder-config';
|
|
3
|
+
import { normalizeCoderHubHttpUrl } from '../../../coder-hub-url';
|
|
4
|
+
import { getCommand } from '../../../command-prefix';
|
|
5
|
+
import * as tui from '../../../tui';
|
|
6
|
+
import { createCommand, createSubcommand } from '../../../types';
|
|
7
|
+
|
|
8
|
+
const setUrlSubcommand = createSubcommand({
|
|
9
|
+
name: 'url',
|
|
10
|
+
description: 'Set the default Coder Hub URL for the active profile',
|
|
11
|
+
tags: ['mutating', 'fast'],
|
|
12
|
+
examples: [
|
|
13
|
+
{
|
|
14
|
+
command: getCommand('coder config set url https://hub.example.com'),
|
|
15
|
+
description: 'Set the default Coder Hub URL',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
command: getCommand('coder config set url ws://127.0.0.1:3650/api/ws'),
|
|
19
|
+
description: 'Store a local dev Hub URL using a WebSocket input',
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
schema: {
|
|
23
|
+
args: z.object({
|
|
24
|
+
url: z.string().min(1).describe('Hub URL to store for the active profile'),
|
|
25
|
+
}),
|
|
26
|
+
response: z.object({
|
|
27
|
+
profile: z.string().describe('Active CLI profile name'),
|
|
28
|
+
hubUrl: z.string().describe('Normalized stored Hub HTTP URL'),
|
|
29
|
+
}),
|
|
30
|
+
},
|
|
31
|
+
async handler(ctx) {
|
|
32
|
+
const { args, options } = ctx;
|
|
33
|
+
const normalized = normalizeCoderHubHttpUrl(args.url);
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
new URL(normalized);
|
|
37
|
+
} catch {
|
|
38
|
+
tui.fatal(
|
|
39
|
+
`Invalid Hub URL: ${args.url}\n\nExpected a full URL such as https://hub.example.com or ws://127.0.0.1:3650/api/ws`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const result = await saveCoderHubUrl(normalized);
|
|
44
|
+
|
|
45
|
+
if (!options.json) {
|
|
46
|
+
tui.success(
|
|
47
|
+
`Default Coder Hub URL set to ${tui.bold(result.hubUrl)} for profile ${tui.bold(result.profileName)}`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
profile: result.profileName,
|
|
53
|
+
hubUrl: result.hubUrl,
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const setApiKeySubcommand = createSubcommand({
|
|
59
|
+
name: 'apikey',
|
|
60
|
+
description: 'Set the default Coder Hub API key for the active profile',
|
|
61
|
+
tags: ['mutating', 'fast'],
|
|
62
|
+
examples: [
|
|
63
|
+
{
|
|
64
|
+
command: getCommand('coder config set apikey agc_...'),
|
|
65
|
+
description: 'Set the default Coder Hub API key',
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
schema: {
|
|
69
|
+
args: z.object({
|
|
70
|
+
apikey: z.string().min(1).describe('Hub API key to store for the active profile'),
|
|
71
|
+
}),
|
|
72
|
+
response: z.object({
|
|
73
|
+
profile: z.string().describe('Active CLI profile name'),
|
|
74
|
+
stored: z.boolean().describe('Whether the API key was stored successfully'),
|
|
75
|
+
}),
|
|
76
|
+
},
|
|
77
|
+
async handler(ctx) {
|
|
78
|
+
const { args, options } = ctx;
|
|
79
|
+
const trimmed = args.apikey.trim();
|
|
80
|
+
if (!trimmed) {
|
|
81
|
+
tui.fatal('Hub API key cannot be empty');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const result = await saveCoderApiKey(trimmed);
|
|
85
|
+
|
|
86
|
+
if (!options.json) {
|
|
87
|
+
tui.success(`Coder Hub API key stored for profile ${tui.bold(result.profileName)}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
profile: result.profileName,
|
|
92
|
+
stored: true,
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
export const setSubcommand = createCommand({
|
|
98
|
+
name: 'set',
|
|
99
|
+
description: 'Set stored Coder Hub configuration values',
|
|
100
|
+
tags: ['mutating', 'fast'],
|
|
101
|
+
examples: [
|
|
102
|
+
{
|
|
103
|
+
command: getCommand('coder config set url https://hub.example.com'),
|
|
104
|
+
description: 'Store the default Hub URL',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
command: getCommand('coder config set apikey agc_...'),
|
|
108
|
+
description: 'Store the default Hub API key',
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
subcommands: [setUrlSubcommand, setApiKeySubcommand],
|
|
112
|
+
});
|
package/src/cmd/coder/hub-url.ts
CHANGED
|
@@ -4,11 +4,27 @@
|
|
|
4
4
|
* Resolution priority:
|
|
5
5
|
* 1. --hub-url flag (explicit per-command override)
|
|
6
6
|
* 2. AGENTUITY_CODER_HUB_URL env var
|
|
7
|
-
* 3.
|
|
7
|
+
* 3. Stored per-profile Hub URL
|
|
8
|
+
* 4. AGENTUITY_DEVMODE_URL env var (dev tunnel URL)
|
|
8
9
|
*/
|
|
9
10
|
|
|
11
|
+
import {
|
|
12
|
+
clearStoredCoderApiKey,
|
|
13
|
+
getStoredCoderApiKey,
|
|
14
|
+
getStoredCoderHubUrl,
|
|
15
|
+
} from '../../coder-config';
|
|
16
|
+
import { normalizeCoderHubHttpUrl, toCoderHubWsUrl } from '../../coder-hub-url';
|
|
17
|
+
import { getCommand } from '../../command-prefix';
|
|
18
|
+
import type { Config } from '../../types';
|
|
10
19
|
import { getVersion } from '../../version';
|
|
11
20
|
|
|
21
|
+
export type HubApiKeySource = 'env' | 'stored' | 'none';
|
|
22
|
+
|
|
23
|
+
export interface ResolvedHubApiKey {
|
|
24
|
+
apiKey: string | null;
|
|
25
|
+
source: HubApiKeySource;
|
|
26
|
+
}
|
|
27
|
+
|
|
12
28
|
/**
|
|
13
29
|
* Resolve the Hub HTTP base URL for REST API calls.
|
|
14
30
|
* Converts ws:// URLs to http:// automatically.
|
|
@@ -16,17 +32,24 @@ import { getVersion } from '../../version';
|
|
|
16
32
|
* @param flagUrl Optional --hub-url flag value
|
|
17
33
|
* @returns HTTP base URL (e.g. "http://localhost:3500") or null if Hub is unreachable
|
|
18
34
|
*/
|
|
19
|
-
export async function resolveHubUrl(
|
|
35
|
+
export async function resolveHubUrl(
|
|
36
|
+
flagUrl?: string,
|
|
37
|
+
config?: Config | null
|
|
38
|
+
): Promise<string | null> {
|
|
20
39
|
// 1. Explicit flag
|
|
21
|
-
if (flagUrl) return
|
|
40
|
+
if (flagUrl) return normalizeCoderHubHttpUrl(flagUrl);
|
|
22
41
|
|
|
23
42
|
// 2. Env var (explicit)
|
|
24
43
|
const envUrl = process.env.AGENTUITY_CODER_HUB_URL;
|
|
25
|
-
if (envUrl) return
|
|
44
|
+
if (envUrl) return normalizeCoderHubHttpUrl(envUrl);
|
|
45
|
+
|
|
46
|
+
// 3. Stored profile config
|
|
47
|
+
const storedUrl = await getStoredCoderHubUrl(config);
|
|
48
|
+
if (storedUrl) return storedUrl;
|
|
26
49
|
|
|
27
|
-
//
|
|
50
|
+
// 4. Dev mode URL (tunnel)
|
|
28
51
|
const devUrl = process.env.AGENTUITY_DEVMODE_URL;
|
|
29
|
-
if (devUrl) return
|
|
52
|
+
if (devUrl) return normalizeCoderHubHttpUrl(devUrl);
|
|
30
53
|
|
|
31
54
|
return null;
|
|
32
55
|
}
|
|
@@ -38,74 +61,145 @@ export async function resolveHubUrl(flagUrl?: string): Promise<string | null> {
|
|
|
38
61
|
* @param flagUrl Optional --hub-url flag value
|
|
39
62
|
* @returns WebSocket URL (e.g. "ws://127.0.0.1:3500/api/ws") or null
|
|
40
63
|
*/
|
|
41
|
-
export async function resolveHubWsUrl(
|
|
42
|
-
|
|
64
|
+
export async function resolveHubWsUrl(
|
|
65
|
+
flagUrl?: string,
|
|
66
|
+
config?: Config | null
|
|
67
|
+
): Promise<string | null> {
|
|
68
|
+
const httpUrl = await resolveHubUrl(flagUrl, config);
|
|
43
69
|
if (!httpUrl) return null;
|
|
44
70
|
return toHubWsUrl(httpUrl);
|
|
45
71
|
}
|
|
46
72
|
|
|
47
73
|
export function toHubWsUrl(hubHttpUrl: string): string {
|
|
48
|
-
return
|
|
74
|
+
return toCoderHubWsUrl(hubHttpUrl);
|
|
49
75
|
}
|
|
50
76
|
|
|
51
77
|
/**
|
|
52
|
-
*
|
|
78
|
+
* Resolve the API key for Hub authentication.
|
|
53
79
|
*/
|
|
54
|
-
function
|
|
55
|
-
|
|
80
|
+
function resolveEnvApiKey(): string | null {
|
|
81
|
+
return process.env.AGENTUITY_CODER_API_KEY || null;
|
|
82
|
+
}
|
|
56
83
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
84
|
+
export async function resolveHubApiKey(config?: Config | null): Promise<ResolvedHubApiKey> {
|
|
85
|
+
const envApiKey = resolveEnvApiKey();
|
|
86
|
+
if (envApiKey) {
|
|
87
|
+
return {
|
|
88
|
+
apiKey: envApiKey,
|
|
89
|
+
source: 'env',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
60
92
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
93
|
+
const storedApiKey = await getStoredCoderApiKey(config);
|
|
94
|
+
if (storedApiKey) {
|
|
95
|
+
return {
|
|
96
|
+
apiKey: storedApiKey,
|
|
97
|
+
source: 'stored',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
66
100
|
|
|
67
|
-
return
|
|
101
|
+
return {
|
|
102
|
+
apiKey: null,
|
|
103
|
+
source: 'none',
|
|
104
|
+
};
|
|
68
105
|
}
|
|
69
106
|
|
|
70
107
|
/**
|
|
71
|
-
*
|
|
108
|
+
* Build headers object with API key if available.
|
|
72
109
|
*/
|
|
73
|
-
function
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
110
|
+
export function hubFetchHeaders(
|
|
111
|
+
extra?: Record<string, string>,
|
|
112
|
+
apiKey?: string | null
|
|
113
|
+
): Record<string, string> {
|
|
114
|
+
const headers: Record<string, string> = { ...extra };
|
|
115
|
+
headers['User-Agent'] = `Agentuity Coder/${getVersion()}`;
|
|
116
|
+
const resolvedApiKey = apiKey === undefined ? resolveEnvApiKey() : apiKey;
|
|
117
|
+
if (resolvedApiKey) headers['x-agentuity-auth-api-key'] = resolvedApiKey;
|
|
118
|
+
return headers;
|
|
119
|
+
}
|
|
77
120
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
121
|
+
export function isHubUnauthorizedStatus(status: number): boolean {
|
|
122
|
+
return status === 401 || status === 403;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function clearStoredHubApiKeyOnUnauthorized(
|
|
126
|
+
status: number,
|
|
127
|
+
resolvedApiKey: ResolvedHubApiKey,
|
|
128
|
+
config?: Config | null,
|
|
129
|
+
clearStoredApiKey: (config?: Config | null) => Promise<unknown> = clearStoredCoderApiKey
|
|
130
|
+
): Promise<boolean> {
|
|
131
|
+
if (!isHubUnauthorizedStatus(status) || resolvedApiKey.source !== 'stored') {
|
|
132
|
+
return false;
|
|
88
133
|
}
|
|
89
134
|
|
|
90
|
-
|
|
135
|
+
await clearStoredApiKey(config);
|
|
136
|
+
return true;
|
|
91
137
|
}
|
|
92
138
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
139
|
+
export function getHubUrlSetupGuidance(): string {
|
|
140
|
+
return (
|
|
141
|
+
`Set a default Hub URL with:\n` +
|
|
142
|
+
` ${getCommand('coder config set url <url>')}\n\n` +
|
|
143
|
+
`Or pass --hub-url for a one-off override, or use AGENTUITY_CODER_HUB_URL.`
|
|
144
|
+
);
|
|
99
145
|
}
|
|
100
146
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return
|
|
147
|
+
export function getHubApiKeySetupGuidance(): string {
|
|
148
|
+
return (
|
|
149
|
+
`Set a Hub API key with:\n` +
|
|
150
|
+
` ${getCommand('coder config set apikey <apikey>')}\n\n` +
|
|
151
|
+
`Or use AGENTUITY_CODER_API_KEY as an override.`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function formatMissingHubUrlMessage(): string {
|
|
156
|
+
return `Could not find a configured Coder Hub URL.\n\n${getHubUrlSetupGuidance()}`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function formatHubUnauthorizedMessage(
|
|
160
|
+
hubUrl: string,
|
|
161
|
+
serverMessage: string,
|
|
162
|
+
options?: {
|
|
163
|
+
clearedStoredKey?: boolean;
|
|
164
|
+
}
|
|
165
|
+
): string {
|
|
166
|
+
const clearedStoredKey = options?.clearedStoredKey ? 'Stored Hub API key cleared.\n\n' : '';
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
`Coder Hub at ${hubUrl} requires a valid API key.\n\n` +
|
|
170
|
+
`${clearedStoredKey}${getHubApiKeySetupGuidance()}\n\n` +
|
|
171
|
+
`Server said: ${serverMessage}`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function getHubResponseErrorMessage(response: Response): Promise<string> {
|
|
176
|
+
const fallback = `${response.status} ${response.statusText}`;
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const payload = (await response.clone().json()) as {
|
|
180
|
+
error?: unknown;
|
|
181
|
+
message?: unknown;
|
|
182
|
+
details?: unknown;
|
|
183
|
+
};
|
|
184
|
+
if (typeof payload.error === 'string' && payload.error.trim()) {
|
|
185
|
+
return payload.error.trim();
|
|
186
|
+
}
|
|
187
|
+
if (typeof payload.message === 'string' && payload.message.trim()) {
|
|
188
|
+
return payload.message.trim();
|
|
189
|
+
}
|
|
190
|
+
if (typeof payload.details === 'string' && payload.details.trim()) {
|
|
191
|
+
return payload.details.trim();
|
|
192
|
+
}
|
|
193
|
+
} catch {
|
|
194
|
+
// Fall back to the response text below.
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const text = (await response.text()).trim();
|
|
199
|
+
if (text) return text;
|
|
200
|
+
} catch {
|
|
201
|
+
// Fall back to the status text below.
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return fallback;
|
|
111
205
|
}
|