@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.d.ts.map +1 -1
- package/dist/cli.js +656 -105
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +11 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/guidance.d.ts.map +1 -1
- package/dist/guidance.js +15 -12
- package/dist/guidance.js.map +1 -1
- package/dist/skills.d.ts +41 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +160 -0
- package/dist/skills.js.map +1 -0
- package/dist/support.d.ts +9 -0
- package/dist/support.d.ts.map +1 -1
- package/dist/support.js +50 -1
- package/dist/support.js.map +1 -1
- package/package.json +3 -2
- package/skills/corners-ask/SKILL.md +223 -0
- package/skills/corners-plan/SKILL.md +253 -0
- package/skills/corners-push/SKILL.md +177 -0
- package/skills/corners-sync/SKILL.md +245 -0
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,38 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile, realpath } from "node:fs/promises";
|
|
2
2
|
import { hostname } from "node:os";
|
|
3
|
-
import { basename,
|
|
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 {
|
|
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 <
|
|
425
|
-
" corners workstream
|
|
426
|
-
" corners workstream
|
|
427
|
-
" corners workstream question
|
|
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 <
|
|
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
|
|
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
|
-
|
|
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
|
|
1564
|
-
const
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
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
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
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 (
|
|
1594
|
-
printLine(runtime.
|
|
1595
|
-
|
|
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:
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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 >
|
|
1829
|
-
? parsed.positionals.
|
|
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:
|
|
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.
|
|
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.
|
|
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 >
|
|
1953
|
-
? parsed.positionals.
|
|
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:
|
|
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 (!
|
|
2053
|
-
throw new CLIError("Usage: corners workstream attach <
|
|
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":
|