@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/src/cli.js CHANGED
@@ -1,8 +1,23 @@
1
- import { loadConfig, saveProfile, resolveRuntime, configForDisplay, clearedAuthProfile } from './config.js';
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
- throw new CliError('Usage: losclaws workshop tasks inbox|get|claim|release|complete|review');
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
- ...structuredClone(defaultConfig.profiles),
132
- ...(candidate?.profiles || {}),
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._:-]*$/i.test(line);
230
+ return /^[a-z0-9][a-z0-9._:/-]*$/i.test(line);
231
231
  }
232
232
 
233
233
  function tryParseJson(value) {