@corners/cli 0.0.1 → 0.0.3

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.js CHANGED
@@ -1,31 +1,11 @@
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 { CLIError, getPackageVersion, openUrlInBrowser, printJson, printLine, readTextFromStdin, sleep, toGraphQLAttachmentKind, toGraphQLWorkstreamUpdateType, toIsoString, } from "./support.js";
8
- const LIST_WORKSTREAMS_QUERY = `
9
- query CliListWorkstreams {
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 { createPromptApi } from "./prompts.js";
8
+ import { CLIError, getPackageVersion, normalizeApiUrl, openUrlInBrowser, printJson, printLine, readTextFromStdin, sleep, toGraphQLAttachmentKind, toGraphQLWorkstreamUpdateType, toIsoString, } from "./support.js";
29
9
  const WORKSTREAM_LOOKUP_QUERY = `
30
10
  query CliWorkstreamLookup($id: ID!) {
31
11
  workstream(id: $id) {
@@ -308,6 +288,47 @@ const REVOKE_MCP_SESSION_MUTATION = `
308
288
  revokeMCPSession(id: $id)
309
289
  }
310
290
  `;
291
+ const WHOAMI_QUERY = `
292
+ query CliWhoAmI {
293
+ me {
294
+ id
295
+ handle
296
+ account {
297
+ id
298
+ workspace
299
+ }
300
+ }
301
+ }
302
+ `;
303
+ const LIST_MEMBER_CORNERS_QUERY = `
304
+ query CliMemberCorners {
305
+ memberCorners(first: 50) {
306
+ edges {
307
+ node {
308
+ id
309
+ name
310
+ }
311
+ }
312
+ }
313
+ }
314
+ `;
315
+ const CREATE_WORKSTREAM_MUTATION = `
316
+ mutation CliCreateWorkstream($input: CreateWorkstreamInput!) {
317
+ createWorkstream(input: $input) {
318
+ id
319
+ cornerId
320
+ name
321
+ summary
322
+ category
323
+ status
324
+ updatedAt
325
+ topic {
326
+ id
327
+ name
328
+ }
329
+ }
330
+ }
331
+ `;
311
332
  function createRuntime() {
312
333
  return {
313
334
  config: new ConfigStore(),
@@ -315,6 +336,7 @@ function createRuntime() {
315
336
  stderr: process.stderr,
316
337
  stdin: process.stdin,
317
338
  cwd: process.cwd(),
339
+ prompts: undefined,
318
340
  openUrl: openUrlInBrowser,
319
341
  createClient: (input) => new DefaultCornersApiClient(input),
320
342
  getVersion: getPackageVersion,
@@ -329,7 +351,10 @@ function printMainHelp(runtime) {
329
351
  "",
330
352
  "Commands:",
331
353
  " auth login|logout|status",
332
- " workstream (ws) list|use|current|pull|push|question|attach|reply-thread",
354
+ " init",
355
+ " corner use",
356
+ " whoami",
357
+ " workstream (ws) list|use|current|create|pull|push|question|attach|reply-thread",
333
358
  " help",
334
359
  " version",
335
360
  "",
@@ -342,6 +367,14 @@ function printMainHelp(runtime) {
342
367
  " --no-browser",
343
368
  ].join("\n"));
344
369
  }
370
+ function printInitHelp(runtime) {
371
+ printLine(runtime.stdout, [
372
+ "corners init",
373
+ "",
374
+ "Usage:",
375
+ " corners init [--profile <name>] [--api-url <url>] [--json]",
376
+ ].join("\n"));
377
+ }
345
378
  function printAuthHelp(runtime) {
346
379
  printLine(runtime.stdout, [
347
380
  "corners auth",
@@ -352,6 +385,22 @@ function printAuthHelp(runtime) {
352
385
  " corners auth status [--profile <name>] [--json]",
353
386
  ].join("\n"));
354
387
  }
388
+ function printCornerHelp(runtime) {
389
+ printLine(runtime.stdout, [
390
+ "corners corner",
391
+ "",
392
+ "Usage:",
393
+ " corners corner use <cornerNameOrId> [--path <path>] [--json]",
394
+ ].join("\n"));
395
+ }
396
+ function printWhoamiHelp(runtime) {
397
+ printLine(runtime.stdout, [
398
+ "corners whoami",
399
+ "",
400
+ "Usage:",
401
+ " corners whoami [--profile <name>] [--api-url <url>] [--json]",
402
+ ].join("\n"));
403
+ }
355
404
  function printWorkstreamHelp(runtime) {
356
405
  printLine(runtime.stdout, [
357
406
  "corners workstream",
@@ -360,12 +409,13 @@ function printWorkstreamHelp(runtime) {
360
409
  " corners workstream list [--json]",
361
410
  " corners workstream use <workstreamId> [--json]",
362
411
  " corners workstream current [--json]",
363
- " corners workstream pull [workstreamId] [--json]",
364
- " corners workstream push [workstreamId] [--type <type>] [--message <text>] [--summary <text>] [--file <path>] [--title <title>] [--json]",
365
- " corners workstream question list [workstreamId] [--json]",
366
- " corners workstream question ask [workstreamId] [--question <text>] [--rationale <text>] [--suggested-answer <text>]... [--json]",
412
+ " corners workstream create <name> [--summary <text>] [--corner <cornerNameOrId>] [--json]",
413
+ " corners workstream pull <workstreamId> [--json]",
414
+ " corners workstream push <workstreamId> [--type <type>] [--message <text>] [--summary <text>] [--file <path>] [--title <title>] [--json]",
415
+ " corners workstream question list <workstreamId> [--json]",
416
+ " corners workstream question ask <workstreamId> [--question <text>] [--rationale <text>] [--suggested-answer <text>]... [--json]",
367
417
  " corners workstream question answer <questionId> [--text <text>] [--json]",
368
- " corners workstream attach [workstreamId] --kind <kind> --entity-id <id> [--json]",
418
+ " corners workstream attach <workstreamId> --kind <kind> --entity-id <id> [--json]",
369
419
  " corners workstream reply-thread <threadId> [--text <text>] [--json]",
370
420
  "",
371
421
  "Update types:",
@@ -462,6 +512,83 @@ async function requireStoredProfile(runtime, common) {
462
512
  }),
463
513
  };
464
514
  }
515
+ async function fetchCurrentIdentity(client) {
516
+ const data = await client.graphql(WHOAMI_QUERY);
517
+ return {
518
+ userId: data.me.id,
519
+ handle: data.me.handle ?? null,
520
+ accountId: data.me.account?.id ?? null,
521
+ workspace: data.me.account?.workspace ?? null,
522
+ };
523
+ }
524
+ async function maybeRefreshStoredProfileIdentity(input) {
525
+ try {
526
+ const identity = await fetchCurrentIdentity(input.client);
527
+ const nextProfile = {
528
+ ...input.profile,
529
+ userId: identity.userId,
530
+ handle: identity.handle,
531
+ accountId: identity.accountId,
532
+ workspace: identity.workspace,
533
+ };
534
+ if (nextProfile.userId !== input.profile.userId ||
535
+ nextProfile.handle !== (input.profile.handle ?? null) ||
536
+ nextProfile.accountId !== input.profile.accountId ||
537
+ nextProfile.workspace !== input.profile.workspace) {
538
+ await input.runtime.config.saveProfile(input.profileName, nextProfile, {
539
+ setActive: false,
540
+ });
541
+ }
542
+ return nextProfile;
543
+ }
544
+ catch {
545
+ return input.profile;
546
+ }
547
+ }
548
+ async function resolveAuthStatus(runtime, common) {
549
+ const selected = await runtime.config.getProfile(common.profile ?? null);
550
+ if (!selected) {
551
+ return {
552
+ loggedIn: false,
553
+ profile: common.profile ?? null,
554
+ };
555
+ }
556
+ const apiUrl = common.apiUrl ?? selected.profile.apiUrl;
557
+ const client = runtime.createClient({
558
+ apiUrl,
559
+ accessToken: selected.profile.accessToken,
560
+ });
561
+ let remote;
562
+ try {
563
+ remote = await client.validateSession();
564
+ }
565
+ catch (error) {
566
+ remote = {
567
+ valid: false,
568
+ error: error.message,
569
+ };
570
+ }
571
+ const profile = remote.valid
572
+ ? await maybeRefreshStoredProfileIdentity({
573
+ runtime,
574
+ profileName: selected.name,
575
+ profile: selected.profile,
576
+ client,
577
+ })
578
+ : selected.profile;
579
+ return {
580
+ loggedIn: true,
581
+ profile: selected.name,
582
+ apiUrl,
583
+ sessionId: profile.sessionId,
584
+ workspace: profile.workspace,
585
+ handle: profile.handle ?? null,
586
+ accountId: profile.accountId,
587
+ userId: profile.userId,
588
+ remoteValid: remote.valid,
589
+ remoteError: remote.valid ? null : (remote.error ?? null),
590
+ };
591
+ }
465
592
  async function pollForDeviceToken(input) {
466
593
  const deadline = Date.now() + input.device.expires_in * 1000;
467
594
  let intervalMs = input.device.interval * 1000;
@@ -489,36 +616,281 @@ async function pollForDeviceToken(input) {
489
616
  printLine(input.stderr, "Device code expired before authorization completed.");
490
617
  throw new CLIError("Device code expired");
491
618
  }
492
- async function resolveExplicitOrBoundWorkstream(runtime, common, client, profileName, explicitWorkstreamId) {
493
- const workstreamId = explicitWorkstreamId ||
494
- (await resolveBoundWorkstreamId(runtime, profileName));
619
+ async function withPrompts(runtime, run) {
620
+ if (runtime.prompts) {
621
+ return run(runtime.prompts);
622
+ }
623
+ if (runtime.stdin.isTTY === false) {
624
+ throw new CLIError("`corners init` requires an interactive terminal.");
625
+ }
626
+ const prompts = createPromptApi(runtime.stdin, runtime.stderr);
627
+ try {
628
+ return await run(prompts);
629
+ }
630
+ finally {
631
+ await prompts.close();
632
+ }
633
+ }
634
+ async function readOptionalUtf8(path) {
635
+ try {
636
+ return await readFile(path, "utf8");
637
+ }
638
+ catch (error) {
639
+ if (error.code === "ENOENT") {
640
+ return null;
641
+ }
642
+ throw error;
643
+ }
644
+ }
645
+ function ensureTrailingNewline(value) {
646
+ return value.endsWith("\n") ? value : `${value}\n`;
647
+ }
648
+ function normalizeCornerLookupValue(value) {
649
+ const normalized = value.trim();
650
+ return normalized.startsWith("#") ? normalized.slice(1) : normalized;
651
+ }
652
+ function normalizeStoredRelativePath(rootDir, targetPath) {
653
+ const relativePath = relative(rootDir, targetPath);
654
+ if (!relativePath) {
655
+ return ".";
656
+ }
657
+ if (relativePath === ".." ||
658
+ relativePath.startsWith(`..${sep}`) ||
659
+ relativePath.includes(`${sep}..${sep}`)) {
660
+ throw new CLIError("The target path must stay within the initialized root.");
661
+ }
662
+ return relativePath.split(sep).join("/");
663
+ }
664
+ function resolvePathWithinRoot(rootDir, cwd, inputPath) {
665
+ const targetPath = inputPath ? resolve(cwd, inputPath) : resolve(cwd);
666
+ normalizeStoredRelativePath(rootDir, targetPath);
667
+ return targetPath;
668
+ }
669
+ function resolveCornerForRelativePath(config, relativePath) {
670
+ let selected = config.defaultCorner;
671
+ let selectedLength = 0;
672
+ for (const override of config.cornerOverrides) {
673
+ const matches = relativePath === override.path ||
674
+ relativePath.startsWith(`${override.path}/`);
675
+ if (!matches) {
676
+ continue;
677
+ }
678
+ if (override.path.length > selectedLength) {
679
+ selected = {
680
+ id: override.cornerId,
681
+ name: override.cornerName,
682
+ };
683
+ selectedLength = override.path.length;
684
+ }
685
+ }
686
+ return selected;
687
+ }
688
+ function resolveCornerForCwd(root, cwd) {
689
+ const absolutePath = resolvePathWithinRoot(root.rootDir, cwd);
690
+ const relativePath = normalizeStoredRelativePath(root.rootDir, absolutePath);
691
+ return {
692
+ relativePath,
693
+ corner: resolveCornerForRelativePath(root.config, relativePath),
694
+ };
695
+ }
696
+ async function requireLocalRoot(runtime, common) {
697
+ const root = await runtime.config.findLocalRoot(runtime.cwd);
698
+ if (!root) {
699
+ throw new CLIError("No local Corners CLI root found. Run `corners init` from your project root first.", { json: common.json });
700
+ }
701
+ return root;
702
+ }
703
+ async function requirePinnedRootProfile(runtime, common, root) {
704
+ if (common.profile && common.profile !== root.config.profile) {
705
+ throw new CLIError(`This initialized root is pinned to profile ${root.config.profile}.`, { json: common.json });
706
+ }
707
+ const selected = await runtime.config.getProfile(root.config.profile);
708
+ if (!selected) {
709
+ throw new CLIError(`The pinned profile ${root.config.profile} is not available. Run \`corners auth login --profile ${root.config.profile}\` first.`, { json: common.json });
710
+ }
711
+ const pinnedApiUrl = normalizeApiUrl(root.config.apiUrl);
712
+ const requestedApiUrl = common.apiUrl ? normalizeApiUrl(common.apiUrl) : null;
713
+ if (requestedApiUrl && requestedApiUrl !== pinnedApiUrl) {
714
+ throw new CLIError(`This initialized root is pinned to API ${pinnedApiUrl}.`, { json: common.json });
715
+ }
716
+ if (root.config.workspace &&
717
+ selected.profile.workspace &&
718
+ root.config.workspace !== selected.profile.workspace) {
719
+ 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 });
720
+ }
721
+ const profile = {
722
+ ...selected.profile,
723
+ apiUrl: pinnedApiUrl,
724
+ };
725
+ return {
726
+ root,
727
+ profileName: selected.name,
728
+ profile,
729
+ client: runtime.createClient({
730
+ apiUrl: profile.apiUrl,
731
+ accessToken: profile.accessToken,
732
+ }),
733
+ };
734
+ }
735
+ async function requireCommandProfile(runtime, common) {
736
+ const root = await runtime.config.findLocalRoot(runtime.cwd);
737
+ if (root) {
738
+ return requirePinnedRootProfile(runtime, common, root);
739
+ }
740
+ const selected = await requireStoredProfile(runtime, common);
741
+ return {
742
+ root: null,
743
+ ...selected,
744
+ };
745
+ }
746
+ async function listMemberCorners(client) {
747
+ const data = await client.graphql(LIST_MEMBER_CORNERS_QUERY);
748
+ return data.memberCorners.edges.map((edge) => edge.node);
749
+ }
750
+ function findMemberCorner(corners, value) {
751
+ const normalized = normalizeCornerLookupValue(value);
752
+ return (corners.find((corner) => corner.id === normalized) ??
753
+ corners.find((corner) => corner.name.toLowerCase() === normalized.toLowerCase()) ??
754
+ null);
755
+ }
756
+ async function resolveMemberCorner(client, common, value) {
757
+ const corners = await listMemberCorners(client);
758
+ const corner = findMemberCorner(corners, value);
759
+ if (!corner) {
760
+ throw new CLIError("Corner not found in your joined corners.", {
761
+ json: common.json,
762
+ });
763
+ }
764
+ return corner;
765
+ }
766
+ async function resolveExplicitWorkstream(client, common, workstreamId) {
495
767
  const data = await client.graphql(WORKSTREAM_LOOKUP_QUERY, { id: workstreamId });
496
768
  if (!data.workstream) {
497
769
  throw new CLIError("Workstream not found", { json: common.json });
498
770
  }
499
771
  return data.workstream;
500
772
  }
501
- async function resolveBoundWorkstreamId(runtime, profileName) {
502
- const binding = await runtime.config.getBinding(runtime.cwd);
503
- if (!binding) {
504
- throw new CLIError("This directory is not bound to a workstream. Run `corners workstream use <workstreamId>` first.");
773
+ function connectWorkstream(config, workstreamId) {
774
+ if (config.connectedWorkstreams.some((entry) => entry.id === workstreamId)) {
775
+ return config;
505
776
  }
506
- if (binding.profile && binding.profile !== profileName) {
507
- throw new CLIError(`This directory is bound to profile ${binding.profile}. Switch profiles or rebind the directory.`);
508
- }
509
- return binding.workstreamId;
777
+ return {
778
+ ...config,
779
+ connectedWorkstreams: [
780
+ ...config.connectedWorkstreams,
781
+ {
782
+ id: workstreamId,
783
+ connectedAt: toIsoString(),
784
+ },
785
+ ],
786
+ };
510
787
  }
511
- function maybeTakeWorkstreamId(positionals) {
512
- if (positionals[0]?.startsWith("ws_")) {
788
+ function upsertCornerOverride(config, relativePath, corner) {
789
+ if (relativePath === ".") {
513
790
  return {
514
- workstreamId: positionals[0],
515
- rest: positionals.slice(1),
791
+ ...config,
792
+ defaultCorner: corner,
516
793
  };
517
794
  }
795
+ const overrides = config.cornerOverrides.filter((entry) => entry.path !== relativePath);
796
+ overrides.push({
797
+ path: relativePath,
798
+ cornerId: corner.id,
799
+ cornerName: corner.name,
800
+ updatedAt: toIsoString(),
801
+ });
802
+ overrides.sort((left, right) => left.path.localeCompare(right.path));
518
803
  return {
519
- rest: positionals,
804
+ ...config,
805
+ cornerOverrides: overrides,
520
806
  };
521
807
  }
808
+ const GUIDANCE_SECTION_START = "<!-- corners-cli:start -->";
809
+ const GUIDANCE_SECTION_END = "<!-- corners-cli:end -->";
810
+ function buildGuidanceSection() {
811
+ return [
812
+ "## Corners CLI",
813
+ "",
814
+ "Corners CLI usage is local to each user. These shared instructions describe how AI should use the CLI, not which corners or workstreams a user has selected locally.",
815
+ "",
816
+ "- Only assume Corners CLI is initialized when `.corners/config.json` exists locally in the current repo root or an ancestor folder.",
817
+ "- The current folder selects the default corner through local path rules managed by `corners corner use`.",
818
+ "- Create new workstreams with `corners workstream create`.",
819
+ "- Connect existing workstreams to the local environment with `corners workstream use <workstreamId>`.",
820
+ "- Pass explicit workstream IDs to `corners workstream pull`, `push`, `question`, and `attach` commands.",
821
+ "- `corners workstream list` shows the workstreams connected for the current local environment.",
822
+ "",
823
+ "If `.corners/config.json` is absent, do not assume the repo is initialized for the current user.",
824
+ ].join("\n");
825
+ }
826
+ function upsertManagedSection(existing, section) {
827
+ const block = `${GUIDANCE_SECTION_START}\n${section}\n${GUIDANCE_SECTION_END}`;
828
+ const startIndex = existing.indexOf(GUIDANCE_SECTION_START);
829
+ const endIndex = existing.indexOf(GUIDANCE_SECTION_END);
830
+ if (startIndex >= 0 && endIndex > startIndex) {
831
+ return ensureTrailingNewline(`${existing.slice(0, startIndex).trimEnd()}\n\n${block}\n${existing
832
+ .slice(endIndex + GUIDANCE_SECTION_END.length)
833
+ .trimStart()}`.trim());
834
+ }
835
+ const trimmed = existing.trim();
836
+ if (!trimmed) {
837
+ return ensureTrailingNewline(block);
838
+ }
839
+ return ensureTrailingNewline(`${trimmed}\n\n${block}`);
840
+ }
841
+ function buildGuidanceFileContent(target, existing) {
842
+ const section = buildGuidanceSection();
843
+ if (target === "cursor") {
844
+ const base = existing?.trim()
845
+ ? existing
846
+ : [
847
+ "---",
848
+ "description: Corners CLI guidance",
849
+ "alwaysApply: true",
850
+ "---",
851
+ "",
852
+ ].join("\n");
853
+ return upsertManagedSection(base, section);
854
+ }
855
+ return upsertManagedSection(existing ?? "", section);
856
+ }
857
+ async function buildPlannedEdit(path, nextContent) {
858
+ const existing = await readOptionalUtf8(path);
859
+ if (existing === nextContent) {
860
+ return null;
861
+ }
862
+ return {
863
+ path,
864
+ action: existing === null ? "create" : "update",
865
+ content: nextContent,
866
+ };
867
+ }
868
+ function ensureGitignoreEntry(existing) {
869
+ const content = existing ?? "";
870
+ const lines = content
871
+ .split(/\r?\n/)
872
+ .map((line) => line.trim())
873
+ .filter(Boolean);
874
+ if (lines.includes(".corners") ||
875
+ lines.includes(".corners/") ||
876
+ lines.includes(LOCAL_ROOT_CONFIG_RELATIVE_PATH)) {
877
+ return ensureTrailingNewline(content);
878
+ }
879
+ const trimmed = content.trimEnd();
880
+ if (!trimmed) {
881
+ return `${LOCAL_ROOT_CONFIG_RELATIVE_PATH}\n`;
882
+ }
883
+ return `${trimmed}\n${LOCAL_ROOT_CONFIG_RELATIVE_PATH}\n`;
884
+ }
885
+ async function writePlannedEdits(edits) {
886
+ for (const edit of edits) {
887
+ await mkdir(dirname(edit.path), { recursive: true });
888
+ await writeFile(edit.path, edit.content, "utf8");
889
+ }
890
+ }
891
+ function formatWorkstreamLine(workstream) {
892
+ return `${workstream.id} ${workstream.name} ${workstream.status.toLowerCase()}${workstream.summary ? ` ${workstream.summary}` : ""}`;
893
+ }
522
894
  async function handleAuth(args, runtime, inherited) {
523
895
  const subcommand = args[0];
524
896
  if (!subcommand || subcommand === "help" || subcommand === "--help") {
@@ -579,15 +951,32 @@ async function handleAuth(args, runtime, inherited) {
579
951
  if (!token.access_token) {
580
952
  throw new CLIError("Login completed without an access token");
581
953
  }
582
- const profile = {
954
+ let profile = {
583
955
  apiUrl: client.apiUrl,
584
956
  accessToken: token.access_token,
585
957
  sessionId: token.session_id ?? null,
586
958
  workspace: token.workspace ?? null,
587
959
  accountId: token.account_id ?? null,
588
960
  userId: token.user_id ?? null,
961
+ handle: null,
589
962
  createdAt: toIsoString(),
590
963
  };
964
+ try {
965
+ const identity = await fetchCurrentIdentity(runtime.createClient({
966
+ apiUrl: profile.apiUrl,
967
+ accessToken: profile.accessToken,
968
+ }));
969
+ profile = {
970
+ ...profile,
971
+ workspace: identity.workspace,
972
+ accountId: identity.accountId,
973
+ userId: identity.userId,
974
+ handle: identity.handle,
975
+ };
976
+ }
977
+ catch {
978
+ // Best-effort profile enrichment. Authentication already succeeded.
979
+ }
591
980
  await runtime.config.saveProfile(profileName, profile, {
592
981
  setActive: true,
593
982
  });
@@ -598,6 +987,7 @@ async function handleAuth(args, runtime, inherited) {
598
987
  apiUrl: profile.apiUrl,
599
988
  sessionId: profile.sessionId,
600
989
  workspace: profile.workspace,
990
+ handle: profile.handle,
601
991
  accountId: profile.accountId,
602
992
  userId: profile.userId,
603
993
  });
@@ -693,12 +1083,8 @@ async function handleAuth(args, runtime, inherited) {
693
1083
  printAuthHelp(runtime);
694
1084
  return 0;
695
1085
  }
696
- const selected = await runtime.config.getProfile(common.profile ?? null);
697
- if (!selected) {
698
- const status = {
699
- loggedIn: false,
700
- profile: common.profile ?? null,
701
- };
1086
+ const status = await resolveAuthStatus(runtime, common);
1087
+ if (!status.loggedIn) {
702
1088
  if (common.json) {
703
1089
  printJson(runtime.stdout, status);
704
1090
  }
@@ -707,31 +1093,6 @@ async function handleAuth(args, runtime, inherited) {
707
1093
  }
708
1094
  return 0;
709
1095
  }
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
1096
  if (common.json) {
736
1097
  printJson(runtime.stdout, status);
737
1098
  }
@@ -740,6 +1101,7 @@ async function handleAuth(args, runtime, inherited) {
740
1101
  `Profile: ${status.profile}`,
741
1102
  `API URL: ${status.apiUrl}`,
742
1103
  `Workspace: ${status.workspace ?? "-"}`,
1104
+ `Handle: ${status.handle ? `@${status.handle}` : "-"}`,
743
1105
  `Account: ${status.accountId ?? "-"}`,
744
1106
  `User: ${status.userId ?? "-"}`,
745
1107
  `Session: ${status.sessionId ?? "-"}`,
@@ -752,6 +1114,259 @@ async function handleAuth(args, runtime, inherited) {
752
1114
  throw new CLIError(`Unknown auth command: ${subcommand}`);
753
1115
  }
754
1116
  }
1117
+ async function handleWhoAmI(args, runtime, inherited) {
1118
+ const parsed = parseArgs({
1119
+ args,
1120
+ allowPositionals: false,
1121
+ options: {
1122
+ json: { type: "boolean" },
1123
+ help: { type: "boolean", short: "h" },
1124
+ profile: { type: "string" },
1125
+ "api-url": { type: "string" },
1126
+ },
1127
+ });
1128
+ const common = mergeCommonOptions(inherited, {
1129
+ json: parsed.values.json,
1130
+ profile: parsed.values.profile,
1131
+ apiUrl: parsed.values["api-url"],
1132
+ });
1133
+ if (parsed.values.help) {
1134
+ printWhoamiHelp(runtime);
1135
+ return 0;
1136
+ }
1137
+ const status = await resolveAuthStatus(runtime, common);
1138
+ if (!status.loggedIn) {
1139
+ if (common.json) {
1140
+ printJson(runtime.stdout, status);
1141
+ }
1142
+ else {
1143
+ printLine(runtime.stdout, "Not logged in.");
1144
+ }
1145
+ return 0;
1146
+ }
1147
+ if (common.json) {
1148
+ printJson(runtime.stdout, status);
1149
+ }
1150
+ else {
1151
+ const identity = status.handle
1152
+ ? `@${status.handle}`
1153
+ : (status.userId ?? status.profile);
1154
+ const workspaceSuffix = status.workspace ? ` (${status.workspace})` : "";
1155
+ printLine(runtime.stdout, `${identity}${workspaceSuffix}`);
1156
+ }
1157
+ return 0;
1158
+ }
1159
+ async function handleInit(args, runtime, inherited) {
1160
+ const parsed = parseArgs({
1161
+ args,
1162
+ allowPositionals: false,
1163
+ options: {
1164
+ json: { type: "boolean" },
1165
+ help: { type: "boolean", short: "h" },
1166
+ profile: { type: "string" },
1167
+ "api-url": { type: "string" },
1168
+ },
1169
+ });
1170
+ const common = mergeCommonOptions(inherited, {
1171
+ json: parsed.values.json,
1172
+ profile: parsed.values.profile,
1173
+ apiUrl: parsed.values["api-url"],
1174
+ });
1175
+ if (parsed.values.help) {
1176
+ printInitHelp(runtime);
1177
+ return 0;
1178
+ }
1179
+ const currentRootPath = await realpath(runtime.cwd);
1180
+ const existingRoot = await runtime.config.findLocalRoot(runtime.cwd);
1181
+ if (existingRoot && existingRoot.rootDir !== currentRootPath) {
1182
+ throw new CLIError(`This directory is already inside initialized root ${existingRoot.rootDir}. Run \`corners init\` there.`, { json: common.json });
1183
+ }
1184
+ const { profileName, profile, client } = await requireStoredProfile(runtime, common);
1185
+ const joinedCorners = await listMemberCorners(client);
1186
+ if (joinedCorners.length === 0) {
1187
+ throw new CLIError("No joined corners are available for this profile.", {
1188
+ json: common.json,
1189
+ });
1190
+ }
1191
+ const rootDir = existingRoot?.rootDir ?? currentRootPath;
1192
+ const existingConfig = existingRoot?.config ?? null;
1193
+ return withPrompts(runtime, async (prompts) => {
1194
+ const guidanceTargets = [
1195
+ {
1196
+ key: "agents",
1197
+ label: "Manage AGENTS.md guidance",
1198
+ relativePath: "AGENTS.md",
1199
+ },
1200
+ {
1201
+ key: "claude",
1202
+ label: "Manage CLAUDE.md guidance",
1203
+ relativePath: "CLAUDE.md",
1204
+ },
1205
+ {
1206
+ key: "cursor",
1207
+ label: "Manage Cursor rule guidance",
1208
+ relativePath: ".cursor/rules/corners-cli.mdc",
1209
+ },
1210
+ {
1211
+ key: "copilot",
1212
+ label: "Manage Copilot instructions",
1213
+ relativePath: ".github/copilot-instructions.md",
1214
+ },
1215
+ ];
1216
+ const selectedTargets = [];
1217
+ for (const target of guidanceTargets) {
1218
+ const include = await prompts.confirm(target.label, {
1219
+ defaultValue: true,
1220
+ });
1221
+ if (include) {
1222
+ selectedTargets.push(target);
1223
+ }
1224
+ }
1225
+ const selectedCornerId = await prompts.select("Choose the default corner for this local root.", joinedCorners.map((corner) => ({
1226
+ label: `${corner.name} (${corner.id})`,
1227
+ value: corner.id,
1228
+ })), {
1229
+ defaultValue: existingConfig?.defaultCorner.id ?? joinedCorners[0]?.id,
1230
+ });
1231
+ const defaultCorner = joinedCorners.find((corner) => corner.id === selectedCornerId) ??
1232
+ joinedCorners[0];
1233
+ const nextLocalConfig = {
1234
+ version: 1,
1235
+ profile: profileName,
1236
+ workspace: profile.workspace,
1237
+ apiUrl: profile.apiUrl,
1238
+ defaultCorner: {
1239
+ id: defaultCorner.id,
1240
+ name: defaultCorner.name,
1241
+ },
1242
+ cornerOverrides: existingConfig?.cornerOverrides ?? [],
1243
+ connectedWorkstreams: existingConfig?.connectedWorkstreams ?? [],
1244
+ };
1245
+ const plannedEdits = [];
1246
+ const gitignorePath = join(rootDir, ".gitignore");
1247
+ const gitignoreContent = ensureGitignoreEntry(await readOptionalUtf8(gitignorePath));
1248
+ const gitignoreEdit = await buildPlannedEdit(gitignorePath, gitignoreContent);
1249
+ if (gitignoreEdit) {
1250
+ plannedEdits.push(gitignoreEdit);
1251
+ }
1252
+ const localConfigPath = runtime.config.getLocalConfigPath(rootDir);
1253
+ const localConfigEdit = await buildPlannedEdit(localConfigPath, `${JSON.stringify(nextLocalConfig, null, 2)}\n`);
1254
+ if (localConfigEdit) {
1255
+ plannedEdits.push(localConfigEdit);
1256
+ }
1257
+ for (const target of selectedTargets) {
1258
+ const targetPath = join(rootDir, target.relativePath);
1259
+ const nextContent = buildGuidanceFileContent(target.key, await readOptionalUtf8(targetPath));
1260
+ const edit = await buildPlannedEdit(targetPath, nextContent);
1261
+ if (edit) {
1262
+ plannedEdits.push(edit);
1263
+ }
1264
+ }
1265
+ if (plannedEdits.length > 0 && !common.json) {
1266
+ printLine(runtime.stderr, [
1267
+ "Planned changes:",
1268
+ ...plannedEdits.map((edit) => ` ${edit.action} ${relative(rootDir, edit.path) || "."}`),
1269
+ ].join("\n"));
1270
+ }
1271
+ if (plannedEdits.length > 0) {
1272
+ const confirmed = await prompts.confirm("Proceed with these changes?", {
1273
+ defaultValue: true,
1274
+ });
1275
+ if (!confirmed) {
1276
+ if (common.json) {
1277
+ printJson(runtime.stdout, {
1278
+ ok: false,
1279
+ root: rootDir,
1280
+ aborted: true,
1281
+ });
1282
+ }
1283
+ else {
1284
+ printLine(runtime.stdout, "Aborted. No files were changed.");
1285
+ }
1286
+ return 0;
1287
+ }
1288
+ await writePlannedEdits(plannedEdits);
1289
+ }
1290
+ const payload = {
1291
+ ok: true,
1292
+ root: rootDir,
1293
+ profile: profileName,
1294
+ workspace: profile.workspace,
1295
+ defaultCorner,
1296
+ files: plannedEdits.map((edit) => ({
1297
+ action: edit.action,
1298
+ path: relative(rootDir, edit.path) || ".",
1299
+ })),
1300
+ };
1301
+ if (common.json) {
1302
+ printJson(runtime.stdout, payload);
1303
+ }
1304
+ else {
1305
+ printLine(runtime.stdout, `Initialized Corners CLI at ${rootDir} with default corner ${defaultCorner.name}.`);
1306
+ }
1307
+ return 0;
1308
+ });
1309
+ }
1310
+ async function handleCorner(args, runtime, inherited) {
1311
+ const subcommand = args[0];
1312
+ if (!subcommand || subcommand === "help" || subcommand === "--help") {
1313
+ printCornerHelp(runtime);
1314
+ return 0;
1315
+ }
1316
+ switch (subcommand) {
1317
+ case "use": {
1318
+ const parsed = parseArgs({
1319
+ args: args.slice(1),
1320
+ allowPositionals: true,
1321
+ options: {
1322
+ json: { type: "boolean" },
1323
+ help: { type: "boolean", short: "h" },
1324
+ profile: { type: "string" },
1325
+ "api-url": { type: "string" },
1326
+ path: { type: "string" },
1327
+ },
1328
+ });
1329
+ const common = mergeCommonOptions(inherited, {
1330
+ json: parsed.values.json,
1331
+ profile: parsed.values.profile,
1332
+ apiUrl: parsed.values["api-url"],
1333
+ });
1334
+ if (parsed.values.help) {
1335
+ printCornerHelp(runtime);
1336
+ return 0;
1337
+ }
1338
+ const cornerNameOrId = parsed.positionals[0];
1339
+ if (!cornerNameOrId) {
1340
+ throw new CLIError("Usage: corners corner use <cornerNameOrId> [--path <path>]", { json: common.json });
1341
+ }
1342
+ const root = await requireLocalRoot(runtime, common);
1343
+ const { client } = await requirePinnedRootProfile(runtime, common, root);
1344
+ const corner = await resolveMemberCorner(client, common, cornerNameOrId);
1345
+ const cwdPath = await realpath(runtime.cwd);
1346
+ const targetAbsolutePath = resolvePathWithinRoot(root.rootDir, cwdPath, parsed.values.path);
1347
+ const relativePath = normalizeStoredRelativePath(root.rootDir, targetAbsolutePath);
1348
+ const nextConfig = upsertCornerOverride(root.config, relativePath, {
1349
+ id: corner.id,
1350
+ name: corner.name,
1351
+ });
1352
+ await runtime.config.writeLocalConfig(root.rootDir, nextConfig);
1353
+ if (common.json) {
1354
+ printJson(runtime.stdout, {
1355
+ ok: true,
1356
+ root: root.rootDir,
1357
+ path: relativePath,
1358
+ corner,
1359
+ });
1360
+ }
1361
+ else {
1362
+ printLine(runtime.stdout, `Set default corner for ${relativePath} to ${corner.name}.`);
1363
+ }
1364
+ return 0;
1365
+ }
1366
+ default:
1367
+ throw new CLIError(`Unknown corner command: ${subcommand}`);
1368
+ }
1369
+ }
755
1370
  async function handleWorkstream(args, runtime, inherited) {
756
1371
  const subcommand = args[0];
757
1372
  if (!subcommand || subcommand === "help" || subcommand === "--help") {
@@ -779,16 +1394,44 @@ async function handleWorkstream(args, runtime, inherited) {
779
1394
  printWorkstreamHelp(runtime);
780
1395
  return 0;
781
1396
  }
782
- const { client } = await requireStoredProfile(runtime, common);
783
- const data = await client.graphql(LIST_WORKSTREAMS_QUERY);
784
- const workstreams = data.workstreams.edges.map((edge) => edge.node);
1397
+ const root = await requireLocalRoot(runtime, common);
1398
+ const { client } = await requirePinnedRootProfile(runtime, common, root);
1399
+ const hydrated = await Promise.all(root.config.connectedWorkstreams.map(async (entry) => {
1400
+ try {
1401
+ return {
1402
+ entry,
1403
+ workstream: await resolveExplicitWorkstream(client, common, entry.id),
1404
+ };
1405
+ }
1406
+ catch {
1407
+ return {
1408
+ entry,
1409
+ workstream: null,
1410
+ };
1411
+ }
1412
+ }));
1413
+ const workstreams = hydrated
1414
+ .filter((entry) => entry.workstream !== null)
1415
+ .map((entry) => entry.workstream);
1416
+ const missingWorkstreamIds = hydrated
1417
+ .filter((entry) => entry.workstream === null)
1418
+ .map((entry) => entry.entry.id);
785
1419
  if (common.json) {
786
- printJson(runtime.stdout, workstreams);
1420
+ printJson(runtime.stdout, {
1421
+ root: root.rootDir,
1422
+ workstreams,
1423
+ missingWorkstreamIds,
1424
+ });
787
1425
  }
788
1426
  else {
789
- printLine(runtime.stdout, workstreams
790
- .map((workstream) => `${workstream.id} ${workstream.name} ${workstream.status.toLowerCase()}${workstream.summary ? ` ${workstream.summary}` : ""}`)
791
- .join("\n"));
1427
+ if (missingWorkstreamIds.length > 0) {
1428
+ printLine(runtime.stderr, missingWorkstreamIds
1429
+ .map((workstreamId) => `Warning: connected workstream ${workstreamId} is missing or inaccessible.`)
1430
+ .join("\n"));
1431
+ }
1432
+ printLine(runtime.stdout, workstreams.length > 0
1433
+ ? workstreams.map(formatWorkstreamLine).join("\n")
1434
+ : "No workstreams are connected to this local environment.");
792
1435
  }
793
1436
  return 0;
794
1437
  }
@@ -818,26 +1461,22 @@ async function handleWorkstream(args, runtime, inherited) {
818
1461
  json: common.json,
819
1462
  });
820
1463
  }
821
- const { profileName, profile, client } = await requireStoredProfile(runtime, common);
822
- const workstream = await resolveExplicitOrBoundWorkstream(runtime, common, client, profileName, workstreamId);
823
- await runtime.config.setBinding(runtime.cwd, {
824
- profile: profileName,
825
- workspace: profile.workspace ?? "",
826
- workstreamId: workstream.id,
827
- cornerId: workstream.cornerId,
828
- boundAt: toIsoString(),
829
- });
1464
+ const root = await requireLocalRoot(runtime, common);
1465
+ const { client } = await requirePinnedRootProfile(runtime, common, root);
1466
+ const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
1467
+ const nextConfig = connectWorkstream(root.config, workstream.id);
1468
+ if (nextConfig !== root.config) {
1469
+ await runtime.config.writeLocalConfig(root.rootDir, nextConfig);
1470
+ }
830
1471
  if (common.json) {
831
1472
  printJson(runtime.stdout, {
832
1473
  ok: true,
833
- cwd: runtime.cwd,
834
- profile: profileName,
835
- workspace: profile.workspace,
1474
+ root: root.rootDir,
836
1475
  workstream,
837
1476
  });
838
1477
  }
839
1478
  else {
840
- printLine(runtime.stdout, `Bound ${runtime.cwd} to ${workstream.id} (${workstream.name}).`);
1479
+ printLine(runtime.stdout, `Connected ${workstream.id} (${workstream.name}) to ${root.rootDir}.`);
841
1480
  }
842
1481
  return 0;
843
1482
  }
@@ -857,30 +1496,85 @@ async function handleWorkstream(args, runtime, inherited) {
857
1496
  printWorkstreamHelp(runtime);
858
1497
  return 0;
859
1498
  }
860
- const binding = await runtime.config.getBinding(runtime.cwd);
861
- if (!binding) {
862
- if (common.json) {
863
- printJson(runtime.stdout, { cwd: runtime.cwd, binding: null });
864
- }
865
- else {
866
- printLine(runtime.stdout, "No workstream is bound to this directory.");
867
- }
1499
+ const root = await requireLocalRoot(runtime, common);
1500
+ const cwdPath = await realpath(runtime.cwd);
1501
+ const resolvedCorner = resolveCornerForCwd(root, cwdPath);
1502
+ const payload = {
1503
+ cwd: runtime.cwd,
1504
+ root: root.rootDir,
1505
+ profile: root.config.profile,
1506
+ workspace: root.config.workspace,
1507
+ resolvedCorner,
1508
+ connectedWorkstreamIds: root.config.connectedWorkstreams.map((entry) => entry.id),
1509
+ };
1510
+ if (common.json) {
1511
+ printJson(runtime.stdout, payload);
1512
+ }
1513
+ else {
1514
+ printLine(runtime.stdout, [
1515
+ `Root: ${payload.root}`,
1516
+ `Directory: ${payload.cwd}`,
1517
+ `Profile: ${payload.profile}`,
1518
+ `Workspace: ${payload.workspace ?? "-"}`,
1519
+ `Resolved corner: ${payload.resolvedCorner.corner.name} (${payload.resolvedCorner.corner.id})`,
1520
+ `Connected workstreams: ${payload.connectedWorkstreamIds.length}`,
1521
+ ].join("\n"));
1522
+ }
1523
+ return 0;
1524
+ }
1525
+ case "create": {
1526
+ const parsed = parseArgs({
1527
+ args: args.slice(1),
1528
+ allowPositionals: true,
1529
+ options: {
1530
+ json: { type: "boolean" },
1531
+ help: { type: "boolean", short: "h" },
1532
+ profile: { type: "string" },
1533
+ "api-url": { type: "string" },
1534
+ summary: { type: "string" },
1535
+ corner: { type: "string" },
1536
+ },
1537
+ });
1538
+ const common = mergeCommonOptions(inherited, {
1539
+ json: parsed.values.json,
1540
+ profile: parsed.values.profile,
1541
+ apiUrl: parsed.values["api-url"],
1542
+ });
1543
+ if (parsed.values.help) {
1544
+ printWorkstreamHelp(runtime);
868
1545
  return 0;
869
1546
  }
1547
+ const name = parsed.positionals.join(" ").trim();
1548
+ if (!name) {
1549
+ throw new CLIError("Usage: corners workstream create <name> [--summary <text>] [--corner <cornerNameOrId>]", { json: common.json });
1550
+ }
1551
+ const root = await requireLocalRoot(runtime, common);
1552
+ const { client } = await requirePinnedRootProfile(runtime, common, root);
1553
+ const cwdPath = await realpath(runtime.cwd);
1554
+ const corner = parsed.values.corner
1555
+ ? await resolveMemberCorner(client, common, parsed.values.corner)
1556
+ : resolveCornerForCwd(root, cwdPath).corner;
1557
+ const result = await client.graphql(CREATE_WORKSTREAM_MUTATION, {
1558
+ input: {
1559
+ cornerId: corner.id,
1560
+ name,
1561
+ summary: parsed.values.summary,
1562
+ },
1563
+ });
1564
+ const nextConfig = connectWorkstream(root.config, result.createWorkstream.id);
1565
+ if (nextConfig !== root.config) {
1566
+ await runtime.config.writeLocalConfig(root.rootDir, nextConfig);
1567
+ }
870
1568
  if (common.json) {
871
1569
  printJson(runtime.stdout, {
872
- cwd: runtime.cwd,
873
- binding,
1570
+ ok: true,
1571
+ root: root.rootDir,
1572
+ corner,
1573
+ workstream: result.createWorkstream,
874
1574
  });
875
1575
  }
876
1576
  else {
877
- printLine(runtime.stdout, [
878
- `Directory: ${runtime.cwd}`,
879
- `Profile: ${binding.profile ?? "-"}`,
880
- `Workspace: ${binding.workspace || "-"}`,
881
- `Workstream: ${binding.workstreamId}`,
882
- `Corner: ${binding.cornerId}`,
883
- ].join("\n"));
1577
+ printLine(runtime.stdout, `Created ${result.createWorkstream.id} (${result.createWorkstream.name}) in ${corner.name}.`);
884
1578
  }
885
1579
  return 0;
886
1580
  }
@@ -904,9 +1598,14 @@ async function handleWorkstream(args, runtime, inherited) {
904
1598
  printWorkstreamHelp(runtime);
905
1599
  return 0;
906
1600
  }
907
- const { workstreamId } = maybeTakeWorkstreamId(parsed.positionals);
908
- const { profileName, client } = await requireStoredProfile(runtime, common);
909
- const workstream = await resolveExplicitOrBoundWorkstream(runtime, common, client, profileName, workstreamId);
1601
+ const workstreamId = parsed.positionals[0];
1602
+ if (!workstreamId) {
1603
+ throw new CLIError("Usage: corners workstream pull <workstreamId>", {
1604
+ json: common.json,
1605
+ });
1606
+ }
1607
+ const { client } = await requireCommandProfile(runtime, common);
1608
+ const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
910
1609
  const data = await client.graphql(WORKSTREAM_PULL_QUERY, {
911
1610
  id: workstream.id,
912
1611
  attachmentsFirst: 50,
@@ -953,16 +1652,21 @@ async function handleWorkstream(args, runtime, inherited) {
953
1652
  printWorkstreamHelp(runtime);
954
1653
  return 0;
955
1654
  }
956
- const { workstreamId, rest } = maybeTakeWorkstreamId(parsed.positionals);
1655
+ const workstreamId = parsed.positionals[0];
1656
+ if (!workstreamId) {
1657
+ throw new CLIError("Usage: corners workstream push <workstreamId>", {
1658
+ json: common.json,
1659
+ });
1660
+ }
957
1661
  const message = parsed.values.message ??
958
- (rest.length > 0
959
- ? rest.join(" ")
1662
+ (parsed.positionals.length > 1
1663
+ ? parsed.positionals.slice(1).join(" ")
960
1664
  : await readTextFromStdin(runtime.stdin));
961
1665
  if (!message) {
962
1666
  throw new CLIError("Workstream updates need a message. Use --message or pipe text on stdin.", { json: common.json });
963
1667
  }
964
- const { profileName, client } = await requireStoredProfile(runtime, common);
965
- const workstream = await resolveExplicitOrBoundWorkstream(runtime, common, client, profileName, workstreamId);
1668
+ const { client } = await requireCommandProfile(runtime, common);
1669
+ const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
966
1670
  let createdDocument = null;
967
1671
  if (parsed.values.file) {
968
1672
  const documentContent = await readFile(parsed.values.file, "utf8");
@@ -1026,9 +1730,12 @@ async function handleWorkstream(args, runtime, inherited) {
1026
1730
  printWorkstreamHelp(runtime);
1027
1731
  return 0;
1028
1732
  }
1029
- const { workstreamId } = maybeTakeWorkstreamId(parsed.positionals);
1030
- const { profileName, client } = await requireStoredProfile(runtime, common);
1031
- const workstream = await resolveExplicitOrBoundWorkstream(runtime, common, client, profileName, workstreamId);
1733
+ const workstreamId = parsed.positionals[0];
1734
+ if (!workstreamId) {
1735
+ throw new CLIError("Usage: corners workstream question list <workstreamId>", { json: common.json });
1736
+ }
1737
+ const { client } = await requireCommandProfile(runtime, common);
1738
+ const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
1032
1739
  const data = await client.graphql(WORKSTREAM_QUESTION_LIST_QUERY, {
1033
1740
  id: workstream.id,
1034
1741
  });
@@ -1071,16 +1778,19 @@ async function handleWorkstream(args, runtime, inherited) {
1071
1778
  printWorkstreamHelp(runtime);
1072
1779
  return 0;
1073
1780
  }
1074
- const { workstreamId, rest } = maybeTakeWorkstreamId(parsed.positionals);
1781
+ const workstreamId = parsed.positionals[0];
1782
+ if (!workstreamId) {
1783
+ throw new CLIError("Usage: corners workstream question ask <workstreamId> [--question <text>]", { json: common.json });
1784
+ }
1075
1785
  const question = parsed.values.question ??
1076
- (rest.length > 0
1077
- ? rest.join(" ")
1786
+ (parsed.positionals.length > 1
1787
+ ? parsed.positionals.slice(1).join(" ")
1078
1788
  : await readTextFromStdin(runtime.stdin));
1079
1789
  if (!question) {
1080
1790
  throw new CLIError("Question text is required. Use --question or pipe text on stdin.", { json: common.json });
1081
1791
  }
1082
- const { profileName, client } = await requireStoredProfile(runtime, common);
1083
- const workstream = await resolveExplicitOrBoundWorkstream(runtime, common, client, profileName, workstreamId);
1792
+ const { client } = await requireCommandProfile(runtime, common);
1793
+ const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
1084
1794
  const result = await client.graphql(CREATE_WORKSTREAM_QUESTION_MUTATION, {
1085
1795
  input: {
1086
1796
  workstreamId: workstream.id,
@@ -1131,7 +1841,7 @@ async function handleWorkstream(args, runtime, inherited) {
1131
1841
  json: common.json,
1132
1842
  });
1133
1843
  }
1134
- const { client } = await requireStoredProfile(runtime, common);
1844
+ const { client } = await requireCommandProfile(runtime, common);
1135
1845
  const result = await client.graphql(ANSWER_WORKSTREAM_QUESTION_MUTATION, {
1136
1846
  questionId,
1137
1847
  answerText: answer,
@@ -1170,14 +1880,14 @@ async function handleWorkstream(args, runtime, inherited) {
1170
1880
  printWorkstreamHelp(runtime);
1171
1881
  return 0;
1172
1882
  }
1173
- const { workstreamId } = maybeTakeWorkstreamId(parsed.positionals);
1883
+ const workstreamId = parsed.positionals[0];
1174
1884
  const kind = parsed.values.kind;
1175
1885
  const entityId = parsed.values["entity-id"];
1176
- if (!kind || !entityId) {
1177
- throw new CLIError("Usage: corners workstream attach [workstreamId] --kind <kind> --entity-id <id>", { json: common.json });
1886
+ if (!workstreamId || !kind || !entityId) {
1887
+ throw new CLIError("Usage: corners workstream attach <workstreamId> --kind <kind> --entity-id <id>", { json: common.json });
1178
1888
  }
1179
- const { profileName, client } = await requireStoredProfile(runtime, common);
1180
- const workstream = await resolveExplicitOrBoundWorkstream(runtime, common, client, profileName, workstreamId);
1889
+ const { client } = await requireCommandProfile(runtime, common);
1890
+ const workstream = await resolveExplicitWorkstream(client, common, workstreamId);
1181
1891
  const result = await client.graphql(ADD_WORKSTREAM_ATTACHMENT_MUTATION, {
1182
1892
  workstreamId: workstream.id,
1183
1893
  kind: toGraphQLAttachmentKind(kind),
@@ -1220,7 +1930,7 @@ async function handleWorkstream(args, runtime, inherited) {
1220
1930
  if (!threadId || !text) {
1221
1931
  throw new CLIError("Usage: corners workstream reply-thread <threadId> [--text <text>]", { json: common.json });
1222
1932
  }
1223
- const { client } = await requireStoredProfile(runtime, common);
1933
+ const { client } = await requireCommandProfile(runtime, common);
1224
1934
  const result = await client.graphql(REPLY_ARTIFACT_THREAD_MUTATION, {
1225
1935
  id: threadId,
1226
1936
  input: {
@@ -1260,6 +1970,15 @@ export async function runCli(argv, runtime = createRuntime()) {
1260
1970
  if (parsed.rest[1] === "auth") {
1261
1971
  printAuthHelp(runtime);
1262
1972
  }
1973
+ else if (parsed.rest[1] === "init") {
1974
+ printInitHelp(runtime);
1975
+ }
1976
+ else if (parsed.rest[1] === "corner") {
1977
+ printCornerHelp(runtime);
1978
+ }
1979
+ else if (parsed.rest[1] === "whoami") {
1980
+ printWhoamiHelp(runtime);
1981
+ }
1263
1982
  else if (parsed.rest[1] === "workstream" || parsed.rest[1] === "ws") {
1264
1983
  printWorkstreamHelp(runtime);
1265
1984
  }
@@ -1270,8 +1989,14 @@ export async function runCli(argv, runtime = createRuntime()) {
1270
1989
  case "version":
1271
1990
  printLine(runtime.stdout, runtime.getVersion());
1272
1991
  return 0;
1992
+ case "init":
1993
+ return handleInit(parsed.rest.slice(1), runtime, parsed.common);
1273
1994
  case "auth":
1274
1995
  return handleAuth(parsed.rest.slice(1), runtime, parsed.common);
1996
+ case "corner":
1997
+ return handleCorner(parsed.rest.slice(1), runtime, parsed.common);
1998
+ case "whoami":
1999
+ return handleWhoAmI(parsed.rest.slice(1), runtime, parsed.common);
1275
2000
  case "workstream":
1276
2001
  case "ws":
1277
2002
  return handleWorkstream(parsed.rest.slice(1), runtime, parsed.common);