@gh-symphony/cli 0.2.3 → 0.2.5

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.
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  workflow_init_default
4
- } from "./chunk-PLBG7TZA.js";
4
+ } from "./chunk-DTPIJO6S.js";
5
5
  import {
6
6
  fetchGithubProjectIssueByRepositoryAndNumber,
7
7
  inspectManagedProjectSelection,
8
8
  resolveTrackerAdapter
9
- } from "./chunk-ZGNAAHLD.js";
9
+ } from "./chunk-NRABQNAX.js";
10
10
  import {
11
11
  GitHubApiError,
12
12
  createClient,
@@ -14,7 +14,7 @@ import {
14
14
  getGhTokenWithSource,
15
15
  getProjectDetail,
16
16
  validateGitHubToken
17
- } from "./chunk-BOM2BYZQ.js";
17
+ } from "./chunk-SMNIGNS3.js";
18
18
  import {
19
19
  buildPromptVariables,
20
20
  parseWorkflowMarkdown,
@@ -11,7 +11,7 @@ import {
11
11
  listUserProjects,
12
12
  resolveGitHubAuth,
13
13
  validateToken
14
- } from "./chunk-BOM2BYZQ.js";
14
+ } from "./chunk-SMNIGNS3.js";
15
15
  import {
16
16
  formatClaudePreflightText,
17
17
  resolveClaudeCommandBinary,
@@ -1810,18 +1810,14 @@ var LINEAR_ISSUES_BY_STATES_QUERY = (
1810
1810
  /* GraphQL */
1811
1811
  `
1812
1812
  query SymphonyLinearIssues(
1813
- $projectSlug: String!
1814
- $stateNames: [String!]!
1813
+ $filter: IssueFilter!
1815
1814
  $first: Int!
1816
1815
  $after: String
1817
1816
  ) {
1818
1817
  issues(
1819
1818
  first: $first
1820
1819
  after: $after
1821
- filter: {
1822
- project: { slugId: { eq: $projectSlug } }
1823
- state: { name: { in: $stateNames } }
1824
- }
1820
+ filter: $filter
1825
1821
  ) {
1826
1822
  ${LINEAR_ISSUE_FIELDS}
1827
1823
  }
@@ -1832,18 +1828,14 @@ var LINEAR_ISSUES_BY_IDS_QUERY = (
1832
1828
  /* GraphQL */
1833
1829
  `
1834
1830
  query SymphonyLinearIssueStates(
1835
- $projectSlug: String!
1836
- $issueIds: [ID!]!
1831
+ $filter: IssueFilter!
1837
1832
  $first: Int!
1838
1833
  $after: String
1839
1834
  ) {
1840
1835
  issues(
1841
1836
  first: $first
1842
1837
  after: $after
1843
- filter: {
1844
- project: { slugId: { eq: $projectSlug } }
1845
- id: { in: $issueIds }
1846
- }
1838
+ filter: $filter
1847
1839
  ) {
1848
1840
  ${LINEAR_ISSUE_FIELDS}
1849
1841
  }
@@ -1854,18 +1846,14 @@ var LINEAR_ISSUES_BY_IDENTIFIERS_QUERY = (
1854
1846
  /* GraphQL */
1855
1847
  `
1856
1848
  query SymphonyLinearIssueStatesByIdentifier(
1857
- $projectSlug: String!
1858
- $issueIdentifiers: [String!]!
1849
+ $filter: IssueFilter!
1859
1850
  $first: Int!
1860
1851
  $after: String
1861
1852
  ) {
1862
1853
  issues(
1863
1854
  first: $first
1864
1855
  after: $after
1865
- filter: {
1866
- project: { slugId: { eq: $projectSlug } }
1867
- identifier: { in: $issueIdentifiers }
1868
- }
1856
+ filter: $filter
1869
1857
  ) {
1870
1858
  ${LINEAR_ISSUE_FIELDS}
1871
1859
  }
@@ -1940,6 +1928,7 @@ async function listLinearIssues(project, stateNamesInput, dependencies, issueIds
1940
1928
  stateNames,
1941
1929
  issueIds: issueIds && !issueIds.every(isLinearIdentifier) ? [...issueIds] : void 0,
1942
1930
  issueIdentifiers: issueIds && issueIds.every(isLinearIdentifier) ? issueIds.map((identifier) => identifier.trim().toUpperCase()) : void 0,
1931
+ assignedOnly: config.assignedOnly,
1943
1932
  pageSize: config.pageSize
1944
1933
  });
1945
1934
  const issues = result.nodes.map(
@@ -1951,6 +1940,12 @@ async function listLinearIssues(project, stateNamesInput, dependencies, issueIds
1951
1940
  value: result.rateLimits,
1952
1941
  writable: true
1953
1942
  });
1943
+ if (config.assignedOnly) {
1944
+ emitAssignedOnlyFilterEvent2({
1945
+ projectSlug: config.projectSlug,
1946
+ includedCount: issues.length
1947
+ });
1948
+ }
1954
1949
  return issues;
1955
1950
  }
1956
1951
  async function fetchPaginatedLinearIssues(client, input) {
@@ -1960,8 +1955,7 @@ async function fetchPaginatedLinearIssues(client, input) {
1960
1955
  do {
1961
1956
  const query = input.issueIdentifiers ? LINEAR_ISSUES_BY_IDENTIFIERS_QUERY : input.issueIds ? LINEAR_ISSUES_BY_IDS_QUERY : LINEAR_ISSUES_BY_STATES_QUERY;
1962
1957
  const response = await client(query, {
1963
- projectSlug: input.projectSlug,
1964
- ...input.issueIdentifiers ? { issueIdentifiers: input.issueIdentifiers } : input.issueIds ? { issueIds: input.issueIds } : { stateNames: input.stateNames ?? [] },
1958
+ filter: buildLinearIssueFilter(input),
1965
1959
  first: input.pageSize,
1966
1960
  after
1967
1961
  });
@@ -1975,6 +1969,13 @@ async function fetchPaginatedLinearIssues(client, input) {
1975
1969
  rateLimits: latestRateLimits
1976
1970
  };
1977
1971
  }
1972
+ function buildLinearIssueFilter(input) {
1973
+ return {
1974
+ project: { slugId: { eq: input.projectSlug } },
1975
+ ...input.issueIdentifiers ? { identifier: { in: input.issueIdentifiers } } : input.issueIds ? { id: { in: input.issueIds } } : { state: { name: { in: input.stateNames ?? [] } } },
1976
+ ...input.assignedOnly ? { assignee: { isMe: { eq: true } } } : {}
1977
+ };
1978
+ }
1978
1979
  function isLinearIdentifier(value) {
1979
1980
  const trimmed = value.trim();
1980
1981
  return trimmed === trimmed.toUpperCase() && LINEAR_IDENTIFIER_PATTERN.test(trimmed);
@@ -2097,11 +2098,29 @@ function resolveLinearTrackerConfig(project, dependencies) {
2097
2098
  }
2098
2099
  return {
2099
2100
  endpoint: resolveLinearEndpoint(project.tracker),
2101
+ assignedOnly: resolveAssignedOnly2(project.tracker, dependencies),
2100
2102
  pageSize: readPositiveIntegerSetting(project.tracker, "pageSize") ?? DEFAULT_PAGE_SIZE2,
2101
2103
  projectSlug,
2102
2104
  token
2103
2105
  };
2104
2106
  }
2107
+ var warnedLegacyAssignedOnlyProjectIds2 = /* @__PURE__ */ new Set();
2108
+ function resolveAssignedOnly2(tracker, dependencies) {
2109
+ if (dependencies.assignedOnly !== void 0) {
2110
+ return dependencies.assignedOnly;
2111
+ }
2112
+ const legacyAssignedOnly = readBooleanSetting(tracker, "assignedOnly");
2113
+ if (legacyAssignedOnly) {
2114
+ const warningKey = `${tracker.adapter}:${tracker.bindingId}`;
2115
+ if (!warnedLegacyAssignedOnlyProjectIds2.has(warningKey)) {
2116
+ warnedLegacyAssignedOnlyProjectIds2.add(warningKey);
2117
+ console.warn(
2118
+ "[gh-symphony] Deprecated tracker.settings.assignedOnly detected. Use 'gh-symphony repo start --assigned-only' instead; persisted assignedOnly support will be removed in the next major release."
2119
+ );
2120
+ }
2121
+ }
2122
+ return legacyAssignedOnly;
2123
+ }
2105
2124
  function resolveLinearEndpoint(tracker) {
2106
2125
  return tracker.apiUrl?.trim() || DEFAULT_LINEAR_GRAPHQL_URL2;
2107
2126
  }
@@ -2132,6 +2151,10 @@ function readPositiveIntegerSetting(tracker, key) {
2132
2151
  `Tracker adapter "${tracker.adapter}" requires the "${key}" setting to be a positive integer when provided.`
2133
2152
  );
2134
2153
  }
2154
+ function readBooleanSetting(tracker, key) {
2155
+ const value = tracker.settings?.[key];
2156
+ return value === true || value === "true";
2157
+ }
2135
2158
  function readStringArray(value) {
2136
2159
  if (value === void 0) {
2137
2160
  return void 0;
@@ -2144,6 +2167,18 @@ function readStringArray(value) {
2144
2167
  }
2145
2168
  return value.filter((entry) => typeof entry === "string");
2146
2169
  }
2170
+ function emitAssignedOnlyFilterEvent2(input) {
2171
+ console.info(
2172
+ JSON.stringify({
2173
+ event: "tracker-assigned-only-filtered",
2174
+ tracker: "linear",
2175
+ projectSlug: input.projectSlug,
2176
+ assigneeFilter: "isMe",
2177
+ includedCount: input.includedCount,
2178
+ excludedCount: null
2179
+ })
2180
+ );
2181
+ }
2147
2182
  function requireString(value, label) {
2148
2183
  if (typeof value !== "string" || value.length === 0) {
2149
2184
  throw new Error(`${label} is required.`);
@@ -517,9 +517,10 @@ var PROJECT_ITEMS_PAGE_QUERY = `
517
517
  import { execFileSync, spawnSync } from "child_process";
518
518
  var REQUIRED_GH_SCOPES = ["repo", "read:org", "project"];
519
519
  var GhAuthError = class extends Error {
520
- constructor(code, message) {
520
+ constructor(code, message, details = {}) {
521
521
  super(message);
522
522
  this.code = code;
523
+ this.details = details;
523
524
  this.name = "GhAuthError";
524
525
  }
525
526
  };
@@ -527,7 +528,77 @@ function ghTokenReadErrorMessage() {
527
528
  return "Failed to read a GitHub token from gh CLI. Run 'gh auth status' and try again.";
528
529
  }
529
530
  function missingGhScopesMessage(missing) {
530
- return `Run 'gh auth refresh --scopes repo,read:org,project'. Missing scopes: ${missing.join(", ")}`;
531
+ return `Run 'gh auth refresh --scopes ${REQUIRED_GH_SCOPES.join(",")}'. Missing scopes: ${missing.join(", ")}`;
532
+ }
533
+ function parseMissingScopes(message) {
534
+ const matched = message.match(/Missing scopes:\s*([^.\n]+)/i);
535
+ if (!matched) {
536
+ return [];
537
+ }
538
+ return matched[1].split(",").map((scope) => scope.trim()).filter((scope) => scope.length > 0);
539
+ }
540
+ function formatGhAuthRemediation(error, opts) {
541
+ const retryCommand = opts?.retryCommand ?? "gh-symphony repo start";
542
+ switch (error.code) {
543
+ case "not_installed":
544
+ return {
545
+ title: "GitHub authentication is not available",
546
+ message: error.message,
547
+ hint: "Install gh CLI from https://cli.github.com or set GITHUB_GRAPHQL_TOKEN."
548
+ };
549
+ case "not_authenticated": {
550
+ const command = `gh auth login --scopes ${REQUIRED_GH_SCOPES.join(",")}`;
551
+ return {
552
+ title: "GitHub authentication is required",
553
+ message: error.message,
554
+ command,
555
+ hint: `Run '${command}', then re-run '${retryCommand}'.`
556
+ };
557
+ }
558
+ case "missing_scopes": {
559
+ if (error.details.source === "env") {
560
+ return {
561
+ title: "GitHub token is missing required scopes",
562
+ message: error.message,
563
+ hint: `Update GITHUB_GRAPHQL_TOKEN with a token that has ${REQUIRED_GH_SCOPES.join(",")} scopes, or unset it to use gh CLI auth, then re-run '${retryCommand}'.`
564
+ };
565
+ }
566
+ const currentSet = new Set(
567
+ (error.details.currentScopes ?? []).map((scope) => scope.toLowerCase())
568
+ );
569
+ const inferredMissing = currentSet.size > 0 ? REQUIRED_GH_SCOPES.filter((scope) => !currentSet.has(scope)) : [];
570
+ const missing = error.details.missingScopes?.length ? error.details.missingScopes : inferredMissing.length > 0 ? inferredMissing : parseMissingScopes(error.message);
571
+ const scopeArg = missing.length > 0 ? missing.join(",") : REQUIRED_GH_SCOPES.join(",");
572
+ const command = `gh auth refresh --scopes ${scopeArg}`;
573
+ return {
574
+ title: "GitHub token is missing required scopes",
575
+ message: error.message,
576
+ command,
577
+ hint: `Run '${command}', then re-run '${retryCommand}'.`
578
+ };
579
+ }
580
+ case "invalid_token":
581
+ case "token_failed": {
582
+ if (error.details.source === "env") {
583
+ return {
584
+ title: "GitHub token validation failed",
585
+ message: error.message,
586
+ hint: `Update or unset GITHUB_GRAPHQL_TOKEN, then re-run '${retryCommand}'.`
587
+ };
588
+ }
589
+ const command = `gh auth login --scopes ${REQUIRED_GH_SCOPES.join(",")}`;
590
+ return {
591
+ title: "GitHub token validation failed",
592
+ message: error.message,
593
+ command,
594
+ hint: `Run '${command}' to re-authenticate, then re-run '${retryCommand}'.`
595
+ };
596
+ }
597
+ default: {
598
+ const exhaustive = error.code;
599
+ throw new Error(`Unhandled GhAuthError code: ${exhaustive}`);
600
+ }
601
+ }
531
602
  }
532
603
  function classifyTokenValidationError(error, source) {
533
604
  if (error instanceof GhAuthError) {
@@ -537,19 +608,25 @@ function classifyTokenValidationError(error, source) {
537
608
  if (error.status === 401) {
538
609
  return new GhAuthError(
539
610
  source === "env" ? "invalid_token" : "token_failed",
540
- source === "env" ? "GITHUB_GRAPHQL_TOKEN is invalid or expired." : ghTokenReadErrorMessage()
611
+ source === "env" ? "GITHUB_GRAPHQL_TOKEN is invalid or expired." : ghTokenReadErrorMessage(),
612
+ { source }
541
613
  );
542
614
  }
543
615
  const prefix = source === "env" ? "GITHUB_GRAPHQL_TOKEN could not be validated" : "gh CLI token could not be validated";
544
- return new GhAuthError("token_failed", `${prefix}: ${error.message}`);
616
+ return new GhAuthError("token_failed", `${prefix}: ${error.message}`, {
617
+ source
618
+ });
545
619
  }
546
620
  if (error instanceof Error) {
547
621
  const prefix = source === "env" ? "GITHUB_GRAPHQL_TOKEN could not be validated" : "gh CLI token could not be validated";
548
- return new GhAuthError("token_failed", `${prefix}: ${error.message}`);
622
+ return new GhAuthError("token_failed", `${prefix}: ${error.message}`, {
623
+ source
624
+ });
549
625
  }
550
626
  return new GhAuthError(
551
627
  "token_failed",
552
- source === "env" ? "GITHUB_GRAPHQL_TOKEN could not be validated." : "gh CLI token could not be validated."
628
+ source === "env" ? "GITHUB_GRAPHQL_TOKEN could not be validated." : "gh CLI token could not be validated.",
629
+ { source }
553
630
  );
554
631
  }
555
632
  function getEnvGitHubToken() {
@@ -651,12 +728,22 @@ async function validateGitHubToken(token, source, opts) {
651
728
  if (source === "env") {
652
729
  throw new GhAuthError(
653
730
  "missing_scopes",
654
- `GITHUB_GRAPHQL_TOKEN is missing required scopes: ${scopeCheck.missing.join(", ")}`
731
+ `GITHUB_GRAPHQL_TOKEN is missing required scopes: ${scopeCheck.missing.join(", ")}`,
732
+ {
733
+ missingScopes: [...scopeCheck.missing],
734
+ currentScopes: viewer.scopes,
735
+ source
736
+ }
655
737
  );
656
738
  }
657
739
  throw new GhAuthError(
658
740
  "missing_scopes",
659
- missingGhScopesMessage(scopeCheck.missing)
741
+ missingGhScopesMessage(scopeCheck.missing),
742
+ {
743
+ missingScopes: [...scopeCheck.missing],
744
+ currentScopes: viewer.scopes,
745
+ source
746
+ }
660
747
  );
661
748
  }
662
749
  return {
@@ -696,21 +783,28 @@ function ensureGhAuth(opts) {
696
783
  if (!checkGhInstalled({ execImpl })) {
697
784
  throw new GhAuthError(
698
785
  "not_installed",
699
- "gh CLI is not installed. Install it from https://cli.github.com or set GITHUB_GRAPHQL_TOKEN."
786
+ "gh CLI is not installed. Install it from https://cli.github.com or set GITHUB_GRAPHQL_TOKEN.",
787
+ { source: "gh" }
700
788
  );
701
789
  }
702
790
  const auth = checkGhAuthenticated({ spawnImpl });
703
791
  if (!auth.authenticated) {
704
792
  throw new GhAuthError(
705
793
  "not_authenticated",
706
- "Run 'gh auth login --scopes repo,read:org,project' or set GITHUB_GRAPHQL_TOKEN."
794
+ "Run 'gh auth login --scopes repo,read:org,project' or set GITHUB_GRAPHQL_TOKEN.",
795
+ { source: "gh" }
707
796
  );
708
797
  }
709
798
  const scopeCheck = checkGhScopes({ spawnImpl });
710
799
  if (!scopeCheck.valid) {
711
800
  throw new GhAuthError(
712
801
  "missing_scopes",
713
- missingGhScopesMessage(scopeCheck.missing)
802
+ missingGhScopesMessage(scopeCheck.missing),
803
+ {
804
+ missingScopes: [...scopeCheck.missing],
805
+ currentScopes: scopeCheck.scopes,
806
+ source: "gh"
807
+ }
714
808
  );
715
809
  }
716
810
  const { token } = getGhTokenWithSource({
@@ -789,6 +883,7 @@ export {
789
883
  getProjectDetail,
790
884
  REQUIRED_GH_SCOPES,
791
885
  GhAuthError,
886
+ formatGhAuthRemediation,
792
887
  getEnvGitHubToken,
793
888
  checkGhInstalled,
794
889
  checkGhAuthenticated,
@@ -5,13 +5,13 @@ import {
5
5
  parseIssueReference,
6
6
  readGitHubProjectBinding,
7
7
  renderIssueWorkflowPreview
8
- } from "./chunk-AA3T5AAJ.js";
9
- import "./chunk-PLBG7TZA.js";
8
+ } from "./chunk-5U36B7FC.js";
9
+ import "./chunk-DTPIJO6S.js";
10
10
  import {
11
11
  fetchGithubProjectIssueByRepositoryAndNumber,
12
12
  fetchGithubProjectIssues,
13
13
  inspectManagedProjectSelection
14
- } from "./chunk-ZGNAAHLD.js";
14
+ } from "./chunk-NRABQNAX.js";
15
15
  import "./chunk-FAU72YC2.js";
16
16
  import {
17
17
  resolveRuntimeRoot
@@ -31,7 +31,7 @@ import {
31
31
  runGhAuthLogin,
32
32
  runGhAuthRefresh,
33
33
  validateGitHubToken
34
- } from "./chunk-BOM2BYZQ.js";
34
+ } from "./chunk-SMNIGNS3.js";
35
35
  import {
36
36
  isClaudeRuntimeCommand,
37
37
  parseWorkflowMarkdown,
package/dist/index.js CHANGED
@@ -282,7 +282,7 @@ var HELP_SECTIONS = [
282
282
  },
283
283
  {
284
284
  name: "repo start",
285
- description: "Start the orchestrator (foreground)"
285
+ description: "Start the orchestrator after validating tracker authentication"
286
286
  },
287
287
  {
288
288
  name: "repo start --daemon",
@@ -417,13 +417,13 @@ function createRemovedCommandHandler(message) {
417
417
 
418
418
  // src/index.ts
419
419
  var COMMANDS = {
420
- workflow: () => import("./workflow-ZPERNZJT.js"),
421
- setup: () => import("./setup-KZ3U53PY.js"),
422
- doctor: () => import("./doctor-GIJAH7MA.js"),
423
- upgrade: () => import("./upgrade-K2PNQNWE.js"),
424
- repo: () => import("./repo-LNO3Q3O7.js"),
420
+ workflow: () => import("./workflow-AV676KAP.js"),
421
+ setup: () => import("./setup-T2QENR26.js"),
422
+ doctor: () => import("./doctor-TQR54KNZ.js"),
423
+ upgrade: () => import("./upgrade-7452LZXX.js"),
424
+ repo: () => import("./repo-Y6EF2DZP.js"),
425
425
  config: () => import("./config-cmd-AOZVS6GU.js"),
426
- version: () => import("./version-E45DDQPQ.js")
426
+ version: () => import("./version-D3FB3PXO.js")
427
427
  };
428
428
  function addGlobalOptions(command) {
429
429
  return command.option("--config <dir>", "Config directory").addOption(new Option("--config-dir <dir>").hideHelp()).option("-v, --verbose", "Enable verbose output").option("--json", "Output in JSON format").option("--no-color", "Disable color output");
@@ -33,7 +33,7 @@ import {
33
33
  resolveOrchestratorLogLevel,
34
34
  resolveTrackerAdapter,
35
35
  runCli
36
- } from "./chunk-ZGNAAHLD.js";
36
+ } from "./chunk-NRABQNAX.js";
37
37
  import "./chunk-FAU72YC2.js";
38
38
  import {
39
39
  resolveRepoRuntimeRoot,
@@ -41,8 +41,14 @@ import {
41
41
  } from "./chunk-RZ3WO7OV.js";
42
42
  import {
43
43
  GhAuthError,
44
- getGhToken
45
- } from "./chunk-BOM2BYZQ.js";
44
+ GitHubApiError,
45
+ GitHubScopeError,
46
+ formatGhAuthRemediation,
47
+ getGhToken,
48
+ resolveGitHubAuth,
49
+ runGhAuthLogin,
50
+ runGhAuthRefresh
51
+ } from "./chunk-SMNIGNS3.js";
46
52
  import {
47
53
  WorkflowConfigStore,
48
54
  deriveIssueWorkspaceKeyFromIdentifier,
@@ -765,6 +771,7 @@ import { writeFile, mkdir, readFile as readFile5, rm } from "fs/promises";
765
771
  import { dirname as dirname2, join as join6 } from "path";
766
772
  import { spawn } from "child_process";
767
773
  import { createServer as createServer3 } from "http";
774
+ import * as p from "@clack/prompts";
768
775
 
769
776
  // ../dashboard/src/store.ts
770
777
  import { open } from "fs/promises";
@@ -1437,6 +1444,149 @@ function logLine(icon, msg) {
1437
1444
  process.stdout.write(`${timestamp()} ${icon} ${msg}
1438
1445
  `);
1439
1446
  }
1447
+ var REPO_START_COMMAND = "gh-symphony repo start";
1448
+ function isInteractiveTerminal() {
1449
+ return process.stdin.isTTY === true && process.stdout.isTTY === true;
1450
+ }
1451
+ function displayGhAuthError(error) {
1452
+ const remediation = formatGhAuthRemediation(error, {
1453
+ retryCommand: REPO_START_COMMAND
1454
+ });
1455
+ process.stderr.write(`${remediation.title}: ${remediation.message}
1456
+ `);
1457
+ process.stderr.write(`${remediation.hint}
1458
+ `);
1459
+ }
1460
+ function formatAuthSource(source) {
1461
+ return source === "env" ? "GITHUB_GRAPHQL_TOKEN" : "gh CLI";
1462
+ }
1463
+ function displayGitHubAuthSuccess(auth) {
1464
+ process.stdout.write(
1465
+ `Authenticated via ${formatAuthSource(auth.source)} as ${auth.login}
1466
+ `
1467
+ );
1468
+ }
1469
+ async function resolveRepoStartGitHubAuth(input) {
1470
+ try {
1471
+ const auth = await resolveGitHubAuth();
1472
+ process.env.GITHUB_GRAPHQL_TOKEN = auth.token;
1473
+ displayGitHubAuthSuccess(auth);
1474
+ return { ok: true, githubAuthSource: auth.source };
1475
+ } catch (error) {
1476
+ if (!(error instanceof GhAuthError)) {
1477
+ throw error;
1478
+ }
1479
+ displayGhAuthError(error);
1480
+ const remediation = formatGhAuthRemediation(error, {
1481
+ retryCommand: REPO_START_COMMAND
1482
+ });
1483
+ const canRemediate = input.allowInteractiveRemediation && isInteractiveTerminal() && remediation.command !== void 0 && error.details.source !== "env";
1484
+ if (!canRemediate) {
1485
+ process.exitCode = 1;
1486
+ return { ok: false };
1487
+ }
1488
+ const shouldRun = await p.confirm({
1489
+ message: `Run '${remediation.command}' now?`,
1490
+ initialValue: true
1491
+ });
1492
+ if (p.isCancel(shouldRun) || shouldRun !== true) {
1493
+ process.exitCode = 1;
1494
+ return { ok: false };
1495
+ }
1496
+ const result = error.code === "missing_scopes" ? runGhAuthRefresh({ interactive: true }) : runGhAuthLogin({ interactive: true });
1497
+ process.stderr.write(`${result.summary}
1498
+ `);
1499
+ if (result.status !== "applied") {
1500
+ process.exitCode = 1;
1501
+ return { ok: false };
1502
+ }
1503
+ try {
1504
+ const auth = await resolveGitHubAuth();
1505
+ process.env.GITHUB_GRAPHQL_TOKEN = auth.token;
1506
+ displayGitHubAuthSuccess(auth);
1507
+ return { ok: true, githubAuthSource: auth.source };
1508
+ } catch (retryError) {
1509
+ if (retryError instanceof GhAuthError) {
1510
+ displayGhAuthError(retryError);
1511
+ process.exitCode = 1;
1512
+ return { ok: false };
1513
+ }
1514
+ throw retryError;
1515
+ }
1516
+ }
1517
+ }
1518
+ async function preflightRepoStartAuth(projectConfig, input) {
1519
+ if (projectConfig.tracker.adapter === "github-project") {
1520
+ return resolveRepoStartGitHubAuth({
1521
+ allowInteractiveRemediation: !input.daemon
1522
+ });
1523
+ }
1524
+ if (projectConfig.tracker.adapter === "linear") {
1525
+ if (process.env.LINEAR_API_KEY?.trim()) {
1526
+ return { ok: true };
1527
+ }
1528
+ process.stderr.write(
1529
+ "Linear authentication is required. Set LINEAR_API_KEY in the environment before running 'gh-symphony repo start'.\n"
1530
+ );
1531
+ process.exitCode = 1;
1532
+ return { ok: false };
1533
+ }
1534
+ return { ok: true };
1535
+ }
1536
+ function isGitHubAuthRuntimeError(error) {
1537
+ if (error instanceof GitHubScopeError) {
1538
+ return true;
1539
+ }
1540
+ if (error instanceof GhAuthError) {
1541
+ return error.code === "missing_scopes" || error.code === "invalid_token";
1542
+ }
1543
+ if (error instanceof GitHubApiError) {
1544
+ return error.status === 401;
1545
+ }
1546
+ if (error instanceof Error) {
1547
+ const maybeStatus = error.status;
1548
+ if (maybeStatus === 401) {
1549
+ return true;
1550
+ }
1551
+ const message = error.message.toLowerCase();
1552
+ return message.includes("missing required github scopes") || message.includes("missing required scopes") || message.includes("missing required scope") || message.includes("missing_scopes") || message.includes("bad credentials") || message.includes("invalid token") || message.includes("authentication failed") || message.includes("status 401") || message.includes("401 unauthorized");
1553
+ }
1554
+ return false;
1555
+ }
1556
+ function ghRuntimeErrorToAuthError(error, source) {
1557
+ if (error instanceof GhAuthError) {
1558
+ return error;
1559
+ }
1560
+ if (error instanceof GitHubScopeError) {
1561
+ return new GhAuthError(
1562
+ "missing_scopes",
1563
+ `GitHub token is missing required scopes: ${error.requiredScopes.join(", ")}`,
1564
+ {
1565
+ missingScopes: [...error.requiredScopes],
1566
+ currentScopes: [...error.currentScopes],
1567
+ source
1568
+ }
1569
+ );
1570
+ }
1571
+ if (error.message.toLowerCase().includes("missing required github scopes") || error.message.toLowerCase().includes("missing required scopes") || error.message.toLowerCase().includes("missing_scopes")) {
1572
+ return new GhAuthError("missing_scopes", error.message, { source });
1573
+ }
1574
+ return new GhAuthError(
1575
+ "invalid_token",
1576
+ error.message || "GitHub token validation failed.",
1577
+ { source }
1578
+ );
1579
+ }
1580
+ function displayRuntimeAuthShutdown(error, source) {
1581
+ const authError = ghRuntimeErrorToAuthError(error, source);
1582
+ displayGhAuthError(authError);
1583
+ process.stderr.write(
1584
+ "Stopping repo start because GitHub authentication can no longer be validated.\n"
1585
+ );
1586
+ }
1587
+ function shouldElevateGitHubAuthRuntimeError(projectConfig, error) {
1588
+ return projectConfig.tracker.adapter === "github-project" && isGitHubAuthRuntimeError(error);
1589
+ }
1440
1590
  var DEFAULT_HTTP_PORT = 4680;
1441
1591
  var HTTP_HOST = "0.0.0.0";
1442
1592
  function parseStartArgs(args) {
@@ -1762,6 +1912,12 @@ var handler5 = async (args, options) => {
1762
1912
  process.exitCode = 2;
1763
1913
  return;
1764
1914
  }
1915
+ const authPreflight = await preflightRepoStartAuth(projectConfig, {
1916
+ daemon: parsed.daemon
1917
+ });
1918
+ if (!authPreflight.ok) {
1919
+ return;
1920
+ }
1765
1921
  if (parsed.daemon) {
1766
1922
  await startDaemon(
1767
1923
  options,
@@ -1773,12 +1929,6 @@ var handler5 = async (args, options) => {
1773
1929
  );
1774
1930
  return;
1775
1931
  }
1776
- if (!process.env.GITHUB_GRAPHQL_TOKEN) {
1777
- try {
1778
- process.env.GITHUB_GRAPHQL_TOKEN = getGhToken();
1779
- } catch {
1780
- }
1781
- }
1782
1932
  let projectLock = null;
1783
1933
  try {
1784
1934
  projectLock = await acquireProjectLock({
@@ -1789,11 +1939,29 @@ var handler5 = async (args, options) => {
1789
1939
  const store = createStore(runtimeRoot);
1790
1940
  let prevSnapshot = null;
1791
1941
  let isFirst = true;
1942
+ let requestShutdown = null;
1943
+ let authShutdownRequested = false;
1792
1944
  const service = new OrchestratorService(store, projectConfig, {
1793
1945
  logLevel,
1794
1946
  assignedOnly: parsed.assignedOnly,
1795
1947
  onTick: async (snapshot) => {
1796
1948
  try {
1949
+ if (authShutdownRequested) {
1950
+ return;
1951
+ }
1952
+ if (projectConfig.tracker.adapter === "github-project" && snapshot.lastError) {
1953
+ const runtimeError = new Error(snapshot.lastError);
1954
+ if (isGitHubAuthRuntimeError(runtimeError)) {
1955
+ authShutdownRequested = true;
1956
+ displayRuntimeAuthShutdown(
1957
+ runtimeError,
1958
+ authPreflight.githubAuthSource
1959
+ );
1960
+ process.exitCode = 1;
1961
+ requestShutdown?.();
1962
+ return;
1963
+ }
1964
+ }
1797
1965
  logTickResult(snapshot, prevSnapshot, isFirst);
1798
1966
  if (!isFirst) {
1799
1967
  const currentRunIds = new Set(
@@ -1813,6 +1981,13 @@ var handler5 = async (args, options) => {
1813
1981
  prevSnapshot = snapshot;
1814
1982
  isFirst = false;
1815
1983
  } catch (error) {
1984
+ if (shouldElevateGitHubAuthRuntimeError(projectConfig, error)) {
1985
+ authShutdownRequested = true;
1986
+ displayRuntimeAuthShutdown(error, authPreflight.githubAuthSource);
1987
+ process.exitCode = 1;
1988
+ requestShutdown?.();
1989
+ return;
1990
+ }
1816
1991
  logLine(
1817
1992
  red("\u2717"),
1818
1993
  red(
@@ -1850,6 +2025,9 @@ var handler5 = async (args, options) => {
1850
2025
  const handleSigterm = () => {
1851
2026
  void shutdown();
1852
2027
  };
2028
+ requestShutdown = () => {
2029
+ void shutdown();
2030
+ };
1853
2031
  process.on("SIGINT", handleSigint);
1854
2032
  process.on("SIGTERM", handleSigterm);
1855
2033
  try {
@@ -1923,6 +2101,13 @@ var handler5 = async (args, options) => {
1923
2101
  if (shuttingDown) {
1924
2102
  break;
1925
2103
  }
2104
+ if (shouldElevateGitHubAuthRuntimeError(projectConfig, error)) {
2105
+ authShutdownRequested = true;
2106
+ displayRuntimeAuthShutdown(error, authPreflight.githubAuthSource);
2107
+ process.exitCode = 1;
2108
+ await shutdown();
2109
+ return;
2110
+ }
1926
2111
  logLine(
1927
2112
  red("\u2717"),
1928
2113
  red(
@@ -1998,7 +2183,7 @@ async function shutdownForegroundOrchestrator(input) {
1998
2183
  `Failed to release project lock: ${error instanceof Error ? error.message : "Unknown error"}`
1999
2184
  );
2000
2185
  }
2001
- return (input.exit ?? process.exit)(0);
2186
+ return (input.exit ?? process.exit)(process.exitCode ?? 0);
2002
2187
  }
2003
2188
  function hasConfiguredRepository(config) {
2004
2189
  return Boolean(config.repository?.owner && config.repository.name);
@@ -12,7 +12,7 @@ import {
12
12
  validateStateMapping,
13
13
  writeEcosystem,
14
14
  writeWorkflowPlan
15
- } from "./chunk-PLBG7TZA.js";
15
+ } from "./chunk-DTPIJO6S.js";
16
16
  import {
17
17
  initRepoRuntime
18
18
  } from "./chunk-DLZ2XHWY.js";
@@ -20,6 +20,7 @@ import "./chunk-RZ3WO7OV.js";
20
20
  import {
21
21
  GhAuthError,
22
22
  GitHubScopeError,
23
+ REQUIRED_GH_SCOPES,
23
24
  checkRequiredScopes,
24
25
  createClient,
25
26
  ensureGhAuth,
@@ -27,14 +28,13 @@ import {
27
28
  getProjectDetail,
28
29
  listUserProjects,
29
30
  validateToken
30
- } from "./chunk-BOM2BYZQ.js";
31
+ } from "./chunk-SMNIGNS3.js";
31
32
  import "./chunk-3SKN5L3I.js";
32
33
  import "./chunk-4ICDSQCJ.js";
33
34
 
34
35
  // src/commands/setup.ts
35
36
  import * as p from "@clack/prompts";
36
37
  import { resolve } from "path";
37
- var KNOWN_REQUIRED_SCOPES = ["repo", "read:org", "project"];
38
38
  function parseSetupFlags(args) {
39
39
  const flags = {
40
40
  nonInteractive: false,
@@ -74,7 +74,7 @@ function displayScopeError(error, retryCommand) {
74
74
  `Token is missing required scope${plural}: ${error.requiredScopes.join(", ")}`
75
75
  );
76
76
  const currentSet = new Set(error.currentScopes.map((s) => s.toLowerCase()));
77
- const scopesToAdd = KNOWN_REQUIRED_SCOPES.filter((s) => !currentSet.has(s));
77
+ const scopesToAdd = REQUIRED_GH_SCOPES.filter((s) => !currentSet.has(s));
78
78
  const scopeArg = scopesToAdd.length > 0 ? scopesToAdd.join(",") : error.requiredScopes.join(",");
79
79
  p.note(
80
80
  `gh auth refresh --scopes ${scopeArg}
@@ -283,11 +283,17 @@ async function runInteractive(flags, _options) {
283
283
  authSpinner.stop("Authentication failed.");
284
284
  if (error instanceof GhAuthError) {
285
285
  if (error.code === "not_installed") {
286
- p.log.error("gh CLI\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. https://cli.github.com \uC5D0\uC11C \uC124\uCE58\uD558\uC138\uC694.");
286
+ p.log.error(
287
+ "gh CLI\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. https://cli.github.com \uC5D0\uC11C \uC124\uCE58\uD558\uC138\uC694."
288
+ );
287
289
  } else if (error.code === "not_authenticated") {
288
- p.log.error("gh auth login --scopes repo,read:org,project \uB97C \uC2E4\uD589\uD558\uC138\uC694.");
290
+ p.log.error(
291
+ "gh auth login --scopes repo,read:org,project \uB97C \uC2E4\uD589\uD558\uC138\uC694."
292
+ );
289
293
  } else if (error.code === "missing_scopes") {
290
- p.log.error("gh auth refresh --scopes repo,read:org,project \uB97C \uC2E4\uD589\uD558\uC138\uC694.");
294
+ p.log.error(
295
+ "gh auth refresh --scopes repo,read:org,project \uB97C \uC2E4\uD589\uD558\uC138\uC694."
296
+ );
291
297
  } else {
292
298
  p.log.error(error.message);
293
299
  }
@@ -409,7 +415,9 @@ async function runInteractive(flags, _options) {
409
415
  repoDir: process.cwd(),
410
416
  workflowFile: workflowPath
411
417
  });
412
- writeSpinner.stop(`Setup saved for ${runtime.repository.owner}/${runtime.repository.name}.`);
418
+ writeSpinner.stop(
419
+ `Setup saved for ${runtime.repository.owner}/${runtime.repository.name}.`
420
+ );
413
421
  } catch (error) {
414
422
  writeSpinner.stop("Setup failed.");
415
423
  p.log.error(error instanceof Error ? error.message : "Unknown error");
@@ -16,8 +16,8 @@ function execFileAsync(file, args, execFileImpl = execFileCallback) {
16
16
  });
17
17
  }
18
18
  function resolveCurrentCliVersion() {
19
- if ("0.2.3".length > 0) {
20
- return "0.2.3";
19
+ if ("0.2.5".length > 0) {
20
+ return "0.2.5";
21
21
  }
22
22
  const pkg = JSON.parse(
23
23
  readFileSync(new URL("../../package.json", import.meta.url), "utf8")
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/commands/version.ts
4
4
  var handler = async (_args, options) => {
5
- const version = "0.2.3";
5
+ const version = "0.2.5";
6
6
  if (options.json) {
7
7
  process.stdout.write(JSON.stringify({ version }) + "\n");
8
8
  } else {
@@ -6,11 +6,11 @@ import {
6
6
  resetWorkflowCommandDependenciesForTest,
7
7
  setWorkflowCommandDependenciesForTest,
8
8
  workflow_default
9
- } from "./chunk-AA3T5AAJ.js";
10
- import "./chunk-PLBG7TZA.js";
11
- import "./chunk-ZGNAAHLD.js";
9
+ } from "./chunk-5U36B7FC.js";
10
+ import "./chunk-DTPIJO6S.js";
11
+ import "./chunk-NRABQNAX.js";
12
12
  import "./chunk-FAU72YC2.js";
13
- import "./chunk-BOM2BYZQ.js";
13
+ import "./chunk-SMNIGNS3.js";
14
14
  import "./chunk-3SKN5L3I.js";
15
15
  import "./chunk-4ICDSQCJ.js";
16
16
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gh-symphony/cli",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "license": "MIT",
5
5
  "author": "hojinzs",
6
6
  "description": "Interactive CLI for GitHub Symphony orchestration",
@@ -43,11 +43,11 @@
43
43
  "tsup": "^8.5.1",
44
44
  "@gh-symphony/core": "0.0.14",
45
45
  "@gh-symphony/dashboard": "0.0.14",
46
+ "@gh-symphony/control-plane": "0.0.15",
46
47
  "@gh-symphony/orchestrator": "0.0.14",
47
48
  "@gh-symphony/runtime-claude": "0.0.14",
48
- "@gh-symphony/worker": "0.0.14",
49
49
  "@gh-symphony/tracker-github": "0.0.14",
50
- "@gh-symphony/control-plane": "0.0.15"
50
+ "@gh-symphony/worker": "0.0.14"
51
51
  },
52
52
  "scripts": {
53
53
  "build": "tsup",