@corners/cli 0.0.4 → 0.0.6

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/cli.js CHANGED
@@ -1,12 +1,38 @@
1
- import { mkdir, readFile, realpath, writeFile } from "node:fs/promises";
1
+ import { readFile, realpath } from "node:fs/promises";
2
2
  import { hostname } from "node:os";
3
- import { basename, dirname, join, relative, resolve, sep } from "node:path";
3
+ import { basename, join, relative, resolve, sep } from "node:path";
4
4
  import { parseArgs } from "node:util";
5
5
  import { CornersApiClient as DefaultCornersApiClient, } from "./client.js";
6
6
  import { ConfigStore, LOCAL_ROOT_CONFIG_RELATIVE_PATH, } from "./config.js";
7
7
  import { buildExpectedManagedGuidanceBlock, buildGuidanceFileContent, buildGuidanceRevision, extractManagedGuidanceBlock, GUIDANCE_SYNC_COMMAND, GUIDANCE_TARGETS, normalizeGuidanceTargets, } from "./guidance.js";
8
8
  import { createPromptApi } from "./prompts.js";
9
- import { CLIError, getPackageVersion, normalizeApiUrl, openUrlInBrowser, printJson, printLine, readTextFromStdin, sleep, toGraphQLAttachmentKind, toGraphQLWorkstreamUpdateType, toIsoString, } from "./support.js";
9
+ import { BUNDLED_SKILLS, buildInstalledSkillReferences, buildSkillPlannedEdits, getBundledSkillPath, parseSkillFrontmatter, resolveSkillsState, syncSkillsIfNeeded, withUpdatedSkillsConfig, } from "./skills.js";
10
+ import { buildPlannedEdit, CLIError, getPackageVersion, normalizeApiUrl, openUrlInBrowser, printJson, printLine, readOptionalUtf8, readTextFromStdin, sleep, toGraphQLAttachmentKind, toGraphQLWorkstreamStatus, toGraphQLWorkstreamUpdateType, toIsoString, writePlannedEdits, } from "./support.js";
11
+ const WORKSTREAM_LIST_QUERY = `
12
+ query CliWorkstreamList($first: Int!, $filter: WorkstreamsFilter) {
13
+ workstreams(first: $first, filter: $filter) {
14
+ edges {
15
+ node {
16
+ id
17
+ accountId
18
+ cornerId
19
+ name
20
+ isOpen
21
+ summary
22
+ category
23
+ status
24
+ statusDetails
25
+ updatedAt
26
+ topic {
27
+ id
28
+ name
29
+ }
30
+ }
31
+ }
32
+ totalCount
33
+ }
34
+ }
35
+ `;
10
36
  const WORKSTREAM_LOOKUP_QUERY = `
11
37
  query CliWorkstreamLookup($id: ID!) {
12
38
  workstream(id: $id) {
@@ -14,9 +40,11 @@ const WORKSTREAM_LOOKUP_QUERY = `
14
40
  accountId
15
41
  cornerId
16
42
  name
43
+ isOpen
17
44
  summary
18
45
  category
19
46
  status
47
+ statusDetails
20
48
  updatedAt
21
49
  topic {
22
50
  id
@@ -32,9 +60,11 @@ const WORKSTREAM_PULL_QUERY = `
32
60
  accountId
33
61
  cornerId
34
62
  name
63
+ isOpen
35
64
  summary
36
65
  category
37
66
  status
67
+ statusDetails
38
68
  createdAt
39
69
  updatedAt
40
70
  firstAttachmentActivityAt
@@ -124,6 +154,24 @@ const WORKSTREAM_PULL_QUERY = `
124
154
  updatedAt
125
155
  }
126
156
  }
157
+ ... on WorkstreamTodoListAttachment {
158
+ todoList {
159
+ id
160
+ title
161
+ openItemCount
162
+ totalItemCount
163
+ items {
164
+ id
165
+ title
166
+ completed
167
+ itemIndex
168
+ assignee {
169
+ id
170
+ handle
171
+ }
172
+ }
173
+ }
174
+ }
127
175
  }
128
176
  }
129
177
  }
@@ -284,6 +332,91 @@ const RECORD_WORKSTREAM_UPDATE_MUTATION = `
284
332
  }
285
333
  }
286
334
  `;
335
+ const UPDATE_WORKSTREAM_MUTATION = `
336
+ mutation CliUpdateWorkstream($id: ID!, $input: UpdateWorkstreamInput!) {
337
+ updateWorkstream(id: $id, input: $input) {
338
+ id
339
+ accountId
340
+ cornerId
341
+ name
342
+ isOpen
343
+ summary
344
+ category
345
+ status
346
+ statusDetails
347
+ updatedAt
348
+ topic {
349
+ id
350
+ name
351
+ }
352
+ }
353
+ }
354
+ `;
355
+ const WORKSTREAM_TASK_LIST_QUERY = `
356
+ query CliWorkstreamTasks($id: ID!, $attachmentsFirst: Int!) {
357
+ workstream(id: $id) {
358
+ id
359
+ name
360
+ attachments(first: $attachmentsFirst, kind: TODOLIST) {
361
+ edges {
362
+ node {
363
+ __typename
364
+ ... on WorkstreamTodoListAttachment {
365
+ todoList {
366
+ id
367
+ title
368
+ openItemCount
369
+ totalItemCount
370
+ items {
371
+ id
372
+ title
373
+ description
374
+ completed
375
+ completedAt
376
+ itemIndex
377
+ assignee {
378
+ id
379
+ handle
380
+ }
381
+ }
382
+ }
383
+ }
384
+ }
385
+ }
386
+ }
387
+ }
388
+ }
389
+ `;
390
+ const CREATE_TODO_LIST_MUTATION = `
391
+ mutation CliCreateTodoList($input: CreateTodoListInput!) {
392
+ createTodoList(input: $input) {
393
+ id
394
+ title
395
+ openItemCount
396
+ totalItemCount
397
+ }
398
+ }
399
+ `;
400
+ const ADD_TODO_ITEM_MUTATION = `
401
+ mutation CliAddTodoItem($input: AddTodoItemInput!) {
402
+ addTodoItem(input: $input) {
403
+ id
404
+ title
405
+ completed
406
+ itemIndex
407
+ }
408
+ }
409
+ `;
410
+ const TOGGLE_TODO_ITEM_MUTATION = `
411
+ mutation CliToggleTodoItem($id: ID!, $completed: Boolean!) {
412
+ toggleTodoItem(id: $id, completed: $completed) {
413
+ id
414
+ title
415
+ completed
416
+ completedAt
417
+ }
418
+ }
419
+ `;
287
420
  const REVOKE_MCP_SESSION_MUTATION = `
288
421
  mutation CliRevokeMcpSession($id: ID!) {
289
422
  revokeMCPSession(id: $id)
@@ -319,9 +452,11 @@ const CREATE_WORKSTREAM_MUTATION = `
319
452
  id
320
453
  cornerId
321
454
  name
455
+ isOpen
322
456
  summary
323
457
  category
324
458
  status
459
+ statusDetails
325
460
  updatedAt
326
461
  topic {
327
462
  id
@@ -353,10 +488,11 @@ function printMainHelp(runtime) {
353
488
  "Commands:",
354
489
  " auth login|logout|status",
355
490
  " guidance status|sync",
491
+ " skills list|status",
356
492
  " init",
357
493
  " corner use",
358
494
  " whoami",
359
- " workstream (ws) list|use|current|create|pull|push|question|attach|reply-thread",
495
+ " workstream (ws) list|use|current|create|pull|update|push|question|attach|reply-thread",
360
496
  " help",
361
497
  " version",
362
498
  "",
@@ -377,6 +513,17 @@ function printInitHelp(runtime) {
377
513
  " corners init [--profile <name>] [--api-url <url>] [--json]",
378
514
  ].join("\n"));
379
515
  }
516
+ function printSkillsHelp(runtime) {
517
+ printLine(runtime.stdout, [
518
+ "corners skills",
519
+ "",
520
+ "Usage:",
521
+ " corners skills list [--json] List bundled skills",
522
+ " corners skills status [--json] Show installed skill state",
523
+ "",
524
+ "Skills are auto-synced on every CLI run once enabled via `corners init`.",
525
+ ].join("\n"));
526
+ }
380
527
  function printGuidanceHelp(runtime) {
381
528
  printLine(runtime.stdout, [
382
529
  "corners guidance",
@@ -417,17 +564,27 @@ function printWorkstreamHelp(runtime) {
417
564
  "corners workstream",
418
565
  "",
419
566
  "Usage:",
420
- " corners workstream list [--json]",
567
+ " corners workstream list [--all] [--json]",
421
568
  " corners workstream use <workstreamId> [--json]",
422
569
  " corners workstream current [--json]",
423
570
  " corners workstream create <name> [--summary <text>] [--corner <cornerNameOrId>] [--json]",
424
- " corners workstream pull <workstreamId> [--json]",
425
- " corners workstream push <workstreamId> [--type <type>] [--message <text>] [--summary <text>] [--file <path>] [--title <title>] [--json]",
426
- " corners workstream question list <workstreamId> [--json]",
427
- " corners workstream question ask <workstreamId> [--question <text>] [--rationale <text>] [--suggested-answer <text>]... [--json]",
571
+ " corners workstream pull [-w <id>] [--json]",
572
+ " corners workstream update [-w <id>] [--status <status>] [--status-details <text> | --clear-status-details] [--json]",
573
+ " corners workstream push [-w <id>] [--type <type>] [--message <text>] [--summary <text>] [--file <path>] [--title <title>] [--json]",
574
+ " corners workstream question list [-w <id>] [--json]",
575
+ " corners workstream question ask [-w <id>] [--question <text>] [--rationale <text>] [--suggested-answer <text>]... [--json]",
428
576
  " corners workstream question answer <questionId> [--text <text>] [--json]",
429
- " corners workstream attach <workstreamId> --kind <kind> --entity-id <id> [--json]",
577
+ " corners workstream attach [-w <id>] --kind <kind> --entity-id <id> [--json]",
430
578
  " corners workstream reply-thread <threadId> [--text <text>] [--json]",
579
+ " corners workstream task list [-w <id>] [--json]",
580
+ " corners workstream task add [-w <id>] <title> [--list <todoListId>] [--json]",
581
+ " corners workstream task complete <taskItemId> [--undo] [--json]",
582
+ "",
583
+ "The -w/--workstream flag is optional when an active workstream is set.",
584
+ "Set the active workstream with: corners workstream use <workstreamId>",
585
+ "",
586
+ "Lifecycle statuses:",
587
+ " scoping | in_progress | blocked | done",
431
588
  "",
432
589
  "Update types:",
433
590
  " status | blocker | learning | outcome",
@@ -642,17 +799,6 @@ async function withPrompts(runtime, run) {
642
799
  await prompts.close();
643
800
  }
644
801
  }
645
- async function readOptionalUtf8(path) {
646
- try {
647
- return await readFile(path, "utf8");
648
- }
649
- catch (error) {
650
- if (error.code === "ENOENT") {
651
- return null;
652
- }
653
- throw error;
654
- }
655
- }
656
802
  function ensureTrailingNewline(value) {
657
803
  return value.endsWith("\n") ? value : `${value}\n`;
658
804
  }
@@ -710,8 +856,7 @@ async function resolveGuidanceState(root) {
710
856
  target,
711
857
  content: await readOptionalUtf8(join(root.rootDir, target.relativePath)),
712
858
  })));
713
- const discoveredManagedTargets = GUIDANCE_TARGETS.map((target) => target.key)
714
- .filter((key) => {
859
+ const discoveredManagedTargets = GUIDANCE_TARGETS.map((target) => target.key).filter((key) => {
715
860
  const entry = fileEntries.find((candidate) => candidate.target.key === key);
716
861
  return extractManagedGuidanceBlock(entry?.content ?? null) !== null;
717
862
  });
@@ -789,6 +934,7 @@ async function resolveRootContext(runtime, common, root, options) {
789
934
  printLine(runtime.stderr, warning);
790
935
  }
791
936
  }
937
+ await syncSkillsIfNeeded(root.rootDir, root.config, (dir, cfg) => runtime.config.writeLocalConfig(dir, cfg));
792
938
  return {
793
939
  root,
794
940
  guidance,
@@ -875,6 +1021,16 @@ async function resolveExplicitWorkstream(client, common, workstreamId) {
875
1021
  }
876
1022
  return data.workstream;
877
1023
  }
1024
+ async function resolveWorkstreamId(runtime, common, flagValue) {
1025
+ if (flagValue) {
1026
+ return flagValue;
1027
+ }
1028
+ const binding = await runtime.config.getBinding(runtime.cwd);
1029
+ if (binding) {
1030
+ return binding.workstreamId;
1031
+ }
1032
+ throw new CLIError("No --workstream flag and no active workstream. Use `corners workstream use <id>` first.", { json: common.json });
1033
+ }
878
1034
  function connectWorkstream(config, workstreamId) {
879
1035
  if (config.connectedWorkstreams.some((entry) => entry.id === workstreamId)) {
880
1036
  return config;
@@ -910,17 +1066,6 @@ function upsertCornerOverride(config, relativePath, corner) {
910
1066
  cornerOverrides: overrides,
911
1067
  };
912
1068
  }
913
- async function buildPlannedEdit(path, nextContent) {
914
- const existing = await readOptionalUtf8(path);
915
- if (existing === nextContent) {
916
- return null;
917
- }
918
- return {
919
- path,
920
- action: existing === null ? "create" : "update",
921
- content: nextContent,
922
- };
923
- }
924
1069
  function withUpdatedGuidanceConfig(config, managedTargets, syncedAt) {
925
1070
  return {
926
1071
  ...config,
@@ -948,14 +1093,17 @@ function ensureGitignoreEntry(existing) {
948
1093
  }
949
1094
  return `${trimmed}\n${LOCAL_ROOT_CONFIG_RELATIVE_PATH}\n`;
950
1095
  }
951
- async function writePlannedEdits(edits) {
952
- for (const edit of edits) {
953
- await mkdir(dirname(edit.path), { recursive: true });
954
- await writeFile(edit.path, edit.content, "utf8");
955
- }
956
- }
957
1096
  function formatWorkstreamLine(workstream) {
958
- return `${workstream.id} ${workstream.name} ${workstream.status.toLowerCase()}${workstream.summary ? ` ${workstream.summary}` : ""}`;
1097
+ return [
1098
+ workstream.id,
1099
+ workstream.name,
1100
+ workstream.isOpen ? "open" : "archived",
1101
+ workstream.status.toLowerCase(),
1102
+ workstream.statusDetails ?? null,
1103
+ workstream.summary ? workstream.summary : null,
1104
+ ]
1105
+ .filter((part) => Boolean(part))
1106
+ .join(" ");
959
1107
  }
960
1108
  async function handleAuth(args, runtime, inherited) {
961
1109
  const subcommand = args[0];
@@ -1266,6 +1414,7 @@ async function handleInit(args, runtime, inherited) {
1266
1414
  selectedTargets.push(target);
1267
1415
  }
1268
1416
  }
1417
+ const installSkills = await prompts.confirm("Install Corners skills? (sync, push, ask, plan)", { defaultValue: true });
1269
1418
  const selectedCornerId = await prompts.select("Choose the default corner for this local root.", joinedCorners.map((corner) => ({
1270
1419
  label: `${corner.name} (${corner.id})`,
1271
1420
  value: corner.id,
@@ -1275,7 +1424,7 @@ async function handleInit(args, runtime, inherited) {
1275
1424
  const defaultCorner = joinedCorners.find((corner) => corner.id === selectedCornerId) ??
1276
1425
  joinedCorners[0];
1277
1426
  const syncedAt = toIsoString();
1278
- const nextLocalConfig = withUpdatedGuidanceConfig({
1427
+ let nextLocalConfig = withUpdatedGuidanceConfig({
1279
1428
  version: 1,
1280
1429
  profile: profileName,
1281
1430
  workspace: profile.workspace,
@@ -1287,6 +1436,10 @@ async function handleInit(args, runtime, inherited) {
1287
1436
  cornerOverrides: existingConfig?.cornerOverrides ?? [],
1288
1437
  connectedWorkstreams: existingConfig?.connectedWorkstreams ?? [],
1289
1438
  }, selectedTargets.map((target) => target.key), syncedAt);
1439
+ if (installSkills) {
1440
+ const skillRefs = await buildInstalledSkillReferences(syncedAt);
1441
+ nextLocalConfig = withUpdatedSkillsConfig(nextLocalConfig, skillRefs, syncedAt);
1442
+ }
1290
1443
  const plannedEdits = [];
1291
1444
  const gitignorePath = join(rootDir, ".gitignore");
1292
1445
  const gitignoreContent = ensureGitignoreEntry(await readOptionalUtf8(gitignorePath));
@@ -1307,6 +1460,10 @@ async function handleInit(args, runtime, inherited) {
1307
1460
  plannedEdits.push(edit);
1308
1461
  }
1309
1462
  }
1463
+ if (installSkills) {
1464
+ const skillEdits = await buildSkillPlannedEdits(rootDir);
1465
+ plannedEdits.push(...skillEdits);
1466
+ }
1310
1467
  if (plannedEdits.length > 0 && !common.json) {
1311
1468
  printLine(runtime.stderr, [
1312
1469
  "Planned changes:",
@@ -1352,6 +1509,100 @@ async function handleInit(args, runtime, inherited) {
1352
1509
  return 0;
1353
1510
  });
1354
1511
  }
1512
+ async function handleSkills(args, runtime, inherited) {
1513
+ const subcommand = args[0];
1514
+ if (!subcommand || subcommand === "help" || subcommand === "--help") {
1515
+ printSkillsHelp(runtime);
1516
+ return 0;
1517
+ }
1518
+ switch (subcommand) {
1519
+ case "list": {
1520
+ const parsed = parseArgs({
1521
+ args: args.slice(1),
1522
+ allowPositionals: false,
1523
+ options: {
1524
+ json: { type: "boolean" },
1525
+ help: { type: "boolean", short: "h" },
1526
+ },
1527
+ });
1528
+ const common = mergeCommonOptions(inherited, {
1529
+ json: parsed.values.json,
1530
+ });
1531
+ if (parsed.values.help) {
1532
+ printSkillsHelp(runtime);
1533
+ return 0;
1534
+ }
1535
+ const skills = await Promise.all(BUNDLED_SKILLS.map(async (skill) => {
1536
+ const content = await readFile(getBundledSkillPath(skill), "utf8");
1537
+ const fm = parseSkillFrontmatter(content);
1538
+ return {
1539
+ name: skill.name,
1540
+ version: fm?.version ?? "unknown",
1541
+ description: fm?.description ?? "",
1542
+ };
1543
+ }));
1544
+ if (common.json) {
1545
+ printJson(runtime.stdout, { ok: true, skills });
1546
+ }
1547
+ else {
1548
+ printLine(runtime.stdout, [
1549
+ "Available skills:",
1550
+ ...skills.map((s) => ` ${s.name.padEnd(16)} ${s.version.padEnd(8)} ${s.description}`),
1551
+ ].join("\n"));
1552
+ }
1553
+ return 0;
1554
+ }
1555
+ case "status": {
1556
+ const parsed = parseArgs({
1557
+ args: args.slice(1),
1558
+ allowPositionals: false,
1559
+ options: {
1560
+ json: { type: "boolean" },
1561
+ help: { type: "boolean", short: "h" },
1562
+ },
1563
+ });
1564
+ const common = mergeCommonOptions(inherited, {
1565
+ json: parsed.values.json,
1566
+ });
1567
+ if (parsed.values.help) {
1568
+ printSkillsHelp(runtime);
1569
+ return 0;
1570
+ }
1571
+ const rootContext = await requireLocalRoot(runtime, common, {
1572
+ emitWarning: false,
1573
+ });
1574
+ const state = await resolveSkillsState(rootContext.root.rootDir, rootContext.root.config);
1575
+ const payload = {
1576
+ ok: true,
1577
+ root: rootContext.root.rootDir,
1578
+ skills: state,
1579
+ };
1580
+ if (common.json) {
1581
+ printJson(runtime.stdout, payload);
1582
+ }
1583
+ else {
1584
+ printLine(runtime.stdout, [
1585
+ `Root: ${payload.root}`,
1586
+ `Last synced: ${state.lastSyncedAt ?? "-"}`,
1587
+ "",
1588
+ "Skills:",
1589
+ ...state.skills.map((s) => {
1590
+ const stateLabel = s.state.padEnd(8);
1591
+ const version = s.state === "stale"
1592
+ ? `(installed: ${s.installedVersion}, available: ${s.bundledVersion})`
1593
+ : s.state === "missing"
1594
+ ? ""
1595
+ : `(${s.bundledVersion})`;
1596
+ return ` ${s.name.padEnd(16)} ${stateLabel} ${version}`;
1597
+ }),
1598
+ ].join("\n"));
1599
+ }
1600
+ return 0;
1601
+ }
1602
+ default:
1603
+ throw new CLIError(`Unknown skills command: ${subcommand}`);
1604
+ }
1605
+ }
1355
1606
  async function handleGuidance(args, runtime, inherited) {
1356
1607
  const subcommand = args[0];
1357
1608
  if (!subcommand || subcommand === "help" || subcommand === "--help") {
@@ -1549,6 +1800,7 @@ async function handleWorkstream(args, runtime, inherited) {
1549
1800
  help: { type: "boolean", short: "h" },
1550
1801
  profile: { type: "string" },
1551
1802
  "api-url": { type: "string" },
1803
+ all: { type: "boolean" },
1552
1804
  },
1553
1805
  });
1554
1806
  const common = mergeCommonOptions(inherited, {
@@ -1560,44 +1812,41 @@ async function handleWorkstream(args, runtime, inherited) {
1560
1812
  printWorkstreamHelp(runtime);
1561
1813
  return 0;
1562
1814
  }
1563
- const rootContext = await requireLocalRoot(runtime, common);
1564
- const { root, guidance, client } = await requirePinnedRootProfile(runtime, common, rootContext);
1565
- const hydrated = await Promise.all(root.config.connectedWorkstreams.map(async (entry) => {
1566
- try {
1567
- return {
1568
- entry,
1569
- workstream: await resolveExplicitWorkstream(client, common, entry.id),
1570
- };
1571
- }
1572
- catch {
1573
- return {
1574
- entry,
1575
- workstream: null,
1576
- };
1577
- }
1578
- }));
1579
- const workstreams = hydrated
1580
- .filter((entry) => entry.workstream !== null)
1581
- .map((entry) => entry.workstream);
1582
- const missingWorkstreamIds = hydrated
1583
- .filter((entry) => entry.workstream === null)
1584
- .map((entry) => entry.entry.id);
1815
+ const { guidance, client } = await requireCommandProfile(runtime, common);
1816
+ const filter = {};
1817
+ if (!parsed.values.all) {
1818
+ filter.isOpen = true;
1819
+ }
1820
+ const data = await client.graphql(WORKSTREAM_LIST_QUERY, { first: 100, filter });
1821
+ const allWorkstreams = data.workstreams.edges.map((edge) => edge.node);
1822
+ const totalCount = data.workstreams.totalCount;
1823
+ // Try to find local root for connected workstream annotation
1824
+ const root = await runtime.config.findLocalRoot(runtime.cwd);
1825
+ const connectedIds = new Set(root?.config.connectedWorkstreams.map((entry) => entry.id) ?? []);
1585
1826
  if (common.json) {
1586
1827
  printJson(runtime.stdout, withGuidanceJsonMetadata({
1587
- root: root.rootDir,
1588
- workstreams,
1589
- missingWorkstreamIds,
1828
+ workstreams: allWorkstreams.map((ws) => ({
1829
+ ...ws,
1830
+ connected: connectedIds.has(ws.id),
1831
+ })),
1832
+ totalCount,
1590
1833
  }, guidance));
1591
1834
  }
1592
1835
  else {
1593
- if (missingWorkstreamIds.length > 0) {
1594
- printLine(runtime.stderr, missingWorkstreamIds
1595
- .map((workstreamId) => `Warning: connected workstream ${workstreamId} is missing or inaccessible.`)
1836
+ if (allWorkstreams.length === 0) {
1837
+ printLine(runtime.stdout, "No workstreams found.");
1838
+ }
1839
+ else {
1840
+ printLine(runtime.stdout, allWorkstreams
1841
+ .map((ws) => {
1842
+ const prefix = connectedIds.has(ws.id) ? "* " : " ";
1843
+ return `${prefix}${formatWorkstreamLine(ws)}`;
1844
+ })
1596
1845
  .join("\n"));
1846
+ if (totalCount > 100) {
1847
+ printLine(runtime.stderr, `Showing 100 of ${totalCount} workstreams.`);
1848
+ }
1597
1849
  }
1598
- printLine(runtime.stdout, workstreams.length > 0
1599
- ? workstreams.map(formatWorkstreamLine).join("\n")
1600
- : "No workstreams are connected to this local environment.");
1601
1850
  }
1602
1851
  return 0;
1603
1852
  }
@@ -1634,15 +1883,23 @@ async function handleWorkstream(args, runtime, inherited) {
1634
1883
  if (nextConfig !== root.config) {
1635
1884
  await runtime.config.writeLocalConfig(root.rootDir, nextConfig);
1636
1885
  }
1886
+ await runtime.config.setBinding(runtime.cwd, {
1887
+ profile: root.config.profile,
1888
+ workspace: root.config.workspace ?? "",
1889
+ workstreamId: workstream.id,
1890
+ cornerId: workstream.cornerId,
1891
+ boundAt: toIsoString(),
1892
+ });
1637
1893
  if (common.json) {
1638
1894
  printJson(runtime.stdout, withGuidanceJsonMetadata({
1639
1895
  ok: true,
1640
1896
  root: root.rootDir,
1641
1897
  workstream,
1898
+ bound: true,
1642
1899
  }, guidance));
1643
1900
  }
1644
1901
  else {
1645
- printLine(runtime.stdout, `Connected ${workstream.id} (${workstream.name}) to ${root.rootDir}.`);
1902
+ printLine(runtime.stdout, `Connected ${workstream.id} (${workstream.name}) to ${root.rootDir} and set as active workstream.`);
1646
1903
  }
1647
1904
  return 0;
1648
1905
  }
@@ -1665,12 +1922,14 @@ async function handleWorkstream(args, runtime, inherited) {
1665
1922
  const { root, guidance } = await requireLocalRoot(runtime, common);
1666
1923
  const cwdPath = await realpath(runtime.cwd);
1667
1924
  const resolvedCorner = resolveCornerForCwd(root, cwdPath);
1925
+ const binding = await runtime.config.getBinding(runtime.cwd);
1668
1926
  const payload = {
1669
1927
  cwd: runtime.cwd,
1670
1928
  root: root.rootDir,
1671
1929
  profile: root.config.profile,
1672
1930
  workspace: root.config.workspace,
1673
1931
  resolvedCorner,
1932
+ activeWorkstreamId: binding?.workstreamId ?? null,
1674
1933
  connectedWorkstreamIds: root.config.connectedWorkstreams.map((entry) => entry.id),
1675
1934
  };
1676
1935
  if (common.json) {
@@ -1683,6 +1942,7 @@ async function handleWorkstream(args, runtime, inherited) {
1683
1942
  `Profile: ${payload.profile}`,
1684
1943
  `Workspace: ${payload.workspace ?? "-"}`,
1685
1944
  `Resolved corner: ${payload.resolvedCorner.corner.name} (${payload.resolvedCorner.corner.id})`,
1945
+ `Active workstream: ${payload.activeWorkstreamId ?? "none"}`,
1686
1946
  `Connected workstreams: ${payload.connectedWorkstreamIds.length}`,
1687
1947
  ].join("\n"));
1688
1948
  }
@@ -1747,12 +2007,13 @@ async function handleWorkstream(args, runtime, inherited) {
1747
2007
  case "pull": {
1748
2008
  const parsed = parseArgs({
1749
2009
  args: args.slice(1),
1750
- allowPositionals: true,
2010
+ allowPositionals: false,
1751
2011
  options: {
1752
2012
  json: { type: "boolean" },
1753
2013
  help: { type: "boolean", short: "h" },
1754
2014
  profile: { type: "string" },
1755
2015
  "api-url": { type: "string" },
2016
+ workstream: { type: "string", short: "w" },
1756
2017
  },
1757
2018
  });
1758
2019
  const common = mergeCommonOptions(inherited, {
@@ -1764,12 +2025,7 @@ async function handleWorkstream(args, runtime, inherited) {
1764
2025
  printWorkstreamHelp(runtime);
1765
2026
  return 0;
1766
2027
  }
1767
- const workstreamId = parsed.positionals[0];
1768
- if (!workstreamId) {
1769
- throw new CLIError("Usage: corners workstream pull <workstreamId>", {
1770
- json: common.json,
1771
- });
1772
- }
2028
+ const workstreamId = await resolveWorkstreamId(runtime, common, parsed.values.workstream);
1773
2029
  const { guidance, client } = await requireCommandProfile(runtime, common);
1774
2030
  const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
1775
2031
  const data = await client.graphql(WORKSTREAM_PULL_QUERY, {
@@ -1782,13 +2038,104 @@ async function handleWorkstream(args, runtime, inherited) {
1782
2038
  }
1783
2039
  else {
1784
2040
  const snapshot = data.workstream;
1785
- printLine(runtime.stdout, [
2041
+ const lines = [
1786
2042
  `${snapshot.name} (${snapshot.id})`,
1787
2043
  snapshot.summary ? `Summary: ${snapshot.summary}` : "Summary: -",
2044
+ `State: ${snapshot.isOpen === false ? "archived" : "open"}`,
2045
+ `Lifecycle status: ${(snapshot.status ?? "SCOPING").toLowerCase()}`,
2046
+ `Status details: ${snapshot.statusDetails ?? "-"}`,
1788
2047
  `Open questions: ${snapshot.openQuestions?.length ?? 0}`,
1789
2048
  `Answered questions: ${snapshot.answeredQuestions?.length ?? 0}`,
1790
2049
  `Attachments: ${snapshot.attachments?.totalCount ?? 0}`,
1791
2050
  `Feed items returned: ${snapshot.feed?.totalCount ?? 0}`,
2051
+ ];
2052
+ // Append tasks from TodoList attachments
2053
+ const todoLists = (snapshot.attachments?.edges ?? [])
2054
+ .filter((e) => e.node.__typename === "WorkstreamTodoListAttachment" &&
2055
+ e.node.todoList)
2056
+ .map((e) => e.node.todoList);
2057
+ if (todoLists.length > 0) {
2058
+ lines.push("");
2059
+ lines.push("Tasks:");
2060
+ for (const list of todoLists) {
2061
+ const completed = list.totalItemCount - list.openItemCount;
2062
+ lines.push(` ${list.title} (${completed}/${list.totalItemCount} complete)`);
2063
+ for (const item of list.items) {
2064
+ const check = item.completed ? "[x]" : "[ ]";
2065
+ const assignee = item.assignee
2066
+ ? ` (@${item.assignee.handle})`
2067
+ : "";
2068
+ lines.push(` ${check} ${item.title}${assignee}`);
2069
+ }
2070
+ }
2071
+ }
2072
+ printLine(runtime.stdout, lines.join("\n"));
2073
+ }
2074
+ return 0;
2075
+ }
2076
+ case "update": {
2077
+ const parsed = parseArgs({
2078
+ args: args.slice(1),
2079
+ allowPositionals: false,
2080
+ options: {
2081
+ json: { type: "boolean" },
2082
+ help: { type: "boolean", short: "h" },
2083
+ profile: { type: "string" },
2084
+ "api-url": { type: "string" },
2085
+ workstream: { type: "string", short: "w" },
2086
+ status: { type: "string" },
2087
+ "status-details": { type: "string" },
2088
+ "clear-status-details": { type: "boolean" },
2089
+ },
2090
+ });
2091
+ const common = mergeCommonOptions(inherited, {
2092
+ json: parsed.values.json,
2093
+ profile: parsed.values.profile,
2094
+ apiUrl: parsed.values["api-url"],
2095
+ });
2096
+ if (parsed.values.help) {
2097
+ printWorkstreamHelp(runtime);
2098
+ return 0;
2099
+ }
2100
+ const rawStatusDetails = parsed.values["status-details"];
2101
+ const clearStatusDetails = parsed.values["clear-status-details"] === true;
2102
+ if (rawStatusDetails !== undefined && clearStatusDetails) {
2103
+ throw new CLIError("Choose either --status-details or --clear-status-details, not both.", { json: common.json });
2104
+ }
2105
+ if (parsed.values.status === undefined &&
2106
+ rawStatusDetails === undefined &&
2107
+ !clearStatusDetails) {
2108
+ throw new CLIError("Provide --status, --status-details, or --clear-status-details.", { json: common.json });
2109
+ }
2110
+ const workstreamId = await resolveWorkstreamId(runtime, common, parsed.values.workstream);
2111
+ const { guidance, client } = await requireCommandProfile(runtime, common);
2112
+ const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
2113
+ const result = await client.graphql(UPDATE_WORKSTREAM_MUTATION, {
2114
+ id: workstream.id,
2115
+ input: {
2116
+ status: parsed.values.status === undefined
2117
+ ? undefined
2118
+ : toGraphQLWorkstreamStatus(parsed.values.status),
2119
+ statusDetails: clearStatusDetails
2120
+ ? null
2121
+ : rawStatusDetails === undefined
2122
+ ? undefined
2123
+ : rawStatusDetails,
2124
+ },
2125
+ });
2126
+ const payload = {
2127
+ ok: true,
2128
+ workstream: result.updateWorkstream,
2129
+ };
2130
+ if (common.json) {
2131
+ printJson(runtime.stdout, withGuidanceJsonMetadata(payload, guidance));
2132
+ }
2133
+ else {
2134
+ printLine(runtime.stdout, [
2135
+ `Updated ${result.updateWorkstream.id}.`,
2136
+ `State: ${result.updateWorkstream.isOpen ? "open" : "archived"}`,
2137
+ `Lifecycle status: ${result.updateWorkstream.status.toLowerCase()}`,
2138
+ `Status details: ${result.updateWorkstream.statusDetails ?? "-"}`,
1792
2139
  ].join("\n"));
1793
2140
  }
1794
2141
  return 0;
@@ -1802,6 +2149,7 @@ async function handleWorkstream(args, runtime, inherited) {
1802
2149
  help: { type: "boolean", short: "h" },
1803
2150
  profile: { type: "string" },
1804
2151
  "api-url": { type: "string" },
2152
+ workstream: { type: "string", short: "w" },
1805
2153
  type: { type: "string" },
1806
2154
  message: { type: "string" },
1807
2155
  summary: { type: "string" },
@@ -1818,15 +2166,10 @@ async function handleWorkstream(args, runtime, inherited) {
1818
2166
  printWorkstreamHelp(runtime);
1819
2167
  return 0;
1820
2168
  }
1821
- const workstreamId = parsed.positionals[0];
1822
- if (!workstreamId) {
1823
- throw new CLIError("Usage: corners workstream push <workstreamId>", {
1824
- json: common.json,
1825
- });
1826
- }
2169
+ const workstreamId = await resolveWorkstreamId(runtime, common, parsed.values.workstream);
1827
2170
  const message = parsed.values.message ??
1828
- (parsed.positionals.length > 1
1829
- ? parsed.positionals.slice(1).join(" ")
2171
+ (parsed.positionals.length > 0
2172
+ ? parsed.positionals.join(" ")
1830
2173
  : await readTextFromStdin(runtime.stdin));
1831
2174
  if (!message) {
1832
2175
  throw new CLIError("Workstream updates need a message. Use --message or pipe text on stdin.", { json: common.json });
@@ -1879,12 +2222,13 @@ async function handleWorkstream(args, runtime, inherited) {
1879
2222
  case "list": {
1880
2223
  const parsed = parseArgs({
1881
2224
  args: args.slice(2),
1882
- allowPositionals: true,
2225
+ allowPositionals: false,
1883
2226
  options: {
1884
2227
  json: { type: "boolean" },
1885
2228
  help: { type: "boolean", short: "h" },
1886
2229
  profile: { type: "string" },
1887
2230
  "api-url": { type: "string" },
2231
+ workstream: { type: "string", short: "w" },
1888
2232
  },
1889
2233
  });
1890
2234
  const common = mergeCommonOptions(inherited, {
@@ -1896,10 +2240,7 @@ async function handleWorkstream(args, runtime, inherited) {
1896
2240
  printWorkstreamHelp(runtime);
1897
2241
  return 0;
1898
2242
  }
1899
- const workstreamId = parsed.positionals[0];
1900
- if (!workstreamId) {
1901
- throw new CLIError("Usage: corners workstream question list <workstreamId>", { json: common.json });
1902
- }
2243
+ const workstreamId = await resolveWorkstreamId(runtime, common, parsed.values.workstream);
1903
2244
  const { guidance, client } = await requireCommandProfile(runtime, common);
1904
2245
  const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
1905
2246
  const data = await client.graphql(WORKSTREAM_QUESTION_LIST_QUERY, {
@@ -1930,6 +2271,7 @@ async function handleWorkstream(args, runtime, inherited) {
1930
2271
  help: { type: "boolean", short: "h" },
1931
2272
  profile: { type: "string" },
1932
2273
  "api-url": { type: "string" },
2274
+ workstream: { type: "string", short: "w" },
1933
2275
  question: { type: "string" },
1934
2276
  rationale: { type: "string" },
1935
2277
  "suggested-answer": { type: "string", multiple: true },
@@ -1944,13 +2286,10 @@ async function handleWorkstream(args, runtime, inherited) {
1944
2286
  printWorkstreamHelp(runtime);
1945
2287
  return 0;
1946
2288
  }
1947
- const workstreamId = parsed.positionals[0];
1948
- if (!workstreamId) {
1949
- throw new CLIError("Usage: corners workstream question ask <workstreamId> [--question <text>]", { json: common.json });
1950
- }
2289
+ const workstreamId = await resolveWorkstreamId(runtime, common, parsed.values.workstream);
1951
2290
  const question = parsed.values.question ??
1952
- (parsed.positionals.length > 1
1953
- ? parsed.positionals.slice(1).join(" ")
2291
+ (parsed.positionals.length > 0
2292
+ ? parsed.positionals.join(" ")
1954
2293
  : await readTextFromStdin(runtime.stdin));
1955
2294
  if (!question) {
1956
2295
  throw new CLIError("Question text is required. Use --question or pipe text on stdin.", { json: common.json });
@@ -2027,12 +2366,13 @@ async function handleWorkstream(args, runtime, inherited) {
2027
2366
  case "attach": {
2028
2367
  const parsed = parseArgs({
2029
2368
  args: args.slice(1),
2030
- allowPositionals: true,
2369
+ allowPositionals: false,
2031
2370
  options: {
2032
2371
  json: { type: "boolean" },
2033
2372
  help: { type: "boolean", short: "h" },
2034
2373
  profile: { type: "string" },
2035
2374
  "api-url": { type: "string" },
2375
+ workstream: { type: "string", short: "w" },
2036
2376
  kind: { type: "string" },
2037
2377
  "entity-id": { type: "string" },
2038
2378
  },
@@ -2046,12 +2386,12 @@ async function handleWorkstream(args, runtime, inherited) {
2046
2386
  printWorkstreamHelp(runtime);
2047
2387
  return 0;
2048
2388
  }
2049
- const workstreamId = parsed.positionals[0];
2050
2389
  const kind = parsed.values.kind;
2051
2390
  const entityId = parsed.values["entity-id"];
2052
- if (!workstreamId || !kind || !entityId) {
2053
- throw new CLIError("Usage: corners workstream attach <workstreamId> --kind <kind> --entity-id <id>", { json: common.json });
2391
+ if (!kind || !entityId) {
2392
+ throw new CLIError("Usage: corners workstream attach [--workstream <id>] --kind <kind> --entity-id <id>", { json: common.json });
2054
2393
  }
2394
+ const workstreamId = await resolveWorkstreamId(runtime, common, parsed.values.workstream);
2055
2395
  const { guidance, client } = await requireCommandProfile(runtime, common);
2056
2396
  const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
2057
2397
  const result = await client.graphql(ADD_WORKSTREAM_ATTACHMENT_MUTATION, {
@@ -2111,6 +2451,212 @@ async function handleWorkstream(args, runtime, inherited) {
2111
2451
  }
2112
2452
  return 0;
2113
2453
  }
2454
+ case "task": {
2455
+ const nested = args[1];
2456
+ if (!nested || nested === "help" || nested === "--help") {
2457
+ printWorkstreamHelp(runtime);
2458
+ return 0;
2459
+ }
2460
+ switch (nested) {
2461
+ case "list": {
2462
+ const parsed = parseArgs({
2463
+ args: args.slice(2),
2464
+ allowPositionals: false,
2465
+ options: {
2466
+ json: { type: "boolean" },
2467
+ help: { type: "boolean", short: "h" },
2468
+ profile: { type: "string" },
2469
+ "api-url": { type: "string" },
2470
+ workstream: { type: "string", short: "w" },
2471
+ },
2472
+ });
2473
+ const common = mergeCommonOptions(inherited, {
2474
+ json: parsed.values.json,
2475
+ profile: parsed.values.profile,
2476
+ apiUrl: parsed.values["api-url"],
2477
+ });
2478
+ if (parsed.values.help) {
2479
+ printWorkstreamHelp(runtime);
2480
+ return 0;
2481
+ }
2482
+ const workstreamId = await resolveWorkstreamId(runtime, common, parsed.values.workstream);
2483
+ const { guidance, client } = await requireCommandProfile(runtime, common);
2484
+ const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
2485
+ const data = await client.graphql(WORKSTREAM_TASK_LIST_QUERY, {
2486
+ id: workstream.id,
2487
+ attachmentsFirst: 50,
2488
+ });
2489
+ if (!data.workstream) {
2490
+ throw new CLIError("Workstream not found", { json: common.json });
2491
+ }
2492
+ const todoLists = data.workstream.attachments.edges
2493
+ .filter((e) => e.node.todoList)
2494
+ .map((e) => e.node.todoList);
2495
+ if (common.json) {
2496
+ printJson(runtime.stdout, withGuidanceJsonMetadata({
2497
+ workstreamId: workstream.id,
2498
+ workstreamName: workstream.name,
2499
+ todoLists,
2500
+ }, guidance));
2501
+ }
2502
+ else {
2503
+ if (todoLists.length === 0) {
2504
+ printLine(runtime.stdout, "No task lists attached to this workstream.");
2505
+ }
2506
+ else {
2507
+ const lines = [
2508
+ `${workstream.name} (${workstream.id}) - Tasks`,
2509
+ "",
2510
+ ];
2511
+ for (const list of todoLists) {
2512
+ const completed = list.totalItemCount - list.openItemCount;
2513
+ lines.push(`${list.title} (${completed}/${list.totalItemCount} complete)`);
2514
+ for (const item of list.items) {
2515
+ const check = item.completed ? "[x]" : "[ ]";
2516
+ const assignee = item.assignee
2517
+ ? ` (@${item.assignee.handle})`
2518
+ : "";
2519
+ lines.push(` ${check} ${item.title}${assignee}`);
2520
+ }
2521
+ }
2522
+ printLine(runtime.stdout, lines.join("\n"));
2523
+ }
2524
+ }
2525
+ return 0;
2526
+ }
2527
+ case "add": {
2528
+ const parsed = parseArgs({
2529
+ args: args.slice(2),
2530
+ allowPositionals: true,
2531
+ options: {
2532
+ json: { type: "boolean" },
2533
+ help: { type: "boolean", short: "h" },
2534
+ profile: { type: "string" },
2535
+ "api-url": { type: "string" },
2536
+ workstream: { type: "string", short: "w" },
2537
+ title: { type: "string" },
2538
+ list: { type: "string" },
2539
+ description: { type: "string" },
2540
+ },
2541
+ });
2542
+ const common = mergeCommonOptions(inherited, {
2543
+ json: parsed.values.json,
2544
+ profile: parsed.values.profile,
2545
+ apiUrl: parsed.values["api-url"],
2546
+ });
2547
+ if (parsed.values.help) {
2548
+ printWorkstreamHelp(runtime);
2549
+ return 0;
2550
+ }
2551
+ const workstreamId = await resolveWorkstreamId(runtime, common, parsed.values.workstream);
2552
+ const taskTitle = parsed.values.title ??
2553
+ (parsed.positionals.length > 0
2554
+ ? parsed.positionals.join(" ")
2555
+ : await readTextFromStdin(runtime.stdin));
2556
+ if (!taskTitle) {
2557
+ throw new CLIError("Task title is required. Use --title or provide as positional argument.", { json: common.json });
2558
+ }
2559
+ const { guidance, client } = await requireCommandProfile(runtime, common);
2560
+ const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
2561
+ let todoListId = parsed.values.list;
2562
+ if (!todoListId) {
2563
+ // Query existing TODOLIST attachments
2564
+ const data = await client.graphql(WORKSTREAM_TASK_LIST_QUERY, {
2565
+ id: workstream.id,
2566
+ attachmentsFirst: 50,
2567
+ });
2568
+ const lists = (data.workstream?.attachments.edges ?? [])
2569
+ .filter((e) => e.node.todoList)
2570
+ .map((e) => e.node.todoList);
2571
+ if (lists.length === 1) {
2572
+ todoListId = lists[0].id;
2573
+ }
2574
+ else if (lists.length === 0) {
2575
+ // Auto-create a todo list with workstreamId for server-side attachment
2576
+ const createResult = await client.graphql(CREATE_TODO_LIST_MUTATION, {
2577
+ input: {
2578
+ title: `${workstream.name} Tasks`,
2579
+ workstreamId: workstream.id,
2580
+ },
2581
+ });
2582
+ todoListId = createResult.createTodoList.id;
2583
+ }
2584
+ else {
2585
+ const listSummary = lists
2586
+ .map((l) => ` ${l.id} (${l.title})`)
2587
+ .join("\n");
2588
+ throw new CLIError(`Multiple task lists found. Use --list <todoListId> to specify:\n${listSummary}`, { json: common.json });
2589
+ }
2590
+ }
2591
+ const result = await client.graphql(ADD_TODO_ITEM_MUTATION, {
2592
+ input: {
2593
+ todoListId,
2594
+ title: taskTitle,
2595
+ ...(parsed.values.description
2596
+ ? { description: parsed.values.description }
2597
+ : {}),
2598
+ },
2599
+ });
2600
+ if (common.json) {
2601
+ printJson(runtime.stdout, withGuidanceJsonMetadata({
2602
+ ok: true,
2603
+ workstreamId: workstream.id,
2604
+ todoListId,
2605
+ item: result.addTodoItem,
2606
+ }, guidance));
2607
+ }
2608
+ else {
2609
+ printLine(runtime.stdout, `Added task "${result.addTodoItem.title}" to ${workstream.id}.`);
2610
+ }
2611
+ return 0;
2612
+ }
2613
+ case "complete": {
2614
+ const parsed = parseArgs({
2615
+ args: args.slice(2),
2616
+ allowPositionals: true,
2617
+ options: {
2618
+ json: { type: "boolean" },
2619
+ help: { type: "boolean", short: "h" },
2620
+ profile: { type: "string" },
2621
+ "api-url": { type: "string" },
2622
+ undo: { type: "boolean" },
2623
+ },
2624
+ });
2625
+ const common = mergeCommonOptions(inherited, {
2626
+ json: parsed.values.json,
2627
+ profile: parsed.values.profile,
2628
+ apiUrl: parsed.values["api-url"],
2629
+ });
2630
+ if (parsed.values.help) {
2631
+ printWorkstreamHelp(runtime);
2632
+ return 0;
2633
+ }
2634
+ const taskItemId = parsed.positionals[0];
2635
+ if (!taskItemId) {
2636
+ throw new CLIError("Usage: corners workstream task complete <taskItemId> [--undo]", { json: common.json });
2637
+ }
2638
+ const completed = parsed.values.undo !== true;
2639
+ const { guidance, client } = await requireCommandProfile(runtime, common);
2640
+ const result = await client.graphql(TOGGLE_TODO_ITEM_MUTATION, {
2641
+ id: taskItemId,
2642
+ completed,
2643
+ });
2644
+ if (common.json) {
2645
+ printJson(runtime.stdout, withGuidanceJsonMetadata({
2646
+ ok: true,
2647
+ item: result.toggleTodoItem,
2648
+ }, guidance));
2649
+ }
2650
+ else {
2651
+ const verb = completed ? "Completed" : "Reopened";
2652
+ printLine(runtime.stdout, `${verb} task "${result.toggleTodoItem.title}" (${result.toggleTodoItem.id}).`);
2653
+ }
2654
+ return 0;
2655
+ }
2656
+ default:
2657
+ throw new CLIError(`Unknown workstream task command: ${nested}`);
2658
+ }
2659
+ }
2114
2660
  default:
2115
2661
  throw new CLIError(`Unknown workstream command: ${subcommand}`);
2116
2662
  }
@@ -2139,6 +2685,9 @@ export async function runCli(argv, runtime = createRuntime()) {
2139
2685
  else if (parsed.rest[1] === "guidance") {
2140
2686
  printGuidanceHelp(runtime);
2141
2687
  }
2688
+ else if (parsed.rest[1] === "skills") {
2689
+ printSkillsHelp(runtime);
2690
+ }
2142
2691
  else if (parsed.rest[1] === "init") {
2143
2692
  printInitHelp(runtime);
2144
2693
  }
@@ -2162,6 +2711,8 @@ export async function runCli(argv, runtime = createRuntime()) {
2162
2711
  return handleInit(parsed.rest.slice(1), runtime, parsed.common);
2163
2712
  case "guidance":
2164
2713
  return handleGuidance(parsed.rest.slice(1), runtime, parsed.common);
2714
+ case "skills":
2715
+ return handleSkills(parsed.rest.slice(1), runtime, parsed.common);
2165
2716
  case "auth":
2166
2717
  return handleAuth(parsed.rest.slice(1), runtime, parsed.common);
2167
2718
  case "corner":