@profoundlogic/coderflow-cli 0.2.1
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/LICENSE.txt +322 -0
- package/README.md +102 -0
- package/coder.js +202 -0
- package/lib/commands/apply.js +238 -0
- package/lib/commands/attach.js +143 -0
- package/lib/commands/config.js +226 -0
- package/lib/commands/containers.js +213 -0
- package/lib/commands/discard.js +167 -0
- package/lib/commands/interactive.js +292 -0
- package/lib/commands/jira.js +464 -0
- package/lib/commands/license.js +172 -0
- package/lib/commands/list.js +104 -0
- package/lib/commands/login.js +329 -0
- package/lib/commands/logs.js +66 -0
- package/lib/commands/profile.js +539 -0
- package/lib/commands/reject.js +53 -0
- package/lib/commands/results.js +89 -0
- package/lib/commands/run.js +237 -0
- package/lib/commands/server.js +537 -0
- package/lib/commands/status.js +39 -0
- package/lib/commands/test.js +335 -0
- package/lib/config.js +378 -0
- package/lib/help.js +444 -0
- package/lib/http-client.js +180 -0
- package/lib/oidc.js +126 -0
- package/lib/profile.js +296 -0
- package/lib/state-capture.js +336 -0
- package/lib/task-grouping.js +210 -0
- package/lib/terminal-client.js +162 -0
- package/package.json +35 -0
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile command - manage CLI configuration profiles
|
|
3
|
+
*
|
|
4
|
+
* Profiles allow switching between different CoderFlow server configurations
|
|
5
|
+
* (e.g., my-server, team-name, project-x) without changing environment variables.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { promises as fs } from 'fs';
|
|
9
|
+
import readline from 'readline';
|
|
10
|
+
import {
|
|
11
|
+
listProfiles,
|
|
12
|
+
loadProfile,
|
|
13
|
+
saveProfile,
|
|
14
|
+
deleteProfile,
|
|
15
|
+
getActiveProfileName,
|
|
16
|
+
setActiveProfile,
|
|
17
|
+
profileExists,
|
|
18
|
+
isValidProfileName,
|
|
19
|
+
getProfileConfigKeys,
|
|
20
|
+
createProfileFromLegacyConfig,
|
|
21
|
+
getProfilePath,
|
|
22
|
+
loadMainConfig
|
|
23
|
+
} from '../profile.js';
|
|
24
|
+
import { invalidateConfigCache } from '../config.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Handle profile command
|
|
28
|
+
* Usage:
|
|
29
|
+
* coder profile list List all profiles
|
|
30
|
+
* coder profile show [name] Show profile details
|
|
31
|
+
* coder profile create <name> Create new profile
|
|
32
|
+
* coder profile switch <name> Switch active profile
|
|
33
|
+
* coder profile delete <name> Delete a profile
|
|
34
|
+
* coder profile copy <src> <dest> Copy a profile
|
|
35
|
+
* coder profile set <key> <value> Set a value in active profile
|
|
36
|
+
* coder profile get <key> Get a value from active profile
|
|
37
|
+
*/
|
|
38
|
+
export async function handleProfile(args) {
|
|
39
|
+
const subcommand = args[0];
|
|
40
|
+
|
|
41
|
+
if (!subcommand) {
|
|
42
|
+
showProfileHelp();
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
switch (subcommand) {
|
|
48
|
+
case 'list':
|
|
49
|
+
case 'ls':
|
|
50
|
+
await listProfilesCommand();
|
|
51
|
+
break;
|
|
52
|
+
|
|
53
|
+
case 'show':
|
|
54
|
+
await showProfileCommand(args[1]);
|
|
55
|
+
break;
|
|
56
|
+
|
|
57
|
+
case 'create':
|
|
58
|
+
case 'add':
|
|
59
|
+
await createProfileCommand(args[1], args.slice(2));
|
|
60
|
+
break;
|
|
61
|
+
|
|
62
|
+
case 'switch':
|
|
63
|
+
case 'use':
|
|
64
|
+
await switchProfileCommand(args[1]);
|
|
65
|
+
break;
|
|
66
|
+
|
|
67
|
+
case 'delete':
|
|
68
|
+
case 'remove':
|
|
69
|
+
case 'rm':
|
|
70
|
+
await deleteProfileCommand(args[1]);
|
|
71
|
+
break;
|
|
72
|
+
|
|
73
|
+
case 'copy':
|
|
74
|
+
case 'cp':
|
|
75
|
+
await copyProfileCommand(args[1], args[2]);
|
|
76
|
+
break;
|
|
77
|
+
|
|
78
|
+
case 'set':
|
|
79
|
+
await setProfileValueCommand(args[1], args[2]);
|
|
80
|
+
break;
|
|
81
|
+
|
|
82
|
+
case 'get':
|
|
83
|
+
await getProfileValueCommand(args[1]);
|
|
84
|
+
break;
|
|
85
|
+
|
|
86
|
+
case 'current':
|
|
87
|
+
await showCurrentProfile();
|
|
88
|
+
break;
|
|
89
|
+
|
|
90
|
+
case 'migrate':
|
|
91
|
+
await migrateToProfile(args[1]);
|
|
92
|
+
break;
|
|
93
|
+
|
|
94
|
+
default:
|
|
95
|
+
console.error(`Unknown subcommand: ${subcommand}`);
|
|
96
|
+
showProfileHelp();
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error(`Error: ${error.message}`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function showProfileHelp() {
|
|
106
|
+
console.log(`
|
|
107
|
+
Usage: coder profile <command> [options]
|
|
108
|
+
|
|
109
|
+
Commands:
|
|
110
|
+
list List all profiles (shows active profile)
|
|
111
|
+
show [name] Show profile details (active if no name given)
|
|
112
|
+
create <name> Create a new profile
|
|
113
|
+
switch <name> Switch to a different profile
|
|
114
|
+
delete <name> Delete a profile
|
|
115
|
+
copy <source> <dest> Copy an existing profile
|
|
116
|
+
set <key> <value> Set a value in the active profile
|
|
117
|
+
get <key> Get a value from the active profile
|
|
118
|
+
current Show the current active profile name
|
|
119
|
+
migrate <name> Create profile from current legacy config
|
|
120
|
+
|
|
121
|
+
Profile Keys:
|
|
122
|
+
server Server URL (e.g., http://localhost:3000)
|
|
123
|
+
apiKey API authentication key
|
|
124
|
+
default_environment Default environment name
|
|
125
|
+
coder_setup_path Path to coder-setup directory
|
|
126
|
+
server_port Server port number
|
|
127
|
+
profound_coder_path Path to profound-coder directory
|
|
128
|
+
|
|
129
|
+
Examples:
|
|
130
|
+
coder profile list
|
|
131
|
+
coder profile create my-server
|
|
132
|
+
coder profile switch my-server
|
|
133
|
+
coder profile set server https://coderflow.example.com
|
|
134
|
+
coder profile show my-server
|
|
135
|
+
coder profile copy my-server project-x
|
|
136
|
+
coder profile delete project-x
|
|
137
|
+
|
|
138
|
+
Using Profiles:
|
|
139
|
+
- Use 'coder profile switch <name>' to change the active profile
|
|
140
|
+
- Use 'coder --profile=<name> <command>' to use a profile for one command
|
|
141
|
+
- Set CODER_PROFILE=<name> environment variable to override
|
|
142
|
+
`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function listProfilesCommand() {
|
|
146
|
+
const profiles = await listProfiles();
|
|
147
|
+
const activeProfile = await getActiveProfileName();
|
|
148
|
+
|
|
149
|
+
if (profiles.length === 0) {
|
|
150
|
+
console.log('No profiles found.');
|
|
151
|
+
console.log('');
|
|
152
|
+
console.log('Create one with:');
|
|
153
|
+
console.log(' coder profile create <name>');
|
|
154
|
+
console.log('');
|
|
155
|
+
console.log('Or migrate your current config:');
|
|
156
|
+
console.log(' coder profile migrate default');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log('Profiles:');
|
|
161
|
+
console.log('');
|
|
162
|
+
|
|
163
|
+
for (const name of profiles) {
|
|
164
|
+
const isActive = name === activeProfile;
|
|
165
|
+
const marker = isActive ? '*' : ' ';
|
|
166
|
+
const profile = await loadProfile(name);
|
|
167
|
+
const server = profile?.server || '(not set)';
|
|
168
|
+
|
|
169
|
+
console.log(` ${marker} ${name}`);
|
|
170
|
+
console.log(` Server: ${server}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log('');
|
|
174
|
+
if (activeProfile) {
|
|
175
|
+
console.log(`Active profile: ${activeProfile}`);
|
|
176
|
+
} else {
|
|
177
|
+
console.log('No active profile (using legacy config)');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function showProfileCommand(profileName) {
|
|
182
|
+
// If no name provided, show active profile
|
|
183
|
+
if (!profileName) {
|
|
184
|
+
profileName = await getActiveProfileName();
|
|
185
|
+
if (!profileName) {
|
|
186
|
+
console.log('No active profile set.');
|
|
187
|
+
console.log('');
|
|
188
|
+
console.log('Use "coder profile list" to see available profiles.');
|
|
189
|
+
console.log('Use "coder profile switch <name>" to set an active profile.');
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const profile = await loadProfile(profileName);
|
|
195
|
+
if (!profile) {
|
|
196
|
+
console.error(`Profile '${profileName}' not found.`);
|
|
197
|
+
console.error('');
|
|
198
|
+
console.error('Use "coder profile list" to see available profiles.');
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const activeProfile = await getActiveProfileName();
|
|
203
|
+
const isActive = profileName === activeProfile;
|
|
204
|
+
|
|
205
|
+
console.log(`Profile: ${profileName}${isActive ? ' (active)' : ''}`);
|
|
206
|
+
console.log(`Location: ${getProfilePath(profileName)}`);
|
|
207
|
+
console.log('');
|
|
208
|
+
console.log('Settings:');
|
|
209
|
+
|
|
210
|
+
for (const key of getProfileConfigKeys()) {
|
|
211
|
+
const value = profile[key];
|
|
212
|
+
if (value !== undefined) {
|
|
213
|
+
// Mask API key for security
|
|
214
|
+
const displayValue = key === 'apiKey' ? maskApiKey(value) : value;
|
|
215
|
+
console.log(` ${key}: ${displayValue}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function maskApiKey(apiKey) {
|
|
221
|
+
if (!apiKey || apiKey.length < 8) {
|
|
222
|
+
return '****';
|
|
223
|
+
}
|
|
224
|
+
return apiKey.slice(0, 4) + '****' + apiKey.slice(-4);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function createProfileCommand(profileName, extraArgs) {
|
|
228
|
+
if (!profileName) {
|
|
229
|
+
console.error('Usage: coder profile create <name>');
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (!isValidProfileName(profileName)) {
|
|
234
|
+
console.error(`Invalid profile name: ${profileName}`);
|
|
235
|
+
console.error('Profile names can only contain letters, numbers, hyphens, and underscores.');
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (await profileExists(profileName)) {
|
|
240
|
+
console.error(`Profile '${profileName}' already exists.`);
|
|
241
|
+
console.error('Use "coder profile delete" first, or choose a different name.');
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Parse extra arguments for initial values (--server=..., --apiKey=..., etc.)
|
|
246
|
+
const initialValues = parseProfileArgs(extraArgs);
|
|
247
|
+
|
|
248
|
+
// Create profile with initial values
|
|
249
|
+
const profileData = {
|
|
250
|
+
name: profileName,
|
|
251
|
+
...initialValues
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
await saveProfile(profileName, profileData);
|
|
255
|
+
|
|
256
|
+
console.log(`Created profile: ${profileName}`);
|
|
257
|
+
console.log(`Location: ${getProfilePath(profileName)}`);
|
|
258
|
+
console.log('');
|
|
259
|
+
|
|
260
|
+
if (Object.keys(initialValues).length > 0) {
|
|
261
|
+
console.log('Initial settings:');
|
|
262
|
+
for (const [key, value] of Object.entries(initialValues)) {
|
|
263
|
+
const displayValue = key === 'apiKey' ? maskApiKey(value) : value;
|
|
264
|
+
console.log(` ${key}: ${displayValue}`);
|
|
265
|
+
}
|
|
266
|
+
console.log('');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
console.log('To configure this profile:');
|
|
270
|
+
console.log(` coder profile switch ${profileName}`);
|
|
271
|
+
console.log(` coder profile set server <url>`);
|
|
272
|
+
console.log(` coder profile set apiKey <key>`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function parseProfileArgs(args) {
|
|
276
|
+
const values = {};
|
|
277
|
+
const validKeys = new Set(getProfileConfigKeys());
|
|
278
|
+
|
|
279
|
+
for (const arg of args) {
|
|
280
|
+
if (arg.startsWith('--')) {
|
|
281
|
+
const equalIndex = arg.indexOf('=');
|
|
282
|
+
if (equalIndex > 2) {
|
|
283
|
+
const key = arg.slice(2, equalIndex);
|
|
284
|
+
const value = arg.slice(equalIndex + 1);
|
|
285
|
+
if (validKeys.has(key)) {
|
|
286
|
+
values[key] = value;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return values;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function switchProfileCommand(profileName) {
|
|
296
|
+
if (!profileName) {
|
|
297
|
+
// No name provided - show current profile
|
|
298
|
+
const current = await getActiveProfileName();
|
|
299
|
+
if (current) {
|
|
300
|
+
console.log(`Current profile: ${current}`);
|
|
301
|
+
} else {
|
|
302
|
+
console.log('No active profile set (using legacy config)');
|
|
303
|
+
}
|
|
304
|
+
console.log('');
|
|
305
|
+
console.log('Usage: coder profile switch <name>');
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (!await profileExists(profileName)) {
|
|
310
|
+
console.error(`Profile '${profileName}' not found.`);
|
|
311
|
+
console.error('');
|
|
312
|
+
console.error('Available profiles:');
|
|
313
|
+
const profiles = await listProfiles();
|
|
314
|
+
if (profiles.length === 0) {
|
|
315
|
+
console.error(' (none)');
|
|
316
|
+
} else {
|
|
317
|
+
for (const name of profiles) {
|
|
318
|
+
console.error(` - ${name}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
await setActiveProfile(profileName);
|
|
325
|
+
invalidateConfigCache();
|
|
326
|
+
|
|
327
|
+
const profile = await loadProfile(profileName);
|
|
328
|
+
console.log(`Switched to profile: ${profileName}`);
|
|
329
|
+
if (profile?.server) {
|
|
330
|
+
console.log(`Server: ${profile.server}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function deleteProfileCommand(profileName) {
|
|
335
|
+
if (!profileName) {
|
|
336
|
+
console.error('Usage: coder profile delete <name>');
|
|
337
|
+
process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (!await profileExists(profileName)) {
|
|
341
|
+
console.error(`Profile '${profileName}' not found.`);
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Check if this is the active profile
|
|
346
|
+
const activeProfile = await getActiveProfileName();
|
|
347
|
+
if (profileName === activeProfile) {
|
|
348
|
+
console.log(`Warning: '${profileName}' is the current active profile.`);
|
|
349
|
+
|
|
350
|
+
// Prompt for confirmation
|
|
351
|
+
const confirmed = await promptConfirmation('Delete this profile?');
|
|
352
|
+
if (!confirmed) {
|
|
353
|
+
console.log('Cancelled.');
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Clear active profile
|
|
358
|
+
await setActiveProfile(null);
|
|
359
|
+
invalidateConfigCache();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
await deleteProfile(profileName);
|
|
363
|
+
console.log(`Deleted profile: ${profileName}`);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function copyProfileCommand(sourceName, destName) {
|
|
367
|
+
if (!sourceName || !destName) {
|
|
368
|
+
console.error('Usage: coder profile copy <source> <destination>');
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (!isValidProfileName(destName)) {
|
|
373
|
+
console.error(`Invalid profile name: ${destName}`);
|
|
374
|
+
console.error('Profile names can only contain letters, numbers, hyphens, and underscores.');
|
|
375
|
+
process.exit(1);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const sourceProfile = await loadProfile(sourceName);
|
|
379
|
+
if (!sourceProfile) {
|
|
380
|
+
console.error(`Source profile '${sourceName}' not found.`);
|
|
381
|
+
process.exit(1);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (await profileExists(destName)) {
|
|
385
|
+
console.error(`Destination profile '${destName}' already exists.`);
|
|
386
|
+
console.error('Delete it first or choose a different name.');
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Copy profile data
|
|
391
|
+
const newProfile = { ...sourceProfile, name: destName };
|
|
392
|
+
await saveProfile(destName, newProfile);
|
|
393
|
+
|
|
394
|
+
console.log(`Copied profile '${sourceName}' to '${destName}'`);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async function setProfileValueCommand(key, value) {
|
|
398
|
+
if (!key || value === undefined) {
|
|
399
|
+
console.error('Usage: coder profile set <key> <value>');
|
|
400
|
+
console.error('');
|
|
401
|
+
console.error('Available keys:');
|
|
402
|
+
for (const k of getProfileConfigKeys()) {
|
|
403
|
+
console.error(` - ${k}`);
|
|
404
|
+
}
|
|
405
|
+
process.exit(1);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const validKeys = new Set(getProfileConfigKeys());
|
|
409
|
+
if (!validKeys.has(key)) {
|
|
410
|
+
console.error(`Unknown key: ${key}`);
|
|
411
|
+
console.error('');
|
|
412
|
+
console.error('Available keys:');
|
|
413
|
+
for (const k of getProfileConfigKeys()) {
|
|
414
|
+
console.error(` - ${k}`);
|
|
415
|
+
}
|
|
416
|
+
process.exit(1);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const activeProfileName = await getActiveProfileName();
|
|
420
|
+
if (!activeProfileName) {
|
|
421
|
+
console.error('No active profile set.');
|
|
422
|
+
console.error('');
|
|
423
|
+
console.error('Either:');
|
|
424
|
+
console.error(' 1. Switch to a profile: coder profile switch <name>');
|
|
425
|
+
console.error(' 2. Create a new profile: coder profile create <name>');
|
|
426
|
+
console.error(' 3. Use legacy config: coder config set <key> <value>');
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const profile = await loadProfile(activeProfileName) || { name: activeProfileName };
|
|
431
|
+
profile[key] = value;
|
|
432
|
+
await saveProfile(activeProfileName, profile);
|
|
433
|
+
invalidateConfigCache();
|
|
434
|
+
|
|
435
|
+
const displayValue = key === 'apiKey' ? maskApiKey(value) : value;
|
|
436
|
+
console.log(`Set ${key} = ${displayValue} in profile '${activeProfileName}'`);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async function getProfileValueCommand(key) {
|
|
440
|
+
if (!key) {
|
|
441
|
+
console.error('Usage: coder profile get <key>');
|
|
442
|
+
process.exit(1);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const validKeys = new Set(getProfileConfigKeys());
|
|
446
|
+
if (!validKeys.has(key)) {
|
|
447
|
+
console.error(`Unknown key: ${key}`);
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const activeProfileName = await getActiveProfileName();
|
|
452
|
+
if (!activeProfileName) {
|
|
453
|
+
console.error('No active profile set.');
|
|
454
|
+
process.exit(1);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const profile = await loadProfile(activeProfileName);
|
|
458
|
+
if (!profile) {
|
|
459
|
+
console.error(`Profile '${activeProfileName}' not found.`);
|
|
460
|
+
process.exit(1);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const value = profile[key];
|
|
464
|
+
if (value === undefined) {
|
|
465
|
+
console.error(`Key '${key}' not set in profile '${activeProfileName}'`);
|
|
466
|
+
process.exit(1);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Don't mask for get command - user wants the actual value
|
|
470
|
+
console.log(value);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async function showCurrentProfile() {
|
|
474
|
+
const activeProfile = await getActiveProfileName();
|
|
475
|
+
|
|
476
|
+
if (process.env.CODER_PROFILE) {
|
|
477
|
+
console.log(`${activeProfile} (from CODER_PROFILE env var)`);
|
|
478
|
+
} else if (activeProfile) {
|
|
479
|
+
console.log(activeProfile);
|
|
480
|
+
} else {
|
|
481
|
+
console.log('(none)');
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
async function migrateToProfile(profileName) {
|
|
486
|
+
if (!profileName) {
|
|
487
|
+
console.error('Usage: coder profile migrate <name>');
|
|
488
|
+
console.error('');
|
|
489
|
+
console.error('This will create a new profile from your current legacy config.');
|
|
490
|
+
process.exit(1);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (!isValidProfileName(profileName)) {
|
|
494
|
+
console.error(`Invalid profile name: ${profileName}`);
|
|
495
|
+
process.exit(1);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (await profileExists(profileName)) {
|
|
499
|
+
console.error(`Profile '${profileName}' already exists.`);
|
|
500
|
+
process.exit(1);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const result = await createProfileFromLegacyConfig(profileName);
|
|
504
|
+
if (!result) {
|
|
505
|
+
console.log('No legacy configuration found to migrate.');
|
|
506
|
+
console.log('');
|
|
507
|
+
console.log('Create a new profile with:');
|
|
508
|
+
console.log(` coder profile create ${profileName}`);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
console.log(`Created profile '${profileName}' from legacy config.`);
|
|
513
|
+
console.log('');
|
|
514
|
+
console.log('Migrated settings:');
|
|
515
|
+
for (const [key, value] of Object.entries(result)) {
|
|
516
|
+
if (key !== 'name') {
|
|
517
|
+
const displayValue = key === 'apiKey' ? maskApiKey(value) : value;
|
|
518
|
+
console.log(` ${key}: ${displayValue}`);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
console.log('');
|
|
523
|
+
console.log('To use this profile:');
|
|
524
|
+
console.log(` coder profile switch ${profileName}`);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async function promptConfirmation(message) {
|
|
528
|
+
const rl = readline.createInterface({
|
|
529
|
+
input: process.stdin,
|
|
530
|
+
output: process.stdout
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
return new Promise((resolve) => {
|
|
534
|
+
rl.question(`${message} [y/N] `, (answer) => {
|
|
535
|
+
rl.close();
|
|
536
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command: coder reject - Reject task results
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { request } from '../http-client.js';
|
|
6
|
+
|
|
7
|
+
function parseRejectArgs(args = []) {
|
|
8
|
+
const options = {
|
|
9
|
+
taskId: null,
|
|
10
|
+
cleanup: false
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
for (const arg of args) {
|
|
14
|
+
if (!options.taskId && !arg.startsWith('--')) {
|
|
15
|
+
options.taskId = arg;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (arg === '--cleanup') {
|
|
20
|
+
options.cleanup = true;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.error(`Error: Unknown option ${arg}`);
|
|
25
|
+
console.error('Usage: coder reject <task-id> [--cleanup]');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return options;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function rejectTask(args = []) {
|
|
33
|
+
const { taskId, cleanup } = parseRejectArgs(args);
|
|
34
|
+
|
|
35
|
+
if (!taskId) {
|
|
36
|
+
console.error('Error: Task ID required');
|
|
37
|
+
console.error('Usage: coder reject <task-id> [--cleanup]');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log(`Rejecting task ${taskId}...`);
|
|
42
|
+
|
|
43
|
+
const body = cleanup ? { cleanup: true } : {};
|
|
44
|
+
await request(`/tasks/${taskId}/reject`, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
body: JSON.stringify(body)
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
console.log('✓ Task rejected');
|
|
50
|
+
if (cleanup) {
|
|
51
|
+
console.log('✓ Task artifacts cleaned up');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command: coder results - Get results of a completed task
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { request } from '../http-client.js';
|
|
6
|
+
|
|
7
|
+
export async function getResults(taskId) {
|
|
8
|
+
if (!taskId) {
|
|
9
|
+
console.error('Error: Task ID required');
|
|
10
|
+
console.error('Usage: coder results <task-id>');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
console.log(`Fetching results for task ${taskId}...`);
|
|
15
|
+
|
|
16
|
+
const data = await request(`/tasks/${taskId}/results`);
|
|
17
|
+
|
|
18
|
+
console.log(`\nTask Results:`);
|
|
19
|
+
console.log(` Task ID: ${data.task.taskId}`);
|
|
20
|
+
console.log(` Status: ${data.task.status}`);
|
|
21
|
+
console.log(` Exit Code: ${data.task.exitCode}`);
|
|
22
|
+
console.log(` Created: ${data.task.createdAt}`);
|
|
23
|
+
console.log(` Finished: ${data.task.finishedAt}`);
|
|
24
|
+
if (data.task.environment) {
|
|
25
|
+
console.log(` Environment: ${data.task.environment}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (data.task.parameters && Object.keys(data.task.parameters || {}).length > 0) {
|
|
29
|
+
console.log('\nParameters:');
|
|
30
|
+
Object.entries(data.task.parameters).forEach(([key, value]) => {
|
|
31
|
+
console.log(` - ${key}: ${value}`);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (data.task.envVars && Object.keys(data.task.envVars || {}).length > 0) {
|
|
36
|
+
console.log('\nEnvironment Variables:');
|
|
37
|
+
Object.entries(data.task.envVars).forEach(([key, value]) => {
|
|
38
|
+
console.log(` - ${key}: ${value}`);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log(`\nTask Metadata (from task.json):`);
|
|
43
|
+
console.log(` Environment: ${data.result.environment}`);
|
|
44
|
+
console.log(` Task Type: ${data.result.task_type}`);
|
|
45
|
+
console.log(` Container: ${data.result.container_id}`);
|
|
46
|
+
console.log(` Command: ${data.result.command}`);
|
|
47
|
+
|
|
48
|
+
if (data.task.exitCode === 0) {
|
|
49
|
+
console.log(`\n✓ Task completed successfully`);
|
|
50
|
+
} else {
|
|
51
|
+
console.log(`\n✗ Task failed with exit code ${data.task.exitCode}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (data.result.summary) {
|
|
55
|
+
console.log('\nSummary:');
|
|
56
|
+
console.log(data.result.summary.trim());
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const repos = Array.isArray(data.result.repos_changed) ? data.result.repos_changed : [];
|
|
60
|
+
const stats = data.result.stats || {
|
|
61
|
+
repositories: repos.length,
|
|
62
|
+
filesChanged: repos.reduce((sum, repo) => sum + (Number(repo.files_changed) || 0), 0),
|
|
63
|
+
linesAdded: repos.reduce((sum, repo) => sum + (Number(repo.lines_added) || 0), 0),
|
|
64
|
+
linesDeleted: repos.reduce((sum, repo) => sum + (Number(repo.lines_deleted) || 0), 0)
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (repos.length > 0) {
|
|
68
|
+
console.log('\nRepositories Changed:');
|
|
69
|
+
repos.forEach((repo) => {
|
|
70
|
+
const filesChanged = Number(repo.files_changed) || (Array.isArray(repo.files) ? repo.files.length : 0);
|
|
71
|
+
const linesAdded = Number(repo.lines_added) || 0;
|
|
72
|
+
const linesDeleted = Number(repo.lines_deleted) || 0;
|
|
73
|
+
console.log(` • ${repo.name} (${filesChanged} files, +${linesAdded} -${linesDeleted})`);
|
|
74
|
+
|
|
75
|
+
if (Array.isArray(repo.files) && repo.files.length > 0) {
|
|
76
|
+
console.log(' Files:');
|
|
77
|
+
repo.files.forEach((file) => {
|
|
78
|
+
const added = Number(file.added) || 0;
|
|
79
|
+
const deleted = Number(file.deleted) || 0;
|
|
80
|
+
console.log(` - ${file.path} (+${added} -${deleted})`);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
console.log(`\nTotals: ${stats.repositories || repos.length} repos, ${stats.filesChanged || 0} files, +${stats.linesAdded || 0} -${stats.linesDeleted || 0}`);
|
|
86
|
+
} else {
|
|
87
|
+
console.log('\nNo repositories changed.');
|
|
88
|
+
}
|
|
89
|
+
}
|