@corners/cli 0.0.5 → 0.0.7
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 +690 -111
- 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/mcp-serve.d.ts +8 -0
- package/dist/mcp-serve.d.ts.map +1 -0
- package/dist/mcp-serve.js +182 -0
- package/dist/mcp-serve.js.map +1 -0
- 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 +54 -2
- package/dist/support.js.map +1 -1
- package/package.json +6 -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, writeFile } 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:",
|
|
@@ -1370,12 +1489,96 @@ async function handleInit(args, runtime, inherited) {
|
|
|
1370
1489
|
}
|
|
1371
1490
|
await writePlannedEdits(plannedEdits);
|
|
1372
1491
|
}
|
|
1492
|
+
// --- Agent Communication Setup ---
|
|
1493
|
+
const setupAgentComms = await prompts.confirm("Set up agent communication? (auto-connect workstream + CLAUDE.md instructions)", { defaultValue: true });
|
|
1494
|
+
let connectedWorkstream = null;
|
|
1495
|
+
if (setupAgentComms) {
|
|
1496
|
+
// Auto-connect or create a workstream
|
|
1497
|
+
const workstreams = await client.graphql(`query CliInitWorkstreams($cornerId: ID!) {
|
|
1498
|
+
corner(id: $cornerId) { workstreams(first: 20) { edges { node { id name status } } } }
|
|
1499
|
+
}`, { cornerId: defaultCorner.id }).then((r) => {
|
|
1500
|
+
const corner = r.corner;
|
|
1501
|
+
return corner?.workstreams?.edges?.map((e) => e.node) ?? [];
|
|
1502
|
+
}).catch(() => []);
|
|
1503
|
+
if (workstreams.length > 0) {
|
|
1504
|
+
const wsChoice = await prompts.select("Connect a workstream for agent updates:", [
|
|
1505
|
+
...workstreams.map((ws) => ({
|
|
1506
|
+
label: `${ws.name} (${ws.id})`,
|
|
1507
|
+
value: ws.id,
|
|
1508
|
+
})),
|
|
1509
|
+
{ label: "Create new workstream", value: "__new__" },
|
|
1510
|
+
{ label: "Skip", value: "__skip__" },
|
|
1511
|
+
], { defaultValue: workstreams[0]?.id });
|
|
1512
|
+
if (wsChoice === "__new__") {
|
|
1513
|
+
const wsName = `agent/${basename(rootDir)}`;
|
|
1514
|
+
const created = await client.graphql(`mutation CliCreateWsInit($input: CreateWorkstreamInput!) { createWorkstream(input: $input) { id name } }`, { input: { cornerId: defaultCorner.id, name: wsName } });
|
|
1515
|
+
connectedWorkstream = created.createWorkstream;
|
|
1516
|
+
}
|
|
1517
|
+
else if (wsChoice !== "__skip__") {
|
|
1518
|
+
connectedWorkstream = workstreams.find((ws) => ws.id === wsChoice) ?? null;
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
else {
|
|
1522
|
+
const wsName = `agent/${basename(rootDir)}`;
|
|
1523
|
+
const created = await client.graphql(`mutation CliCreateWsInit($input: CreateWorkstreamInput!) { createWorkstream(input: $input) { id name } }`, { input: { cornerId: defaultCorner.id, name: wsName } });
|
|
1524
|
+
connectedWorkstream = created.createWorkstream;
|
|
1525
|
+
}
|
|
1526
|
+
// Connect workstream to local root
|
|
1527
|
+
if (connectedWorkstream) {
|
|
1528
|
+
nextLocalConfig = {
|
|
1529
|
+
...nextLocalConfig,
|
|
1530
|
+
connectedWorkstreams: [
|
|
1531
|
+
...(nextLocalConfig.connectedWorkstreams ?? []),
|
|
1532
|
+
{ id: connectedWorkstream.id, connectedAt: toIsoString() },
|
|
1533
|
+
],
|
|
1534
|
+
};
|
|
1535
|
+
// Re-write the local config with the connected workstream
|
|
1536
|
+
const updatedConfigPath = runtime.config.getLocalConfigPath(rootDir);
|
|
1537
|
+
await writeFile(updatedConfigPath, `${JSON.stringify(nextLocalConfig, null, 2)}\n`, "utf8");
|
|
1538
|
+
}
|
|
1539
|
+
// Write CLAUDE.md agent instructions
|
|
1540
|
+
const claudeMdPath = join(rootDir, "CLAUDE.md");
|
|
1541
|
+
const existingClaudeMd = await readOptionalUtf8(claudeMdPath);
|
|
1542
|
+
const agentSection = `\n## Agent Communication\n\nWhen working on tasks, report progress to the team through Corners:\n- At the start: \`corners workstream push --type session_start --intent "description" --json\`\n- At natural checkpoints: \`corners workstream push --type checkpoint --message "what you did" --files "file1,file2" --json\`\n- When done: \`corners workstream push --type session_end --summary "what was accomplished" --json\`\n`;
|
|
1543
|
+
if (existingClaudeMd && !existingClaudeMd.includes("## Agent Communication")) {
|
|
1544
|
+
await writeFile(claudeMdPath, existingClaudeMd + agentSection, "utf8");
|
|
1545
|
+
if (!common.json) {
|
|
1546
|
+
printLine(runtime.stderr, " Added agent communication instructions to CLAUDE.md");
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
else if (!existingClaudeMd) {
|
|
1550
|
+
await writeFile(claudeMdPath, `# ${basename(rootDir)}\n${agentSection}`, "utf8");
|
|
1551
|
+
if (!common.json) {
|
|
1552
|
+
printLine(runtime.stderr, " Created CLAUDE.md with agent communication instructions");
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
// Send test checkpoint
|
|
1556
|
+
if (connectedWorkstream) {
|
|
1557
|
+
try {
|
|
1558
|
+
await client.graphql(RECORD_WORKSTREAM_UPDATE_MUTATION, {
|
|
1559
|
+
input: {
|
|
1560
|
+
workstreamId: connectedWorkstream.id,
|
|
1561
|
+
updateType: "CHECKPOINT",
|
|
1562
|
+
content: "Agent communication configured via corners init",
|
|
1563
|
+
agentType: "corners-cli",
|
|
1564
|
+
},
|
|
1565
|
+
});
|
|
1566
|
+
if (!common.json) {
|
|
1567
|
+
printLine(runtime.stderr, ` Test checkpoint sent to ${connectedWorkstream.name}`);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
catch {
|
|
1571
|
+
// Non-critical, don't fail init
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1373
1575
|
const payload = {
|
|
1374
1576
|
ok: true,
|
|
1375
1577
|
root: rootDir,
|
|
1376
1578
|
profile: profileName,
|
|
1377
1579
|
workspace: profile.workspace,
|
|
1378
1580
|
defaultCorner,
|
|
1581
|
+
connectedWorkstream,
|
|
1379
1582
|
files: plannedEdits.map((edit) => ({
|
|
1380
1583
|
action: edit.action,
|
|
1381
1584
|
path: relative(rootDir, edit.path) || ".",
|
|
@@ -1385,11 +1588,108 @@ async function handleInit(args, runtime, inherited) {
|
|
|
1385
1588
|
printJson(runtime.stdout, payload);
|
|
1386
1589
|
}
|
|
1387
1590
|
else {
|
|
1388
|
-
|
|
1591
|
+
const wsMsg = connectedWorkstream
|
|
1592
|
+
? ` Connected workstream: ${connectedWorkstream.name}.`
|
|
1593
|
+
: "";
|
|
1594
|
+
printLine(runtime.stdout, `Initialized Corners CLI at ${rootDir} with default corner ${defaultCorner.name}.${wsMsg} Run \`${GUIDANCE_SYNC_COMMAND}\` later to refresh managed guidance.`);
|
|
1389
1595
|
}
|
|
1390
1596
|
return 0;
|
|
1391
1597
|
});
|
|
1392
1598
|
}
|
|
1599
|
+
async function handleSkills(args, runtime, inherited) {
|
|
1600
|
+
const subcommand = args[0];
|
|
1601
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
1602
|
+
printSkillsHelp(runtime);
|
|
1603
|
+
return 0;
|
|
1604
|
+
}
|
|
1605
|
+
switch (subcommand) {
|
|
1606
|
+
case "list": {
|
|
1607
|
+
const parsed = parseArgs({
|
|
1608
|
+
args: args.slice(1),
|
|
1609
|
+
allowPositionals: false,
|
|
1610
|
+
options: {
|
|
1611
|
+
json: { type: "boolean" },
|
|
1612
|
+
help: { type: "boolean", short: "h" },
|
|
1613
|
+
},
|
|
1614
|
+
});
|
|
1615
|
+
const common = mergeCommonOptions(inherited, {
|
|
1616
|
+
json: parsed.values.json,
|
|
1617
|
+
});
|
|
1618
|
+
if (parsed.values.help) {
|
|
1619
|
+
printSkillsHelp(runtime);
|
|
1620
|
+
return 0;
|
|
1621
|
+
}
|
|
1622
|
+
const skills = await Promise.all(BUNDLED_SKILLS.map(async (skill) => {
|
|
1623
|
+
const content = await readFile(getBundledSkillPath(skill), "utf8");
|
|
1624
|
+
const fm = parseSkillFrontmatter(content);
|
|
1625
|
+
return {
|
|
1626
|
+
name: skill.name,
|
|
1627
|
+
version: fm?.version ?? "unknown",
|
|
1628
|
+
description: fm?.description ?? "",
|
|
1629
|
+
};
|
|
1630
|
+
}));
|
|
1631
|
+
if (common.json) {
|
|
1632
|
+
printJson(runtime.stdout, { ok: true, skills });
|
|
1633
|
+
}
|
|
1634
|
+
else {
|
|
1635
|
+
printLine(runtime.stdout, [
|
|
1636
|
+
"Available skills:",
|
|
1637
|
+
...skills.map((s) => ` ${s.name.padEnd(16)} ${s.version.padEnd(8)} ${s.description}`),
|
|
1638
|
+
].join("\n"));
|
|
1639
|
+
}
|
|
1640
|
+
return 0;
|
|
1641
|
+
}
|
|
1642
|
+
case "status": {
|
|
1643
|
+
const parsed = parseArgs({
|
|
1644
|
+
args: args.slice(1),
|
|
1645
|
+
allowPositionals: false,
|
|
1646
|
+
options: {
|
|
1647
|
+
json: { type: "boolean" },
|
|
1648
|
+
help: { type: "boolean", short: "h" },
|
|
1649
|
+
},
|
|
1650
|
+
});
|
|
1651
|
+
const common = mergeCommonOptions(inherited, {
|
|
1652
|
+
json: parsed.values.json,
|
|
1653
|
+
});
|
|
1654
|
+
if (parsed.values.help) {
|
|
1655
|
+
printSkillsHelp(runtime);
|
|
1656
|
+
return 0;
|
|
1657
|
+
}
|
|
1658
|
+
const rootContext = await requireLocalRoot(runtime, common, {
|
|
1659
|
+
emitWarning: false,
|
|
1660
|
+
});
|
|
1661
|
+
const state = await resolveSkillsState(rootContext.root.rootDir, rootContext.root.config);
|
|
1662
|
+
const payload = {
|
|
1663
|
+
ok: true,
|
|
1664
|
+
root: rootContext.root.rootDir,
|
|
1665
|
+
skills: state,
|
|
1666
|
+
};
|
|
1667
|
+
if (common.json) {
|
|
1668
|
+
printJson(runtime.stdout, payload);
|
|
1669
|
+
}
|
|
1670
|
+
else {
|
|
1671
|
+
printLine(runtime.stdout, [
|
|
1672
|
+
`Root: ${payload.root}`,
|
|
1673
|
+
`Last synced: ${state.lastSyncedAt ?? "-"}`,
|
|
1674
|
+
"",
|
|
1675
|
+
"Skills:",
|
|
1676
|
+
...state.skills.map((s) => {
|
|
1677
|
+
const stateLabel = s.state.padEnd(8);
|
|
1678
|
+
const version = s.state === "stale"
|
|
1679
|
+
? `(installed: ${s.installedVersion}, available: ${s.bundledVersion})`
|
|
1680
|
+
: s.state === "missing"
|
|
1681
|
+
? ""
|
|
1682
|
+
: `(${s.bundledVersion})`;
|
|
1683
|
+
return ` ${s.name.padEnd(16)} ${stateLabel} ${version}`;
|
|
1684
|
+
}),
|
|
1685
|
+
].join("\n"));
|
|
1686
|
+
}
|
|
1687
|
+
return 0;
|
|
1688
|
+
}
|
|
1689
|
+
default:
|
|
1690
|
+
throw new CLIError(`Unknown skills command: ${subcommand}`);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1393
1693
|
async function handleGuidance(args, runtime, inherited) {
|
|
1394
1694
|
const subcommand = args[0];
|
|
1395
1695
|
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
@@ -1587,6 +1887,7 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1587
1887
|
help: { type: "boolean", short: "h" },
|
|
1588
1888
|
profile: { type: "string" },
|
|
1589
1889
|
"api-url": { type: "string" },
|
|
1890
|
+
all: { type: "boolean" },
|
|
1590
1891
|
},
|
|
1591
1892
|
});
|
|
1592
1893
|
const common = mergeCommonOptions(inherited, {
|
|
@@ -1598,44 +1899,41 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1598
1899
|
printWorkstreamHelp(runtime);
|
|
1599
1900
|
return 0;
|
|
1600
1901
|
}
|
|
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);
|
|
1902
|
+
const { guidance, client } = await requireCommandProfile(runtime, common);
|
|
1903
|
+
const filter = {};
|
|
1904
|
+
if (!parsed.values.all) {
|
|
1905
|
+
filter.isOpen = true;
|
|
1906
|
+
}
|
|
1907
|
+
const data = await client.graphql(WORKSTREAM_LIST_QUERY, { first: 100, filter });
|
|
1908
|
+
const allWorkstreams = data.workstreams.edges.map((edge) => edge.node);
|
|
1909
|
+
const totalCount = data.workstreams.totalCount;
|
|
1910
|
+
// Try to find local root for connected workstream annotation
|
|
1911
|
+
const root = await runtime.config.findLocalRoot(runtime.cwd);
|
|
1912
|
+
const connectedIds = new Set(root?.config.connectedWorkstreams.map((entry) => entry.id) ?? []);
|
|
1623
1913
|
if (common.json) {
|
|
1624
1914
|
printJson(runtime.stdout, withGuidanceJsonMetadata({
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1915
|
+
workstreams: allWorkstreams.map((ws) => ({
|
|
1916
|
+
...ws,
|
|
1917
|
+
connected: connectedIds.has(ws.id),
|
|
1918
|
+
})),
|
|
1919
|
+
totalCount,
|
|
1628
1920
|
}, guidance));
|
|
1629
1921
|
}
|
|
1630
1922
|
else {
|
|
1631
|
-
if (
|
|
1632
|
-
printLine(runtime.
|
|
1633
|
-
|
|
1923
|
+
if (allWorkstreams.length === 0) {
|
|
1924
|
+
printLine(runtime.stdout, "No workstreams found.");
|
|
1925
|
+
}
|
|
1926
|
+
else {
|
|
1927
|
+
printLine(runtime.stdout, allWorkstreams
|
|
1928
|
+
.map((ws) => {
|
|
1929
|
+
const prefix = connectedIds.has(ws.id) ? "* " : " ";
|
|
1930
|
+
return `${prefix}${formatWorkstreamLine(ws)}`;
|
|
1931
|
+
})
|
|
1634
1932
|
.join("\n"));
|
|
1933
|
+
if (totalCount > 100) {
|
|
1934
|
+
printLine(runtime.stderr, `Showing 100 of ${totalCount} workstreams.`);
|
|
1935
|
+
}
|
|
1635
1936
|
}
|
|
1636
|
-
printLine(runtime.stdout, workstreams.length > 0
|
|
1637
|
-
? workstreams.map(formatWorkstreamLine).join("\n")
|
|
1638
|
-
: "No workstreams are connected to this local environment.");
|
|
1639
1937
|
}
|
|
1640
1938
|
return 0;
|
|
1641
1939
|
}
|
|
@@ -1672,15 +1970,23 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1672
1970
|
if (nextConfig !== root.config) {
|
|
1673
1971
|
await runtime.config.writeLocalConfig(root.rootDir, nextConfig);
|
|
1674
1972
|
}
|
|
1973
|
+
await runtime.config.setBinding(runtime.cwd, {
|
|
1974
|
+
profile: root.config.profile,
|
|
1975
|
+
workspace: root.config.workspace ?? "",
|
|
1976
|
+
workstreamId: workstream.id,
|
|
1977
|
+
cornerId: workstream.cornerId,
|
|
1978
|
+
boundAt: toIsoString(),
|
|
1979
|
+
});
|
|
1675
1980
|
if (common.json) {
|
|
1676
1981
|
printJson(runtime.stdout, withGuidanceJsonMetadata({
|
|
1677
1982
|
ok: true,
|
|
1678
1983
|
root: root.rootDir,
|
|
1679
1984
|
workstream,
|
|
1985
|
+
bound: true,
|
|
1680
1986
|
}, guidance));
|
|
1681
1987
|
}
|
|
1682
1988
|
else {
|
|
1683
|
-
printLine(runtime.stdout, `Connected ${workstream.id} (${workstream.name}) to ${root.rootDir}.`);
|
|
1989
|
+
printLine(runtime.stdout, `Connected ${workstream.id} (${workstream.name}) to ${root.rootDir} and set as active workstream.`);
|
|
1684
1990
|
}
|
|
1685
1991
|
return 0;
|
|
1686
1992
|
}
|
|
@@ -1703,12 +2009,14 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1703
2009
|
const { root, guidance } = await requireLocalRoot(runtime, common);
|
|
1704
2010
|
const cwdPath = await realpath(runtime.cwd);
|
|
1705
2011
|
const resolvedCorner = resolveCornerForCwd(root, cwdPath);
|
|
2012
|
+
const binding = await runtime.config.getBinding(runtime.cwd);
|
|
1706
2013
|
const payload = {
|
|
1707
2014
|
cwd: runtime.cwd,
|
|
1708
2015
|
root: root.rootDir,
|
|
1709
2016
|
profile: root.config.profile,
|
|
1710
2017
|
workspace: root.config.workspace,
|
|
1711
2018
|
resolvedCorner,
|
|
2019
|
+
activeWorkstreamId: binding?.workstreamId ?? null,
|
|
1712
2020
|
connectedWorkstreamIds: root.config.connectedWorkstreams.map((entry) => entry.id),
|
|
1713
2021
|
};
|
|
1714
2022
|
if (common.json) {
|
|
@@ -1721,6 +2029,7 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1721
2029
|
`Profile: ${payload.profile}`,
|
|
1722
2030
|
`Workspace: ${payload.workspace ?? "-"}`,
|
|
1723
2031
|
`Resolved corner: ${payload.resolvedCorner.corner.name} (${payload.resolvedCorner.corner.id})`,
|
|
2032
|
+
`Active workstream: ${payload.activeWorkstreamId ?? "none"}`,
|
|
1724
2033
|
`Connected workstreams: ${payload.connectedWorkstreamIds.length}`,
|
|
1725
2034
|
].join("\n"));
|
|
1726
2035
|
}
|
|
@@ -1785,12 +2094,13 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1785
2094
|
case "pull": {
|
|
1786
2095
|
const parsed = parseArgs({
|
|
1787
2096
|
args: args.slice(1),
|
|
1788
|
-
allowPositionals:
|
|
2097
|
+
allowPositionals: false,
|
|
1789
2098
|
options: {
|
|
1790
2099
|
json: { type: "boolean" },
|
|
1791
2100
|
help: { type: "boolean", short: "h" },
|
|
1792
2101
|
profile: { type: "string" },
|
|
1793
2102
|
"api-url": { type: "string" },
|
|
2103
|
+
workstream: { type: "string", short: "w" },
|
|
1794
2104
|
},
|
|
1795
2105
|
});
|
|
1796
2106
|
const common = mergeCommonOptions(inherited, {
|
|
@@ -1802,12 +2112,7 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1802
2112
|
printWorkstreamHelp(runtime);
|
|
1803
2113
|
return 0;
|
|
1804
2114
|
}
|
|
1805
|
-
const workstreamId = parsed.
|
|
1806
|
-
if (!workstreamId) {
|
|
1807
|
-
throw new CLIError("Usage: corners workstream pull <workstreamId>", {
|
|
1808
|
-
json: common.json,
|
|
1809
|
-
});
|
|
1810
|
-
}
|
|
2115
|
+
const workstreamId = await resolveWorkstreamId(runtime, common, parsed.values.workstream);
|
|
1811
2116
|
const { guidance, client } = await requireCommandProfile(runtime, common);
|
|
1812
2117
|
const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
|
|
1813
2118
|
const data = await client.graphql(WORKSTREAM_PULL_QUERY, {
|
|
@@ -1820,7 +2125,7 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1820
2125
|
}
|
|
1821
2126
|
else {
|
|
1822
2127
|
const snapshot = data.workstream;
|
|
1823
|
-
|
|
2128
|
+
const lines = [
|
|
1824
2129
|
`${snapshot.name} (${snapshot.id})`,
|
|
1825
2130
|
snapshot.summary ? `Summary: ${snapshot.summary}` : "Summary: -",
|
|
1826
2131
|
`State: ${snapshot.isOpen === false ? "archived" : "open"}`,
|
|
@@ -1830,19 +2135,41 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1830
2135
|
`Answered questions: ${snapshot.answeredQuestions?.length ?? 0}`,
|
|
1831
2136
|
`Attachments: ${snapshot.attachments?.totalCount ?? 0}`,
|
|
1832
2137
|
`Feed items returned: ${snapshot.feed?.totalCount ?? 0}`,
|
|
1833
|
-
]
|
|
2138
|
+
];
|
|
2139
|
+
// Append tasks from TodoList attachments
|
|
2140
|
+
const todoLists = (snapshot.attachments?.edges ?? []).flatMap((e) => e.node.__typename === "WorkstreamTodoListAttachment" &&
|
|
2141
|
+
e.node.todoList
|
|
2142
|
+
? [e.node.todoList]
|
|
2143
|
+
: []);
|
|
2144
|
+
if (todoLists.length > 0) {
|
|
2145
|
+
lines.push("");
|
|
2146
|
+
lines.push("Tasks:");
|
|
2147
|
+
for (const list of todoLists) {
|
|
2148
|
+
const completed = list.totalItemCount - list.openItemCount;
|
|
2149
|
+
lines.push(` ${list.title} (${completed}/${list.totalItemCount} complete)`);
|
|
2150
|
+
for (const item of list.items) {
|
|
2151
|
+
const check = item.completed ? "[x]" : "[ ]";
|
|
2152
|
+
const assignee = item.assignee
|
|
2153
|
+
? ` (@${item.assignee.handle})`
|
|
2154
|
+
: "";
|
|
2155
|
+
lines.push(` ${check} ${item.title}${assignee}`);
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
printLine(runtime.stdout, lines.join("\n"));
|
|
1834
2160
|
}
|
|
1835
2161
|
return 0;
|
|
1836
2162
|
}
|
|
1837
2163
|
case "update": {
|
|
1838
2164
|
const parsed = parseArgs({
|
|
1839
2165
|
args: args.slice(1),
|
|
1840
|
-
allowPositionals:
|
|
2166
|
+
allowPositionals: false,
|
|
1841
2167
|
options: {
|
|
1842
2168
|
json: { type: "boolean" },
|
|
1843
2169
|
help: { type: "boolean", short: "h" },
|
|
1844
2170
|
profile: { type: "string" },
|
|
1845
2171
|
"api-url": { type: "string" },
|
|
2172
|
+
workstream: { type: "string", short: "w" },
|
|
1846
2173
|
status: { type: "string" },
|
|
1847
2174
|
"status-details": { type: "string" },
|
|
1848
2175
|
"clear-status-details": { type: "boolean" },
|
|
@@ -1857,10 +2184,6 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1857
2184
|
printWorkstreamHelp(runtime);
|
|
1858
2185
|
return 0;
|
|
1859
2186
|
}
|
|
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
2187
|
const rawStatusDetails = parsed.values["status-details"];
|
|
1865
2188
|
const clearStatusDetails = parsed.values["clear-status-details"] === true;
|
|
1866
2189
|
if (rawStatusDetails !== undefined && clearStatusDetails) {
|
|
@@ -1871,6 +2194,7 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1871
2194
|
!clearStatusDetails) {
|
|
1872
2195
|
throw new CLIError("Provide --status, --status-details, or --clear-status-details.", { json: common.json });
|
|
1873
2196
|
}
|
|
2197
|
+
const workstreamId = await resolveWorkstreamId(runtime, common, parsed.values.workstream);
|
|
1874
2198
|
const { guidance, client } = await requireCommandProfile(runtime, common);
|
|
1875
2199
|
const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
|
|
1876
2200
|
const result = await client.graphql(UPDATE_WORKSTREAM_MUTATION, {
|
|
@@ -1912,11 +2236,16 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1912
2236
|
help: { type: "boolean", short: "h" },
|
|
1913
2237
|
profile: { type: "string" },
|
|
1914
2238
|
"api-url": { type: "string" },
|
|
2239
|
+
workstream: { type: "string", short: "w" },
|
|
1915
2240
|
type: { type: "string" },
|
|
1916
2241
|
message: { type: "string" },
|
|
1917
2242
|
summary: { type: "string" },
|
|
1918
2243
|
file: { type: "string" },
|
|
1919
2244
|
title: { type: "string" },
|
|
2245
|
+
"agent-type": { type: "string" },
|
|
2246
|
+
files: { type: "string" },
|
|
2247
|
+
scope: { type: "string" },
|
|
2248
|
+
intent: { type: "string" },
|
|
1920
2249
|
},
|
|
1921
2250
|
});
|
|
1922
2251
|
const common = mergeCommonOptions(inherited, {
|
|
@@ -1928,15 +2257,10 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1928
2257
|
printWorkstreamHelp(runtime);
|
|
1929
2258
|
return 0;
|
|
1930
2259
|
}
|
|
1931
|
-
const workstreamId = parsed.
|
|
1932
|
-
if (!workstreamId) {
|
|
1933
|
-
throw new CLIError("Usage: corners workstream push <workstreamId>", {
|
|
1934
|
-
json: common.json,
|
|
1935
|
-
});
|
|
1936
|
-
}
|
|
2260
|
+
const workstreamId = await resolveWorkstreamId(runtime, common, parsed.values.workstream);
|
|
1937
2261
|
const message = parsed.values.message ??
|
|
1938
|
-
(parsed.positionals.length >
|
|
1939
|
-
? parsed.positionals.
|
|
2262
|
+
(parsed.positionals.length > 0
|
|
2263
|
+
? parsed.positionals.join(" ")
|
|
1940
2264
|
: await readTextFromStdin(runtime.stdin));
|
|
1941
2265
|
if (!message) {
|
|
1942
2266
|
throw new CLIError("Workstream updates need a message. Use --message or pipe text on stdin.", { json: common.json });
|
|
@@ -1956,18 +2280,38 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1956
2280
|
});
|
|
1957
2281
|
createdDocument = documentResult.createDocument;
|
|
1958
2282
|
}
|
|
2283
|
+
// Parse agent-specific fields
|
|
2284
|
+
const agentType = parsed.values["agent-type"] ??
|
|
2285
|
+
(process.env.CLAUDE_CODE ? "claude-code" : null) ??
|
|
2286
|
+
(process.env.CODEX_SESSION ? "codex" : null) ??
|
|
2287
|
+
null;
|
|
2288
|
+
const filesTouched = parsed.values.files
|
|
2289
|
+
? parsed.values.files.split(",").map((f) => f.trim())
|
|
2290
|
+
: null;
|
|
2291
|
+
const scope = parsed.values.scope ?? null;
|
|
2292
|
+
// For session_start, --intent is an alias for --message
|
|
2293
|
+
const updateType = parsed.values.type ?? "status";
|
|
2294
|
+
const effectiveContent = updateType === "session_start" && parsed.values.intent
|
|
2295
|
+
? parsed.values.intent
|
|
2296
|
+
: message;
|
|
1959
2297
|
const updateResult = await client.graphql(RECORD_WORKSTREAM_UPDATE_MUTATION, {
|
|
1960
2298
|
input: {
|
|
1961
2299
|
workstreamId: workstream.id,
|
|
1962
|
-
updateType: toGraphQLWorkstreamUpdateType(
|
|
1963
|
-
content:
|
|
2300
|
+
updateType: toGraphQLWorkstreamUpdateType(updateType),
|
|
2301
|
+
content: effectiveContent,
|
|
1964
2302
|
summary: parsed.values.summary,
|
|
1965
2303
|
documentId: createdDocument?.id ?? null,
|
|
2304
|
+
...(agentType ? { agentType } : {}),
|
|
2305
|
+
...(filesTouched ? { filesTouched } : {}),
|
|
2306
|
+
...(scope ? { scope } : {}),
|
|
1966
2307
|
},
|
|
1967
2308
|
});
|
|
1968
2309
|
const payload = {
|
|
1969
2310
|
ok: true,
|
|
2311
|
+
success: true,
|
|
1970
2312
|
workstreamId: workstream.id,
|
|
2313
|
+
type: updateType,
|
|
2314
|
+
messageId: updateResult.recordWorkstreamUpdate.id,
|
|
1971
2315
|
update: updateResult.recordWorkstreamUpdate,
|
|
1972
2316
|
document: createdDocument,
|
|
1973
2317
|
};
|
|
@@ -1989,12 +2333,13 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1989
2333
|
case "list": {
|
|
1990
2334
|
const parsed = parseArgs({
|
|
1991
2335
|
args: args.slice(2),
|
|
1992
|
-
allowPositionals:
|
|
2336
|
+
allowPositionals: false,
|
|
1993
2337
|
options: {
|
|
1994
2338
|
json: { type: "boolean" },
|
|
1995
2339
|
help: { type: "boolean", short: "h" },
|
|
1996
2340
|
profile: { type: "string" },
|
|
1997
2341
|
"api-url": { type: "string" },
|
|
2342
|
+
workstream: { type: "string", short: "w" },
|
|
1998
2343
|
},
|
|
1999
2344
|
});
|
|
2000
2345
|
const common = mergeCommonOptions(inherited, {
|
|
@@ -2006,10 +2351,7 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
2006
2351
|
printWorkstreamHelp(runtime);
|
|
2007
2352
|
return 0;
|
|
2008
2353
|
}
|
|
2009
|
-
const workstreamId = parsed.
|
|
2010
|
-
if (!workstreamId) {
|
|
2011
|
-
throw new CLIError("Usage: corners workstream question list <workstreamId>", { json: common.json });
|
|
2012
|
-
}
|
|
2354
|
+
const workstreamId = await resolveWorkstreamId(runtime, common, parsed.values.workstream);
|
|
2013
2355
|
const { guidance, client } = await requireCommandProfile(runtime, common);
|
|
2014
2356
|
const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
|
|
2015
2357
|
const data = await client.graphql(WORKSTREAM_QUESTION_LIST_QUERY, {
|
|
@@ -2040,6 +2382,7 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
2040
2382
|
help: { type: "boolean", short: "h" },
|
|
2041
2383
|
profile: { type: "string" },
|
|
2042
2384
|
"api-url": { type: "string" },
|
|
2385
|
+
workstream: { type: "string", short: "w" },
|
|
2043
2386
|
question: { type: "string" },
|
|
2044
2387
|
rationale: { type: "string" },
|
|
2045
2388
|
"suggested-answer": { type: "string", multiple: true },
|
|
@@ -2054,13 +2397,10 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
2054
2397
|
printWorkstreamHelp(runtime);
|
|
2055
2398
|
return 0;
|
|
2056
2399
|
}
|
|
2057
|
-
const workstreamId = parsed.
|
|
2058
|
-
if (!workstreamId) {
|
|
2059
|
-
throw new CLIError("Usage: corners workstream question ask <workstreamId> [--question <text>]", { json: common.json });
|
|
2060
|
-
}
|
|
2400
|
+
const workstreamId = await resolveWorkstreamId(runtime, common, parsed.values.workstream);
|
|
2061
2401
|
const question = parsed.values.question ??
|
|
2062
|
-
(parsed.positionals.length >
|
|
2063
|
-
? parsed.positionals.
|
|
2402
|
+
(parsed.positionals.length > 0
|
|
2403
|
+
? parsed.positionals.join(" ")
|
|
2064
2404
|
: await readTextFromStdin(runtime.stdin));
|
|
2065
2405
|
if (!question) {
|
|
2066
2406
|
throw new CLIError("Question text is required. Use --question or pipe text on stdin.", { json: common.json });
|
|
@@ -2137,12 +2477,13 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
2137
2477
|
case "attach": {
|
|
2138
2478
|
const parsed = parseArgs({
|
|
2139
2479
|
args: args.slice(1),
|
|
2140
|
-
allowPositionals:
|
|
2480
|
+
allowPositionals: false,
|
|
2141
2481
|
options: {
|
|
2142
2482
|
json: { type: "boolean" },
|
|
2143
2483
|
help: { type: "boolean", short: "h" },
|
|
2144
2484
|
profile: { type: "string" },
|
|
2145
2485
|
"api-url": { type: "string" },
|
|
2486
|
+
workstream: { type: "string", short: "w" },
|
|
2146
2487
|
kind: { type: "string" },
|
|
2147
2488
|
"entity-id": { type: "string" },
|
|
2148
2489
|
},
|
|
@@ -2156,12 +2497,12 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
2156
2497
|
printWorkstreamHelp(runtime);
|
|
2157
2498
|
return 0;
|
|
2158
2499
|
}
|
|
2159
|
-
const workstreamId = parsed.positionals[0];
|
|
2160
2500
|
const kind = parsed.values.kind;
|
|
2161
2501
|
const entityId = parsed.values["entity-id"];
|
|
2162
|
-
if (!
|
|
2163
|
-
throw new CLIError("Usage: corners workstream attach <
|
|
2502
|
+
if (!kind || !entityId) {
|
|
2503
|
+
throw new CLIError("Usage: corners workstream attach [--workstream <id>] --kind <kind> --entity-id <id>", { json: common.json });
|
|
2164
2504
|
}
|
|
2505
|
+
const workstreamId = await resolveWorkstreamId(runtime, common, parsed.values.workstream);
|
|
2165
2506
|
const { guidance, client } = await requireCommandProfile(runtime, common);
|
|
2166
2507
|
const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
|
|
2167
2508
|
const result = await client.graphql(ADD_WORKSTREAM_ATTACHMENT_MUTATION, {
|
|
@@ -2221,10 +2562,241 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
2221
2562
|
}
|
|
2222
2563
|
return 0;
|
|
2223
2564
|
}
|
|
2565
|
+
case "task": {
|
|
2566
|
+
const nested = args[1];
|
|
2567
|
+
if (!nested || nested === "help" || nested === "--help") {
|
|
2568
|
+
printWorkstreamHelp(runtime);
|
|
2569
|
+
return 0;
|
|
2570
|
+
}
|
|
2571
|
+
switch (nested) {
|
|
2572
|
+
case "list": {
|
|
2573
|
+
const parsed = parseArgs({
|
|
2574
|
+
args: args.slice(2),
|
|
2575
|
+
allowPositionals: false,
|
|
2576
|
+
options: {
|
|
2577
|
+
json: { type: "boolean" },
|
|
2578
|
+
help: { type: "boolean", short: "h" },
|
|
2579
|
+
profile: { type: "string" },
|
|
2580
|
+
"api-url": { type: "string" },
|
|
2581
|
+
workstream: { type: "string", short: "w" },
|
|
2582
|
+
},
|
|
2583
|
+
});
|
|
2584
|
+
const common = mergeCommonOptions(inherited, {
|
|
2585
|
+
json: parsed.values.json,
|
|
2586
|
+
profile: parsed.values.profile,
|
|
2587
|
+
apiUrl: parsed.values["api-url"],
|
|
2588
|
+
});
|
|
2589
|
+
if (parsed.values.help) {
|
|
2590
|
+
printWorkstreamHelp(runtime);
|
|
2591
|
+
return 0;
|
|
2592
|
+
}
|
|
2593
|
+
const workstreamId = await resolveWorkstreamId(runtime, common, parsed.values.workstream);
|
|
2594
|
+
const { guidance, client } = await requireCommandProfile(runtime, common);
|
|
2595
|
+
const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
|
|
2596
|
+
const data = await client.graphql(WORKSTREAM_TASK_LIST_QUERY, {
|
|
2597
|
+
id: workstream.id,
|
|
2598
|
+
attachmentsFirst: 50,
|
|
2599
|
+
});
|
|
2600
|
+
if (!data.workstream) {
|
|
2601
|
+
throw new CLIError("Workstream not found", { json: common.json });
|
|
2602
|
+
}
|
|
2603
|
+
const todoLists = data.workstream.attachments.edges.flatMap((e) => e.node.todoList ? [e.node.todoList] : []);
|
|
2604
|
+
if (common.json) {
|
|
2605
|
+
printJson(runtime.stdout, withGuidanceJsonMetadata({
|
|
2606
|
+
workstreamId: workstream.id,
|
|
2607
|
+
workstreamName: workstream.name,
|
|
2608
|
+
todoLists,
|
|
2609
|
+
}, guidance));
|
|
2610
|
+
}
|
|
2611
|
+
else {
|
|
2612
|
+
if (todoLists.length === 0) {
|
|
2613
|
+
printLine(runtime.stdout, "No task lists attached to this workstream.");
|
|
2614
|
+
}
|
|
2615
|
+
else {
|
|
2616
|
+
const lines = [
|
|
2617
|
+
`${workstream.name} (${workstream.id}) - Tasks`,
|
|
2618
|
+
"",
|
|
2619
|
+
];
|
|
2620
|
+
for (const list of todoLists) {
|
|
2621
|
+
const completed = list.totalItemCount - list.openItemCount;
|
|
2622
|
+
lines.push(`${list.title} (${completed}/${list.totalItemCount} complete)`);
|
|
2623
|
+
for (const item of list.items) {
|
|
2624
|
+
const check = item.completed ? "[x]" : "[ ]";
|
|
2625
|
+
const assignee = item.assignee
|
|
2626
|
+
? ` (@${item.assignee.handle})`
|
|
2627
|
+
: "";
|
|
2628
|
+
lines.push(` ${check} ${item.title}${assignee}`);
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
printLine(runtime.stdout, lines.join("\n"));
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
return 0;
|
|
2635
|
+
}
|
|
2636
|
+
case "add": {
|
|
2637
|
+
const parsed = parseArgs({
|
|
2638
|
+
args: args.slice(2),
|
|
2639
|
+
allowPositionals: true,
|
|
2640
|
+
options: {
|
|
2641
|
+
json: { type: "boolean" },
|
|
2642
|
+
help: { type: "boolean", short: "h" },
|
|
2643
|
+
profile: { type: "string" },
|
|
2644
|
+
"api-url": { type: "string" },
|
|
2645
|
+
workstream: { type: "string", short: "w" },
|
|
2646
|
+
title: { type: "string" },
|
|
2647
|
+
list: { type: "string" },
|
|
2648
|
+
description: { type: "string" },
|
|
2649
|
+
},
|
|
2650
|
+
});
|
|
2651
|
+
const common = mergeCommonOptions(inherited, {
|
|
2652
|
+
json: parsed.values.json,
|
|
2653
|
+
profile: parsed.values.profile,
|
|
2654
|
+
apiUrl: parsed.values["api-url"],
|
|
2655
|
+
});
|
|
2656
|
+
if (parsed.values.help) {
|
|
2657
|
+
printWorkstreamHelp(runtime);
|
|
2658
|
+
return 0;
|
|
2659
|
+
}
|
|
2660
|
+
const workstreamId = await resolveWorkstreamId(runtime, common, parsed.values.workstream);
|
|
2661
|
+
const taskTitle = parsed.values.title ??
|
|
2662
|
+
(parsed.positionals.length > 0
|
|
2663
|
+
? parsed.positionals.join(" ")
|
|
2664
|
+
: await readTextFromStdin(runtime.stdin));
|
|
2665
|
+
if (!taskTitle) {
|
|
2666
|
+
throw new CLIError("Task title is required. Use --title or provide as positional argument.", { json: common.json });
|
|
2667
|
+
}
|
|
2668
|
+
const { guidance, client } = await requireCommandProfile(runtime, common);
|
|
2669
|
+
const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
|
|
2670
|
+
let todoListId = parsed.values.list;
|
|
2671
|
+
if (!todoListId) {
|
|
2672
|
+
// Query existing TODOLIST attachments
|
|
2673
|
+
const data = await client.graphql(WORKSTREAM_TASK_LIST_QUERY, {
|
|
2674
|
+
id: workstream.id,
|
|
2675
|
+
attachmentsFirst: 50,
|
|
2676
|
+
});
|
|
2677
|
+
const lists = (data.workstream?.attachments.edges ?? []).flatMap((e) => (e.node.todoList ? [e.node.todoList] : []));
|
|
2678
|
+
if (lists.length === 1) {
|
|
2679
|
+
todoListId = lists[0].id;
|
|
2680
|
+
}
|
|
2681
|
+
else if (lists.length === 0) {
|
|
2682
|
+
// Auto-create a todo list with workstreamId for server-side attachment
|
|
2683
|
+
const createResult = await client.graphql(CREATE_TODO_LIST_MUTATION, {
|
|
2684
|
+
input: {
|
|
2685
|
+
title: `${workstream.name} Tasks`,
|
|
2686
|
+
workstreamId: workstream.id,
|
|
2687
|
+
},
|
|
2688
|
+
});
|
|
2689
|
+
todoListId = createResult.createTodoList.id;
|
|
2690
|
+
}
|
|
2691
|
+
else {
|
|
2692
|
+
const listSummary = lists
|
|
2693
|
+
.map((l) => ` ${l.id} (${l.title})`)
|
|
2694
|
+
.join("\n");
|
|
2695
|
+
throw new CLIError(`Multiple task lists found. Use --list <todoListId> to specify:\n${listSummary}`, { json: common.json });
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
const result = await client.graphql(ADD_TODO_ITEM_MUTATION, {
|
|
2699
|
+
input: {
|
|
2700
|
+
todoListId,
|
|
2701
|
+
title: taskTitle,
|
|
2702
|
+
...(parsed.values.description
|
|
2703
|
+
? { description: parsed.values.description }
|
|
2704
|
+
: {}),
|
|
2705
|
+
},
|
|
2706
|
+
});
|
|
2707
|
+
if (common.json) {
|
|
2708
|
+
printJson(runtime.stdout, withGuidanceJsonMetadata({
|
|
2709
|
+
ok: true,
|
|
2710
|
+
workstreamId: workstream.id,
|
|
2711
|
+
todoListId,
|
|
2712
|
+
item: result.addTodoItem,
|
|
2713
|
+
}, guidance));
|
|
2714
|
+
}
|
|
2715
|
+
else {
|
|
2716
|
+
printLine(runtime.stdout, `Added task "${result.addTodoItem.title}" to ${workstream.id}.`);
|
|
2717
|
+
}
|
|
2718
|
+
return 0;
|
|
2719
|
+
}
|
|
2720
|
+
case "complete": {
|
|
2721
|
+
const parsed = parseArgs({
|
|
2722
|
+
args: args.slice(2),
|
|
2723
|
+
allowPositionals: true,
|
|
2724
|
+
options: {
|
|
2725
|
+
json: { type: "boolean" },
|
|
2726
|
+
help: { type: "boolean", short: "h" },
|
|
2727
|
+
profile: { type: "string" },
|
|
2728
|
+
"api-url": { type: "string" },
|
|
2729
|
+
undo: { type: "boolean" },
|
|
2730
|
+
},
|
|
2731
|
+
});
|
|
2732
|
+
const common = mergeCommonOptions(inherited, {
|
|
2733
|
+
json: parsed.values.json,
|
|
2734
|
+
profile: parsed.values.profile,
|
|
2735
|
+
apiUrl: parsed.values["api-url"],
|
|
2736
|
+
});
|
|
2737
|
+
if (parsed.values.help) {
|
|
2738
|
+
printWorkstreamHelp(runtime);
|
|
2739
|
+
return 0;
|
|
2740
|
+
}
|
|
2741
|
+
const taskItemId = parsed.positionals[0];
|
|
2742
|
+
if (!taskItemId) {
|
|
2743
|
+
throw new CLIError("Usage: corners workstream task complete <taskItemId> [--undo]", { json: common.json });
|
|
2744
|
+
}
|
|
2745
|
+
const completed = parsed.values.undo !== true;
|
|
2746
|
+
const { guidance, client } = await requireCommandProfile(runtime, common);
|
|
2747
|
+
const result = await client.graphql(TOGGLE_TODO_ITEM_MUTATION, {
|
|
2748
|
+
id: taskItemId,
|
|
2749
|
+
completed,
|
|
2750
|
+
});
|
|
2751
|
+
if (common.json) {
|
|
2752
|
+
printJson(runtime.stdout, withGuidanceJsonMetadata({
|
|
2753
|
+
ok: true,
|
|
2754
|
+
item: result.toggleTodoItem,
|
|
2755
|
+
}, guidance));
|
|
2756
|
+
}
|
|
2757
|
+
else {
|
|
2758
|
+
const verb = completed ? "Completed" : "Reopened";
|
|
2759
|
+
printLine(runtime.stdout, `${verb} task "${result.toggleTodoItem.title}" (${result.toggleTodoItem.id}).`);
|
|
2760
|
+
}
|
|
2761
|
+
return 0;
|
|
2762
|
+
}
|
|
2763
|
+
default:
|
|
2764
|
+
throw new CLIError(`Unknown workstream task command: ${nested}`);
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2224
2767
|
default:
|
|
2225
2768
|
throw new CLIError(`Unknown workstream command: ${subcommand}`);
|
|
2226
2769
|
}
|
|
2227
2770
|
}
|
|
2771
|
+
async function handleMcp(args, runtime, inherited) {
|
|
2772
|
+
const subcommand = args[0];
|
|
2773
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
2774
|
+
printLine(runtime.stdout, "Usage: corners mcp serve [--profile <name>] [--api-url <url>]");
|
|
2775
|
+
printLine(runtime.stdout, "");
|
|
2776
|
+
printLine(runtime.stdout, "Run an MCP server over stdio exposing agent communication tools.");
|
|
2777
|
+
printLine(runtime.stdout, "Configure in Claude Code: corners mcp serve");
|
|
2778
|
+
return 0;
|
|
2779
|
+
}
|
|
2780
|
+
if (subcommand !== "serve") {
|
|
2781
|
+
throw new CLIError(`Unknown mcp command: ${subcommand}. Use "corners mcp serve".`);
|
|
2782
|
+
}
|
|
2783
|
+
const parsed = parseArgs({
|
|
2784
|
+
args: args.slice(1),
|
|
2785
|
+
allowPositionals: false,
|
|
2786
|
+
options: {
|
|
2787
|
+
profile: { type: "string" },
|
|
2788
|
+
"api-url": { type: "string" },
|
|
2789
|
+
},
|
|
2790
|
+
});
|
|
2791
|
+
const common = mergeCommonOptions(inherited, {
|
|
2792
|
+
profile: parsed.values.profile,
|
|
2793
|
+
apiUrl: parsed.values["api-url"],
|
|
2794
|
+
});
|
|
2795
|
+
const { client } = await requireCommandProfile(runtime, common);
|
|
2796
|
+
const { runMcpServer } = await import("./mcp-serve.js");
|
|
2797
|
+
await runMcpServer(client);
|
|
2798
|
+
return 0;
|
|
2799
|
+
}
|
|
2228
2800
|
export async function runCli(argv, runtime = createRuntime()) {
|
|
2229
2801
|
const normalizedArgv = argv[0] === "--" ? argv.slice(1) : argv;
|
|
2230
2802
|
const parsed = parseLeadingCommonOptions(normalizedArgv);
|
|
@@ -2249,6 +2821,9 @@ export async function runCli(argv, runtime = createRuntime()) {
|
|
|
2249
2821
|
else if (parsed.rest[1] === "guidance") {
|
|
2250
2822
|
printGuidanceHelp(runtime);
|
|
2251
2823
|
}
|
|
2824
|
+
else if (parsed.rest[1] === "skills") {
|
|
2825
|
+
printSkillsHelp(runtime);
|
|
2826
|
+
}
|
|
2252
2827
|
else if (parsed.rest[1] === "init") {
|
|
2253
2828
|
printInitHelp(runtime);
|
|
2254
2829
|
}
|
|
@@ -2272,6 +2847,8 @@ export async function runCli(argv, runtime = createRuntime()) {
|
|
|
2272
2847
|
return handleInit(parsed.rest.slice(1), runtime, parsed.common);
|
|
2273
2848
|
case "guidance":
|
|
2274
2849
|
return handleGuidance(parsed.rest.slice(1), runtime, parsed.common);
|
|
2850
|
+
case "skills":
|
|
2851
|
+
return handleSkills(parsed.rest.slice(1), runtime, parsed.common);
|
|
2275
2852
|
case "auth":
|
|
2276
2853
|
return handleAuth(parsed.rest.slice(1), runtime, parsed.common);
|
|
2277
2854
|
case "corner":
|
|
@@ -2281,6 +2858,8 @@ export async function runCli(argv, runtime = createRuntime()) {
|
|
|
2281
2858
|
case "workstream":
|
|
2282
2859
|
case "ws":
|
|
2283
2860
|
return handleWorkstream(parsed.rest.slice(1), runtime, parsed.common);
|
|
2861
|
+
case "mcp":
|
|
2862
|
+
return handleMcp(parsed.rest.slice(1), runtime, parsed.common);
|
|
2284
2863
|
default:
|
|
2285
2864
|
throw new CLIError(`Unknown command: ${command}`, {
|
|
2286
2865
|
json: parsed.common.json,
|