@corners/cli 0.0.8 → 0.0.10
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 +383 -52
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +12 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +40 -1
- package/dist/config.js.map +1 -1
- package/dist/corners +0 -0
- package/dist/guidance.d.ts +0 -1
- package/dist/guidance.d.ts.map +1 -1
- package/dist/guidance.js +18 -30
- package/dist/guidance.js.map +1 -1
- package/dist/mcp-serve.d.ts.map +1 -1
- package/dist/mcp-serve.js +1 -4
- package/dist/mcp-serve.js.map +1 -1
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +6 -1
- package/dist/skills.js.map +1 -1
- package/dist/support.d.ts.map +1 -1
- package/dist/support.js +6 -0
- package/dist/support.js.map +1 -1
- package/package.json +3 -2
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,KAAK,gBAAgB,EAKtB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,WAAW,EASZ,MAAM,aAAa,CAAC;AAUrB,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AA0f/D,UAAU,OAAO;IACf,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,cAAc,CAAC;IACtC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACpD,QAAQ,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE;QAC7B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC7B,KAAK,gBAAgB,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,MAAM,CAAC;CACnC;AAkvHD,wBAAsB,MAAM,CAC1B,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE,OAAyB,GACjC,OAAO,CAAC,MAAM,CAAC,CAqEjB"}
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { mkdir, readFile, realpath, writeFile } from "node:fs/promises";
|
|
2
3
|
import { hostname } from "node:os";
|
|
3
4
|
import { basename, join, relative, resolve, sep } from "node:path";
|
|
4
5
|
import { parseArgs } from "node:util";
|
|
5
6
|
import { CornersApiClient as DefaultCornersApiClient, } from "./client.js";
|
|
6
7
|
import { ConfigStore, LOCAL_ROOT_CONFIG_RELATIVE_PATH, } from "./config.js";
|
|
7
|
-
import { buildExpectedManagedGuidanceBlock, buildGuidanceFileContent, buildGuidanceRevision, extractManagedGuidanceBlock,
|
|
8
|
+
import { buildExpectedManagedGuidanceBlock, buildGuidanceFileContent, buildGuidanceRevision, extractManagedGuidanceBlock, GUIDANCE_TARGETS, normalizeGuidanceTargets, } from "./guidance.js";
|
|
8
9
|
import { createPromptApi } from "./prompts.js";
|
|
9
10
|
import { BUNDLED_SKILLS, buildInstalledSkillReferences, buildSkillPlannedEdits, getBundledSkillPath, parseSkillFrontmatter, resolveSkillsState, syncSkillsIfNeeded, withUpdatedSkillsConfig, } from "./skills.js";
|
|
10
11
|
import { buildPlannedEdit, CLIError, getPackageVersion, normalizeApiUrl, openUrlInBrowser, printJson, printLine, readOptionalUtf8, readTextFromStdin, sleep, toGraphQLAttachmentKind, toGraphQLWorkstreamStatus, toGraphQLWorkstreamUpdateType, toIsoString, writePlannedEdits, } from "./support.js";
|
|
@@ -492,7 +493,9 @@ function printMainHelp(runtime) {
|
|
|
492
493
|
" init",
|
|
493
494
|
" corner use",
|
|
494
495
|
" whoami",
|
|
496
|
+
" agent start|checkpoint|intent|end",
|
|
495
497
|
" workstream (ws) list|use|current|create|pull|update|push|question|attach|reply-thread",
|
|
498
|
+
" open [--workstream <id>] [--corner <slug>]",
|
|
496
499
|
" help",
|
|
497
500
|
" version",
|
|
498
501
|
"",
|
|
@@ -663,6 +666,29 @@ function mergeCommonOptions(base, values) {
|
|
|
663
666
|
async function requireStoredProfile(runtime, common) {
|
|
664
667
|
const selected = await runtime.config.getProfile(common.profile ?? null);
|
|
665
668
|
if (!selected) {
|
|
669
|
+
// Fallback: try reading the desktop app's auth token
|
|
670
|
+
const desktopToken = await runtime.config.getDesktopToken();
|
|
671
|
+
if (desktopToken) {
|
|
672
|
+
const apiUrl = common.apiUrl ?? desktopToken.apiUrl;
|
|
673
|
+
const desktopProfile = {
|
|
674
|
+
apiUrl,
|
|
675
|
+
accessToken: desktopToken.accessToken,
|
|
676
|
+
sessionId: null,
|
|
677
|
+
workspace: desktopToken.workspace,
|
|
678
|
+
accountId: null,
|
|
679
|
+
userId: null,
|
|
680
|
+
handle: null,
|
|
681
|
+
createdAt: new Date().toISOString(),
|
|
682
|
+
};
|
|
683
|
+
return {
|
|
684
|
+
profileName: "desktop",
|
|
685
|
+
profile: desktopProfile,
|
|
686
|
+
client: runtime.createClient({
|
|
687
|
+
apiUrl,
|
|
688
|
+
accessToken: desktopToken.accessToken,
|
|
689
|
+
}),
|
|
690
|
+
};
|
|
691
|
+
}
|
|
666
692
|
throw new CLIError("Not logged in. Run `corners auth login` first.", {
|
|
667
693
|
json: common.json,
|
|
668
694
|
});
|
|
@@ -716,6 +742,34 @@ async function maybeRefreshStoredProfileIdentity(input) {
|
|
|
716
742
|
async function resolveAuthStatus(runtime, common) {
|
|
717
743
|
const selected = await runtime.config.getProfile(common.profile ?? null);
|
|
718
744
|
if (!selected) {
|
|
745
|
+
// Fallback: check desktop app's auth token
|
|
746
|
+
const desktopToken = await runtime.config.getDesktopToken();
|
|
747
|
+
if (desktopToken) {
|
|
748
|
+
const apiUrl = common.apiUrl ?? desktopToken.apiUrl;
|
|
749
|
+
const client = runtime.createClient({
|
|
750
|
+
apiUrl,
|
|
751
|
+
accessToken: desktopToken.accessToken,
|
|
752
|
+
});
|
|
753
|
+
let remote;
|
|
754
|
+
try {
|
|
755
|
+
remote = await client.validateSession();
|
|
756
|
+
}
|
|
757
|
+
catch (error) {
|
|
758
|
+
remote = { valid: false, error: error.message };
|
|
759
|
+
}
|
|
760
|
+
return {
|
|
761
|
+
loggedIn: true,
|
|
762
|
+
profile: "desktop (via Corners app)",
|
|
763
|
+
apiUrl,
|
|
764
|
+
sessionId: null,
|
|
765
|
+
workspace: desktopToken.workspace,
|
|
766
|
+
handle: remote.valid ? null : null,
|
|
767
|
+
accountId: null,
|
|
768
|
+
userId: null,
|
|
769
|
+
remoteValid: remote.valid,
|
|
770
|
+
remoteError: remote.valid ? null : (remote.error ?? null),
|
|
771
|
+
};
|
|
772
|
+
}
|
|
719
773
|
return {
|
|
720
774
|
loggedIn: false,
|
|
721
775
|
profile: common.profile ?? null,
|
|
@@ -898,21 +952,41 @@ async function resolveGuidanceState(root) {
|
|
|
898
952
|
files,
|
|
899
953
|
};
|
|
900
954
|
}
|
|
901
|
-
|
|
955
|
+
/**
|
|
956
|
+
* Silently syncs stale guidance files and updates config.
|
|
957
|
+
* Called automatically from resolveRootContext() so users never need to run `guidance sync` manually.
|
|
958
|
+
*/
|
|
959
|
+
async function autoSyncGuidanceIfStale(root, guidance, writeConfig) {
|
|
902
960
|
if (!guidance.stale) {
|
|
903
|
-
return
|
|
904
|
-
}
|
|
905
|
-
const
|
|
906
|
-
?
|
|
907
|
-
:
|
|
908
|
-
|
|
961
|
+
return { root, guidance };
|
|
962
|
+
}
|
|
963
|
+
const managedTargets = root.config.guidance?.managedTargets !== undefined
|
|
964
|
+
? normalizeGuidanceTargets(root.config.guidance.managedTargets)
|
|
965
|
+
: guidance.managedTargets;
|
|
966
|
+
const edits = [];
|
|
967
|
+
for (const target of GUIDANCE_TARGETS) {
|
|
968
|
+
if (!managedTargets.includes(target.key))
|
|
969
|
+
continue;
|
|
970
|
+
const targetPath = join(root.rootDir, target.relativePath);
|
|
971
|
+
const nextContent = buildGuidanceFileContent(target.key, await readOptionalUtf8(targetPath));
|
|
972
|
+
const edit = await buildPlannedEdit(targetPath, nextContent);
|
|
973
|
+
if (edit)
|
|
974
|
+
edits.push(edit);
|
|
975
|
+
}
|
|
976
|
+
if (edits.length > 0) {
|
|
977
|
+
await writePlannedEdits(edits);
|
|
978
|
+
}
|
|
979
|
+
const nextConfig = withUpdatedGuidanceConfig(root.config, managedTargets, toIsoString());
|
|
980
|
+
await writeConfig(root.rootDir, nextConfig);
|
|
981
|
+
const nextRoot = { ...root, config: nextConfig };
|
|
982
|
+
const nextGuidance = await resolveGuidanceState(nextRoot);
|
|
983
|
+
return { root: nextRoot, guidance: nextGuidance };
|
|
909
984
|
}
|
|
910
985
|
function buildGuidanceJsonMetadata(guidance) {
|
|
911
986
|
return {
|
|
912
987
|
stale: guidance.stale,
|
|
913
988
|
lastSyncedAt: guidance.lastSyncedAt,
|
|
914
989
|
staleFiles: guidance.staleFiles,
|
|
915
|
-
syncCommand: GUIDANCE_SYNC_COMMAND,
|
|
916
990
|
};
|
|
917
991
|
}
|
|
918
992
|
function withGuidanceJsonMetadata(payload, guidance) {
|
|
@@ -926,26 +1000,21 @@ function withGuidanceJsonMetadata(payload, guidance) {
|
|
|
926
1000
|
},
|
|
927
1001
|
};
|
|
928
1002
|
}
|
|
929
|
-
async function resolveRootContext(runtime,
|
|
1003
|
+
async function resolveRootContext(runtime, _common, root) {
|
|
930
1004
|
const guidance = await resolveGuidanceState(root);
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
if (warning) {
|
|
934
|
-
printLine(runtime.stderr, warning);
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
await syncSkillsIfNeeded(root.rootDir, root.config, (dir, cfg) => runtime.config.writeLocalConfig(dir, cfg));
|
|
1005
|
+
const synced = await autoSyncGuidanceIfStale(root, guidance, (dir, cfg) => runtime.config.writeLocalConfig(dir, cfg));
|
|
1006
|
+
await syncSkillsIfNeeded(synced.root.rootDir, synced.root.config, (dir, cfg) => runtime.config.writeLocalConfig(dir, cfg));
|
|
938
1007
|
return {
|
|
939
|
-
root,
|
|
940
|
-
guidance,
|
|
1008
|
+
root: synced.root,
|
|
1009
|
+
guidance: synced.guidance,
|
|
941
1010
|
};
|
|
942
1011
|
}
|
|
943
|
-
async function requireLocalRoot(runtime, common
|
|
1012
|
+
async function requireLocalRoot(runtime, common) {
|
|
944
1013
|
const root = await runtime.config.findLocalRoot(runtime.cwd);
|
|
945
1014
|
if (!root) {
|
|
946
1015
|
throw new CLIError("No local Corners CLI root found. Run `corners init` from your project root first.", { json: common.json });
|
|
947
1016
|
}
|
|
948
|
-
return resolveRootContext(runtime, common, root
|
|
1017
|
+
return resolveRootContext(runtime, common, root);
|
|
949
1018
|
}
|
|
950
1019
|
async function requirePinnedRootProfile(runtime, common, rootContext) {
|
|
951
1020
|
const { root, guidance } = rootContext;
|
|
@@ -1489,17 +1558,88 @@ async function handleInit(args, runtime, inherited) {
|
|
|
1489
1558
|
}
|
|
1490
1559
|
await writePlannedEdits(plannedEdits);
|
|
1491
1560
|
}
|
|
1561
|
+
// --- Claude Code Settings Hook ---
|
|
1562
|
+
if (selectedTargets.some((t) => t.key === "claude")) {
|
|
1563
|
+
const claudeSettingsPath = join(rootDir, ".claude", "settings.json");
|
|
1564
|
+
try {
|
|
1565
|
+
await mkdir(join(rootDir, ".claude"), { recursive: true });
|
|
1566
|
+
let existing = {};
|
|
1567
|
+
try {
|
|
1568
|
+
const raw = await readOptionalUtf8(claudeSettingsPath);
|
|
1569
|
+
if (raw) {
|
|
1570
|
+
existing = JSON.parse(raw);
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
catch {
|
|
1574
|
+
// Malformed JSON — start fresh
|
|
1575
|
+
existing = {};
|
|
1576
|
+
}
|
|
1577
|
+
// Merge permissions.allow
|
|
1578
|
+
const existingPerms = existing.permissions ?? {};
|
|
1579
|
+
const existingAllow = Array.isArray(existingPerms.allow)
|
|
1580
|
+
? existingPerms.allow
|
|
1581
|
+
: [];
|
|
1582
|
+
const newPerms = [
|
|
1583
|
+
"Bash(corners workstream:*)",
|
|
1584
|
+
"Bash(corners agent:*)",
|
|
1585
|
+
];
|
|
1586
|
+
const mergedAllow = [
|
|
1587
|
+
...existingAllow,
|
|
1588
|
+
...newPerms.filter((p) => !existingAllow.includes(p)),
|
|
1589
|
+
];
|
|
1590
|
+
// Merge hooks.SessionStart
|
|
1591
|
+
const existingHooks = existing.hooks ?? {};
|
|
1592
|
+
const existingSessionStart = Array.isArray(existingHooks.SessionStart)
|
|
1593
|
+
? existingHooks.SessionStart
|
|
1594
|
+
: [];
|
|
1595
|
+
const hookCommand = "command -v corners >/dev/null 2>&1 && corners workstream current --json || true";
|
|
1596
|
+
const alreadyHasHook = existingSessionStart.some((entry) => Array.isArray(entry.hooks) &&
|
|
1597
|
+
entry.hooks.some((h) => h.command === hookCommand));
|
|
1598
|
+
const mergedSessionStart = alreadyHasHook
|
|
1599
|
+
? existingSessionStart
|
|
1600
|
+
: [
|
|
1601
|
+
...existingSessionStart,
|
|
1602
|
+
{
|
|
1603
|
+
matcher: "",
|
|
1604
|
+
hooks: [{ type: "command", command: hookCommand }],
|
|
1605
|
+
},
|
|
1606
|
+
];
|
|
1607
|
+
const merged = {
|
|
1608
|
+
...existing,
|
|
1609
|
+
permissions: {
|
|
1610
|
+
...existingPerms,
|
|
1611
|
+
allow: mergedAllow,
|
|
1612
|
+
},
|
|
1613
|
+
hooks: {
|
|
1614
|
+
...existingHooks,
|
|
1615
|
+
SessionStart: mergedSessionStart,
|
|
1616
|
+
},
|
|
1617
|
+
};
|
|
1618
|
+
await writeFile(claudeSettingsPath, `${JSON.stringify(merged, null, 2)}\n`, "utf8");
|
|
1619
|
+
if (!common.json) {
|
|
1620
|
+
printLine(runtime.stderr, " Wrote Claude Code settings with SessionStart hook");
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
catch (error) {
|
|
1624
|
+
if (!common.json) {
|
|
1625
|
+
printLine(runtime.stderr, ` Warning: could not write .claude/settings.json: ${error.message}`);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1492
1629
|
// --- Agent Communication Setup ---
|
|
1493
1630
|
const setupAgentComms = await prompts.confirm("Set up agent communication? (auto-connect workstream + CLAUDE.md instructions)", { defaultValue: true });
|
|
1494
1631
|
let connectedWorkstream = null;
|
|
1495
1632
|
if (setupAgentComms) {
|
|
1496
1633
|
// Auto-connect or create a workstream
|
|
1497
|
-
const workstreams = await client
|
|
1634
|
+
const workstreams = await client
|
|
1635
|
+
.graphql(`query CliInitWorkstreams($cornerId: ID!) {
|
|
1498
1636
|
corner(id: $cornerId) { workstreams(first: 20) { edges { node { id name status } } } }
|
|
1499
|
-
}`, { cornerId: defaultCorner.id })
|
|
1637
|
+
}`, { cornerId: defaultCorner.id })
|
|
1638
|
+
.then((r) => {
|
|
1500
1639
|
const corner = r.corner;
|
|
1501
1640
|
return corner?.workstreams?.edges?.map((e) => e.node) ?? [];
|
|
1502
|
-
})
|
|
1641
|
+
})
|
|
1642
|
+
.catch(() => []);
|
|
1503
1643
|
if (workstreams.length > 0) {
|
|
1504
1644
|
const wsChoice = await prompts.select("Connect a workstream for agent updates:", [
|
|
1505
1645
|
...workstreams.map((ws) => ({
|
|
@@ -1515,7 +1655,9 @@ async function handleInit(args, runtime, inherited) {
|
|
|
1515
1655
|
connectedWorkstream = created.createWorkstream;
|
|
1516
1656
|
}
|
|
1517
1657
|
else if (wsChoice !== "__skip__") {
|
|
1518
|
-
connectedWorkstream =
|
|
1658
|
+
connectedWorkstream =
|
|
1659
|
+
workstreams.find((ws) => ws.id === wsChoice) ??
|
|
1660
|
+
null;
|
|
1519
1661
|
}
|
|
1520
1662
|
}
|
|
1521
1663
|
else {
|
|
@@ -1536,22 +1678,8 @@ async function handleInit(args, runtime, inherited) {
|
|
|
1536
1678
|
const updatedConfigPath = runtime.config.getLocalConfigPath(rootDir);
|
|
1537
1679
|
await writeFile(updatedConfigPath, `${JSON.stringify(nextLocalConfig, null, 2)}\n`, "utf8");
|
|
1538
1680
|
}
|
|
1539
|
-
//
|
|
1540
|
-
|
|
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
|
-
}
|
|
1681
|
+
// Agent communication instructions are now part of the managed guidance
|
|
1682
|
+
// block in buildGuidanceSection(). No separate append needed.
|
|
1555
1683
|
// Send test checkpoint
|
|
1556
1684
|
if (connectedWorkstream) {
|
|
1557
1685
|
try {
|
|
@@ -1591,7 +1719,7 @@ async function handleInit(args, runtime, inherited) {
|
|
|
1591
1719
|
const wsMsg = connectedWorkstream
|
|
1592
1720
|
? ` Connected workstream: ${connectedWorkstream.name}.`
|
|
1593
1721
|
: "";
|
|
1594
|
-
printLine(runtime.stdout, `Initialized Corners CLI at ${rootDir} with default corner ${defaultCorner.name}.${wsMsg}
|
|
1722
|
+
printLine(runtime.stdout, `Initialized Corners CLI at ${rootDir} with default corner ${defaultCorner.name}.${wsMsg}`);
|
|
1595
1723
|
}
|
|
1596
1724
|
return 0;
|
|
1597
1725
|
});
|
|
@@ -1655,9 +1783,7 @@ async function handleSkills(args, runtime, inherited) {
|
|
|
1655
1783
|
printSkillsHelp(runtime);
|
|
1656
1784
|
return 0;
|
|
1657
1785
|
}
|
|
1658
|
-
const rootContext = await requireLocalRoot(runtime, common
|
|
1659
|
-
emitWarning: false,
|
|
1660
|
-
});
|
|
1786
|
+
const rootContext = await requireLocalRoot(runtime, common);
|
|
1661
1787
|
const state = await resolveSkillsState(rootContext.root.rootDir, rootContext.root.config);
|
|
1662
1788
|
const payload = {
|
|
1663
1789
|
ok: true,
|
|
@@ -1713,9 +1839,7 @@ async function handleGuidance(args, runtime, inherited) {
|
|
|
1713
1839
|
printGuidanceHelp(runtime);
|
|
1714
1840
|
return 0;
|
|
1715
1841
|
}
|
|
1716
|
-
const rootContext = await requireLocalRoot(runtime, common
|
|
1717
|
-
emitWarning: false,
|
|
1718
|
-
});
|
|
1842
|
+
const rootContext = await requireLocalRoot(runtime, common);
|
|
1719
1843
|
const payload = {
|
|
1720
1844
|
ok: true,
|
|
1721
1845
|
root: rootContext.root.rootDir,
|
|
@@ -1751,9 +1875,7 @@ async function handleGuidance(args, runtime, inherited) {
|
|
|
1751
1875
|
printGuidanceHelp(runtime);
|
|
1752
1876
|
return 0;
|
|
1753
1877
|
}
|
|
1754
|
-
const rootContext = await requireLocalRoot(runtime, common
|
|
1755
|
-
emitWarning: false,
|
|
1756
|
-
});
|
|
1878
|
+
const rootContext = await requireLocalRoot(runtime, common);
|
|
1757
1879
|
const managedTargets = rootContext.root.config.guidance?.managedTargets !== undefined
|
|
1758
1880
|
? normalizeGuidanceTargets(rootContext.root.config.guidance.managedTargets)
|
|
1759
1881
|
: rootContext.guidance.managedTargets;
|
|
@@ -1871,6 +1993,167 @@ async function handleCorner(args, runtime, inherited) {
|
|
|
1871
1993
|
throw new CLIError(`Unknown corner command: ${subcommand}`);
|
|
1872
1994
|
}
|
|
1873
1995
|
}
|
|
1996
|
+
function printAgentHelp(runtime) {
|
|
1997
|
+
printLine(runtime.stdout, [
|
|
1998
|
+
"corners agent",
|
|
1999
|
+
"",
|
|
2000
|
+
"Usage:",
|
|
2001
|
+
" corners agent start [--intent <text>] [--json]",
|
|
2002
|
+
" corners agent checkpoint --message <text> [--files <paths>] [--json]",
|
|
2003
|
+
" corners agent intent --scope <text> [--files <paths>] [--json]",
|
|
2004
|
+
" corners agent end [--summary <text>] [--json]",
|
|
2005
|
+
"",
|
|
2006
|
+
"Thin wrappers over workstream push for agent session lifecycle.",
|
|
2007
|
+
"Auto-creates a workstream if none is active.",
|
|
2008
|
+
].join("\n"));
|
|
2009
|
+
}
|
|
2010
|
+
function resolveGitBranch() {
|
|
2011
|
+
try {
|
|
2012
|
+
return (execSync("git branch --show-current", {
|
|
2013
|
+
encoding: "utf8",
|
|
2014
|
+
timeout: 3000,
|
|
2015
|
+
}).trim() || "unknown");
|
|
2016
|
+
}
|
|
2017
|
+
catch {
|
|
2018
|
+
return "unknown";
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
async function resolveAgentWorkstreamId(runtime, common, client) {
|
|
2022
|
+
// Try the normal resolution first (explicit flag or binding)
|
|
2023
|
+
const binding = await runtime.config.getBinding(runtime.cwd);
|
|
2024
|
+
if (binding) {
|
|
2025
|
+
return binding.workstreamId;
|
|
2026
|
+
}
|
|
2027
|
+
// Auto-create a workstream named after the git branch
|
|
2028
|
+
const root = await runtime.config.findLocalRoot(runtime.cwd);
|
|
2029
|
+
if (!root) {
|
|
2030
|
+
throw new CLIError("No Corners root found. Run `corners init` first.", {
|
|
2031
|
+
json: common.json,
|
|
2032
|
+
});
|
|
2033
|
+
}
|
|
2034
|
+
const branch = resolveGitBranch();
|
|
2035
|
+
const wsName = `agent/${branch}`;
|
|
2036
|
+
const result = await client.graphql(`mutation CliAgentAutoCreate($input: CreateWorkstreamInput!) { createWorkstream(input: $input) { id name } }`, { input: { cornerId: root.config.defaultCorner.id, name: wsName } });
|
|
2037
|
+
// Bind the auto-created workstream so future commands use it
|
|
2038
|
+
await runtime.config.setBinding(runtime.cwd, {
|
|
2039
|
+
profile: root.config.profile,
|
|
2040
|
+
workspace: root.config.workspace ?? "",
|
|
2041
|
+
workstreamId: result.createWorkstream.id,
|
|
2042
|
+
cornerId: root.config.defaultCorner.id,
|
|
2043
|
+
boundAt: toIsoString(),
|
|
2044
|
+
});
|
|
2045
|
+
if (!common.json) {
|
|
2046
|
+
printLine(runtime.stderr, `Auto-created workstream "${wsName}" (${result.createWorkstream.id})`);
|
|
2047
|
+
}
|
|
2048
|
+
return result.createWorkstream.id;
|
|
2049
|
+
}
|
|
2050
|
+
async function handleAgent(args, runtime, inherited) {
|
|
2051
|
+
const subcommand = args[0];
|
|
2052
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
2053
|
+
printAgentHelp(runtime);
|
|
2054
|
+
return 0;
|
|
2055
|
+
}
|
|
2056
|
+
const parsed = parseArgs({
|
|
2057
|
+
args: args.slice(1),
|
|
2058
|
+
allowPositionals: false,
|
|
2059
|
+
options: {
|
|
2060
|
+
json: { type: "boolean" },
|
|
2061
|
+
help: { type: "boolean", short: "h" },
|
|
2062
|
+
profile: { type: "string" },
|
|
2063
|
+
"api-url": { type: "string" },
|
|
2064
|
+
workstream: { type: "string", short: "w" },
|
|
2065
|
+
message: { type: "string" },
|
|
2066
|
+
intent: { type: "string" },
|
|
2067
|
+
summary: { type: "string" },
|
|
2068
|
+
scope: { type: "string" },
|
|
2069
|
+
files: { type: "string" },
|
|
2070
|
+
},
|
|
2071
|
+
});
|
|
2072
|
+
const common = mergeCommonOptions(inherited, {
|
|
2073
|
+
json: parsed.values.json,
|
|
2074
|
+
profile: parsed.values.profile,
|
|
2075
|
+
apiUrl: parsed.values["api-url"],
|
|
2076
|
+
});
|
|
2077
|
+
if (parsed.values.help) {
|
|
2078
|
+
printAgentHelp(runtime);
|
|
2079
|
+
return 0;
|
|
2080
|
+
}
|
|
2081
|
+
// Resolve update type and synthesize message from subcommand-specific flags
|
|
2082
|
+
let updateType;
|
|
2083
|
+
let message;
|
|
2084
|
+
switch (subcommand) {
|
|
2085
|
+
case "start":
|
|
2086
|
+
updateType = "session_start";
|
|
2087
|
+
message = parsed.values.intent ?? "session started";
|
|
2088
|
+
break;
|
|
2089
|
+
case "checkpoint":
|
|
2090
|
+
updateType = "checkpoint";
|
|
2091
|
+
message = parsed.values.message ?? "";
|
|
2092
|
+
if (!message) {
|
|
2093
|
+
throw new CLIError("Checkpoint needs a message. Use --message.", {
|
|
2094
|
+
json: common.json,
|
|
2095
|
+
});
|
|
2096
|
+
}
|
|
2097
|
+
break;
|
|
2098
|
+
case "intent":
|
|
2099
|
+
updateType = "intent";
|
|
2100
|
+
message = parsed.values.scope ?? "";
|
|
2101
|
+
if (!message) {
|
|
2102
|
+
throw new CLIError("Intent needs a scope. Use --scope.", {
|
|
2103
|
+
json: common.json,
|
|
2104
|
+
});
|
|
2105
|
+
}
|
|
2106
|
+
break;
|
|
2107
|
+
case "end":
|
|
2108
|
+
updateType = "session_end";
|
|
2109
|
+
message = parsed.values.summary ?? "session ended";
|
|
2110
|
+
break;
|
|
2111
|
+
default:
|
|
2112
|
+
throw new CLIError(`Unknown agent command: ${subcommand}`, {
|
|
2113
|
+
json: common.json,
|
|
2114
|
+
});
|
|
2115
|
+
}
|
|
2116
|
+
const { guidance, client } = await requireCommandProfile(runtime, common);
|
|
2117
|
+
// Resolve workstream with auto-create fallback
|
|
2118
|
+
const workstreamId = parsed.values.workstream
|
|
2119
|
+
? parsed.values.workstream
|
|
2120
|
+
: await resolveAgentWorkstreamId(runtime, common, client);
|
|
2121
|
+
const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
|
|
2122
|
+
// Detect agent type from environment
|
|
2123
|
+
const agentType = (process.env.CLAUDE_CODE ? "claude-code" : null) ??
|
|
2124
|
+
(process.env.CODEX_SESSION ? "codex" : null) ??
|
|
2125
|
+
null;
|
|
2126
|
+
const filesTouched = parsed.values.files
|
|
2127
|
+
? parsed.values.files.split(",").map((f) => f.trim())
|
|
2128
|
+
: null;
|
|
2129
|
+
const scope = parsed.values.scope ?? null;
|
|
2130
|
+
const updateResult = await client.graphql(RECORD_WORKSTREAM_UPDATE_MUTATION, {
|
|
2131
|
+
input: {
|
|
2132
|
+
workstreamId: workstream.id,
|
|
2133
|
+
updateType: toGraphQLWorkstreamUpdateType(updateType),
|
|
2134
|
+
content: message,
|
|
2135
|
+
summary: parsed.values.summary,
|
|
2136
|
+
...(agentType ? { agentType } : {}),
|
|
2137
|
+
...(filesTouched ? { filesTouched } : {}),
|
|
2138
|
+
...(scope ? { scope } : {}),
|
|
2139
|
+
},
|
|
2140
|
+
});
|
|
2141
|
+
const payload = {
|
|
2142
|
+
ok: true,
|
|
2143
|
+
success: true,
|
|
2144
|
+
workstreamId: workstream.id,
|
|
2145
|
+
type: updateType,
|
|
2146
|
+
messageId: updateResult.recordWorkstreamUpdate.id,
|
|
2147
|
+
update: updateResult.recordWorkstreamUpdate,
|
|
2148
|
+
};
|
|
2149
|
+
if (common.json) {
|
|
2150
|
+
printJson(runtime.stdout, withGuidanceJsonMetadata(payload, guidance));
|
|
2151
|
+
}
|
|
2152
|
+
else {
|
|
2153
|
+
printLine(runtime.stdout, `Recorded agent ${subcommand} on ${workstream.id}.`);
|
|
2154
|
+
}
|
|
2155
|
+
return 0;
|
|
2156
|
+
}
|
|
1874
2157
|
async function handleWorkstream(args, runtime, inherited) {
|
|
1875
2158
|
const subcommand = args[0];
|
|
1876
2159
|
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
@@ -2768,6 +3051,50 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
2768
3051
|
throw new CLIError(`Unknown workstream command: ${subcommand}`);
|
|
2769
3052
|
}
|
|
2770
3053
|
}
|
|
3054
|
+
async function handleOpen(args, runtime) {
|
|
3055
|
+
const parsed = parseArgs({
|
|
3056
|
+
args,
|
|
3057
|
+
allowPositionals: false,
|
|
3058
|
+
options: {
|
|
3059
|
+
workstream: { type: "string", short: "w" },
|
|
3060
|
+
corner: { type: "string", short: "c" },
|
|
3061
|
+
help: { type: "boolean", short: "h" },
|
|
3062
|
+
},
|
|
3063
|
+
});
|
|
3064
|
+
if (parsed.values.help) {
|
|
3065
|
+
printLine(runtime.stdout, [
|
|
3066
|
+
"corners open",
|
|
3067
|
+
"",
|
|
3068
|
+
"Open the Corners desktop app, optionally navigating to a specific workstream or corner.",
|
|
3069
|
+
"",
|
|
3070
|
+
"Usage:",
|
|
3071
|
+
" corners open [--workstream <id>] [--corner <slug>]",
|
|
3072
|
+
"",
|
|
3073
|
+
"Options:",
|
|
3074
|
+
" --workstream, -w <id> Open to a specific workstream",
|
|
3075
|
+
" --corner, -c <slug> Open to a specific corner",
|
|
3076
|
+
" --help, -h Show this help",
|
|
3077
|
+
"",
|
|
3078
|
+
"Examples:",
|
|
3079
|
+
" corners open",
|
|
3080
|
+
" corners open --workstream ws_abc123",
|
|
3081
|
+
" corners open --corner engineering",
|
|
3082
|
+
].join("\n"));
|
|
3083
|
+
return 0;
|
|
3084
|
+
}
|
|
3085
|
+
let url = "corners://";
|
|
3086
|
+
if (parsed.values.workstream) {
|
|
3087
|
+
url = `corners://open?workstream=${encodeURIComponent(parsed.values.workstream)}`;
|
|
3088
|
+
}
|
|
3089
|
+
else if (parsed.values.corner) {
|
|
3090
|
+
url = `corners://open?corner=${encodeURIComponent(parsed.values.corner)}`;
|
|
3091
|
+
}
|
|
3092
|
+
const opened = await openUrlInBrowser(url);
|
|
3093
|
+
if (!opened) {
|
|
3094
|
+
throw new CLIError("Could not open the Corners desktop app. Is it installed?");
|
|
3095
|
+
}
|
|
3096
|
+
return 0;
|
|
3097
|
+
}
|
|
2771
3098
|
async function handleMcp(args, runtime, inherited) {
|
|
2772
3099
|
const subcommand = args[0];
|
|
2773
3100
|
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
@@ -2855,11 +3182,15 @@ export async function runCli(argv, runtime = createRuntime()) {
|
|
|
2855
3182
|
return handleCorner(parsed.rest.slice(1), runtime, parsed.common);
|
|
2856
3183
|
case "whoami":
|
|
2857
3184
|
return handleWhoAmI(parsed.rest.slice(1), runtime, parsed.common);
|
|
3185
|
+
case "agent":
|
|
3186
|
+
return handleAgent(parsed.rest.slice(1), runtime, parsed.common);
|
|
2858
3187
|
case "workstream":
|
|
2859
3188
|
case "ws":
|
|
2860
3189
|
return handleWorkstream(parsed.rest.slice(1), runtime, parsed.common);
|
|
2861
3190
|
case "mcp":
|
|
2862
3191
|
return handleMcp(parsed.rest.slice(1), runtime, parsed.common);
|
|
3192
|
+
case "open":
|
|
3193
|
+
return handleOpen(parsed.rest.slice(1), runtime);
|
|
2863
3194
|
default:
|
|
2864
3195
|
throw new CLIError(`Unknown command: ${command}`, {
|
|
2865
3196
|
json: parsed.common.json,
|