@corners/cli 0.0.1 → 0.0.4
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 +2 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1046 -150
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +41 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +29 -0
- package/dist/config.js.map +1 -1
- package/dist/guidance.d.ts +17 -0
- package/dist/guidance.d.ts.map +1 -0
- package/dist/guidance.js +121 -0
- package/dist/guidance.js.map +1 -0
- package/dist/prompts.d.ts +15 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +59 -0
- package/dist/prompts.js.map +1 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,31 +1,12 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
1
|
+
import { mkdir, readFile, realpath, writeFile } from "node:fs/promises";
|
|
2
2
|
import { hostname } from "node:os";
|
|
3
|
-
import { basename } from "node:path";
|
|
3
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { parseArgs } from "node:util";
|
|
5
5
|
import { CornersApiClient as DefaultCornersApiClient, } from "./client.js";
|
|
6
|
-
import { ConfigStore } from "./config.js";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
workstreams(filter: { status: ACTIVE }, first: 100) {
|
|
11
|
-
edges {
|
|
12
|
-
node {
|
|
13
|
-
id
|
|
14
|
-
cornerId
|
|
15
|
-
name
|
|
16
|
-
summary
|
|
17
|
-
category
|
|
18
|
-
status
|
|
19
|
-
updatedAt
|
|
20
|
-
topic {
|
|
21
|
-
id
|
|
22
|
-
name
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
`;
|
|
6
|
+
import { ConfigStore, LOCAL_ROOT_CONFIG_RELATIVE_PATH, } from "./config.js";
|
|
7
|
+
import { buildExpectedManagedGuidanceBlock, buildGuidanceFileContent, buildGuidanceRevision, extractManagedGuidanceBlock, GUIDANCE_SYNC_COMMAND, GUIDANCE_TARGETS, normalizeGuidanceTargets, } from "./guidance.js";
|
|
8
|
+
import { createPromptApi } from "./prompts.js";
|
|
9
|
+
import { CLIError, getPackageVersion, normalizeApiUrl, openUrlInBrowser, printJson, printLine, readTextFromStdin, sleep, toGraphQLAttachmentKind, toGraphQLWorkstreamUpdateType, toIsoString, } from "./support.js";
|
|
29
10
|
const WORKSTREAM_LOOKUP_QUERY = `
|
|
30
11
|
query CliWorkstreamLookup($id: ID!) {
|
|
31
12
|
workstream(id: $id) {
|
|
@@ -308,6 +289,47 @@ const REVOKE_MCP_SESSION_MUTATION = `
|
|
|
308
289
|
revokeMCPSession(id: $id)
|
|
309
290
|
}
|
|
310
291
|
`;
|
|
292
|
+
const WHOAMI_QUERY = `
|
|
293
|
+
query CliWhoAmI {
|
|
294
|
+
me {
|
|
295
|
+
id
|
|
296
|
+
handle
|
|
297
|
+
account {
|
|
298
|
+
id
|
|
299
|
+
workspace
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
`;
|
|
304
|
+
const LIST_MEMBER_CORNERS_QUERY = `
|
|
305
|
+
query CliMemberCorners {
|
|
306
|
+
memberCorners(first: 50) {
|
|
307
|
+
edges {
|
|
308
|
+
node {
|
|
309
|
+
id
|
|
310
|
+
name
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
`;
|
|
316
|
+
const CREATE_WORKSTREAM_MUTATION = `
|
|
317
|
+
mutation CliCreateWorkstream($input: CreateWorkstreamInput!) {
|
|
318
|
+
createWorkstream(input: $input) {
|
|
319
|
+
id
|
|
320
|
+
cornerId
|
|
321
|
+
name
|
|
322
|
+
summary
|
|
323
|
+
category
|
|
324
|
+
status
|
|
325
|
+
updatedAt
|
|
326
|
+
topic {
|
|
327
|
+
id
|
|
328
|
+
name
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
`;
|
|
311
333
|
function createRuntime() {
|
|
312
334
|
return {
|
|
313
335
|
config: new ConfigStore(),
|
|
@@ -315,6 +337,7 @@ function createRuntime() {
|
|
|
315
337
|
stderr: process.stderr,
|
|
316
338
|
stdin: process.stdin,
|
|
317
339
|
cwd: process.cwd(),
|
|
340
|
+
prompts: undefined,
|
|
318
341
|
openUrl: openUrlInBrowser,
|
|
319
342
|
createClient: (input) => new DefaultCornersApiClient(input),
|
|
320
343
|
getVersion: getPackageVersion,
|
|
@@ -329,7 +352,11 @@ function printMainHelp(runtime) {
|
|
|
329
352
|
"",
|
|
330
353
|
"Commands:",
|
|
331
354
|
" auth login|logout|status",
|
|
332
|
-
"
|
|
355
|
+
" guidance status|sync",
|
|
356
|
+
" init",
|
|
357
|
+
" corner use",
|
|
358
|
+
" whoami",
|
|
359
|
+
" workstream (ws) list|use|current|create|pull|push|question|attach|reply-thread",
|
|
333
360
|
" help",
|
|
334
361
|
" version",
|
|
335
362
|
"",
|
|
@@ -342,6 +369,23 @@ function printMainHelp(runtime) {
|
|
|
342
369
|
" --no-browser",
|
|
343
370
|
].join("\n"));
|
|
344
371
|
}
|
|
372
|
+
function printInitHelp(runtime) {
|
|
373
|
+
printLine(runtime.stdout, [
|
|
374
|
+
"corners init",
|
|
375
|
+
"",
|
|
376
|
+
"Usage:",
|
|
377
|
+
" corners init [--profile <name>] [--api-url <url>] [--json]",
|
|
378
|
+
].join("\n"));
|
|
379
|
+
}
|
|
380
|
+
function printGuidanceHelp(runtime) {
|
|
381
|
+
printLine(runtime.stdout, [
|
|
382
|
+
"corners guidance",
|
|
383
|
+
"",
|
|
384
|
+
"Usage:",
|
|
385
|
+
" corners guidance status [--json]",
|
|
386
|
+
" corners guidance sync [--json]",
|
|
387
|
+
].join("\n"));
|
|
388
|
+
}
|
|
345
389
|
function printAuthHelp(runtime) {
|
|
346
390
|
printLine(runtime.stdout, [
|
|
347
391
|
"corners auth",
|
|
@@ -352,6 +396,22 @@ function printAuthHelp(runtime) {
|
|
|
352
396
|
" corners auth status [--profile <name>] [--json]",
|
|
353
397
|
].join("\n"));
|
|
354
398
|
}
|
|
399
|
+
function printCornerHelp(runtime) {
|
|
400
|
+
printLine(runtime.stdout, [
|
|
401
|
+
"corners corner",
|
|
402
|
+
"",
|
|
403
|
+
"Usage:",
|
|
404
|
+
" corners corner use <cornerNameOrId> [--path <path>] [--json]",
|
|
405
|
+
].join("\n"));
|
|
406
|
+
}
|
|
407
|
+
function printWhoamiHelp(runtime) {
|
|
408
|
+
printLine(runtime.stdout, [
|
|
409
|
+
"corners whoami",
|
|
410
|
+
"",
|
|
411
|
+
"Usage:",
|
|
412
|
+
" corners whoami [--profile <name>] [--api-url <url>] [--json]",
|
|
413
|
+
].join("\n"));
|
|
414
|
+
}
|
|
355
415
|
function printWorkstreamHelp(runtime) {
|
|
356
416
|
printLine(runtime.stdout, [
|
|
357
417
|
"corners workstream",
|
|
@@ -360,12 +420,13 @@ function printWorkstreamHelp(runtime) {
|
|
|
360
420
|
" corners workstream list [--json]",
|
|
361
421
|
" corners workstream use <workstreamId> [--json]",
|
|
362
422
|
" corners workstream current [--json]",
|
|
363
|
-
" corners workstream
|
|
364
|
-
" corners workstream
|
|
365
|
-
" corners workstream
|
|
366
|
-
" corners workstream question
|
|
423
|
+
" corners workstream create <name> [--summary <text>] [--corner <cornerNameOrId>] [--json]",
|
|
424
|
+
" corners workstream pull <workstreamId> [--json]",
|
|
425
|
+
" corners workstream push <workstreamId> [--type <type>] [--message <text>] [--summary <text>] [--file <path>] [--title <title>] [--json]",
|
|
426
|
+
" corners workstream question list <workstreamId> [--json]",
|
|
427
|
+
" corners workstream question ask <workstreamId> [--question <text>] [--rationale <text>] [--suggested-answer <text>]... [--json]",
|
|
367
428
|
" corners workstream question answer <questionId> [--text <text>] [--json]",
|
|
368
|
-
" corners workstream attach
|
|
429
|
+
" corners workstream attach <workstreamId> --kind <kind> --entity-id <id> [--json]",
|
|
369
430
|
" corners workstream reply-thread <threadId> [--text <text>] [--json]",
|
|
370
431
|
"",
|
|
371
432
|
"Update types:",
|
|
@@ -462,6 +523,83 @@ async function requireStoredProfile(runtime, common) {
|
|
|
462
523
|
}),
|
|
463
524
|
};
|
|
464
525
|
}
|
|
526
|
+
async function fetchCurrentIdentity(client) {
|
|
527
|
+
const data = await client.graphql(WHOAMI_QUERY);
|
|
528
|
+
return {
|
|
529
|
+
userId: data.me.id,
|
|
530
|
+
handle: data.me.handle ?? null,
|
|
531
|
+
accountId: data.me.account?.id ?? null,
|
|
532
|
+
workspace: data.me.account?.workspace ?? null,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
async function maybeRefreshStoredProfileIdentity(input) {
|
|
536
|
+
try {
|
|
537
|
+
const identity = await fetchCurrentIdentity(input.client);
|
|
538
|
+
const nextProfile = {
|
|
539
|
+
...input.profile,
|
|
540
|
+
userId: identity.userId,
|
|
541
|
+
handle: identity.handle,
|
|
542
|
+
accountId: identity.accountId,
|
|
543
|
+
workspace: identity.workspace,
|
|
544
|
+
};
|
|
545
|
+
if (nextProfile.userId !== input.profile.userId ||
|
|
546
|
+
nextProfile.handle !== (input.profile.handle ?? null) ||
|
|
547
|
+
nextProfile.accountId !== input.profile.accountId ||
|
|
548
|
+
nextProfile.workspace !== input.profile.workspace) {
|
|
549
|
+
await input.runtime.config.saveProfile(input.profileName, nextProfile, {
|
|
550
|
+
setActive: false,
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
return nextProfile;
|
|
554
|
+
}
|
|
555
|
+
catch {
|
|
556
|
+
return input.profile;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
async function resolveAuthStatus(runtime, common) {
|
|
560
|
+
const selected = await runtime.config.getProfile(common.profile ?? null);
|
|
561
|
+
if (!selected) {
|
|
562
|
+
return {
|
|
563
|
+
loggedIn: false,
|
|
564
|
+
profile: common.profile ?? null,
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
const apiUrl = common.apiUrl ?? selected.profile.apiUrl;
|
|
568
|
+
const client = runtime.createClient({
|
|
569
|
+
apiUrl,
|
|
570
|
+
accessToken: selected.profile.accessToken,
|
|
571
|
+
});
|
|
572
|
+
let remote;
|
|
573
|
+
try {
|
|
574
|
+
remote = await client.validateSession();
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
remote = {
|
|
578
|
+
valid: false,
|
|
579
|
+
error: error.message,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
const profile = remote.valid
|
|
583
|
+
? await maybeRefreshStoredProfileIdentity({
|
|
584
|
+
runtime,
|
|
585
|
+
profileName: selected.name,
|
|
586
|
+
profile: selected.profile,
|
|
587
|
+
client,
|
|
588
|
+
})
|
|
589
|
+
: selected.profile;
|
|
590
|
+
return {
|
|
591
|
+
loggedIn: true,
|
|
592
|
+
profile: selected.name,
|
|
593
|
+
apiUrl,
|
|
594
|
+
sessionId: profile.sessionId,
|
|
595
|
+
workspace: profile.workspace,
|
|
596
|
+
handle: profile.handle ?? null,
|
|
597
|
+
accountId: profile.accountId,
|
|
598
|
+
userId: profile.userId,
|
|
599
|
+
remoteValid: remote.valid,
|
|
600
|
+
remoteError: remote.valid ? null : (remote.error ?? null),
|
|
601
|
+
};
|
|
602
|
+
}
|
|
465
603
|
async function pollForDeviceToken(input) {
|
|
466
604
|
const deadline = Date.now() + input.device.expires_in * 1000;
|
|
467
605
|
let intervalMs = input.device.interval * 1000;
|
|
@@ -489,36 +627,336 @@ async function pollForDeviceToken(input) {
|
|
|
489
627
|
printLine(input.stderr, "Device code expired before authorization completed.");
|
|
490
628
|
throw new CLIError("Device code expired");
|
|
491
629
|
}
|
|
492
|
-
async function
|
|
493
|
-
|
|
494
|
-
|
|
630
|
+
async function withPrompts(runtime, run) {
|
|
631
|
+
if (runtime.prompts) {
|
|
632
|
+
return run(runtime.prompts);
|
|
633
|
+
}
|
|
634
|
+
if (runtime.stdin.isTTY === false) {
|
|
635
|
+
throw new CLIError("`corners init` requires an interactive terminal.");
|
|
636
|
+
}
|
|
637
|
+
const prompts = createPromptApi(runtime.stdin, runtime.stderr);
|
|
638
|
+
try {
|
|
639
|
+
return await run(prompts);
|
|
640
|
+
}
|
|
641
|
+
finally {
|
|
642
|
+
await prompts.close();
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
async function readOptionalUtf8(path) {
|
|
646
|
+
try {
|
|
647
|
+
return await readFile(path, "utf8");
|
|
648
|
+
}
|
|
649
|
+
catch (error) {
|
|
650
|
+
if (error.code === "ENOENT") {
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
throw error;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
function ensureTrailingNewline(value) {
|
|
657
|
+
return value.endsWith("\n") ? value : `${value}\n`;
|
|
658
|
+
}
|
|
659
|
+
function normalizeCornerLookupValue(value) {
|
|
660
|
+
const normalized = value.trim();
|
|
661
|
+
return normalized.startsWith("#") ? normalized.slice(1) : normalized;
|
|
662
|
+
}
|
|
663
|
+
function normalizeStoredRelativePath(rootDir, targetPath) {
|
|
664
|
+
const relativePath = relative(rootDir, targetPath);
|
|
665
|
+
if (!relativePath) {
|
|
666
|
+
return ".";
|
|
667
|
+
}
|
|
668
|
+
if (relativePath === ".." ||
|
|
669
|
+
relativePath.startsWith(`..${sep}`) ||
|
|
670
|
+
relativePath.includes(`${sep}..${sep}`)) {
|
|
671
|
+
throw new CLIError("The target path must stay within the initialized root.");
|
|
672
|
+
}
|
|
673
|
+
return relativePath.split(sep).join("/");
|
|
674
|
+
}
|
|
675
|
+
function resolvePathWithinRoot(rootDir, cwd, inputPath) {
|
|
676
|
+
const targetPath = inputPath ? resolve(cwd, inputPath) : resolve(cwd);
|
|
677
|
+
normalizeStoredRelativePath(rootDir, targetPath);
|
|
678
|
+
return targetPath;
|
|
679
|
+
}
|
|
680
|
+
function resolveCornerForRelativePath(config, relativePath) {
|
|
681
|
+
let selected = config.defaultCorner;
|
|
682
|
+
let selectedLength = 0;
|
|
683
|
+
for (const override of config.cornerOverrides) {
|
|
684
|
+
const matches = relativePath === override.path ||
|
|
685
|
+
relativePath.startsWith(`${override.path}/`);
|
|
686
|
+
if (!matches) {
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
if (override.path.length > selectedLength) {
|
|
690
|
+
selected = {
|
|
691
|
+
id: override.cornerId,
|
|
692
|
+
name: override.cornerName,
|
|
693
|
+
};
|
|
694
|
+
selectedLength = override.path.length;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return selected;
|
|
698
|
+
}
|
|
699
|
+
function resolveCornerForCwd(root, cwd) {
|
|
700
|
+
const absolutePath = resolvePathWithinRoot(root.rootDir, cwd);
|
|
701
|
+
const relativePath = normalizeStoredRelativePath(root.rootDir, absolutePath);
|
|
702
|
+
return {
|
|
703
|
+
relativePath,
|
|
704
|
+
corner: resolveCornerForRelativePath(root.config, relativePath),
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
async function resolveGuidanceState(root) {
|
|
708
|
+
const expectedBlock = buildExpectedManagedGuidanceBlock();
|
|
709
|
+
const fileEntries = await Promise.all(GUIDANCE_TARGETS.map(async (target) => ({
|
|
710
|
+
target,
|
|
711
|
+
content: await readOptionalUtf8(join(root.rootDir, target.relativePath)),
|
|
712
|
+
})));
|
|
713
|
+
const discoveredManagedTargets = GUIDANCE_TARGETS.map((target) => target.key)
|
|
714
|
+
.filter((key) => {
|
|
715
|
+
const entry = fileEntries.find((candidate) => candidate.target.key === key);
|
|
716
|
+
return extractManagedGuidanceBlock(entry?.content ?? null) !== null;
|
|
717
|
+
});
|
|
718
|
+
const configuredManagedTargets = root.config.guidance?.managedTargets;
|
|
719
|
+
const managedTargets = configuredManagedTargets !== undefined
|
|
720
|
+
? normalizeGuidanceTargets(configuredManagedTargets)
|
|
721
|
+
: discoveredManagedTargets;
|
|
722
|
+
const files = fileEntries.map(({ target, content }) => {
|
|
723
|
+
if (content === null) {
|
|
724
|
+
return {
|
|
725
|
+
target: target.key,
|
|
726
|
+
path: target.relativePath,
|
|
727
|
+
state: "missing",
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
if (!managedTargets.includes(target.key)) {
|
|
731
|
+
return {
|
|
732
|
+
target: target.key,
|
|
733
|
+
path: target.relativePath,
|
|
734
|
+
state: "unmanaged",
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
return {
|
|
738
|
+
target: target.key,
|
|
739
|
+
path: target.relativePath,
|
|
740
|
+
state: extractManagedGuidanceBlock(content) === expectedBlock
|
|
741
|
+
? "current"
|
|
742
|
+
: "stale",
|
|
743
|
+
};
|
|
744
|
+
});
|
|
745
|
+
const staleFiles = files
|
|
746
|
+
.filter((file) => file.state === "stale")
|
|
747
|
+
.map((file) => file.path);
|
|
748
|
+
return {
|
|
749
|
+
stale: staleFiles.length > 0,
|
|
750
|
+
lastSyncedAt: root.config.guidance?.syncedAt ?? null,
|
|
751
|
+
managedTargets,
|
|
752
|
+
staleFiles,
|
|
753
|
+
files,
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
function buildGuidanceWarning(guidance) {
|
|
757
|
+
if (!guidance.stale) {
|
|
758
|
+
return null;
|
|
759
|
+
}
|
|
760
|
+
const syncedSuffix = guidance.lastSyncedAt
|
|
761
|
+
? ` Last synced: ${guidance.lastSyncedAt}.`
|
|
762
|
+
: "";
|
|
763
|
+
return `Warning: managed Corners guidance is out of date in ${guidance.staleFiles.length} file(s).${syncedSuffix} Run \`${GUIDANCE_SYNC_COMMAND}\`.`;
|
|
764
|
+
}
|
|
765
|
+
function buildGuidanceJsonMetadata(guidance) {
|
|
766
|
+
return {
|
|
767
|
+
stale: guidance.stale,
|
|
768
|
+
lastSyncedAt: guidance.lastSyncedAt,
|
|
769
|
+
staleFiles: guidance.staleFiles,
|
|
770
|
+
syncCommand: GUIDANCE_SYNC_COMMAND,
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
function withGuidanceJsonMetadata(payload, guidance) {
|
|
774
|
+
if (!guidance) {
|
|
775
|
+
return payload;
|
|
776
|
+
}
|
|
777
|
+
return {
|
|
778
|
+
...payload,
|
|
779
|
+
_corners: {
|
|
780
|
+
guidance: buildGuidanceJsonMetadata(guidance),
|
|
781
|
+
},
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
async function resolveRootContext(runtime, common, root, options) {
|
|
785
|
+
const guidance = await resolveGuidanceState(root);
|
|
786
|
+
if ((options?.emitWarning ?? true) && !common.json) {
|
|
787
|
+
const warning = buildGuidanceWarning(guidance);
|
|
788
|
+
if (warning) {
|
|
789
|
+
printLine(runtime.stderr, warning);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return {
|
|
793
|
+
root,
|
|
794
|
+
guidance,
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
async function requireLocalRoot(runtime, common, options) {
|
|
798
|
+
const root = await runtime.config.findLocalRoot(runtime.cwd);
|
|
799
|
+
if (!root) {
|
|
800
|
+
throw new CLIError("No local Corners CLI root found. Run `corners init` from your project root first.", { json: common.json });
|
|
801
|
+
}
|
|
802
|
+
return resolveRootContext(runtime, common, root, options);
|
|
803
|
+
}
|
|
804
|
+
async function requirePinnedRootProfile(runtime, common, rootContext) {
|
|
805
|
+
const { root, guidance } = rootContext;
|
|
806
|
+
if (common.profile && common.profile !== root.config.profile) {
|
|
807
|
+
throw new CLIError(`This initialized root is pinned to profile ${root.config.profile}.`, { json: common.json });
|
|
808
|
+
}
|
|
809
|
+
const selected = await runtime.config.getProfile(root.config.profile);
|
|
810
|
+
if (!selected) {
|
|
811
|
+
throw new CLIError(`The pinned profile ${root.config.profile} is not available. Run \`corners auth login --profile ${root.config.profile}\` first.`, { json: common.json });
|
|
812
|
+
}
|
|
813
|
+
const pinnedApiUrl = normalizeApiUrl(root.config.apiUrl);
|
|
814
|
+
const requestedApiUrl = common.apiUrl ? normalizeApiUrl(common.apiUrl) : null;
|
|
815
|
+
if (requestedApiUrl && requestedApiUrl !== pinnedApiUrl) {
|
|
816
|
+
throw new CLIError(`This initialized root is pinned to API ${pinnedApiUrl}.`, { json: common.json });
|
|
817
|
+
}
|
|
818
|
+
if (root.config.workspace &&
|
|
819
|
+
selected.profile.workspace &&
|
|
820
|
+
root.config.workspace !== selected.profile.workspace) {
|
|
821
|
+
throw new CLIError(`The pinned profile ${root.config.profile} is now on workspace ${selected.profile.workspace}. Re-run \`corners init\` to refresh this root.`, { json: common.json });
|
|
822
|
+
}
|
|
823
|
+
const profile = {
|
|
824
|
+
...selected.profile,
|
|
825
|
+
apiUrl: pinnedApiUrl,
|
|
826
|
+
};
|
|
827
|
+
return {
|
|
828
|
+
root,
|
|
829
|
+
guidance,
|
|
830
|
+
profileName: selected.name,
|
|
831
|
+
profile,
|
|
832
|
+
client: runtime.createClient({
|
|
833
|
+
apiUrl: profile.apiUrl,
|
|
834
|
+
accessToken: profile.accessToken,
|
|
835
|
+
}),
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
async function requireCommandProfile(runtime, common) {
|
|
839
|
+
const root = await runtime.config.findLocalRoot(runtime.cwd);
|
|
840
|
+
if (root) {
|
|
841
|
+
const rootContext = await resolveRootContext(runtime, common, root);
|
|
842
|
+
return requirePinnedRootProfile(runtime, common, rootContext);
|
|
843
|
+
}
|
|
844
|
+
const selected = await requireStoredProfile(runtime, common);
|
|
845
|
+
return {
|
|
846
|
+
root: null,
|
|
847
|
+
guidance: null,
|
|
848
|
+
...selected,
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
async function listMemberCorners(client) {
|
|
852
|
+
const data = await client.graphql(LIST_MEMBER_CORNERS_QUERY);
|
|
853
|
+
return data.memberCorners.edges.map((edge) => edge.node);
|
|
854
|
+
}
|
|
855
|
+
function findMemberCorner(corners, value) {
|
|
856
|
+
const normalized = normalizeCornerLookupValue(value);
|
|
857
|
+
return (corners.find((corner) => corner.id === normalized) ??
|
|
858
|
+
corners.find((corner) => corner.name.toLowerCase() === normalized.toLowerCase()) ??
|
|
859
|
+
null);
|
|
860
|
+
}
|
|
861
|
+
async function resolveMemberCorner(client, common, value) {
|
|
862
|
+
const corners = await listMemberCorners(client);
|
|
863
|
+
const corner = findMemberCorner(corners, value);
|
|
864
|
+
if (!corner) {
|
|
865
|
+
throw new CLIError("Corner not found in your joined corners.", {
|
|
866
|
+
json: common.json,
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
return corner;
|
|
870
|
+
}
|
|
871
|
+
async function resolveExplicitWorkstream(client, common, workstreamId) {
|
|
495
872
|
const data = await client.graphql(WORKSTREAM_LOOKUP_QUERY, { id: workstreamId });
|
|
496
873
|
if (!data.workstream) {
|
|
497
874
|
throw new CLIError("Workstream not found", { json: common.json });
|
|
498
875
|
}
|
|
499
876
|
return data.workstream;
|
|
500
877
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
throw new CLIError("This directory is not bound to a workstream. Run `corners workstream use <workstreamId>` first.");
|
|
878
|
+
function connectWorkstream(config, workstreamId) {
|
|
879
|
+
if (config.connectedWorkstreams.some((entry) => entry.id === workstreamId)) {
|
|
880
|
+
return config;
|
|
505
881
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
882
|
+
return {
|
|
883
|
+
...config,
|
|
884
|
+
connectedWorkstreams: [
|
|
885
|
+
...config.connectedWorkstreams,
|
|
886
|
+
{
|
|
887
|
+
id: workstreamId,
|
|
888
|
+
connectedAt: toIsoString(),
|
|
889
|
+
},
|
|
890
|
+
],
|
|
891
|
+
};
|
|
510
892
|
}
|
|
511
|
-
function
|
|
512
|
-
if (
|
|
893
|
+
function upsertCornerOverride(config, relativePath, corner) {
|
|
894
|
+
if (relativePath === ".") {
|
|
513
895
|
return {
|
|
514
|
-
|
|
515
|
-
|
|
896
|
+
...config,
|
|
897
|
+
defaultCorner: corner,
|
|
516
898
|
};
|
|
517
899
|
}
|
|
900
|
+
const overrides = config.cornerOverrides.filter((entry) => entry.path !== relativePath);
|
|
901
|
+
overrides.push({
|
|
902
|
+
path: relativePath,
|
|
903
|
+
cornerId: corner.id,
|
|
904
|
+
cornerName: corner.name,
|
|
905
|
+
updatedAt: toIsoString(),
|
|
906
|
+
});
|
|
907
|
+
overrides.sort((left, right) => left.path.localeCompare(right.path));
|
|
518
908
|
return {
|
|
519
|
-
|
|
909
|
+
...config,
|
|
910
|
+
cornerOverrides: overrides,
|
|
520
911
|
};
|
|
521
912
|
}
|
|
913
|
+
async function buildPlannedEdit(path, nextContent) {
|
|
914
|
+
const existing = await readOptionalUtf8(path);
|
|
915
|
+
if (existing === nextContent) {
|
|
916
|
+
return null;
|
|
917
|
+
}
|
|
918
|
+
return {
|
|
919
|
+
path,
|
|
920
|
+
action: existing === null ? "create" : "update",
|
|
921
|
+
content: nextContent,
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
function withUpdatedGuidanceConfig(config, managedTargets, syncedAt) {
|
|
925
|
+
return {
|
|
926
|
+
...config,
|
|
927
|
+
guidance: {
|
|
928
|
+
managedTargets: normalizeGuidanceTargets(managedTargets),
|
|
929
|
+
syncedAt,
|
|
930
|
+
revision: buildGuidanceRevision(),
|
|
931
|
+
},
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
function ensureGitignoreEntry(existing) {
|
|
935
|
+
const content = existing ?? "";
|
|
936
|
+
const lines = content
|
|
937
|
+
.split(/\r?\n/)
|
|
938
|
+
.map((line) => line.trim())
|
|
939
|
+
.filter(Boolean);
|
|
940
|
+
if (lines.includes(".corners") ||
|
|
941
|
+
lines.includes(".corners/") ||
|
|
942
|
+
lines.includes(LOCAL_ROOT_CONFIG_RELATIVE_PATH)) {
|
|
943
|
+
return ensureTrailingNewline(content);
|
|
944
|
+
}
|
|
945
|
+
const trimmed = content.trimEnd();
|
|
946
|
+
if (!trimmed) {
|
|
947
|
+
return `${LOCAL_ROOT_CONFIG_RELATIVE_PATH}\n`;
|
|
948
|
+
}
|
|
949
|
+
return `${trimmed}\n${LOCAL_ROOT_CONFIG_RELATIVE_PATH}\n`;
|
|
950
|
+
}
|
|
951
|
+
async function writePlannedEdits(edits) {
|
|
952
|
+
for (const edit of edits) {
|
|
953
|
+
await mkdir(dirname(edit.path), { recursive: true });
|
|
954
|
+
await writeFile(edit.path, edit.content, "utf8");
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
function formatWorkstreamLine(workstream) {
|
|
958
|
+
return `${workstream.id} ${workstream.name} ${workstream.status.toLowerCase()}${workstream.summary ? ` ${workstream.summary}` : ""}`;
|
|
959
|
+
}
|
|
522
960
|
async function handleAuth(args, runtime, inherited) {
|
|
523
961
|
const subcommand = args[0];
|
|
524
962
|
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
@@ -579,15 +1017,32 @@ async function handleAuth(args, runtime, inherited) {
|
|
|
579
1017
|
if (!token.access_token) {
|
|
580
1018
|
throw new CLIError("Login completed without an access token");
|
|
581
1019
|
}
|
|
582
|
-
|
|
1020
|
+
let profile = {
|
|
583
1021
|
apiUrl: client.apiUrl,
|
|
584
1022
|
accessToken: token.access_token,
|
|
585
1023
|
sessionId: token.session_id ?? null,
|
|
586
1024
|
workspace: token.workspace ?? null,
|
|
587
1025
|
accountId: token.account_id ?? null,
|
|
588
1026
|
userId: token.user_id ?? null,
|
|
1027
|
+
handle: null,
|
|
589
1028
|
createdAt: toIsoString(),
|
|
590
1029
|
};
|
|
1030
|
+
try {
|
|
1031
|
+
const identity = await fetchCurrentIdentity(runtime.createClient({
|
|
1032
|
+
apiUrl: profile.apiUrl,
|
|
1033
|
+
accessToken: profile.accessToken,
|
|
1034
|
+
}));
|
|
1035
|
+
profile = {
|
|
1036
|
+
...profile,
|
|
1037
|
+
workspace: identity.workspace,
|
|
1038
|
+
accountId: identity.accountId,
|
|
1039
|
+
userId: identity.userId,
|
|
1040
|
+
handle: identity.handle,
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
catch {
|
|
1044
|
+
// Best-effort profile enrichment. Authentication already succeeded.
|
|
1045
|
+
}
|
|
591
1046
|
await runtime.config.saveProfile(profileName, profile, {
|
|
592
1047
|
setActive: true,
|
|
593
1048
|
});
|
|
@@ -598,6 +1053,7 @@ async function handleAuth(args, runtime, inherited) {
|
|
|
598
1053
|
apiUrl: profile.apiUrl,
|
|
599
1054
|
sessionId: profile.sessionId,
|
|
600
1055
|
workspace: profile.workspace,
|
|
1056
|
+
handle: profile.handle,
|
|
601
1057
|
accountId: profile.accountId,
|
|
602
1058
|
userId: profile.userId,
|
|
603
1059
|
});
|
|
@@ -693,12 +1149,8 @@ async function handleAuth(args, runtime, inherited) {
|
|
|
693
1149
|
printAuthHelp(runtime);
|
|
694
1150
|
return 0;
|
|
695
1151
|
}
|
|
696
|
-
const
|
|
697
|
-
if (!
|
|
698
|
-
const status = {
|
|
699
|
-
loggedIn: false,
|
|
700
|
-
profile: common.profile ?? null,
|
|
701
|
-
};
|
|
1152
|
+
const status = await resolveAuthStatus(runtime, common);
|
|
1153
|
+
if (!status.loggedIn) {
|
|
702
1154
|
if (common.json) {
|
|
703
1155
|
printJson(runtime.stdout, status);
|
|
704
1156
|
}
|
|
@@ -707,31 +1159,6 @@ async function handleAuth(args, runtime, inherited) {
|
|
|
707
1159
|
}
|
|
708
1160
|
return 0;
|
|
709
1161
|
}
|
|
710
|
-
const client = runtime.createClient({
|
|
711
|
-
apiUrl: common.apiUrl ?? selected.profile.apiUrl,
|
|
712
|
-
accessToken: selected.profile.accessToken,
|
|
713
|
-
});
|
|
714
|
-
let remote;
|
|
715
|
-
try {
|
|
716
|
-
remote = await client.validateSession();
|
|
717
|
-
}
|
|
718
|
-
catch (error) {
|
|
719
|
-
remote = {
|
|
720
|
-
valid: false,
|
|
721
|
-
error: error.message,
|
|
722
|
-
};
|
|
723
|
-
}
|
|
724
|
-
const status = {
|
|
725
|
-
loggedIn: true,
|
|
726
|
-
profile: selected.name,
|
|
727
|
-
apiUrl: common.apiUrl ?? selected.profile.apiUrl,
|
|
728
|
-
sessionId: selected.profile.sessionId,
|
|
729
|
-
workspace: selected.profile.workspace,
|
|
730
|
-
accountId: selected.profile.accountId,
|
|
731
|
-
userId: selected.profile.userId,
|
|
732
|
-
remoteValid: remote.valid,
|
|
733
|
-
remoteError: remote.valid ? null : (remote.error ?? null),
|
|
734
|
-
};
|
|
735
1162
|
if (common.json) {
|
|
736
1163
|
printJson(runtime.stdout, status);
|
|
737
1164
|
}
|
|
@@ -740,6 +1167,7 @@ async function handleAuth(args, runtime, inherited) {
|
|
|
740
1167
|
`Profile: ${status.profile}`,
|
|
741
1168
|
`API URL: ${status.apiUrl}`,
|
|
742
1169
|
`Workspace: ${status.workspace ?? "-"}`,
|
|
1170
|
+
`Handle: ${status.handle ? `@${status.handle}` : "-"}`,
|
|
743
1171
|
`Account: ${status.accountId ?? "-"}`,
|
|
744
1172
|
`User: ${status.userId ?? "-"}`,
|
|
745
1173
|
`Session: ${status.sessionId ?? "-"}`,
|
|
@@ -752,6 +1180,359 @@ async function handleAuth(args, runtime, inherited) {
|
|
|
752
1180
|
throw new CLIError(`Unknown auth command: ${subcommand}`);
|
|
753
1181
|
}
|
|
754
1182
|
}
|
|
1183
|
+
async function handleWhoAmI(args, runtime, inherited) {
|
|
1184
|
+
const parsed = parseArgs({
|
|
1185
|
+
args,
|
|
1186
|
+
allowPositionals: false,
|
|
1187
|
+
options: {
|
|
1188
|
+
json: { type: "boolean" },
|
|
1189
|
+
help: { type: "boolean", short: "h" },
|
|
1190
|
+
profile: { type: "string" },
|
|
1191
|
+
"api-url": { type: "string" },
|
|
1192
|
+
},
|
|
1193
|
+
});
|
|
1194
|
+
const common = mergeCommonOptions(inherited, {
|
|
1195
|
+
json: parsed.values.json,
|
|
1196
|
+
profile: parsed.values.profile,
|
|
1197
|
+
apiUrl: parsed.values["api-url"],
|
|
1198
|
+
});
|
|
1199
|
+
if (parsed.values.help) {
|
|
1200
|
+
printWhoamiHelp(runtime);
|
|
1201
|
+
return 0;
|
|
1202
|
+
}
|
|
1203
|
+
const status = await resolveAuthStatus(runtime, common);
|
|
1204
|
+
if (!status.loggedIn) {
|
|
1205
|
+
if (common.json) {
|
|
1206
|
+
printJson(runtime.stdout, status);
|
|
1207
|
+
}
|
|
1208
|
+
else {
|
|
1209
|
+
printLine(runtime.stdout, "Not logged in.");
|
|
1210
|
+
}
|
|
1211
|
+
return 0;
|
|
1212
|
+
}
|
|
1213
|
+
if (common.json) {
|
|
1214
|
+
printJson(runtime.stdout, status);
|
|
1215
|
+
}
|
|
1216
|
+
else {
|
|
1217
|
+
const identity = status.handle
|
|
1218
|
+
? `@${status.handle}`
|
|
1219
|
+
: (status.userId ?? status.profile);
|
|
1220
|
+
const workspaceSuffix = status.workspace ? ` (${status.workspace})` : "";
|
|
1221
|
+
printLine(runtime.stdout, `${identity}${workspaceSuffix}`);
|
|
1222
|
+
}
|
|
1223
|
+
return 0;
|
|
1224
|
+
}
|
|
1225
|
+
async function handleInit(args, runtime, inherited) {
|
|
1226
|
+
const parsed = parseArgs({
|
|
1227
|
+
args,
|
|
1228
|
+
allowPositionals: false,
|
|
1229
|
+
options: {
|
|
1230
|
+
json: { type: "boolean" },
|
|
1231
|
+
help: { type: "boolean", short: "h" },
|
|
1232
|
+
profile: { type: "string" },
|
|
1233
|
+
"api-url": { type: "string" },
|
|
1234
|
+
},
|
|
1235
|
+
});
|
|
1236
|
+
const common = mergeCommonOptions(inherited, {
|
|
1237
|
+
json: parsed.values.json,
|
|
1238
|
+
profile: parsed.values.profile,
|
|
1239
|
+
apiUrl: parsed.values["api-url"],
|
|
1240
|
+
});
|
|
1241
|
+
if (parsed.values.help) {
|
|
1242
|
+
printInitHelp(runtime);
|
|
1243
|
+
return 0;
|
|
1244
|
+
}
|
|
1245
|
+
const currentRootPath = await realpath(runtime.cwd);
|
|
1246
|
+
const existingRoot = await runtime.config.findLocalRoot(runtime.cwd);
|
|
1247
|
+
if (existingRoot && existingRoot.rootDir !== currentRootPath) {
|
|
1248
|
+
throw new CLIError(`This directory is already inside initialized root ${existingRoot.rootDir}. Run \`corners init\` there.`, { json: common.json });
|
|
1249
|
+
}
|
|
1250
|
+
const { profileName, profile, client } = await requireStoredProfile(runtime, common);
|
|
1251
|
+
const joinedCorners = await listMemberCorners(client);
|
|
1252
|
+
if (joinedCorners.length === 0) {
|
|
1253
|
+
throw new CLIError("No joined corners are available for this profile.", {
|
|
1254
|
+
json: common.json,
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
const rootDir = existingRoot?.rootDir ?? currentRootPath;
|
|
1258
|
+
const existingConfig = existingRoot?.config ?? null;
|
|
1259
|
+
return withPrompts(runtime, async (prompts) => {
|
|
1260
|
+
const selectedTargets = [];
|
|
1261
|
+
for (const target of GUIDANCE_TARGETS) {
|
|
1262
|
+
const include = await prompts.confirm(target.label, {
|
|
1263
|
+
defaultValue: true,
|
|
1264
|
+
});
|
|
1265
|
+
if (include) {
|
|
1266
|
+
selectedTargets.push(target);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
const selectedCornerId = await prompts.select("Choose the default corner for this local root.", joinedCorners.map((corner) => ({
|
|
1270
|
+
label: `${corner.name} (${corner.id})`,
|
|
1271
|
+
value: corner.id,
|
|
1272
|
+
})), {
|
|
1273
|
+
defaultValue: existingConfig?.defaultCorner.id ?? joinedCorners[0]?.id,
|
|
1274
|
+
});
|
|
1275
|
+
const defaultCorner = joinedCorners.find((corner) => corner.id === selectedCornerId) ??
|
|
1276
|
+
joinedCorners[0];
|
|
1277
|
+
const syncedAt = toIsoString();
|
|
1278
|
+
const nextLocalConfig = withUpdatedGuidanceConfig({
|
|
1279
|
+
version: 1,
|
|
1280
|
+
profile: profileName,
|
|
1281
|
+
workspace: profile.workspace,
|
|
1282
|
+
apiUrl: profile.apiUrl,
|
|
1283
|
+
defaultCorner: {
|
|
1284
|
+
id: defaultCorner.id,
|
|
1285
|
+
name: defaultCorner.name,
|
|
1286
|
+
},
|
|
1287
|
+
cornerOverrides: existingConfig?.cornerOverrides ?? [],
|
|
1288
|
+
connectedWorkstreams: existingConfig?.connectedWorkstreams ?? [],
|
|
1289
|
+
}, selectedTargets.map((target) => target.key), syncedAt);
|
|
1290
|
+
const plannedEdits = [];
|
|
1291
|
+
const gitignorePath = join(rootDir, ".gitignore");
|
|
1292
|
+
const gitignoreContent = ensureGitignoreEntry(await readOptionalUtf8(gitignorePath));
|
|
1293
|
+
const gitignoreEdit = await buildPlannedEdit(gitignorePath, gitignoreContent);
|
|
1294
|
+
if (gitignoreEdit) {
|
|
1295
|
+
plannedEdits.push(gitignoreEdit);
|
|
1296
|
+
}
|
|
1297
|
+
const localConfigPath = runtime.config.getLocalConfigPath(rootDir);
|
|
1298
|
+
const localConfigEdit = await buildPlannedEdit(localConfigPath, `${JSON.stringify(nextLocalConfig, null, 2)}\n`);
|
|
1299
|
+
if (localConfigEdit) {
|
|
1300
|
+
plannedEdits.push(localConfigEdit);
|
|
1301
|
+
}
|
|
1302
|
+
for (const target of selectedTargets) {
|
|
1303
|
+
const targetPath = join(rootDir, target.relativePath);
|
|
1304
|
+
const nextContent = buildGuidanceFileContent(target.key, await readOptionalUtf8(targetPath));
|
|
1305
|
+
const edit = await buildPlannedEdit(targetPath, nextContent);
|
|
1306
|
+
if (edit) {
|
|
1307
|
+
plannedEdits.push(edit);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
if (plannedEdits.length > 0 && !common.json) {
|
|
1311
|
+
printLine(runtime.stderr, [
|
|
1312
|
+
"Planned changes:",
|
|
1313
|
+
...plannedEdits.map((edit) => ` ${edit.action} ${relative(rootDir, edit.path) || "."}`),
|
|
1314
|
+
].join("\n"));
|
|
1315
|
+
}
|
|
1316
|
+
if (plannedEdits.length > 0) {
|
|
1317
|
+
const confirmed = await prompts.confirm("Proceed with these changes?", {
|
|
1318
|
+
defaultValue: true,
|
|
1319
|
+
});
|
|
1320
|
+
if (!confirmed) {
|
|
1321
|
+
if (common.json) {
|
|
1322
|
+
printJson(runtime.stdout, {
|
|
1323
|
+
ok: false,
|
|
1324
|
+
root: rootDir,
|
|
1325
|
+
aborted: true,
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
else {
|
|
1329
|
+
printLine(runtime.stdout, "Aborted. No files were changed.");
|
|
1330
|
+
}
|
|
1331
|
+
return 0;
|
|
1332
|
+
}
|
|
1333
|
+
await writePlannedEdits(plannedEdits);
|
|
1334
|
+
}
|
|
1335
|
+
const payload = {
|
|
1336
|
+
ok: true,
|
|
1337
|
+
root: rootDir,
|
|
1338
|
+
profile: profileName,
|
|
1339
|
+
workspace: profile.workspace,
|
|
1340
|
+
defaultCorner,
|
|
1341
|
+
files: plannedEdits.map((edit) => ({
|
|
1342
|
+
action: edit.action,
|
|
1343
|
+
path: relative(rootDir, edit.path) || ".",
|
|
1344
|
+
})),
|
|
1345
|
+
};
|
|
1346
|
+
if (common.json) {
|
|
1347
|
+
printJson(runtime.stdout, payload);
|
|
1348
|
+
}
|
|
1349
|
+
else {
|
|
1350
|
+
printLine(runtime.stdout, `Initialized Corners CLI at ${rootDir} with default corner ${defaultCorner.name}. Run \`${GUIDANCE_SYNC_COMMAND}\` later to refresh managed guidance.`);
|
|
1351
|
+
}
|
|
1352
|
+
return 0;
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
async function handleGuidance(args, runtime, inherited) {
|
|
1356
|
+
const subcommand = args[0];
|
|
1357
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
1358
|
+
printGuidanceHelp(runtime);
|
|
1359
|
+
return 0;
|
|
1360
|
+
}
|
|
1361
|
+
switch (subcommand) {
|
|
1362
|
+
case "status": {
|
|
1363
|
+
const parsed = parseArgs({
|
|
1364
|
+
args: args.slice(1),
|
|
1365
|
+
allowPositionals: false,
|
|
1366
|
+
options: {
|
|
1367
|
+
json: { type: "boolean" },
|
|
1368
|
+
help: { type: "boolean", short: "h" },
|
|
1369
|
+
},
|
|
1370
|
+
});
|
|
1371
|
+
const common = mergeCommonOptions(inherited, {
|
|
1372
|
+
json: parsed.values.json,
|
|
1373
|
+
});
|
|
1374
|
+
if (parsed.values.help) {
|
|
1375
|
+
printGuidanceHelp(runtime);
|
|
1376
|
+
return 0;
|
|
1377
|
+
}
|
|
1378
|
+
const rootContext = await requireLocalRoot(runtime, common, {
|
|
1379
|
+
emitWarning: false,
|
|
1380
|
+
});
|
|
1381
|
+
const payload = {
|
|
1382
|
+
ok: true,
|
|
1383
|
+
root: rootContext.root.rootDir,
|
|
1384
|
+
guidance: rootContext.guidance,
|
|
1385
|
+
};
|
|
1386
|
+
if (common.json) {
|
|
1387
|
+
printJson(runtime.stdout, payload);
|
|
1388
|
+
}
|
|
1389
|
+
else {
|
|
1390
|
+
printLine(runtime.stdout, [
|
|
1391
|
+
`Root: ${payload.root}`,
|
|
1392
|
+
`Stale: ${payload.guidance.stale ? "yes" : "no"}`,
|
|
1393
|
+
`Last synced: ${payload.guidance.lastSyncedAt ?? "-"}`,
|
|
1394
|
+
`Managed targets: ${payload.guidance.managedTargets.join(", ") || "-"}`,
|
|
1395
|
+
...payload.guidance.files.map((file) => `${file.path}: ${file.state}`),
|
|
1396
|
+
].join("\n"));
|
|
1397
|
+
}
|
|
1398
|
+
return 0;
|
|
1399
|
+
}
|
|
1400
|
+
case "sync": {
|
|
1401
|
+
const parsed = parseArgs({
|
|
1402
|
+
args: args.slice(1),
|
|
1403
|
+
allowPositionals: false,
|
|
1404
|
+
options: {
|
|
1405
|
+
json: { type: "boolean" },
|
|
1406
|
+
help: { type: "boolean", short: "h" },
|
|
1407
|
+
},
|
|
1408
|
+
});
|
|
1409
|
+
const common = mergeCommonOptions(inherited, {
|
|
1410
|
+
json: parsed.values.json,
|
|
1411
|
+
});
|
|
1412
|
+
if (parsed.values.help) {
|
|
1413
|
+
printGuidanceHelp(runtime);
|
|
1414
|
+
return 0;
|
|
1415
|
+
}
|
|
1416
|
+
const rootContext = await requireLocalRoot(runtime, common, {
|
|
1417
|
+
emitWarning: false,
|
|
1418
|
+
});
|
|
1419
|
+
const managedTargets = rootContext.root.config.guidance?.managedTargets !== undefined
|
|
1420
|
+
? normalizeGuidanceTargets(rootContext.root.config.guidance.managedTargets)
|
|
1421
|
+
: rootContext.guidance.managedTargets;
|
|
1422
|
+
const edits = [];
|
|
1423
|
+
const actions = new Map();
|
|
1424
|
+
for (const target of GUIDANCE_TARGETS) {
|
|
1425
|
+
if (!managedTargets.includes(target.key)) {
|
|
1426
|
+
actions.set(target.key, "skipped");
|
|
1427
|
+
continue;
|
|
1428
|
+
}
|
|
1429
|
+
const targetPath = join(rootContext.root.rootDir, target.relativePath);
|
|
1430
|
+
const nextContent = buildGuidanceFileContent(target.key, await readOptionalUtf8(targetPath));
|
|
1431
|
+
const edit = await buildPlannedEdit(targetPath, nextContent);
|
|
1432
|
+
if (edit) {
|
|
1433
|
+
edits.push(edit);
|
|
1434
|
+
actions.set(target.key, edit.action);
|
|
1435
|
+
}
|
|
1436
|
+
else {
|
|
1437
|
+
actions.set(target.key, "unchanged");
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
if (edits.length > 0) {
|
|
1441
|
+
await writePlannedEdits(edits);
|
|
1442
|
+
}
|
|
1443
|
+
const syncedAt = toIsoString();
|
|
1444
|
+
const nextConfig = withUpdatedGuidanceConfig(rootContext.root.config, managedTargets, syncedAt);
|
|
1445
|
+
await runtime.config.writeLocalConfig(rootContext.root.rootDir, nextConfig);
|
|
1446
|
+
const nextRoot = {
|
|
1447
|
+
...rootContext.root,
|
|
1448
|
+
config: nextConfig,
|
|
1449
|
+
};
|
|
1450
|
+
const nextGuidance = await resolveGuidanceState(nextRoot);
|
|
1451
|
+
const guidance = {
|
|
1452
|
+
...nextGuidance,
|
|
1453
|
+
files: nextGuidance.files.map((file) => ({
|
|
1454
|
+
...file,
|
|
1455
|
+
action: actions.get(file.target) ?? "skipped",
|
|
1456
|
+
})),
|
|
1457
|
+
};
|
|
1458
|
+
const payload = {
|
|
1459
|
+
ok: true,
|
|
1460
|
+
root: nextRoot.rootDir,
|
|
1461
|
+
guidance,
|
|
1462
|
+
};
|
|
1463
|
+
if (common.json) {
|
|
1464
|
+
printJson(runtime.stdout, payload);
|
|
1465
|
+
}
|
|
1466
|
+
else {
|
|
1467
|
+
const updatedCount = guidance.files.filter((file) => file.action === "create" || file.action === "update").length;
|
|
1468
|
+
printLine(runtime.stdout, `Synchronized Corners guidance at ${payload.root}. Updated ${updatedCount} file(s).`);
|
|
1469
|
+
}
|
|
1470
|
+
return 0;
|
|
1471
|
+
}
|
|
1472
|
+
default:
|
|
1473
|
+
throw new CLIError(`Unknown guidance command: ${subcommand}`);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
async function handleCorner(args, runtime, inherited) {
|
|
1477
|
+
const subcommand = args[0];
|
|
1478
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
1479
|
+
printCornerHelp(runtime);
|
|
1480
|
+
return 0;
|
|
1481
|
+
}
|
|
1482
|
+
switch (subcommand) {
|
|
1483
|
+
case "use": {
|
|
1484
|
+
const parsed = parseArgs({
|
|
1485
|
+
args: args.slice(1),
|
|
1486
|
+
allowPositionals: true,
|
|
1487
|
+
options: {
|
|
1488
|
+
json: { type: "boolean" },
|
|
1489
|
+
help: { type: "boolean", short: "h" },
|
|
1490
|
+
profile: { type: "string" },
|
|
1491
|
+
"api-url": { type: "string" },
|
|
1492
|
+
path: { type: "string" },
|
|
1493
|
+
},
|
|
1494
|
+
});
|
|
1495
|
+
const common = mergeCommonOptions(inherited, {
|
|
1496
|
+
json: parsed.values.json,
|
|
1497
|
+
profile: parsed.values.profile,
|
|
1498
|
+
apiUrl: parsed.values["api-url"],
|
|
1499
|
+
});
|
|
1500
|
+
if (parsed.values.help) {
|
|
1501
|
+
printCornerHelp(runtime);
|
|
1502
|
+
return 0;
|
|
1503
|
+
}
|
|
1504
|
+
const cornerNameOrId = parsed.positionals[0];
|
|
1505
|
+
if (!cornerNameOrId) {
|
|
1506
|
+
throw new CLIError("Usage: corners corner use <cornerNameOrId> [--path <path>]", { json: common.json });
|
|
1507
|
+
}
|
|
1508
|
+
const rootContext = await requireLocalRoot(runtime, common);
|
|
1509
|
+
const { root, guidance, client } = await requirePinnedRootProfile(runtime, common, rootContext);
|
|
1510
|
+
const corner = await resolveMemberCorner(client, common, cornerNameOrId);
|
|
1511
|
+
const cwdPath = await realpath(runtime.cwd);
|
|
1512
|
+
const targetAbsolutePath = resolvePathWithinRoot(root.rootDir, cwdPath, parsed.values.path);
|
|
1513
|
+
const relativePath = normalizeStoredRelativePath(root.rootDir, targetAbsolutePath);
|
|
1514
|
+
const nextConfig = upsertCornerOverride(root.config, relativePath, {
|
|
1515
|
+
id: corner.id,
|
|
1516
|
+
name: corner.name,
|
|
1517
|
+
});
|
|
1518
|
+
await runtime.config.writeLocalConfig(root.rootDir, nextConfig);
|
|
1519
|
+
if (common.json) {
|
|
1520
|
+
printJson(runtime.stdout, withGuidanceJsonMetadata({
|
|
1521
|
+
ok: true,
|
|
1522
|
+
root: root.rootDir,
|
|
1523
|
+
path: relativePath,
|
|
1524
|
+
corner,
|
|
1525
|
+
}, guidance));
|
|
1526
|
+
}
|
|
1527
|
+
else {
|
|
1528
|
+
printLine(runtime.stdout, `Set default corner for ${relativePath} to ${corner.name}.`);
|
|
1529
|
+
}
|
|
1530
|
+
return 0;
|
|
1531
|
+
}
|
|
1532
|
+
default:
|
|
1533
|
+
throw new CLIError(`Unknown corner command: ${subcommand}`);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
755
1536
|
async function handleWorkstream(args, runtime, inherited) {
|
|
756
1537
|
const subcommand = args[0];
|
|
757
1538
|
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
@@ -779,16 +1560,44 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
779
1560
|
printWorkstreamHelp(runtime);
|
|
780
1561
|
return 0;
|
|
781
1562
|
}
|
|
782
|
-
const
|
|
783
|
-
const
|
|
784
|
-
const
|
|
1563
|
+
const rootContext = await requireLocalRoot(runtime, common);
|
|
1564
|
+
const { root, guidance, client } = await requirePinnedRootProfile(runtime, common, rootContext);
|
|
1565
|
+
const hydrated = await Promise.all(root.config.connectedWorkstreams.map(async (entry) => {
|
|
1566
|
+
try {
|
|
1567
|
+
return {
|
|
1568
|
+
entry,
|
|
1569
|
+
workstream: await resolveExplicitWorkstream(client, common, entry.id),
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
catch {
|
|
1573
|
+
return {
|
|
1574
|
+
entry,
|
|
1575
|
+
workstream: null,
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
}));
|
|
1579
|
+
const workstreams = hydrated
|
|
1580
|
+
.filter((entry) => entry.workstream !== null)
|
|
1581
|
+
.map((entry) => entry.workstream);
|
|
1582
|
+
const missingWorkstreamIds = hydrated
|
|
1583
|
+
.filter((entry) => entry.workstream === null)
|
|
1584
|
+
.map((entry) => entry.entry.id);
|
|
785
1585
|
if (common.json) {
|
|
786
|
-
printJson(runtime.stdout,
|
|
1586
|
+
printJson(runtime.stdout, withGuidanceJsonMetadata({
|
|
1587
|
+
root: root.rootDir,
|
|
1588
|
+
workstreams,
|
|
1589
|
+
missingWorkstreamIds,
|
|
1590
|
+
}, guidance));
|
|
787
1591
|
}
|
|
788
1592
|
else {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
1593
|
+
if (missingWorkstreamIds.length > 0) {
|
|
1594
|
+
printLine(runtime.stderr, missingWorkstreamIds
|
|
1595
|
+
.map((workstreamId) => `Warning: connected workstream ${workstreamId} is missing or inaccessible.`)
|
|
1596
|
+
.join("\n"));
|
|
1597
|
+
}
|
|
1598
|
+
printLine(runtime.stdout, workstreams.length > 0
|
|
1599
|
+
? workstreams.map(formatWorkstreamLine).join("\n")
|
|
1600
|
+
: "No workstreams are connected to this local environment.");
|
|
792
1601
|
}
|
|
793
1602
|
return 0;
|
|
794
1603
|
}
|
|
@@ -818,26 +1627,22 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
818
1627
|
json: common.json,
|
|
819
1628
|
});
|
|
820
1629
|
}
|
|
821
|
-
const
|
|
822
|
-
const
|
|
823
|
-
await
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
boundAt: toIsoString(),
|
|
829
|
-
});
|
|
1630
|
+
const rootContext = await requireLocalRoot(runtime, common);
|
|
1631
|
+
const { root, guidance, client } = await requirePinnedRootProfile(runtime, common, rootContext);
|
|
1632
|
+
const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
|
|
1633
|
+
const nextConfig = connectWorkstream(root.config, workstream.id);
|
|
1634
|
+
if (nextConfig !== root.config) {
|
|
1635
|
+
await runtime.config.writeLocalConfig(root.rootDir, nextConfig);
|
|
1636
|
+
}
|
|
830
1637
|
if (common.json) {
|
|
831
|
-
printJson(runtime.stdout, {
|
|
1638
|
+
printJson(runtime.stdout, withGuidanceJsonMetadata({
|
|
832
1639
|
ok: true,
|
|
833
|
-
|
|
834
|
-
profile: profileName,
|
|
835
|
-
workspace: profile.workspace,
|
|
1640
|
+
root: root.rootDir,
|
|
836
1641
|
workstream,
|
|
837
|
-
});
|
|
1642
|
+
}, guidance));
|
|
838
1643
|
}
|
|
839
1644
|
else {
|
|
840
|
-
printLine(runtime.stdout, `
|
|
1645
|
+
printLine(runtime.stdout, `Connected ${workstream.id} (${workstream.name}) to ${root.rootDir}.`);
|
|
841
1646
|
}
|
|
842
1647
|
return 0;
|
|
843
1648
|
}
|
|
@@ -857,33 +1662,88 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
857
1662
|
printWorkstreamHelp(runtime);
|
|
858
1663
|
return 0;
|
|
859
1664
|
}
|
|
860
|
-
const
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
1665
|
+
const { root, guidance } = await requireLocalRoot(runtime, common);
|
|
1666
|
+
const cwdPath = await realpath(runtime.cwd);
|
|
1667
|
+
const resolvedCorner = resolveCornerForCwd(root, cwdPath);
|
|
1668
|
+
const payload = {
|
|
1669
|
+
cwd: runtime.cwd,
|
|
1670
|
+
root: root.rootDir,
|
|
1671
|
+
profile: root.config.profile,
|
|
1672
|
+
workspace: root.config.workspace,
|
|
1673
|
+
resolvedCorner,
|
|
1674
|
+
connectedWorkstreamIds: root.config.connectedWorkstreams.map((entry) => entry.id),
|
|
1675
|
+
};
|
|
870
1676
|
if (common.json) {
|
|
871
|
-
printJson(runtime.stdout,
|
|
872
|
-
cwd: runtime.cwd,
|
|
873
|
-
binding,
|
|
874
|
-
});
|
|
1677
|
+
printJson(runtime.stdout, withGuidanceJsonMetadata(payload, guidance));
|
|
875
1678
|
}
|
|
876
1679
|
else {
|
|
877
1680
|
printLine(runtime.stdout, [
|
|
878
|
-
`
|
|
879
|
-
`
|
|
880
|
-
`
|
|
881
|
-
`
|
|
882
|
-
`
|
|
1681
|
+
`Root: ${payload.root}`,
|
|
1682
|
+
`Directory: ${payload.cwd}`,
|
|
1683
|
+
`Profile: ${payload.profile}`,
|
|
1684
|
+
`Workspace: ${payload.workspace ?? "-"}`,
|
|
1685
|
+
`Resolved corner: ${payload.resolvedCorner.corner.name} (${payload.resolvedCorner.corner.id})`,
|
|
1686
|
+
`Connected workstreams: ${payload.connectedWorkstreamIds.length}`,
|
|
883
1687
|
].join("\n"));
|
|
884
1688
|
}
|
|
885
1689
|
return 0;
|
|
886
1690
|
}
|
|
1691
|
+
case "create": {
|
|
1692
|
+
const parsed = parseArgs({
|
|
1693
|
+
args: args.slice(1),
|
|
1694
|
+
allowPositionals: true,
|
|
1695
|
+
options: {
|
|
1696
|
+
json: { type: "boolean" },
|
|
1697
|
+
help: { type: "boolean", short: "h" },
|
|
1698
|
+
profile: { type: "string" },
|
|
1699
|
+
"api-url": { type: "string" },
|
|
1700
|
+
summary: { type: "string" },
|
|
1701
|
+
corner: { type: "string" },
|
|
1702
|
+
},
|
|
1703
|
+
});
|
|
1704
|
+
const common = mergeCommonOptions(inherited, {
|
|
1705
|
+
json: parsed.values.json,
|
|
1706
|
+
profile: parsed.values.profile,
|
|
1707
|
+
apiUrl: parsed.values["api-url"],
|
|
1708
|
+
});
|
|
1709
|
+
if (parsed.values.help) {
|
|
1710
|
+
printWorkstreamHelp(runtime);
|
|
1711
|
+
return 0;
|
|
1712
|
+
}
|
|
1713
|
+
const name = parsed.positionals.join(" ").trim();
|
|
1714
|
+
if (!name) {
|
|
1715
|
+
throw new CLIError("Usage: corners workstream create <name> [--summary <text>] [--corner <cornerNameOrId>]", { json: common.json });
|
|
1716
|
+
}
|
|
1717
|
+
const rootContext = await requireLocalRoot(runtime, common);
|
|
1718
|
+
const { root, guidance, client } = await requirePinnedRootProfile(runtime, common, rootContext);
|
|
1719
|
+
const cwdPath = await realpath(runtime.cwd);
|
|
1720
|
+
const corner = parsed.values.corner
|
|
1721
|
+
? await resolveMemberCorner(client, common, parsed.values.corner)
|
|
1722
|
+
: resolveCornerForCwd(root, cwdPath).corner;
|
|
1723
|
+
const result = await client.graphql(CREATE_WORKSTREAM_MUTATION, {
|
|
1724
|
+
input: {
|
|
1725
|
+
cornerId: corner.id,
|
|
1726
|
+
name,
|
|
1727
|
+
summary: parsed.values.summary,
|
|
1728
|
+
},
|
|
1729
|
+
});
|
|
1730
|
+
const nextConfig = connectWorkstream(root.config, result.createWorkstream.id);
|
|
1731
|
+
if (nextConfig !== root.config) {
|
|
1732
|
+
await runtime.config.writeLocalConfig(root.rootDir, nextConfig);
|
|
1733
|
+
}
|
|
1734
|
+
if (common.json) {
|
|
1735
|
+
printJson(runtime.stdout, withGuidanceJsonMetadata({
|
|
1736
|
+
ok: true,
|
|
1737
|
+
root: root.rootDir,
|
|
1738
|
+
corner,
|
|
1739
|
+
workstream: result.createWorkstream,
|
|
1740
|
+
}, guidance));
|
|
1741
|
+
}
|
|
1742
|
+
else {
|
|
1743
|
+
printLine(runtime.stdout, `Created ${result.createWorkstream.id} (${result.createWorkstream.name}) in ${corner.name}.`);
|
|
1744
|
+
}
|
|
1745
|
+
return 0;
|
|
1746
|
+
}
|
|
887
1747
|
case "pull": {
|
|
888
1748
|
const parsed = parseArgs({
|
|
889
1749
|
args: args.slice(1),
|
|
@@ -904,16 +1764,21 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
904
1764
|
printWorkstreamHelp(runtime);
|
|
905
1765
|
return 0;
|
|
906
1766
|
}
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
1767
|
+
const workstreamId = parsed.positionals[0];
|
|
1768
|
+
if (!workstreamId) {
|
|
1769
|
+
throw new CLIError("Usage: corners workstream pull <workstreamId>", {
|
|
1770
|
+
json: common.json,
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
const { guidance, client } = await requireCommandProfile(runtime, common);
|
|
1774
|
+
const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
|
|
910
1775
|
const data = await client.graphql(WORKSTREAM_PULL_QUERY, {
|
|
911
1776
|
id: workstream.id,
|
|
912
1777
|
attachmentsFirst: 50,
|
|
913
1778
|
feedFirst: 20,
|
|
914
1779
|
});
|
|
915
1780
|
if (common.json) {
|
|
916
|
-
printJson(runtime.stdout, data.workstream);
|
|
1781
|
+
printJson(runtime.stdout, withGuidanceJsonMetadata(data.workstream, guidance));
|
|
917
1782
|
}
|
|
918
1783
|
else {
|
|
919
1784
|
const snapshot = data.workstream;
|
|
@@ -953,16 +1818,21 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
953
1818
|
printWorkstreamHelp(runtime);
|
|
954
1819
|
return 0;
|
|
955
1820
|
}
|
|
956
|
-
const
|
|
1821
|
+
const workstreamId = parsed.positionals[0];
|
|
1822
|
+
if (!workstreamId) {
|
|
1823
|
+
throw new CLIError("Usage: corners workstream push <workstreamId>", {
|
|
1824
|
+
json: common.json,
|
|
1825
|
+
});
|
|
1826
|
+
}
|
|
957
1827
|
const message = parsed.values.message ??
|
|
958
|
-
(
|
|
959
|
-
?
|
|
1828
|
+
(parsed.positionals.length > 1
|
|
1829
|
+
? parsed.positionals.slice(1).join(" ")
|
|
960
1830
|
: await readTextFromStdin(runtime.stdin));
|
|
961
1831
|
if (!message) {
|
|
962
1832
|
throw new CLIError("Workstream updates need a message. Use --message or pipe text on stdin.", { json: common.json });
|
|
963
1833
|
}
|
|
964
|
-
const {
|
|
965
|
-
const workstream = await
|
|
1834
|
+
const { guidance, client } = await requireCommandProfile(runtime, common);
|
|
1835
|
+
const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
|
|
966
1836
|
let createdDocument = null;
|
|
967
1837
|
if (parsed.values.file) {
|
|
968
1838
|
const documentContent = await readFile(parsed.values.file, "utf8");
|
|
@@ -992,7 +1862,7 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
992
1862
|
document: createdDocument,
|
|
993
1863
|
};
|
|
994
1864
|
if (common.json) {
|
|
995
|
-
printJson(runtime.stdout, payload);
|
|
1865
|
+
printJson(runtime.stdout, withGuidanceJsonMetadata(payload, guidance));
|
|
996
1866
|
}
|
|
997
1867
|
else {
|
|
998
1868
|
printLine(runtime.stdout, `Recorded ${String(parsed.values.type ?? "status")} update on ${workstream.id}.`);
|
|
@@ -1026,9 +1896,12 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1026
1896
|
printWorkstreamHelp(runtime);
|
|
1027
1897
|
return 0;
|
|
1028
1898
|
}
|
|
1029
|
-
const
|
|
1030
|
-
|
|
1031
|
-
|
|
1899
|
+
const workstreamId = parsed.positionals[0];
|
|
1900
|
+
if (!workstreamId) {
|
|
1901
|
+
throw new CLIError("Usage: corners workstream question list <workstreamId>", { json: common.json });
|
|
1902
|
+
}
|
|
1903
|
+
const { guidance, client } = await requireCommandProfile(runtime, common);
|
|
1904
|
+
const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
|
|
1032
1905
|
const data = await client.graphql(WORKSTREAM_QUESTION_LIST_QUERY, {
|
|
1033
1906
|
id: workstream.id,
|
|
1034
1907
|
});
|
|
@@ -1036,7 +1909,7 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1036
1909
|
throw new CLIError("Workstream not found", { json: common.json });
|
|
1037
1910
|
}
|
|
1038
1911
|
if (common.json) {
|
|
1039
|
-
printJson(runtime.stdout, data.workstream);
|
|
1912
|
+
printJson(runtime.stdout, withGuidanceJsonMetadata(data.workstream, guidance));
|
|
1040
1913
|
}
|
|
1041
1914
|
else {
|
|
1042
1915
|
printLine(runtime.stdout, [
|
|
@@ -1071,16 +1944,19 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1071
1944
|
printWorkstreamHelp(runtime);
|
|
1072
1945
|
return 0;
|
|
1073
1946
|
}
|
|
1074
|
-
const
|
|
1947
|
+
const workstreamId = parsed.positionals[0];
|
|
1948
|
+
if (!workstreamId) {
|
|
1949
|
+
throw new CLIError("Usage: corners workstream question ask <workstreamId> [--question <text>]", { json: common.json });
|
|
1950
|
+
}
|
|
1075
1951
|
const question = parsed.values.question ??
|
|
1076
|
-
(
|
|
1077
|
-
?
|
|
1952
|
+
(parsed.positionals.length > 1
|
|
1953
|
+
? parsed.positionals.slice(1).join(" ")
|
|
1078
1954
|
: await readTextFromStdin(runtime.stdin));
|
|
1079
1955
|
if (!question) {
|
|
1080
1956
|
throw new CLIError("Question text is required. Use --question or pipe text on stdin.", { json: common.json });
|
|
1081
1957
|
}
|
|
1082
|
-
const {
|
|
1083
|
-
const workstream = await
|
|
1958
|
+
const { guidance, client } = await requireCommandProfile(runtime, common);
|
|
1959
|
+
const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
|
|
1084
1960
|
const result = await client.graphql(CREATE_WORKSTREAM_QUESTION_MUTATION, {
|
|
1085
1961
|
input: {
|
|
1086
1962
|
workstreamId: workstream.id,
|
|
@@ -1090,7 +1966,7 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1090
1966
|
},
|
|
1091
1967
|
});
|
|
1092
1968
|
if (common.json) {
|
|
1093
|
-
printJson(runtime.stdout, result.createWorkstreamQuestion);
|
|
1969
|
+
printJson(runtime.stdout, withGuidanceJsonMetadata(result.createWorkstreamQuestion, guidance));
|
|
1094
1970
|
}
|
|
1095
1971
|
else {
|
|
1096
1972
|
printLine(runtime.stdout, `Created question on ${workstream.id}.`);
|
|
@@ -1131,13 +2007,13 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1131
2007
|
json: common.json,
|
|
1132
2008
|
});
|
|
1133
2009
|
}
|
|
1134
|
-
const { client } = await
|
|
2010
|
+
const { guidance, client } = await requireCommandProfile(runtime, common);
|
|
1135
2011
|
const result = await client.graphql(ANSWER_WORKSTREAM_QUESTION_MUTATION, {
|
|
1136
2012
|
questionId,
|
|
1137
2013
|
answerText: answer,
|
|
1138
2014
|
});
|
|
1139
2015
|
if (common.json) {
|
|
1140
|
-
printJson(runtime.stdout, result.answerWorkstreamQuestion);
|
|
2016
|
+
printJson(runtime.stdout, withGuidanceJsonMetadata(result.answerWorkstreamQuestion, guidance));
|
|
1141
2017
|
}
|
|
1142
2018
|
else {
|
|
1143
2019
|
printLine(runtime.stdout, `Answered ${questionId}.`);
|
|
@@ -1170,21 +2046,21 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1170
2046
|
printWorkstreamHelp(runtime);
|
|
1171
2047
|
return 0;
|
|
1172
2048
|
}
|
|
1173
|
-
const
|
|
2049
|
+
const workstreamId = parsed.positionals[0];
|
|
1174
2050
|
const kind = parsed.values.kind;
|
|
1175
2051
|
const entityId = parsed.values["entity-id"];
|
|
1176
|
-
if (!kind || !entityId) {
|
|
1177
|
-
throw new CLIError("Usage: corners workstream attach
|
|
2052
|
+
if (!workstreamId || !kind || !entityId) {
|
|
2053
|
+
throw new CLIError("Usage: corners workstream attach <workstreamId> --kind <kind> --entity-id <id>", { json: common.json });
|
|
1178
2054
|
}
|
|
1179
|
-
const {
|
|
1180
|
-
const workstream = await
|
|
2055
|
+
const { guidance, client } = await requireCommandProfile(runtime, common);
|
|
2056
|
+
const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
|
|
1181
2057
|
const result = await client.graphql(ADD_WORKSTREAM_ATTACHMENT_MUTATION, {
|
|
1182
2058
|
workstreamId: workstream.id,
|
|
1183
2059
|
kind: toGraphQLAttachmentKind(kind),
|
|
1184
2060
|
entityId,
|
|
1185
2061
|
});
|
|
1186
2062
|
if (common.json) {
|
|
1187
|
-
printJson(runtime.stdout, result.addWorkstreamAttachment);
|
|
2063
|
+
printJson(runtime.stdout, withGuidanceJsonMetadata(result.addWorkstreamAttachment, guidance));
|
|
1188
2064
|
}
|
|
1189
2065
|
else {
|
|
1190
2066
|
printLine(runtime.stdout, `Attached ${kind}:${entityId} to ${workstream.id}.`);
|
|
@@ -1220,7 +2096,7 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1220
2096
|
if (!threadId || !text) {
|
|
1221
2097
|
throw new CLIError("Usage: corners workstream reply-thread <threadId> [--text <text>]", { json: common.json });
|
|
1222
2098
|
}
|
|
1223
|
-
const { client } = await
|
|
2099
|
+
const { guidance, client } = await requireCommandProfile(runtime, common);
|
|
1224
2100
|
const result = await client.graphql(REPLY_ARTIFACT_THREAD_MUTATION, {
|
|
1225
2101
|
id: threadId,
|
|
1226
2102
|
input: {
|
|
@@ -1228,7 +2104,7 @@ async function handleWorkstream(args, runtime, inherited) {
|
|
|
1228
2104
|
},
|
|
1229
2105
|
});
|
|
1230
2106
|
if (common.json) {
|
|
1231
|
-
printJson(runtime.stdout, result.replyArtifactThread);
|
|
2107
|
+
printJson(runtime.stdout, withGuidanceJsonMetadata(result.replyArtifactThread, guidance));
|
|
1232
2108
|
}
|
|
1233
2109
|
else {
|
|
1234
2110
|
printLine(runtime.stdout, `Replied to ${threadId}.`);
|
|
@@ -1260,6 +2136,18 @@ export async function runCli(argv, runtime = createRuntime()) {
|
|
|
1260
2136
|
if (parsed.rest[1] === "auth") {
|
|
1261
2137
|
printAuthHelp(runtime);
|
|
1262
2138
|
}
|
|
2139
|
+
else if (parsed.rest[1] === "guidance") {
|
|
2140
|
+
printGuidanceHelp(runtime);
|
|
2141
|
+
}
|
|
2142
|
+
else if (parsed.rest[1] === "init") {
|
|
2143
|
+
printInitHelp(runtime);
|
|
2144
|
+
}
|
|
2145
|
+
else if (parsed.rest[1] === "corner") {
|
|
2146
|
+
printCornerHelp(runtime);
|
|
2147
|
+
}
|
|
2148
|
+
else if (parsed.rest[1] === "whoami") {
|
|
2149
|
+
printWhoamiHelp(runtime);
|
|
2150
|
+
}
|
|
1263
2151
|
else if (parsed.rest[1] === "workstream" || parsed.rest[1] === "ws") {
|
|
1264
2152
|
printWorkstreamHelp(runtime);
|
|
1265
2153
|
}
|
|
@@ -1270,8 +2158,16 @@ export async function runCli(argv, runtime = createRuntime()) {
|
|
|
1270
2158
|
case "version":
|
|
1271
2159
|
printLine(runtime.stdout, runtime.getVersion());
|
|
1272
2160
|
return 0;
|
|
2161
|
+
case "init":
|
|
2162
|
+
return handleInit(parsed.rest.slice(1), runtime, parsed.common);
|
|
2163
|
+
case "guidance":
|
|
2164
|
+
return handleGuidance(parsed.rest.slice(1), runtime, parsed.common);
|
|
1273
2165
|
case "auth":
|
|
1274
2166
|
return handleAuth(parsed.rest.slice(1), runtime, parsed.common);
|
|
2167
|
+
case "corner":
|
|
2168
|
+
return handleCorner(parsed.rest.slice(1), runtime, parsed.common);
|
|
2169
|
+
case "whoami":
|
|
2170
|
+
return handleWhoAmI(parsed.rest.slice(1), runtime, parsed.common);
|
|
1275
2171
|
case "workstream":
|
|
1276
2172
|
case "ws":
|
|
1277
2173
|
return handleWorkstream(parsed.rest.slice(1), runtime, parsed.common);
|