@corners/cli 0.0.5 → 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 +549 -108
- 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 +8 -0
- package/dist/support.d.ts.map +1 -1
- package/dist/support.js +35 -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) {
|
|
@@ -128,6 +154,24 @@ const WORKSTREAM_PULL_QUERY = `
|
|
|
128
154
|
updatedAt
|
|
129
155
|
}
|
|
130
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
|
+
}
|
|
131
175
|
}
|
|
132
176
|
}
|
|
133
177
|
}
|
|
@@ -308,6 +352,71 @@ const UPDATE_WORKSTREAM_MUTATION = `
|
|
|
308
352
|
}
|
|
309
353
|
}
|
|
310
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
|
+
`;
|
|
311
420
|
const REVOKE_MCP_SESSION_MUTATION = `
|
|
312
421
|
mutation CliRevokeMcpSession($id: ID!) {
|
|
313
422
|
revokeMCPSession(id: $id)
|
|
@@ -379,6 +488,7 @@ function printMainHelp(runtime) {
|
|
|
379
488
|
"Commands:",
|
|
380
489
|
" auth login|logout|status",
|
|
381
490
|
" guidance status|sync",
|
|
491
|
+
" skills list|status",
|
|
382
492
|
" init",
|
|
383
493
|
" corner use",
|
|
384
494
|
" whoami",
|
|
@@ -403,6 +513,17 @@ function printInitHelp(runtime) {
|
|
|
403
513
|
" corners init [--profile <name>] [--api-url <url>] [--json]",
|
|
404
514
|
].join("\n"));
|
|
405
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
|
+
}
|
|
406
527
|
function printGuidanceHelp(runtime) {
|
|
407
528
|
printLine(runtime.stdout, [
|
|
408
529
|
"corners guidance",
|
|
@@ -443,18 +564,24 @@ function printWorkstreamHelp(runtime) {
|
|
|
443
564
|
"corners workstream",
|
|
444
565
|
"",
|
|
445
566
|
"Usage:",
|
|
446
|
-
" corners workstream list [--json]",
|
|
567
|
+
" corners workstream list [--all] [--json]",
|
|
447
568
|
" corners workstream use <workstreamId> [--json]",
|
|
448
569
|
" corners workstream current [--json]",
|
|
449
570
|
" corners workstream create <name> [--summary <text>] [--corner <cornerNameOrId>] [--json]",
|
|
450
|
-
" corners workstream pull <
|
|
451
|
-
" corners workstream update <
|
|
452
|
-
" corners workstream push <
|
|
453
|
-
" corners workstream question list <
|
|
454
|
-
" corners workstream question ask <
|
|
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]",
|
|
455
576
|
" corners workstream question answer <questionId> [--text <text>] [--json]",
|
|
456
|
-
" corners workstream attach <
|
|
577
|
+
" corners workstream attach [-w <id>] --kind <kind> --entity-id <id> [--json]",
|
|
457
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>",
|
|
458
585
|
"",
|
|
459
586
|
"Lifecycle statuses:",
|
|
460
587
|
" scoping | in_progress | blocked | done",
|
|
@@ -672,17 +799,6 @@ async function withPrompts(runtime, run) {
|
|
|
672
799
|
await prompts.close();
|
|
673
800
|
}
|
|
674
801
|
}
|
|
675
|
-
async function readOptionalUtf8(path) {
|
|
676
|
-
try {
|
|
677
|
-
return await readFile(path, "utf8");
|
|
678
|
-
}
|
|
679
|
-
catch (error) {
|
|
680
|
-
if (error.code === "ENOENT") {
|
|
681
|
-
return null;
|
|
682
|
-
}
|
|
683
|
-
throw error;
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
802
|
function ensureTrailingNewline(value) {
|
|
687
803
|
return value.endsWith("\n") ? value : `${value}\n`;
|
|
688
804
|
}
|
|
@@ -818,6 +934,7 @@ async function resolveRootContext(runtime, common, root, options) {
|
|
|
818
934
|
printLine(runtime.stderr, warning);
|
|
819
935
|
}
|
|
820
936
|
}
|
|
937
|
+
await syncSkillsIfNeeded(root.rootDir, root.config, (dir, cfg) => runtime.config.writeLocalConfig(dir, cfg));
|
|
821
938
|
return {
|
|
822
939
|
root,
|
|
823
940
|
guidance,
|
|
@@ -904,6 +1021,16 @@ async function resolveExplicitWorkstream(client, common, workstreamId) {
|
|
|
904
1021
|
}
|
|
905
1022
|
return data.workstream;
|
|
906
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
|
+
}
|
|
907
1034
|
function connectWorkstream(config, workstreamId) {
|
|
908
1035
|
if (config.connectedWorkstreams.some((entry) => entry.id === workstreamId)) {
|
|
909
1036
|
return config;
|
|
@@ -939,17 +1066,6 @@ function upsertCornerOverride(config, relativePath, corner) {
|
|
|
939
1066
|
cornerOverrides: overrides,
|
|
940
1067
|
};
|
|
941
1068
|
}
|
|
942
|
-
async function buildPlannedEdit(path, nextContent) {
|
|
943
|
-
const existing = await readOptionalUtf8(path);
|
|
944
|
-
if (existing === nextContent) {
|
|
945
|
-
return null;
|
|
946
|
-
}
|
|
947
|
-
return {
|
|
948
|
-
path,
|
|
949
|
-
action: existing === null ? "create" : "update",
|
|
950
|
-
content: nextContent,
|
|
951
|
-
};
|
|
952
|
-
}
|
|
953
1069
|
function withUpdatedGuidanceConfig(config, managedTargets, syncedAt) {
|
|
954
1070
|
return {
|
|
955
1071
|
...config,
|
|
@@ -977,12 +1093,6 @@ function ensureGitignoreEntry(existing) {
|
|
|
977
1093
|
}
|
|
978
1094
|
return `${trimmed}\n${LOCAL_ROOT_CONFIG_RELATIVE_PATH}\n`;
|
|
979
1095
|
}
|
|
980
|
-
async function writePlannedEdits(edits) {
|
|
981
|
-
for (const edit of edits) {
|
|
982
|
-
await mkdir(dirname(edit.path), { recursive: true });
|
|
983
|
-
await writeFile(edit.path, edit.content, "utf8");
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
1096
|
function formatWorkstreamLine(workstream) {
|
|
987
1097
|
return [
|
|
988
1098
|
workstream.id,
|
|
@@ -1304,6 +1414,7 @@ async function handleInit(args, runtime, inherited) {
|
|
|
1304
1414
|
selectedTargets.push(target);
|
|
1305
1415
|
}
|
|
1306
1416
|
}
|
|
1417
|
+
const installSkills = await prompts.confirm("Install Corners skills? (sync, push, ask, plan)", { defaultValue: true });
|
|
1307
1418
|
const selectedCornerId = await prompts.select("Choose the default corner for this local root.", joinedCorners.map((corner) => ({
|
|
1308
1419
|
label: `${corner.name} (${corner.id})`,
|
|
1309
1420
|
value: corner.id,
|
|
@@ -1313,7 +1424,7 @@ async function handleInit(args, runtime, inherited) {
|
|
|
1313
1424
|
const defaultCorner = joinedCorners.find((corner) => corner.id === selectedCornerId) ??
|
|
1314
1425
|
joinedCorners[0];
|
|
1315
1426
|
const syncedAt = toIsoString();
|
|
1316
|
-
|
|
1427
|
+
let nextLocalConfig = withUpdatedGuidanceConfig({
|
|
1317
1428
|
version: 1,
|
|
1318
1429
|
profile: profileName,
|
|
1319
1430
|
workspace: profile.workspace,
|
|
@@ -1325,6 +1436,10 @@ async function handleInit(args, runtime, inherited) {
|
|
|
1325
1436
|
cornerOverrides: existingConfig?.cornerOverrides ?? [],
|
|
1326
1437
|
connectedWorkstreams: existingConfig?.connectedWorkstreams ?? [],
|
|
1327
1438
|
}, selectedTargets.map((target) => target.key), syncedAt);
|
|
1439
|
+
if (installSkills) {
|
|
1440
|
+
const skillRefs = await buildInstalledSkillReferences(syncedAt);
|
|
1441
|
+
nextLocalConfig = withUpdatedSkillsConfig(nextLocalConfig, skillRefs, syncedAt);
|
|
1442
|
+
}
|
|
1328
1443
|
const plannedEdits = [];
|
|
1329
1444
|
const gitignorePath = join(rootDir, ".gitignore");
|
|
1330
1445
|
const gitignoreContent = ensureGitignoreEntry(await readOptionalUtf8(gitignorePath));
|
|
@@ -1345,6 +1460,10 @@ async function handleInit(args, runtime, inherited) {
|
|
|
1345
1460
|
plannedEdits.push(edit);
|
|
1346
1461
|
}
|
|
1347
1462
|
}
|
|
1463
|
+
if (installSkills) {
|
|
1464
|
+
const skillEdits = await buildSkillPlannedEdits(rootDir);
|
|
1465
|
+
plannedEdits.push(...skillEdits);
|
|
1466
|
+
}
|
|
1348
1467
|
if (plannedEdits.length > 0 && !common.json) {
|
|
1349
1468
|
printLine(runtime.stderr, [
|
|
1350
1469
|
"Planned changes:",
|
|
@@ -1390,6 +1509,100 @@ async function handleInit(args, runtime, inherited) {
|
|
|
1390
1509
|
return 0;
|
|
1391
1510
|
});
|
|
1392
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
|
+
}
|
|
1393
1606
|
async function handleGuidance(args, runtime, inherited) {
|
|
1394
1607
|
const subcommand = args[0];
|
|
1395
1608
|
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
@@ -1587,6 +1800,7 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1587
1800
|
help: { type: "boolean", short: "h" },
|
|
1588
1801
|
profile: { type: "string" },
|
|
1589
1802
|
"api-url": { type: "string" },
|
|
1803
|
+
all: { type: "boolean" },
|
|
1590
1804
|
},
|
|
1591
1805
|
});
|
|
1592
1806
|
const common = mergeCommonOptions(inherited, {
|
|
@@ -1598,44 +1812,41 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1598
1812
|
printWorkstreamHelp(runtime);
|
|
1599
1813
|
return 0;
|
|
1600
1814
|
}
|
|
1601
|
-
const
|
|
1602
|
-
const
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
entry,
|
|
1613
|
-
workstream: null,
|
|
1614
|
-
};
|
|
1615
|
-
}
|
|
1616
|
-
}));
|
|
1617
|
-
const workstreams = hydrated
|
|
1618
|
-
.filter((entry) => entry.workstream !== null)
|
|
1619
|
-
.map((entry) => entry.workstream);
|
|
1620
|
-
const missingWorkstreamIds = hydrated
|
|
1621
|
-
.filter((entry) => entry.workstream === null)
|
|
1622
|
-
.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) ?? []);
|
|
1623
1826
|
if (common.json) {
|
|
1624
1827
|
printJson(runtime.stdout, withGuidanceJsonMetadata({
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1828
|
+
workstreams: allWorkstreams.map((ws) => ({
|
|
1829
|
+
...ws,
|
|
1830
|
+
connected: connectedIds.has(ws.id),
|
|
1831
|
+
})),
|
|
1832
|
+
totalCount,
|
|
1628
1833
|
}, guidance));
|
|
1629
1834
|
}
|
|
1630
1835
|
else {
|
|
1631
|
-
if (
|
|
1632
|
-
printLine(runtime.
|
|
1633
|
-
|
|
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
|
+
})
|
|
1634
1845
|
.join("\n"));
|
|
1846
|
+
if (totalCount > 100) {
|
|
1847
|
+
printLine(runtime.stderr, `Showing 100 of ${totalCount} workstreams.`);
|
|
1848
|
+
}
|
|
1635
1849
|
}
|
|
1636
|
-
printLine(runtime.stdout, workstreams.length > 0
|
|
1637
|
-
? workstreams.map(formatWorkstreamLine).join("\n")
|
|
1638
|
-
: "No workstreams are connected to this local environment.");
|
|
1639
1850
|
}
|
|
1640
1851
|
return 0;
|
|
1641
1852
|
}
|
|
@@ -1672,15 +1883,23 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1672
1883
|
if (nextConfig !== root.config) {
|
|
1673
1884
|
await runtime.config.writeLocalConfig(root.rootDir, nextConfig);
|
|
1674
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
|
+
});
|
|
1675
1893
|
if (common.json) {
|
|
1676
1894
|
printJson(runtime.stdout, withGuidanceJsonMetadata({
|
|
1677
1895
|
ok: true,
|
|
1678
1896
|
root: root.rootDir,
|
|
1679
1897
|
workstream,
|
|
1898
|
+
bound: true,
|
|
1680
1899
|
}, guidance));
|
|
1681
1900
|
}
|
|
1682
1901
|
else {
|
|
1683
|
-
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.`);
|
|
1684
1903
|
}
|
|
1685
1904
|
return 0;
|
|
1686
1905
|
}
|
|
@@ -1703,12 +1922,14 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1703
1922
|
const { root, guidance } = await requireLocalRoot(runtime, common);
|
|
1704
1923
|
const cwdPath = await realpath(runtime.cwd);
|
|
1705
1924
|
const resolvedCorner = resolveCornerForCwd(root, cwdPath);
|
|
1925
|
+
const binding = await runtime.config.getBinding(runtime.cwd);
|
|
1706
1926
|
const payload = {
|
|
1707
1927
|
cwd: runtime.cwd,
|
|
1708
1928
|
root: root.rootDir,
|
|
1709
1929
|
profile: root.config.profile,
|
|
1710
1930
|
workspace: root.config.workspace,
|
|
1711
1931
|
resolvedCorner,
|
|
1932
|
+
activeWorkstreamId: binding?.workstreamId ?? null,
|
|
1712
1933
|
connectedWorkstreamIds: root.config.connectedWorkstreams.map((entry) => entry.id),
|
|
1713
1934
|
};
|
|
1714
1935
|
if (common.json) {
|
|
@@ -1721,6 +1942,7 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1721
1942
|
`Profile: ${payload.profile}`,
|
|
1722
1943
|
`Workspace: ${payload.workspace ?? "-"}`,
|
|
1723
1944
|
`Resolved corner: ${payload.resolvedCorner.corner.name} (${payload.resolvedCorner.corner.id})`,
|
|
1945
|
+
`Active workstream: ${payload.activeWorkstreamId ?? "none"}`,
|
|
1724
1946
|
`Connected workstreams: ${payload.connectedWorkstreamIds.length}`,
|
|
1725
1947
|
].join("\n"));
|
|
1726
1948
|
}
|
|
@@ -1785,12 +2007,13 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1785
2007
|
case "pull": {
|
|
1786
2008
|
const parsed = parseArgs({
|
|
1787
2009
|
args: args.slice(1),
|
|
1788
|
-
allowPositionals:
|
|
2010
|
+
allowPositionals: false,
|
|
1789
2011
|
options: {
|
|
1790
2012
|
json: { type: "boolean" },
|
|
1791
2013
|
help: { type: "boolean", short: "h" },
|
|
1792
2014
|
profile: { type: "string" },
|
|
1793
2015
|
"api-url": { type: "string" },
|
|
2016
|
+
workstream: { type: "string", short: "w" },
|
|
1794
2017
|
},
|
|
1795
2018
|
});
|
|
1796
2019
|
const common = mergeCommonOptions(inherited, {
|
|
@@ -1802,12 +2025,7 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1802
2025
|
printWorkstreamHelp(runtime);
|
|
1803
2026
|
return 0;
|
|
1804
2027
|
}
|
|
1805
|
-
const workstreamId = parsed.
|
|
1806
|
-
if (!workstreamId) {
|
|
1807
|
-
throw new CLIError("Usage: corners workstream pull <workstreamId>", {
|
|
1808
|
-
json: common.json,
|
|
1809
|
-
});
|
|
1810
|
-
}
|
|
2028
|
+
const workstreamId = await resolveWorkstreamId(runtime, common, parsed.values.workstream);
|
|
1811
2029
|
const { guidance, client } = await requireCommandProfile(runtime, common);
|
|
1812
2030
|
const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
|
|
1813
2031
|
const data = await client.graphql(WORKSTREAM_PULL_QUERY, {
|
|
@@ -1820,7 +2038,7 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1820
2038
|
}
|
|
1821
2039
|
else {
|
|
1822
2040
|
const snapshot = data.workstream;
|
|
1823
|
-
|
|
2041
|
+
const lines = [
|
|
1824
2042
|
`${snapshot.name} (${snapshot.id})`,
|
|
1825
2043
|
snapshot.summary ? `Summary: ${snapshot.summary}` : "Summary: -",
|
|
1826
2044
|
`State: ${snapshot.isOpen === false ? "archived" : "open"}`,
|
|
@@ -1830,19 +2048,41 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1830
2048
|
`Answered questions: ${snapshot.answeredQuestions?.length ?? 0}`,
|
|
1831
2049
|
`Attachments: ${snapshot.attachments?.totalCount ?? 0}`,
|
|
1832
2050
|
`Feed items returned: ${snapshot.feed?.totalCount ?? 0}`,
|
|
1833
|
-
]
|
|
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"));
|
|
1834
2073
|
}
|
|
1835
2074
|
return 0;
|
|
1836
2075
|
}
|
|
1837
2076
|
case "update": {
|
|
1838
2077
|
const parsed = parseArgs({
|
|
1839
2078
|
args: args.slice(1),
|
|
1840
|
-
allowPositionals:
|
|
2079
|
+
allowPositionals: false,
|
|
1841
2080
|
options: {
|
|
1842
2081
|
json: { type: "boolean" },
|
|
1843
2082
|
help: { type: "boolean", short: "h" },
|
|
1844
2083
|
profile: { type: "string" },
|
|
1845
2084
|
"api-url": { type: "string" },
|
|
2085
|
+
workstream: { type: "string", short: "w" },
|
|
1846
2086
|
status: { type: "string" },
|
|
1847
2087
|
"status-details": { type: "string" },
|
|
1848
2088
|
"clear-status-details": { type: "boolean" },
|
|
@@ -1857,10 +2097,6 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1857
2097
|
printWorkstreamHelp(runtime);
|
|
1858
2098
|
return 0;
|
|
1859
2099
|
}
|
|
1860
|
-
const workstreamId = parsed.positionals[0];
|
|
1861
|
-
if (!workstreamId) {
|
|
1862
|
-
throw new CLIError("Usage: corners workstream update <workstreamId> [--status <status>] [--status-details <text> | --clear-status-details]", { json: common.json });
|
|
1863
|
-
}
|
|
1864
2100
|
const rawStatusDetails = parsed.values["status-details"];
|
|
1865
2101
|
const clearStatusDetails = parsed.values["clear-status-details"] === true;
|
|
1866
2102
|
if (rawStatusDetails !== undefined && clearStatusDetails) {
|
|
@@ -1871,6 +2107,7 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1871
2107
|
!clearStatusDetails) {
|
|
1872
2108
|
throw new CLIError("Provide --status, --status-details, or --clear-status-details.", { json: common.json });
|
|
1873
2109
|
}
|
|
2110
|
+
const workstreamId = await resolveWorkstreamId(runtime, common, parsed.values.workstream);
|
|
1874
2111
|
const { guidance, client } = await requireCommandProfile(runtime, common);
|
|
1875
2112
|
const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
|
|
1876
2113
|
const result = await client.graphql(UPDATE_WORKSTREAM_MUTATION, {
|
|
@@ -1912,6 +2149,7 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1912
2149
|
help: { type: "boolean", short: "h" },
|
|
1913
2150
|
profile: { type: "string" },
|
|
1914
2151
|
"api-url": { type: "string" },
|
|
2152
|
+
workstream: { type: "string", short: "w" },
|
|
1915
2153
|
type: { type: "string" },
|
|
1916
2154
|
message: { type: "string" },
|
|
1917
2155
|
summary: { type: "string" },
|
|
@@ -1928,15 +2166,10 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1928
2166
|
printWorkstreamHelp(runtime);
|
|
1929
2167
|
return 0;
|
|
1930
2168
|
}
|
|
1931
|
-
const workstreamId = parsed.
|
|
1932
|
-
if (!workstreamId) {
|
|
1933
|
-
throw new CLIError("Usage: corners workstream push <workstreamId>", {
|
|
1934
|
-
json: common.json,
|
|
1935
|
-
});
|
|
1936
|
-
}
|
|
2169
|
+
const workstreamId = await resolveWorkstreamId(runtime, common, parsed.values.workstream);
|
|
1937
2170
|
const message = parsed.values.message ??
|
|
1938
|
-
(parsed.positionals.length >
|
|
1939
|
-
? parsed.positionals.
|
|
2171
|
+
(parsed.positionals.length > 0
|
|
2172
|
+
? parsed.positionals.join(" ")
|
|
1940
2173
|
: await readTextFromStdin(runtime.stdin));
|
|
1941
2174
|
if (!message) {
|
|
1942
2175
|
throw new CLIError("Workstream updates need a message. Use --message or pipe text on stdin.", { json: common.json });
|
|
@@ -1989,12 +2222,13 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1989
2222
|
case "list": {
|
|
1990
2223
|
const parsed = parseArgs({
|
|
1991
2224
|
args: args.slice(2),
|
|
1992
|
-
allowPositionals:
|
|
2225
|
+
allowPositionals: false,
|
|
1993
2226
|
options: {
|
|
1994
2227
|
json: { type: "boolean" },
|
|
1995
2228
|
help: { type: "boolean", short: "h" },
|
|
1996
2229
|
profile: { type: "string" },
|
|
1997
2230
|
"api-url": { type: "string" },
|
|
2231
|
+
workstream: { type: "string", short: "w" },
|
|
1998
2232
|
},
|
|
1999
2233
|
});
|
|
2000
2234
|
const common = mergeCommonOptions(inherited, {
|
|
@@ -2006,10 +2240,7 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
2006
2240
|
printWorkstreamHelp(runtime);
|
|
2007
2241
|
return 0;
|
|
2008
2242
|
}
|
|
2009
|
-
const workstreamId = parsed.
|
|
2010
|
-
if (!workstreamId) {
|
|
2011
|
-
throw new CLIError("Usage: corners workstream question list <workstreamId>", { json: common.json });
|
|
2012
|
-
}
|
|
2243
|
+
const workstreamId = await resolveWorkstreamId(runtime, common, parsed.values.workstream);
|
|
2013
2244
|
const { guidance, client } = await requireCommandProfile(runtime, common);
|
|
2014
2245
|
const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
|
|
2015
2246
|
const data = await client.graphql(WORKSTREAM_QUESTION_LIST_QUERY, {
|
|
@@ -2040,6 +2271,7 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
2040
2271
|
help: { type: "boolean", short: "h" },
|
|
2041
2272
|
profile: { type: "string" },
|
|
2042
2273
|
"api-url": { type: "string" },
|
|
2274
|
+
workstream: { type: "string", short: "w" },
|
|
2043
2275
|
question: { type: "string" },
|
|
2044
2276
|
rationale: { type: "string" },
|
|
2045
2277
|
"suggested-answer": { type: "string", multiple: true },
|
|
@@ -2054,13 +2286,10 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
2054
2286
|
printWorkstreamHelp(runtime);
|
|
2055
2287
|
return 0;
|
|
2056
2288
|
}
|
|
2057
|
-
const workstreamId = parsed.
|
|
2058
|
-
if (!workstreamId) {
|
|
2059
|
-
throw new CLIError("Usage: corners workstream question ask <workstreamId> [--question <text>]", { json: common.json });
|
|
2060
|
-
}
|
|
2289
|
+
const workstreamId = await resolveWorkstreamId(runtime, common, parsed.values.workstream);
|
|
2061
2290
|
const question = parsed.values.question ??
|
|
2062
|
-
(parsed.positionals.length >
|
|
2063
|
-
? parsed.positionals.
|
|
2291
|
+
(parsed.positionals.length > 0
|
|
2292
|
+
? parsed.positionals.join(" ")
|
|
2064
2293
|
: await readTextFromStdin(runtime.stdin));
|
|
2065
2294
|
if (!question) {
|
|
2066
2295
|
throw new CLIError("Question text is required. Use --question or pipe text on stdin.", { json: common.json });
|
|
@@ -2137,12 +2366,13 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
2137
2366
|
case "attach": {
|
|
2138
2367
|
const parsed = parseArgs({
|
|
2139
2368
|
args: args.slice(1),
|
|
2140
|
-
allowPositionals:
|
|
2369
|
+
allowPositionals: false,
|
|
2141
2370
|
options: {
|
|
2142
2371
|
json: { type: "boolean" },
|
|
2143
2372
|
help: { type: "boolean", short: "h" },
|
|
2144
2373
|
profile: { type: "string" },
|
|
2145
2374
|
"api-url": { type: "string" },
|
|
2375
|
+
workstream: { type: "string", short: "w" },
|
|
2146
2376
|
kind: { type: "string" },
|
|
2147
2377
|
"entity-id": { type: "string" },
|
|
2148
2378
|
},
|
|
@@ -2156,12 +2386,12 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
2156
2386
|
printWorkstreamHelp(runtime);
|
|
2157
2387
|
return 0;
|
|
2158
2388
|
}
|
|
2159
|
-
const workstreamId = parsed.positionals[0];
|
|
2160
2389
|
const kind = parsed.values.kind;
|
|
2161
2390
|
const entityId = parsed.values["entity-id"];
|
|
2162
|
-
if (!
|
|
2163
|
-
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 });
|
|
2164
2393
|
}
|
|
2394
|
+
const workstreamId = await resolveWorkstreamId(runtime, common, parsed.values.workstream);
|
|
2165
2395
|
const { guidance, client } = await requireCommandProfile(runtime, common);
|
|
2166
2396
|
const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
|
|
2167
2397
|
const result = await client.graphql(ADD_WORKSTREAM_ATTACHMENT_MUTATION, {
|
|
@@ -2221,6 +2451,212 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
2221
2451
|
}
|
|
2222
2452
|
return 0;
|
|
2223
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
|
+
}
|
|
2224
2660
|
default:
|
|
2225
2661
|
throw new CLIError(`Unknown workstream command: ${subcommand}`);
|
|
2226
2662
|
}
|
|
@@ -2249,6 +2685,9 @@ export async function runCli(argv, runtime = createRuntime()) {
|
|
|
2249
2685
|
else if (parsed.rest[1] === "guidance") {
|
|
2250
2686
|
printGuidanceHelp(runtime);
|
|
2251
2687
|
}
|
|
2688
|
+
else if (parsed.rest[1] === "skills") {
|
|
2689
|
+
printSkillsHelp(runtime);
|
|
2690
|
+
}
|
|
2252
2691
|
else if (parsed.rest[1] === "init") {
|
|
2253
2692
|
printInitHelp(runtime);
|
|
2254
2693
|
}
|
|
@@ -2272,6 +2711,8 @@ export async function runCli(argv, runtime = createRuntime()) {
|
|
|
2272
2711
|
return handleInit(parsed.rest.slice(1), runtime, parsed.common);
|
|
2273
2712
|
case "guidance":
|
|
2274
2713
|
return handleGuidance(parsed.rest.slice(1), runtime, parsed.common);
|
|
2714
|
+
case "skills":
|
|
2715
|
+
return handleSkills(parsed.rest.slice(1), runtime, parsed.common);
|
|
2275
2716
|
case "auth":
|
|
2276
2717
|
return handleAuth(parsed.rest.slice(1), runtime, parsed.common);
|
|
2277
2718
|
case "corner":
|