@losclaws/cli 0.1.1 → 0.1.3
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 +33 -2
- package/package.json +5 -2
- package/src/acp-runtime.js +1083 -0
- package/src/cli.js +179 -3
- package/src/config.js +25 -2
- package/src/inspect.js +1 -1
- package/src/workshop-local.js +785 -0
- package/test/acp-runtime.test.js +231 -0
- package/test/config.test.js +6 -1
- package/test/inspect.test.js +26 -0
- package/test/workshop-local.test.js +83 -0
- package/testdata/mock-acp-agent.js +135 -0
package/src/cli.js
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
loadConfig,
|
|
3
|
+
saveProfile,
|
|
4
|
+
resolveRuntime,
|
|
5
|
+
configForDisplay,
|
|
6
|
+
clearedAuthProfile,
|
|
7
|
+
} from './config.js';
|
|
8
|
+
import { runWorkshopAcpTask } from './acp-runtime.js';
|
|
2
9
|
import { CliError } from './errors.js';
|
|
3
10
|
import { requestJson, openEventStream } from './http.js';
|
|
4
11
|
import { inspectCodingAgents } from './inspect.js';
|
|
5
12
|
import { requireReadableStream, streamEvents } from './sse.js';
|
|
13
|
+
import {
|
|
14
|
+
getWorkshopSyncStatus,
|
|
15
|
+
initWorkshopProjectRoot,
|
|
16
|
+
initWorkshopWorkspaceRoot,
|
|
17
|
+
pullWorkshopArtifacts,
|
|
18
|
+
pushWorkshopArtifacts,
|
|
19
|
+
syncWorkshopSkills,
|
|
20
|
+
} from './workshop-local.js';
|
|
6
21
|
import {
|
|
7
22
|
ensureNumber,
|
|
8
23
|
ensureString,
|
|
@@ -410,11 +425,139 @@ async function handleWorkshop(context, positionals) {
|
|
|
410
425
|
return;
|
|
411
426
|
}
|
|
412
427
|
|
|
428
|
+
if (command === 'init') {
|
|
429
|
+
await handleWorkshopInit(context, positionals.slice(1));
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (command === 'sync') {
|
|
434
|
+
await handleWorkshopSync(context, positionals.slice(1));
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (command === 'run') {
|
|
439
|
+
await handleWorkshopRun(context, positionals.slice(1));
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
|
|
413
443
|
throw new CliError(
|
|
414
|
-
'Usage: losclaws workshop config|me|workspaces|project-types|projects|flows|tasks|artifacts|events|inspect',
|
|
444
|
+
'Usage: losclaws workshop config|me|workspaces|project-types|projects|flows|tasks|artifacts|events|inspect|init|sync|run',
|
|
415
445
|
);
|
|
416
446
|
}
|
|
417
447
|
|
|
448
|
+
async function handleWorkshopInit(context, positionals) {
|
|
449
|
+
const target = positionals[0];
|
|
450
|
+
const rootPath = context.options.root || process.cwd();
|
|
451
|
+
const role = context.options.role || 'worker';
|
|
452
|
+
const request = (params) => requestWorkshop(context, params);
|
|
453
|
+
|
|
454
|
+
if (target === 'workspace') {
|
|
455
|
+
const result = await initWorkshopWorkspaceRoot({
|
|
456
|
+
requestWorkshop: request,
|
|
457
|
+
workspaceId: ensureString(context.options.id, '--id'),
|
|
458
|
+
rootPath,
|
|
459
|
+
role,
|
|
460
|
+
syncArtifacts: context.options.syncArtifacts !== false,
|
|
461
|
+
syncSkills: context.options.syncSkills !== false,
|
|
462
|
+
skillsRepoUrl: context.options.skillsRepo || '',
|
|
463
|
+
skillsRepoRef: context.options.skillsRepoRef || 'main',
|
|
464
|
+
});
|
|
465
|
+
printResult(context.options, result);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (target === 'project') {
|
|
470
|
+
const result = await initWorkshopProjectRoot({
|
|
471
|
+
requestWorkshop: request,
|
|
472
|
+
projectId: ensureString(context.options.id, '--id'),
|
|
473
|
+
rootPath,
|
|
474
|
+
role,
|
|
475
|
+
syncArtifacts: context.options.syncArtifacts !== false,
|
|
476
|
+
syncSkills: context.options.syncSkills !== false,
|
|
477
|
+
skillsRepoUrl: context.options.skillsRepo || '',
|
|
478
|
+
skillsRepoRef: context.options.skillsRepoRef || 'main',
|
|
479
|
+
});
|
|
480
|
+
printResult(context.options, result);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
throw new CliError('Usage: losclaws workshop init workspace|project --id ID [--root PATH] [--role ROLE]');
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async function handleWorkshopSync(context, positionals) {
|
|
488
|
+
const command = positionals[0];
|
|
489
|
+
const rootPath = context.options.root || process.cwd();
|
|
490
|
+
|
|
491
|
+
if (command === 'status') {
|
|
492
|
+
const result = await getWorkshopSyncStatus({ rootPath });
|
|
493
|
+
printResult(context.options, result);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (command === 'pull') {
|
|
498
|
+
const result = await pullWorkshopArtifacts({
|
|
499
|
+
requestWorkshop: (params) => requestWorkshop(context, params),
|
|
500
|
+
rootPath,
|
|
501
|
+
});
|
|
502
|
+
printResult(context.options, result);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (command === 'push') {
|
|
507
|
+
const result = await pushWorkshopArtifacts({
|
|
508
|
+
requestWorkshop: (params) => requestWorkshop(context, params),
|
|
509
|
+
rootPath,
|
|
510
|
+
});
|
|
511
|
+
printResult(context.options, result);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (command === 'skills') {
|
|
516
|
+
const result = await syncWorkshopSkills({
|
|
517
|
+
requestWorkshop: (params) => requestWorkshop(context, params),
|
|
518
|
+
rootPath,
|
|
519
|
+
role: context.options.role,
|
|
520
|
+
skillsRepoUrl: context.options.skillsRepo || '',
|
|
521
|
+
skillsRepoRef: context.options.skillsRepoRef || 'main',
|
|
522
|
+
});
|
|
523
|
+
printResult(context.options, result);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
throw new CliError('Usage: losclaws workshop sync status|pull|push|skills [--root PATH]');
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
async function handleWorkshopRun(context, positionals) {
|
|
531
|
+
if (positionals.length > 0) {
|
|
532
|
+
throw new CliError(
|
|
533
|
+
'Usage: losclaws workshop run --id ID [--root PATH] [--role worker|reviewer] [--approval-policy interactive|read-only|auto-allow|auto-reject|remote] [--feedback-timeout MS] [--acp-session-id ID] [--message TEXT|--message-file PATH] [--command CMD] [--args JSON] [--env JSON] [--model NAME] [--close-session] [--dry-run]',
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const message = context.options.messageFile
|
|
538
|
+
? await readTextFile(context.options.messageFile)
|
|
539
|
+
: context.options.message || '';
|
|
540
|
+
const args = await loadOptionalJsonOption(context.options.args, context.options.argsFile);
|
|
541
|
+
const env = await loadOptionalJsonOption(context.options.env, context.options.envFile);
|
|
542
|
+
if (args !== undefined && !Array.isArray(args)) {
|
|
543
|
+
throw new CliError('--args must be a JSON array.');
|
|
544
|
+
}
|
|
545
|
+
if (env !== undefined && (typeof env !== 'object' || Array.isArray(env) || env === null)) {
|
|
546
|
+
throw new CliError('--env must be a JSON object.');
|
|
547
|
+
}
|
|
548
|
+
const result = await runWorkshopAcpTask({
|
|
549
|
+
requestWorkshop: (params) => requestWorkshop(context, params),
|
|
550
|
+
state: context.state,
|
|
551
|
+
options: {
|
|
552
|
+
...context.options,
|
|
553
|
+
message,
|
|
554
|
+
...(args !== undefined ? { args } : {}),
|
|
555
|
+
...(env !== undefined ? { env } : {}),
|
|
556
|
+
},
|
|
557
|
+
});
|
|
558
|
+
printResult(context.options, result);
|
|
559
|
+
}
|
|
560
|
+
|
|
418
561
|
async function handleWorkshopWorkspaces(context, positionals) {
|
|
419
562
|
const command = positionals[0];
|
|
420
563
|
if (command === 'list') {
|
|
@@ -858,7 +1001,27 @@ async function handleWorkshopTasks(context, positionals) {
|
|
|
858
1001
|
return;
|
|
859
1002
|
}
|
|
860
1003
|
|
|
861
|
-
|
|
1004
|
+
if (command === 'request-feedback') {
|
|
1005
|
+
const taskId = ensureString(context.options.id, '--id');
|
|
1006
|
+
const options = await loadOptionalJsonOption(context.options.options, context.options.optionsFile);
|
|
1007
|
+
if (options !== undefined && !Array.isArray(options)) {
|
|
1008
|
+
throw new CliError('Task feedback options must be a JSON array when provided.');
|
|
1009
|
+
}
|
|
1010
|
+
const result = await requestWorkshop(context, {
|
|
1011
|
+
method: 'POST',
|
|
1012
|
+
path: `/api/v1/tasks/${taskId}/feedback-requests`,
|
|
1013
|
+
body: {
|
|
1014
|
+
expectedVersion: ensureNumber(context.options.expectedVersion, '--expected-version'),
|
|
1015
|
+
kind: ensureString(context.options.kind || 'single_select', '--kind'),
|
|
1016
|
+
prompt: ensureString(context.options.prompt, '--prompt'),
|
|
1017
|
+
...(options !== undefined ? { options } : {}),
|
|
1018
|
+
},
|
|
1019
|
+
});
|
|
1020
|
+
printResult(context.options, result);
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
throw new CliError('Usage: losclaws workshop tasks inbox|get|claim|release|complete|review|request-feedback');
|
|
862
1025
|
}
|
|
863
1026
|
|
|
864
1027
|
async function handleWorkshopArtifacts(context, positionals) {
|
|
@@ -1267,6 +1430,18 @@ Usage:
|
|
|
1267
1430
|
losclaws workshop config
|
|
1268
1431
|
losclaws workshop me
|
|
1269
1432
|
losclaws workshop inspect
|
|
1433
|
+
losclaws workshop init workspace --id ID [--root PATH] [--role worker|reviewer] [--skills-repo URL] [--skills-repo-ref REF]
|
|
1434
|
+
losclaws workshop init project --id ID [--root PATH] [--role worker|reviewer] [--skills-repo URL] [--skills-repo-ref REF]
|
|
1435
|
+
losclaws workshop agents list
|
|
1436
|
+
losclaws workshop agents get --name NAME
|
|
1437
|
+
losclaws workshop agents set --name NAME --command CMD [--title TEXT] [--args JSON|--args-file PATH] [--env JSON|--env-file PATH] [--cwd PATH] [--auth-method-id ID] [--mcp-servers JSON|--mcp-servers-file PATH] [--default-prompt TEXT]
|
|
1438
|
+
losclaws workshop agents delete --name NAME
|
|
1439
|
+
losclaws workshop sync status [--root PATH]
|
|
1440
|
+
losclaws workshop sync pull [--root PATH]
|
|
1441
|
+
losclaws workshop sync push [--root PATH]
|
|
1442
|
+
losclaws workshop sync skills [--root PATH] [--role worker|reviewer] [--skills-repo URL] [--skills-repo-ref REF]
|
|
1443
|
+
losclaws workshop acp run --id ID [--root PATH] [--agent NAME] [--role worker|reviewer] [--approval-policy interactive|read-only|auto-allow|auto-reject] [--acp-session-id ID] [--workshop-session-id ID] [--workshop-session-version N] [--message TEXT|--message-file PATH] [--close-session] [--dry-run]
|
|
1444
|
+
losclaws workshop sessions update --id ID --expected-version N [--events JSON|--events-file PATH] [--status STATUS] [--log-message TEXT] [--feedback-response JSON]
|
|
1270
1445
|
losclaws workshop workspaces list|get --id ID
|
|
1271
1446
|
losclaws workshop workspaces create --slug SLUG --name NAME [--default-locale en]
|
|
1272
1447
|
losclaws workshop workspaces update --id ID [--slug SLUG] [--name NAME] [--default-locale en]
|
|
@@ -1296,6 +1471,7 @@ Usage:
|
|
|
1296
1471
|
losclaws workshop tasks release --id ID --expected-version N
|
|
1297
1472
|
losclaws workshop tasks complete --id ID --expected-version N (--outputs JSON | --outputs-file PATH)
|
|
1298
1473
|
losclaws workshop tasks review --id ID --expected-version N --expected-session-version N --outcome approved|revise [--comment TEXT]
|
|
1474
|
+
losclaws workshop tasks request-feedback --id ID --expected-version N --kind single_select|multi_select|text --prompt TEXT [--options JSON|--options-file PATH]
|
|
1299
1475
|
losclaws workshop artifacts get --id ID
|
|
1300
1476
|
losclaws workshop artifacts revise --id ID --expected-version N --content-kind KIND --base-revision-no N [--mime-type TYPE] [--body-text TEXT | --body-text-file PATH | --body-json JSON | --body-json-file PATH | --body-base64 BASE64]
|
|
1301
1477
|
losclaws workshop events list [--workspace-id ID] [--project-id ID] [--flow-id ID] [--since-seq N] [--limit N] [--order asc|desc]
|
package/src/config.js
CHANGED
|
@@ -62,6 +62,14 @@ export async function saveProfile(state, patch) {
|
|
|
62
62
|
const nextProfile = {
|
|
63
63
|
...state.profile,
|
|
64
64
|
...patch,
|
|
65
|
+
acp: {
|
|
66
|
+
...(state.profile.acp || { agents: {} }),
|
|
67
|
+
...(patch.acp || {}),
|
|
68
|
+
agents:
|
|
69
|
+
patch.acp && 'agents' in patch.acp
|
|
70
|
+
? patch.acp.agents
|
|
71
|
+
: (state.profile.acp && state.profile.acp.agents) || {},
|
|
72
|
+
},
|
|
65
73
|
lastUpdatedAt: new Date().toISOString(),
|
|
66
74
|
};
|
|
67
75
|
|
|
@@ -124,12 +132,27 @@ export function configForDisplay(state) {
|
|
|
124
132
|
}
|
|
125
133
|
|
|
126
134
|
function mergeConfig(candidate) {
|
|
135
|
+
const defaultProfile = structuredClone(defaultConfig.profiles.default);
|
|
136
|
+
const mergedProfiles = Object.fromEntries(
|
|
137
|
+
Object.entries(candidate?.profiles || {}).map(([name, profile]) => [
|
|
138
|
+
name,
|
|
139
|
+
mergeProfile(defaultProfile, profile),
|
|
140
|
+
]),
|
|
141
|
+
);
|
|
142
|
+
|
|
127
143
|
return {
|
|
128
144
|
...structuredClone(defaultConfig),
|
|
129
145
|
...candidate,
|
|
130
146
|
profiles: {
|
|
131
|
-
|
|
132
|
-
...
|
|
147
|
+
default: defaultProfile,
|
|
148
|
+
...mergedProfiles,
|
|
133
149
|
},
|
|
134
150
|
};
|
|
135
151
|
}
|
|
152
|
+
|
|
153
|
+
function mergeProfile(defaultProfile, candidateProfile) {
|
|
154
|
+
return {
|
|
155
|
+
...structuredClone(defaultProfile),
|
|
156
|
+
...(candidateProfile || {}),
|
|
157
|
+
};
|
|
158
|
+
}
|
package/src/inspect.js
CHANGED
|
@@ -227,7 +227,7 @@ function looksLikeModelName(line) {
|
|
|
227
227
|
if (/^usage:/i.test(line) || /^error:/i.test(line)) {
|
|
228
228
|
return false;
|
|
229
229
|
}
|
|
230
|
-
return /^[a-z0-9][a-z0-9._
|
|
230
|
+
return /^[a-z0-9][a-z0-9._:/-]*$/i.test(line);
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
function tryParseJson(value) {
|