@gh-symphony/cli 0.4.6 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  workflow_init_default
4
- } from "./chunk-DENDF6S6.js";
4
+ } from "./chunk-775A5LB5.js";
5
5
  import {
6
6
  fetchGithubProjectIssueByRepositoryAndNumber,
7
7
  inspectManagedProjectSelection,
8
8
  resolveTrackerAdapter
9
- } from "./chunk-RMNLHTIK.js";
9
+ } from "./chunk-C4QCVQVY.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-SMNIGNS3.js";
17
+ } from "./chunk-FFY5VKNV.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-SMNIGNS3.js";
14
+ } from "./chunk-FFY5VKNV.js";
15
15
  import {
16
16
  formatClaudePreflightText,
17
17
  resolveClaudeCommandBinary,
@@ -1470,8 +1470,10 @@ var githubProjectTrackerAdapter = {
1470
1470
  return fetchProjectIssueStatesByIds(project, issueIds, dependencies);
1471
1471
  },
1472
1472
  buildWorkerEnvironment(project) {
1473
+ const apiUrl = project.tracker.apiUrl?.trim();
1473
1474
  return {
1474
- GITHUB_PROJECT_ID: requireTrackerSetting(project.tracker, "projectId")
1475
+ GITHUB_PROJECT_ID: requireTrackerSetting(project.tracker, "projectId"),
1476
+ ...apiUrl ? { GITHUB_GRAPHQL_API_URL: apiUrl } : {}
1475
1477
  };
1476
1478
  },
1477
1479
  reviveIssue(project, run) {
@@ -1564,10 +1566,7 @@ function resolveAssignedOnly(tracker, dependencies) {
1564
1566
  if (dependencies.assignedOnly !== void 0) {
1565
1567
  return dependencies.assignedOnly;
1566
1568
  }
1567
- const legacyAssignedOnly = readBooleanTrackerSetting(
1568
- tracker,
1569
- "assignedOnly"
1570
- );
1569
+ const legacyAssignedOnly = readBooleanTrackerSetting(tracker, "assignedOnly");
1571
1570
  if (legacyAssignedOnly) {
1572
1571
  const warningKey = `${tracker.adapter}:${tracker.bindingId}`;
1573
1572
  if (!warnedLegacyAssignedOnlyProjectIds.has(warningKey)) {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/github/client.ts
4
- var DEFAULT_API_URL = "https://api.github.com/graphql";
4
+ var DEFAULT_GITHUB_GRAPHQL_API_URL = "https://api.github.com/graphql";
5
5
  var REST_API_URL = "https://api.github.com";
6
6
  function findLinkedRepository(project, owner, name) {
7
7
  const normalizedOwner = owner.trim().toLowerCase();
@@ -28,13 +28,35 @@ var GitHubScopeError = class extends GitHubApiError {
28
28
  function createClient(token, options) {
29
29
  return {
30
30
  token,
31
- apiUrl: options?.apiUrl ?? DEFAULT_API_URL,
31
+ apiUrl: options?.apiUrl ?? DEFAULT_GITHUB_GRAPHQL_API_URL,
32
32
  fetchImpl: options?.fetchImpl ?? fetch
33
33
  };
34
34
  }
35
+ function deriveGitHubRestApiUrl(graphqlApiUrl) {
36
+ try {
37
+ const url = new URL(graphqlApiUrl);
38
+ const normalizedPath = url.pathname.replace(/\/+$/, "");
39
+ if (url.hostname.toLowerCase() === "api.github.com") {
40
+ return REST_API_URL;
41
+ }
42
+ if (normalizedPath === "/api/graphql") {
43
+ url.pathname = "/api/v3";
44
+ url.search = "";
45
+ url.hash = "";
46
+ return url.toString().replace(/\/$/, "");
47
+ }
48
+ if (normalizedPath.endsWith("/graphql")) {
49
+ url.pathname = normalizedPath.slice(0, -"/graphql".length) || "/";
50
+ url.search = "";
51
+ url.hash = "";
52
+ return url.toString().replace(/\/$/, "");
53
+ }
54
+ } catch {
55
+ }
56
+ return REST_API_URL;
57
+ }
35
58
  async function listRepositoryLabels(client, owner, name) {
36
- const restUrl = client.apiUrl.replace("/graphql", "");
37
- const baseUrl = restUrl === client.apiUrl ? REST_API_URL : restUrl;
59
+ const baseUrl = deriveGitHubRestApiUrl(client.apiUrl);
38
60
  const labels = [];
39
61
  let page = 1;
40
62
  while (true) {
@@ -75,8 +97,7 @@ async function listRepositoryLabels(client, owner, name) {
75
97
  return labels;
76
98
  }
77
99
  async function validateToken(client) {
78
- const restUrl = client.apiUrl.replace("/graphql", "");
79
- const baseUrl = restUrl === client.apiUrl ? REST_API_URL : restUrl;
100
+ const baseUrl = deriveGitHubRestApiUrl(client.apiUrl);
80
101
  const response = await client.fetchImpl(`${baseUrl}/user`, {
81
102
  headers: {
82
103
  authorization: `Bearer ${client.token}`,
@@ -646,12 +667,20 @@ function checkGhInstalled(opts) {
646
667
  throw error;
647
668
  }
648
669
  }
670
+ function ghAuthHostArgs(hostname) {
671
+ const trimmed = hostname?.trim();
672
+ return trimmed ? ["--hostname", trimmed] : [];
673
+ }
649
674
  function checkGhAuthenticated(opts) {
650
675
  const spawnImpl = opts?.spawnImpl ?? spawnSync;
651
- const result = spawnImpl("gh", ["auth", "status"], {
652
- encoding: "utf8",
653
- stdio: ["pipe", "pipe", "pipe"]
654
- });
676
+ const result = spawnImpl(
677
+ "gh",
678
+ ["auth", "status", ...ghAuthHostArgs(opts?.hostname)],
679
+ {
680
+ encoding: "utf8",
681
+ stdio: ["pipe", "pipe", "pipe"]
682
+ }
683
+ );
655
684
  if ((result.status ?? 1) !== 0) {
656
685
  return { authenticated: false };
657
686
  }
@@ -660,10 +689,14 @@ function checkGhAuthenticated(opts) {
660
689
  }
661
690
  function checkGhScopes(opts) {
662
691
  const spawnImpl = opts?.spawnImpl ?? spawnSync;
663
- const result = spawnImpl("gh", ["auth", "status"], {
664
- encoding: "utf8",
665
- stdio: ["pipe", "pipe", "pipe"]
666
- });
692
+ const result = spawnImpl(
693
+ "gh",
694
+ ["auth", "status", ...ghAuthHostArgs(opts?.hostname)],
695
+ {
696
+ encoding: "utf8",
697
+ stdio: ["pipe", "pipe", "pipe"]
698
+ }
699
+ );
667
700
  const output = (result.stdout ?? "").toString();
668
701
  const scopes = parseScopes(output);
669
702
  if (scopes.length === 0) {
@@ -686,7 +719,8 @@ function getGhToken(opts) {
686
719
  }
687
720
  return getGhTokenWithSource({
688
721
  execImpl: opts?.execImpl,
689
- envToken: void 0
722
+ envToken: void 0,
723
+ hostname: opts?.hostname
690
724
  }).token;
691
725
  }
692
726
  function getGhTokenWithSource(opts) {
@@ -697,10 +731,14 @@ function getGhTokenWithSource(opts) {
697
731
  }
698
732
  const execImpl = opts?.execImpl ?? execFileSync;
699
733
  try {
700
- const token = execImpl("gh", ["auth", "token"], {
701
- encoding: "utf8",
702
- stdio: ["pipe", "pipe", "pipe"]
703
- }).toString().trim();
734
+ const token = execImpl(
735
+ "gh",
736
+ ["auth", "token", ...ghAuthHostArgs(opts?.hostname)],
737
+ {
738
+ encoding: "utf8",
739
+ stdio: ["pipe", "pipe", "pipe"]
740
+ }
741
+ ).toString().trim();
704
742
  if (!token) {
705
743
  throw new GhAuthError("token_failed", ghTokenReadErrorMessage());
706
744
  }
@@ -718,7 +756,9 @@ async function validateGitHubToken(token, source, opts) {
718
756
  const checkRequiredScopesImpl = opts?.checkRequiredScopesImpl ?? checkRequiredScopes;
719
757
  let viewer;
720
758
  try {
721
- const client = createClientImpl(token);
759
+ const client = createClientImpl(token, {
760
+ apiUrl: opts?.apiUrl
761
+ });
722
762
  viewer = await validateTokenImpl(client);
723
763
  } catch (error) {
724
764
  throw classifyTokenValidationError(error, source);
@@ -787,7 +827,7 @@ function ensureGhAuth(opts) {
787
827
  { source: "gh" }
788
828
  );
789
829
  }
790
- const auth = checkGhAuthenticated({ spawnImpl });
830
+ const auth = checkGhAuthenticated({ spawnImpl, hostname: opts?.hostname });
791
831
  if (!auth.authenticated) {
792
832
  throw new GhAuthError(
793
833
  "not_authenticated",
@@ -795,7 +835,7 @@ function ensureGhAuth(opts) {
795
835
  { source: "gh" }
796
836
  );
797
837
  }
798
- const scopeCheck = checkGhScopes({ spawnImpl });
838
+ const scopeCheck = checkGhScopes({ spawnImpl, hostname: opts?.hostname });
799
839
  if (!scopeCheck.valid) {
800
840
  throw new GhAuthError(
801
841
  "missing_scopes",
@@ -809,7 +849,8 @@ function ensureGhAuth(opts) {
809
849
  }
810
850
  const { token } = getGhTokenWithSource({
811
851
  execImpl,
812
- envToken: void 0
852
+ envToken: void 0,
853
+ hostname: opts?.hostname
813
854
  });
814
855
  return { login: auth.login ?? "unknown", token, source: "gh" };
815
856
  }
@@ -858,7 +899,7 @@ function runGhAuthRefresh(opts) {
858
899
  }
859
900
  function parseLogin(output) {
860
901
  const matched = output.match(
861
- /Logged in to github\.com account\s+\*?\*?([A-Za-z0-9_-]+)\*?\*?/i
902
+ /Logged in to \S+ account\s+\*?\*?([A-Za-z0-9_-]+)\*?\*?/i
862
903
  );
863
904
  return matched?.[1];
864
905
  }
@@ -871,6 +912,7 @@ function parseScopes(output) {
871
912
  }
872
913
 
873
914
  export {
915
+ DEFAULT_GITHUB_GRAPHQL_API_URL,
874
916
  findLinkedRepository,
875
917
  GitHubApiError,
876
918
  GitHubScopeError,
@@ -5,18 +5,19 @@ import {
5
5
  parseIssueReference,
6
6
  readGitHubProjectBinding,
7
7
  renderIssueWorkflowPreview
8
- } from "./chunk-FBJRJWE5.js";
9
- import "./chunk-DENDF6S6.js";
8
+ } from "./chunk-3ACU7O47.js";
9
+ import "./chunk-775A5LB5.js";
10
10
  import {
11
11
  fetchGithubProjectIssueByRepositoryAndNumber,
12
12
  fetchGithubProjectIssues,
13
13
  inspectManagedProjectSelection
14
- } from "./chunk-RMNLHTIK.js";
14
+ } from "./chunk-C4QCVQVY.js";
15
15
  import "./chunk-XLDJTMW5.js";
16
16
  import {
17
17
  resolveRuntimeRoot
18
18
  } from "./chunk-3IRPSPAF.js";
19
19
  import {
20
+ DEFAULT_GITHUB_GRAPHQL_API_URL,
20
21
  GitHubApiError,
21
22
  REQUIRED_GH_SCOPES,
22
23
  checkGhAuthenticated,
@@ -31,7 +32,7 @@ import {
31
32
  runGhAuthLogin,
32
33
  runGhAuthRefresh,
33
34
  validateGitHubToken
34
- } from "./chunk-SMNIGNS3.js";
35
+ } from "./chunk-FFY5VKNV.js";
35
36
  import {
36
37
  isClaudeRuntimeCommand,
37
38
  parseWorkflowMarkdown,
@@ -537,6 +538,67 @@ function warnCheck(id, title, summary, remediation, details) {
537
538
  function formatAuthSource(source) {
538
539
  return source === "env" ? "GITHUB_GRAPHQL_TOKEN" : "gh CLI";
539
540
  }
541
+ function normalizeGitHubGraphqlEndpoint(value) {
542
+ const trimmed = value?.trim();
543
+ if (!trimmed) {
544
+ return null;
545
+ }
546
+ try {
547
+ const url = new URL(trimmed);
548
+ url.hostname = url.hostname.toLowerCase();
549
+ url.pathname = url.pathname.replace(/\/+$/, "") || "/";
550
+ url.search = "";
551
+ url.hash = "";
552
+ return url.toString().replace(/\/$/, "");
553
+ } catch {
554
+ return trimmed.replace(/\/+$/, "");
555
+ }
556
+ }
557
+ function deriveGhHostnameFromGraphqlEndpoint(endpoint) {
558
+ try {
559
+ const hostname = new URL(endpoint).hostname.toLowerCase();
560
+ return hostname === "api.github.com" ? "github.com" : hostname;
561
+ } catch {
562
+ return void 0;
563
+ }
564
+ }
565
+ function resolveGitHubGraphqlEndpoint(input) {
566
+ const trackerApiUrl = normalizeGitHubGraphqlEndpoint(input.trackerApiUrl);
567
+ const envApiUrl = normalizeGitHubGraphqlEndpoint(input.envApiUrl);
568
+ const resolvedEndpoint = trackerApiUrl ?? envApiUrl ?? DEFAULT_GITHUB_GRAPHQL_API_URL;
569
+ return {
570
+ resolvedEndpoint,
571
+ source: trackerApiUrl ? "tracker" : envApiUrl ? "env" : "default",
572
+ trackerApiUrl,
573
+ envApiUrl,
574
+ mismatch: trackerApiUrl !== null && envApiUrl !== null && trackerApiUrl !== envApiUrl,
575
+ ghHostname: deriveGhHostnameFromGraphqlEndpoint(resolvedEndpoint)
576
+ };
577
+ }
578
+ function buildGitHubGraphqlEndpointCheck(input) {
579
+ const details = {
580
+ resolvedEndpoint: input.resolvedEndpoint,
581
+ source: input.source,
582
+ trackerApiUrl: input.trackerApiUrl,
583
+ envApiUrl: input.envApiUrl,
584
+ ghHostname: input.ghHostname
585
+ };
586
+ if (input.mismatch) {
587
+ return warnCheck(
588
+ "github_graphql_endpoint",
589
+ "GitHub GraphQL endpoint",
590
+ `Resolved GitHub GraphQL endpoint is ${input.resolvedEndpoint}, but GITHUB_GRAPHQL_API_URL is ${input.envApiUrl}.`,
591
+ "Keep WORKFLOW.md tracker.endpoint/tracker.apiUrl and GITHUB_GRAPHQL_API_URL aligned, or unset GITHUB_GRAPHQL_API_URL so the workflow endpoint is authoritative.",
592
+ details
593
+ );
594
+ }
595
+ return passCheck(
596
+ "github_graphql_endpoint",
597
+ "GitHub GraphQL endpoint",
598
+ `Resolved GitHub GraphQL endpoint: ${input.resolvedEndpoint}.`,
599
+ details
600
+ );
601
+ }
540
602
  function remediationStep(id, checkId, title, status, summary, command, details) {
541
603
  return { id, checkId, title, status, summary, command, details };
542
604
  }
@@ -768,7 +830,10 @@ function buildGithubTrackerConfig(input) {
768
830
  }
769
831
  async function checkLinearTrackerResolution(input) {
770
832
  const tracker = input.projectConfig.projectConfig.tracker;
771
- const projectSlug = readStringSetting(tracker.settings, "projectSlug")?.trim();
833
+ const projectSlug = readStringSetting(
834
+ tracker.settings,
835
+ "projectSlug"
836
+ )?.trim();
772
837
  const activeStates = readLinearActiveStates(tracker.settings);
773
838
  const pickupLabels = readLinearPickupLabels(tracker.settings);
774
839
  if (!projectSlug) {
@@ -915,7 +980,9 @@ async function fetchLinearProjectBySlug(input) {
915
980
  })
916
981
  });
917
982
  if (!response.ok) {
918
- throw new Error(`Linear GraphQL request failed with HTTP ${response.status}.`);
983
+ throw new Error(
984
+ `Linear GraphQL request failed with HTTP ${response.status}.`
985
+ );
919
986
  }
920
987
  const payload = await response.json();
921
988
  if (payload.errors?.length) {
@@ -1453,6 +1520,7 @@ ${DOCTOR_USAGE}`);
1453
1520
  let resolvedProjectConfig = null;
1454
1521
  let resolvedGithubProjectDetail = null;
1455
1522
  let resolvedGithubProjectBindingId = null;
1523
+ let githubGraphqlEndpoint = null;
1456
1524
  const envToken = deps.getEnvGitHubToken();
1457
1525
  const currentNodeVersion = deps.processVersion;
1458
1526
  const currentNodeMajor = parseMajorNodeVersion(currentNodeVersion);
@@ -1531,19 +1599,68 @@ ${DOCTOR_USAGE}`);
1531
1599
  )
1532
1600
  );
1533
1601
  }
1534
- const ghAuth = ghInstalled ? deps.checkGhAuthenticated() : { authenticated: false };
1535
- const ghScopes = ghInstalled && ghAuth.authenticated ? deps.checkGhScopes() : { valid: false, missing: [...REQUIRED_GH_SCOPES], scopes: [] };
1602
+ resolvedProjectConfig = await deps.inspectManagedProjectSelection({
1603
+ configDir: options.configDir,
1604
+ requestedProjectId: parsedArgs.projectId
1605
+ });
1606
+ if (resolvedProjectConfig.kind === "resolved") {
1607
+ resolvedProjectId = resolvedProjectConfig.projectId;
1608
+ checks.push(
1609
+ passCheck(
1610
+ "managed_project",
1611
+ "Managed project selection",
1612
+ `Resolved managed project "${resolvedProjectConfig.projectId}".`,
1613
+ {
1614
+ projectId: resolvedProjectConfig.projectId,
1615
+ workspaceDir: resolvedProjectConfig.projectConfig.workspaceDir
1616
+ }
1617
+ )
1618
+ );
1619
+ if (resolvedProjectConfig.projectConfig.tracker.adapter !== "linear") {
1620
+ githubGraphqlEndpoint = resolveGitHubGraphqlEndpoint({
1621
+ trackerApiUrl: resolvedProjectConfig.projectConfig.tracker.apiUrl,
1622
+ envApiUrl: process.env.GITHUB_GRAPHQL_API_URL
1623
+ });
1624
+ checks.push(buildGitHubGraphqlEndpointCheck(githubGraphqlEndpoint));
1625
+ }
1626
+ } else {
1627
+ checks.push(
1628
+ failCheck(
1629
+ "managed_project",
1630
+ "Managed project selection",
1631
+ resolvedProjectConfig.message,
1632
+ "Run 'gh-symphony repo init' from the target repository.",
1633
+ {
1634
+ reason: resolvedProjectConfig.kind,
1635
+ ...resolvedProjectConfig.projectId ? { projectId: resolvedProjectConfig.projectId } : {}
1636
+ }
1637
+ )
1638
+ );
1639
+ }
1640
+ const ghHostname = githubGraphqlEndpoint?.ghHostname;
1641
+ const ghAuth = ghInstalled ? deps.checkGhAuthenticated({ hostname: ghHostname }) : { authenticated: false };
1642
+ const ghScopes = ghInstalled && ghAuth.authenticated ? deps.checkGhScopes({ hostname: ghHostname }) : { valid: false, missing: [...REQUIRED_GH_SCOPES], scopes: [] };
1643
+ const ghHostnameArg = ghHostname ? ` --hostname ${ghHostname}` : "";
1644
+ const ghLoginCommand = `gh auth login --scopes ${REQUIRED_GH_SCOPES.join(",")}${ghHostnameArg}`;
1645
+ const ghRefreshCommand = `gh auth refresh --scopes ${REQUIRED_GH_SCOPES.join(",")}${ghHostnameArg}`;
1536
1646
  if (envToken) {
1537
1647
  try {
1538
- auth = await deps.validateGitHubToken(envToken, "env");
1648
+ auth = await deps.validateGitHubToken(envToken, "env", {
1649
+ apiUrl: githubGraphqlEndpoint?.resolvedEndpoint
1650
+ });
1539
1651
  } catch (error) {
1540
1652
  envTokenError = error instanceof Error ? error.message : "Unknown token validation error.";
1541
1653
  }
1542
1654
  }
1543
1655
  if (!auth && ghInstalled && ghAuth.authenticated && ghScopes.valid) {
1544
1656
  try {
1545
- const ghToken = deps.getGhToken({ allowEnv: false });
1546
- auth = await deps.validateGitHubToken(ghToken, "gh");
1657
+ const ghToken = deps.getGhToken({
1658
+ allowEnv: false,
1659
+ hostname: ghHostname
1660
+ });
1661
+ auth = await deps.validateGitHubToken(ghToken, "gh", {
1662
+ apiUrl: githubGraphqlEndpoint?.resolvedEndpoint
1663
+ });
1547
1664
  } catch (error) {
1548
1665
  tokenError = error instanceof Error ? error.message : "Unknown token retrieval error.";
1549
1666
  }
@@ -1575,7 +1692,7 @@ ${DOCTOR_USAGE}`);
1575
1692
  "gh_authentication",
1576
1693
  "GitHub authentication",
1577
1694
  "gh auth status failed or no GitHub login is configured.",
1578
- `Run 'gh auth login --scopes ${REQUIRED_GH_SCOPES.join(",")}' and re-run the doctor command.`
1695
+ `Run '${ghLoginCommand}' and re-run the doctor command.`
1579
1696
  )
1580
1697
  );
1581
1698
  }
@@ -1605,42 +1722,11 @@ ${DOCTOR_USAGE}`);
1605
1722
  "gh_scopes",
1606
1723
  "GitHub token scopes",
1607
1724
  `Missing required scopes: ${missingScopes.join(", ")}.`,
1608
- `Run 'gh auth refresh --scopes ${REQUIRED_GH_SCOPES.join(",")}' and confirm 'gh auth status' shows the updated scopes.`,
1725
+ `Run '${ghRefreshCommand}' and confirm 'gh auth status${ghHostnameArg}' shows the updated scopes.`,
1609
1726
  { missing: missingScopes, scopes: ghScopes.scopes }
1610
1727
  )
1611
1728
  );
1612
1729
  }
1613
- resolvedProjectConfig = await deps.inspectManagedProjectSelection({
1614
- configDir: options.configDir,
1615
- requestedProjectId: parsedArgs.projectId
1616
- });
1617
- if (resolvedProjectConfig.kind === "resolved") {
1618
- resolvedProjectId = resolvedProjectConfig.projectId;
1619
- checks.push(
1620
- passCheck(
1621
- "managed_project",
1622
- "Managed project selection",
1623
- `Resolved managed project "${resolvedProjectConfig.projectId}".`,
1624
- {
1625
- projectId: resolvedProjectConfig.projectId,
1626
- workspaceDir: resolvedProjectConfig.projectConfig.workspaceDir
1627
- }
1628
- )
1629
- );
1630
- } else {
1631
- checks.push(
1632
- failCheck(
1633
- "managed_project",
1634
- "Managed project selection",
1635
- resolvedProjectConfig.message,
1636
- "Run 'gh-symphony repo init' from the target repository.",
1637
- {
1638
- reason: resolvedProjectConfig.kind,
1639
- ...resolvedProjectConfig.projectId ? { projectId: resolvedProjectConfig.projectId } : {}
1640
- }
1641
- )
1642
- );
1643
- }
1644
1730
  if (resolvedProjectConfig.kind === "resolved" && resolvedProjectConfig.projectConfig.tracker.adapter === "linear") {
1645
1731
  checks.push(
1646
1732
  await checkLinearTrackerResolution({
@@ -1683,7 +1769,9 @@ ${DOCTOR_USAGE}`);
1683
1769
  throw new Error("Managed project is not bound to a GitHub Project.");
1684
1770
  }
1685
1771
  resolvedGithubProjectBindingId = bindingId;
1686
- const client = deps.createClient(auth.token);
1772
+ const client = deps.createClient(auth.token, {
1773
+ apiUrl: githubGraphqlEndpoint?.resolvedEndpoint
1774
+ });
1687
1775
  const detail = await deps.getProjectDetail(client, bindingId);
1688
1776
  resolvedGithubProjectDetail = detail;
1689
1777
  checks.push(
package/dist/index.d.ts CHANGED
@@ -1,10 +1,49 @@
1
1
  type GlobalOptions = {
2
2
  configDir: string;
3
+ configDirOverride?: boolean;
4
+ configDirSource?: "cli" | "env" | "default";
3
5
  verbose: boolean;
4
6
  json: boolean;
5
7
  noColor: boolean;
6
8
  };
7
9
  type CommandHandler = (args: string[], options: GlobalOptions) => Promise<void>;
10
+ type CliOptionValues = Partial<GlobalOptions & {
11
+ assignedOnly?: boolean;
12
+ config?: string;
13
+ daemon?: boolean;
14
+ dryRun?: boolean;
15
+ file?: string;
16
+ fix?: boolean;
17
+ follow?: boolean;
18
+ force?: boolean;
19
+ http?: string | boolean;
20
+ issue?: string;
21
+ level?: string;
22
+ logLevel?: string;
23
+ linearProjectSlug?: string;
24
+ nonInteractive?: boolean;
25
+ once?: boolean;
26
+ output?: string;
27
+ project?: string;
28
+ projectId?: string;
29
+ prune?: boolean;
30
+ run?: string;
31
+ runtime?: string;
32
+ skipContext?: boolean;
33
+ skipSkills?: boolean;
34
+ version?: boolean;
35
+ web?: string | boolean;
36
+ repoDir?: string;
37
+ workflowFile?: string;
38
+ workflow?: string;
39
+ watch?: boolean;
40
+ sample?: string;
41
+ smoke?: boolean;
42
+ bundle?: string | boolean;
43
+ attempt?: string;
44
+ tracker?: string;
45
+ }>;
46
+ declare function resolveGlobalOptions(values: CliOptionValues): GlobalOptions;
8
47
  declare function runCli(argv: string[]): Promise<void>;
9
48
 
10
- export { type CommandHandler, type GlobalOptions, runCli };
49
+ export { type CommandHandler, type GlobalOptions, resolveGlobalOptions, runCli };
package/dist/index.js CHANGED
@@ -417,21 +417,25 @@ function createRemovedCommandHandler(message) {
417
417
 
418
418
  // src/index.ts
419
419
  var COMMANDS = {
420
- workflow: () => import("./workflow-WG55ZIZ6.js"),
421
- setup: () => import("./setup-IJMKV5YA.js"),
422
- doctor: () => import("./doctor-YV5NV4HX.js"),
423
- upgrade: () => import("./upgrade-TS42ZOSU.js"),
424
- repo: () => import("./repo-LBNPFDDF.js"),
420
+ workflow: () => import("./workflow-JQNOLYAM.js"),
421
+ setup: () => import("./setup-KCBVNMUJ.js"),
422
+ doctor: () => import("./doctor-TFKCAGZF.js"),
423
+ upgrade: () => import("./upgrade-YOYZAIIY.js"),
424
+ repo: () => import("./repo-7UOBO3QG.js"),
425
425
  config: () => import("./config-cmd-OIVIUKG7.js"),
426
- version: () => import("./version-QXB4FBVW.js")
426
+ version: () => import("./version-ZTY3SPRG.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");
430
430
  }
431
431
  function resolveGlobalOptions(values) {
432
432
  const configInput = typeof values.config === "string" ? values.config : typeof values.configDir === "string" ? values.configDir : void 0;
433
+ const configDirSource = configInput !== void 0 ? "cli" : typeof process.env.GH_SYMPHONY_CONFIG_DIR === "string" ? "env" : "default";
434
+ const hasConfigOverride = configDirSource === "cli" || hasExplicitConfigEnvOverride();
433
435
  const options = {
434
436
  configDir: resolveConfigDir(configInput),
437
+ configDirOverride: hasConfigOverride,
438
+ configDirSource,
435
439
  verbose: Boolean(values.verbose),
436
440
  json: Boolean(values.json),
437
441
  noColor: Boolean(values.noColor)
@@ -442,6 +446,13 @@ function resolveGlobalOptions(values) {
442
446
  setNoColor(options.noColor);
443
447
  return options;
444
448
  }
449
+ function hasExplicitConfigEnvOverride() {
450
+ const envConfigDir = process.env.GH_SYMPHONY_CONFIG_DIR;
451
+ if (!envConfigDir) {
452
+ return false;
453
+ }
454
+ return envConfigDir !== "/var/lib/gh-symphony";
455
+ }
445
456
  function resolveProjectId(values) {
446
457
  return values.projectId ?? values.project;
447
458
  }
@@ -706,7 +717,7 @@ function createProgram() {
706
717
  addGlobalOptions(
707
718
  repo.command("start").description("Start the orchestrator for the current repository").option("-d, --daemon", "Start in daemon mode").option("--once", "Run a single orchestration tick and exit").option("--assigned-only", "Limit this run to assigned issues").option(
708
719
  "--http [port]",
709
- "Expose dashboard and refresh endpoints over HTTP"
720
+ "Expose the JSON status API and refresh endpoints over HTTP"
710
721
  ).option(
711
722
  "--web [port]",
712
723
  "Expose the control plane web dashboard and API over HTTP"
@@ -872,5 +883,6 @@ if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.ar
872
883
  });
873
884
  }
874
885
  export {
886
+ resolveGlobalOptions,
875
887
  runCli
876
888
  };
@@ -33,7 +33,7 @@ import {
33
33
  resolveOrchestratorLogLevel,
34
34
  resolveTrackerAdapter,
35
35
  runCli
36
- } from "./chunk-RMNLHTIK.js";
36
+ } from "./chunk-C4QCVQVY.js";
37
37
  import "./chunk-XLDJTMW5.js";
38
38
  import {
39
39
  resolveRepoRuntimeRoot,
@@ -48,7 +48,7 @@ import {
48
48
  resolveGitHubAuth,
49
49
  runGhAuthLogin,
50
50
  runGhAuthRefresh
51
- } from "./chunk-SMNIGNS3.js";
51
+ } from "./chunk-FFY5VKNV.js";
52
52
  import {
53
53
  WorkflowConfigStore,
54
54
  deriveIssueWorkspaceKeyFromIdentifier,
@@ -61,6 +61,7 @@ import {
61
61
  safeReadDir
62
62
  } from "./chunk-RGCSM2KZ.js";
63
63
  import {
64
+ configFilePath,
64
65
  daemonPidPath,
65
66
  httpStatusPath,
66
67
  loadActiveProjectConfig,
@@ -68,11 +69,15 @@ import {
68
69
  writeJsonFile
69
70
  } from "./chunk-YZP5N5XP.js";
70
71
 
72
+ // src/commands/repo.ts
73
+ import { existsSync } from "fs";
74
+
71
75
  // src/commands/logs.ts
72
76
  import { readFile, readdir, stat } from "fs/promises";
73
77
  import { join, resolve } from "path";
74
78
  import { createReadStream } from "fs";
75
79
  import { createInterface } from "readline";
80
+ var LOG_LEVELS = ["error", "warn", "info"];
76
81
  function parseLogsArgs(args) {
77
82
  const parsed = { follow: false };
78
83
  for (let i = 0; i < args.length; i += 1) {
@@ -101,6 +106,17 @@ function parseLogsArgs(args) {
101
106
  }
102
107
  var handler = async (args, options) => {
103
108
  const parsed = parseLogsArgs(args);
109
+ const level = parseLogLevel(parsed.level);
110
+ if (parsed.level && !level) {
111
+ process.stderr.write(
112
+ `Unknown --level "${parsed.level}". Valid values: ${LOG_LEVELS.join(
113
+ ", "
114
+ )}.
115
+ `
116
+ );
117
+ process.exitCode = 1;
118
+ return;
119
+ }
104
120
  const runtimeRoot = resolve(options.configDir);
105
121
  if (parsed.run) {
106
122
  const eventsPath = parsed.projectId ? join(
@@ -120,14 +136,19 @@ var handler = async (args, options) => {
120
136
  try {
121
137
  const content = await readFile(eventsPath, "utf8");
122
138
  const lines = content.trim().split("\n").filter(Boolean);
139
+ let matchedEvents2 = 0;
123
140
  for (const line of lines) {
124
141
  const event = JSON.parse(line);
125
142
  if (parsed.projectId && getProjectId(event) !== parsed.projectId)
126
143
  continue;
127
- if (parsed.level && getLevel(event) !== parsed.level) continue;
144
+ if (level && getLevel(event) !== level) continue;
128
145
  if (parsed.issue && getIssueIdentifier(event) !== parsed.issue)
129
146
  continue;
130
147
  process.stdout.write(formatEvent(event) + "\n");
148
+ matchedEvents2 += 1;
149
+ }
150
+ if (level && matchedEvents2 === 0) {
151
+ process.stderr.write(noMatchedEventsMessage(level));
131
152
  }
132
153
  } catch {
133
154
  process.stderr.write(`No events found for run: ${parsed.run}
@@ -180,6 +201,7 @@ var handler = async (args, options) => {
180
201
  }
181
202
  const runRoots = parsed.projectId ? [join(runtimeRoot, "projects", parsed.projectId, "runs")] : await listProjectRunRoots(runtimeRoot);
182
203
  let foundRuns = false;
204
+ let matchedEvents = 0;
183
205
  const events = [];
184
206
  try {
185
207
  for (const runsDir of runRoots) {
@@ -197,10 +219,11 @@ var handler = async (args, options) => {
197
219
  const event = JSON.parse(line);
198
220
  if (parsed.projectId && getProjectId(event) !== parsed.projectId)
199
221
  continue;
200
- if (parsed.level && getLevel(event) !== parsed.level) continue;
222
+ if (level && getLevel(event) !== level) continue;
201
223
  if (parsed.issue && getIssueIdentifier(event) !== parsed.issue)
202
224
  continue;
203
225
  events.push(event);
226
+ matchedEvents += 1;
204
227
  }
205
228
  } catch {
206
229
  }
@@ -212,6 +235,10 @@ var handler = async (args, options) => {
212
235
  process.stderr.write("No runs found. Start the orchestrator first.\n");
213
236
  return;
214
237
  }
238
+ if (level && matchedEvents === 0) {
239
+ process.stderr.write(noMatchedEventsMessage(level));
240
+ return;
241
+ }
215
242
  events.sort(
216
243
  (left, right) => String(left.at ?? "").localeCompare(String(right.at ?? ""))
217
244
  );
@@ -233,7 +260,28 @@ function getProjectId(event) {
233
260
  return typeof event.projectId === "string" ? event.projectId : void 0;
234
261
  }
235
262
  function getLevel(event) {
236
- return typeof event.level === "string" ? event.level : void 0;
263
+ switch (event.event) {
264
+ case "run-failed":
265
+ case "turn_failed":
266
+ case "worker-error":
267
+ case "hook-failed":
268
+ return "error";
269
+ case "run-suppressed":
270
+ case "run-retried":
271
+ return "warn";
272
+ default:
273
+ return "info";
274
+ }
275
+ }
276
+ function parseLogLevel(level) {
277
+ if (!level) {
278
+ return void 0;
279
+ }
280
+ return LOG_LEVELS.includes(level) ? level : void 0;
281
+ }
282
+ function noMatchedEventsMessage(level) {
283
+ return `No events matched the provided filters (--level ${level}).
284
+ `;
237
285
  }
238
286
  function getIssueIdentifier(event) {
239
287
  return typeof event.issueIdentifier === "string" ? event.issueIdentifier : "";
@@ -2089,7 +2137,7 @@ var handler5 = async (args, options) => {
2089
2137
  if (httpServer) {
2090
2138
  logLine(
2091
2139
  cyan("\u25A1"),
2092
- parsed.webPort !== void 0 ? `Web dashboard listening on ${httpServer.url}` : `HTTP dashboard listening on ${httpServer.url}`
2140
+ parsed.webPort !== void 0 ? `Web dashboard listening on ${httpServer.url}` : `HTTP status API listening on ${httpServer.url}`
2093
2141
  );
2094
2142
  }
2095
2143
  logLine(
@@ -2108,7 +2156,7 @@ var handler5 = async (args, options) => {
2108
2156
  if (httpServer) {
2109
2157
  logLine(
2110
2158
  cyan("\u25A1"),
2111
- parsed.webPort !== void 0 ? "One-shot tick completed; web dashboard remains available until Ctrl+C" : "One-shot tick completed; HTTP dashboard remains available until Ctrl+C"
2159
+ parsed.webPort !== void 0 ? "One-shot tick completed; web dashboard remains available until Ctrl+C" : "One-shot tick completed; HTTP status API remains available until Ctrl+C"
2112
2160
  );
2113
2161
  if (shuttingDown) {
2114
2162
  break;
@@ -2830,6 +2878,7 @@ var handler7 = async (args, options) => {
2830
2878
  var stop_default = handler7;
2831
2879
 
2832
2880
  // src/commands/repo.ts
2881
+ var DOCKER_DEFAULT_CONFIG_DIR = "/var/lib/gh-symphony";
2833
2882
  var handler8 = async (args, options) => {
2834
2883
  const [subcommand, ...rest] = args;
2835
2884
  switch (subcommand) {
@@ -2873,11 +2922,21 @@ var handler8 = async (args, options) => {
2873
2922
  };
2874
2923
  var repo_default = handler8;
2875
2924
  function repoOptions(options) {
2925
+ const repoRuntimeRoot = resolveRepoRuntimeRoot();
2876
2926
  return {
2877
2927
  ...options,
2878
- configDir: resolveRepoRuntimeRoot()
2928
+ configDir: shouldUseConfigDirOverride(options, repoRuntimeRoot) ? options.configDir : repoRuntimeRoot
2879
2929
  };
2880
2930
  }
2931
+ function shouldUseConfigDirOverride(options, repoRuntimeRoot) {
2932
+ if (!options.configDirOverride) {
2933
+ return false;
2934
+ }
2935
+ if (options.configDirSource === "env" && options.configDir === DOCKER_DEFAULT_CONFIG_DIR && existsSync(configFilePath(repoRuntimeRoot))) {
2936
+ return false;
2937
+ }
2938
+ return true;
2939
+ }
2881
2940
  async function repoInit(args, options) {
2882
2941
  if (rejectRemovedProjectId(args)) {
2883
2942
  return;
@@ -2921,5 +2980,6 @@ function formatRepoSpec(repo) {
2921
2980
  return `${repo.owner}/${repo.name}`;
2922
2981
  }
2923
2982
  export {
2924
- repo_default as default
2983
+ repo_default as default,
2984
+ repoOptions
2925
2985
  };
@@ -16,7 +16,7 @@ import {
16
16
  warnDeprecatedSkipContext,
17
17
  writeEcosystem,
18
18
  writeWorkflowPlan
19
- } from "./chunk-DENDF6S6.js";
19
+ } from "./chunk-775A5LB5.js";
20
20
  import {
21
21
  initRepoRuntime
22
22
  } from "./chunk-EILO332E.js";
@@ -32,7 +32,7 @@ import {
32
32
  getProjectDetail,
33
33
  listUserProjects,
34
34
  validateToken
35
- } from "./chunk-SMNIGNS3.js";
35
+ } from "./chunk-FFY5VKNV.js";
36
36
  import "./chunk-RGCSM2KZ.js";
37
37
  import "./chunk-YZP5N5XP.js";
38
38
 
@@ -16,8 +16,8 @@ function execFileAsync(file, args, execFileImpl = execFileCallback) {
16
16
  });
17
17
  }
18
18
  function resolveCurrentCliVersion() {
19
- if ("0.4.6".length > 0) {
20
- return "0.4.6";
19
+ if ("0.4.7".length > 0) {
20
+ return "0.4.7";
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.4.6";
5
+ const version = "0.4.7";
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-FBJRJWE5.js";
10
- import "./chunk-DENDF6S6.js";
11
- import "./chunk-RMNLHTIK.js";
9
+ } from "./chunk-3ACU7O47.js";
10
+ import "./chunk-775A5LB5.js";
11
+ import "./chunk-C4QCVQVY.js";
12
12
  import "./chunk-XLDJTMW5.js";
13
- import "./chunk-SMNIGNS3.js";
13
+ import "./chunk-FFY5VKNV.js";
14
14
  import "./chunk-RGCSM2KZ.js";
15
15
  import "./chunk-YZP5N5XP.js";
16
16
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gh-symphony/cli",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "license": "MIT",
5
5
  "author": "hojinzs",
6
6
  "description": "Interactive CLI for GitHub Symphony orchestration",
@@ -42,12 +42,12 @@
42
42
  "devDependencies": {
43
43
  "tsup": "^8.5.1",
44
44
  "@gh-symphony/core": "0.0.14",
45
- "@gh-symphony/dashboard": "0.0.14",
45
+ "@gh-symphony/control-plane": "0.0.15",
46
46
  "@gh-symphony/orchestrator": "0.0.14",
47
- "@gh-symphony/tracker-github": "0.0.14",
48
47
  "@gh-symphony/runtime-claude": "0.0.14",
49
- "@gh-symphony/worker": "0.0.14",
50
- "@gh-symphony/control-plane": "0.0.15"
48
+ "@gh-symphony/tracker-github": "0.0.14",
49
+ "@gh-symphony/dashboard": "0.0.14",
50
+ "@gh-symphony/worker": "0.0.14"
51
51
  },
52
52
  "scripts": {
53
53
  "build": "tsup",