@gh-symphony/cli 0.0.20 → 0.0.22

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.
Files changed (40) hide show
  1. package/README.md +66 -2
  2. package/dist/chunk-2TSM3INR.js +1085 -0
  3. package/dist/chunk-2UW7NQLX.js +684 -0
  4. package/dist/{chunk-MVRF7BES.js → chunk-36KYEDEO.js} +10 -1
  5. package/dist/{chunk-TILHWBP6.js → chunk-C67H3OUL.js} +239 -36
  6. package/dist/{chunk-C7G7RJ4G.js → chunk-DDL4BWSL.js} +1 -1
  7. package/dist/{chunk-XN5ABWZ6.js → chunk-DFLXHNYQ.js} +26 -30
  8. package/dist/{chunk-EKKT5USP.js → chunk-E7HYEEZD.js} +487 -133
  9. package/dist/chunk-EEQQWTXS.js +3257 -0
  10. package/dist/chunk-GDE6FYN4.js +26 -0
  11. package/dist/{chunk-Y6TYJMNT.js → chunk-GSX2FV3M.js} +10 -16
  12. package/dist/{chunk-RN2PACNV.js → chunk-HMLBBZNY.js} +731 -75
  13. package/dist/{chunk-5NV3LSAJ.js → chunk-IWFX2FMA.js} +5 -1
  14. package/dist/{chunk-HZVDTAPS.js → chunk-PUDXVBSN.js} +1549 -1458
  15. package/dist/{chunk-ROGRTUFI.js → chunk-QIRE2VXS.js} +14 -3
  16. package/dist/{chunk-3AWF54PI.js → chunk-ZHOKYUO3.js} +394 -42
  17. package/dist/{config-cmd-DNXNL26Z.js → config-cmd-Z3A7V6NC.js} +1 -1
  18. package/dist/{doctor-IYHCFXOZ.js → doctor-EJUMPBMW.js} +105 -40
  19. package/dist/index.js +112 -24
  20. package/dist/{init-KZT6YNOH.js → init-54HMKNYI.js} +8 -3
  21. package/dist/{logs-6JKKYDGJ.js → logs-GTZ4U5JE.js} +2 -2
  22. package/dist/project-RMYMZSFV.js +25 -0
  23. package/dist/{recover-5KQI7WH5.js → recover-LTLKMTRX.js} +7 -5
  24. package/dist/repo-WI7GF6XQ.js +749 -0
  25. package/dist/{run-ETC5UTRA.js → run-IHN3ZL35.js} +21 -7
  26. package/dist/{setup-VWB7RZUQ.js → setup-TZJSM3QV.js} +53 -14
  27. package/dist/start-RTAHQMR2.js +19 -0
  28. package/dist/status-F4D52OVK.js +12 -0
  29. package/dist/stop-MDKMJPVR.js +10 -0
  30. package/dist/{upgrade-3YNF3VKY.js → upgrade-O33S2SJK.js} +2 -2
  31. package/dist/{version-NUBTTOG7.js → version-CW54Q7BK.js} +1 -1
  32. package/dist/worker-entry.js +848 -693
  33. package/dist/{workflow-TBIFY5MO.js → workflow-L3KT6HB7.js} +177 -11
  34. package/package.json +4 -2
  35. package/dist/chunk-M3IFVLQS.js +0 -1155
  36. package/dist/project-UUVHS3ZR.js +0 -22
  37. package/dist/repo-HDDE7OUI.js +0 -321
  38. package/dist/start-ENFLZUI6.js +0 -16
  39. package/dist/status-QSCFVGRQ.js +0 -11
  40. package/dist/stop-7MFCBQVW.js +0 -9
@@ -3,6 +3,13 @@
3
3
  // src/github/client.ts
4
4
  var DEFAULT_API_URL = "https://api.github.com/graphql";
5
5
  var REST_API_URL = "https://api.github.com";
6
+ function findLinkedRepository(project, owner, name) {
7
+ const normalizedOwner = owner.trim().toLowerCase();
8
+ const normalizedName = name.trim().toLowerCase();
9
+ return project.linkedRepositories.find(
10
+ (repository) => repository.owner.trim().toLowerCase() === normalizedOwner && repository.name.trim().toLowerCase() === normalizedName
11
+ ) ?? null;
12
+ }
6
13
  var GitHubApiError = class extends Error {
7
14
  constructor(message, status) {
8
15
  super(message);
@@ -18,6 +25,14 @@ var GitHubScopeError = class extends GitHubApiError {
18
25
  this.name = "GitHubScopeError";
19
26
  }
20
27
  };
28
+ var GitHubRepositoryLookupError = class extends GitHubApiError {
29
+ constructor(reason, message, remediation, status) {
30
+ super(message, status);
31
+ this.reason = reason;
32
+ this.remediation = remediation;
33
+ this.name = "GitHubRepositoryLookupError";
34
+ }
35
+ };
21
36
  function createClient(token, options) {
22
37
  return {
23
38
  token,
@@ -25,6 +40,77 @@ function createClient(token, options) {
25
40
  fetchImpl: options?.fetchImpl ?? fetch
26
41
  };
27
42
  }
43
+ async function getRepositoryMetadata(client, owner, name) {
44
+ const restUrl = client.apiUrl.replace("/graphql", "");
45
+ const baseUrl = restUrl === client.apiUrl ? REST_API_URL : restUrl;
46
+ const repoPath = `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`;
47
+ let response;
48
+ try {
49
+ response = await client.fetchImpl(`${baseUrl}${repoPath}`, {
50
+ headers: {
51
+ authorization: `Bearer ${client.token}`,
52
+ accept: "application/vnd.github+json"
53
+ }
54
+ });
55
+ } catch (error) {
56
+ const detail = error instanceof Error && error.message.length > 0 ? ` ${error.message}` : "";
57
+ throw new GitHubRepositoryLookupError(
58
+ "offline",
59
+ `GitHub repository validation could not reach the API.${detail}`.trim(),
60
+ "Check your network connection and re-run the command to validate before saving."
61
+ );
62
+ }
63
+ if (!response.ok) {
64
+ const payload = await response.json().catch(() => null);
65
+ const message = payload?.message?.trim() || response.statusText;
66
+ if (response.status === 403 && (response.headers.get("x-ratelimit-remaining") === "0" || /rate limit/i.test(message))) {
67
+ throw new GitHubRepositoryLookupError(
68
+ "rate_limited",
69
+ "GitHub API rate limit blocked repository validation.",
70
+ "Wait for the rate limit window to reset, then re-run 'gh-symphony repo add owner/name'.",
71
+ response.status
72
+ );
73
+ }
74
+ if (response.status === 401) {
75
+ throw new GitHubRepositoryLookupError(
76
+ "invalid_token",
77
+ "GitHub token is invalid or expired.",
78
+ "Run 'gh auth login --scopes repo,read:org,project' or refresh GITHUB_GRAPHQL_TOKEN, then retry.",
79
+ response.status
80
+ );
81
+ }
82
+ if (response.status === 403 || /resource not accessible|saml|single sign-on|access denied/i.test(message)) {
83
+ throw new GitHubRepositoryLookupError(
84
+ "no_access",
85
+ `GitHub denied access to ${owner}/${name}.`,
86
+ "Confirm that the authenticated user can read this repository and that the token has the required access.",
87
+ response.status
88
+ );
89
+ }
90
+ if (response.status === 404) {
91
+ throw new GitHubRepositoryLookupError(
92
+ "not_found",
93
+ `Repository ${owner}/${name} was not found.`,
94
+ "Check the owner/name spelling. If the repository is private, confirm the current token can access it.",
95
+ response.status
96
+ );
97
+ }
98
+ throw new GitHubRepositoryLookupError(
99
+ "unknown",
100
+ `GitHub repository validation failed: ${response.status} ${message}`.trim(),
101
+ "Retry the command. If the problem continues, verify GitHub API access separately.",
102
+ response.status
103
+ );
104
+ }
105
+ const repo = await response.json();
106
+ return {
107
+ owner: repo.owner.login,
108
+ name: repo.name,
109
+ url: repo.html_url,
110
+ cloneUrl: repo.clone_url,
111
+ visibility: repo.visibility ?? (repo.private === true ? "private" : "public")
112
+ };
113
+ }
28
114
  async function validateToken(client) {
29
115
  const restUrl = client.apiUrl.replace("/graphql", "");
30
116
  const baseUrl = restUrl === client.apiUrl ? REST_API_URL : restUrl;
@@ -57,34 +143,123 @@ function checkRequiredScopes(scopes) {
57
143
  const missing = required.filter((r) => !normalizedScopes.includes(r));
58
144
  return { valid: missing.length === 0, missing };
59
145
  }
60
- async function listUserProjects(client) {
61
- const data = await graphql(
62
- client,
63
- VIEWER_PROJECTS_QUERY
64
- );
146
+ var PROJECT_PAGE_SIZE = 50;
147
+ var ORGANIZATION_PAGE_SIZE = 20;
148
+ var MAX_PROJECT_DISCOVERY_REQUESTS = 40;
149
+ var MAX_DISCOVERED_PROJECTS = 1e3;
150
+ async function discoverUserProjects(client) {
65
151
  const projects = [];
66
- for (const node of data.viewer.projectsV2?.nodes ?? []) {
67
- if (!node) continue;
68
- projects.push(
69
- normalizeProjectSummary(node, {
70
- login: data.viewer.login,
71
- type: "User"
72
- })
152
+ const seenProjectIds = /* @__PURE__ */ new Set();
153
+ const orgLogins = [];
154
+ let requestCount = 0;
155
+ let partial = false;
156
+ let reason = null;
157
+ const tryStartRequest = () => {
158
+ if (requestCount >= MAX_PROJECT_DISCOVERY_REQUESTS) {
159
+ partial = true;
160
+ reason ??= "request_limit";
161
+ return false;
162
+ }
163
+ requestCount += 1;
164
+ return true;
165
+ };
166
+ const collectProject = (node, owner) => {
167
+ if (seenProjectIds.has(node.id)) {
168
+ return true;
169
+ }
170
+ if (projects.length >= MAX_DISCOVERED_PROJECTS) {
171
+ partial = true;
172
+ reason ??= "result_limit";
173
+ return false;
174
+ }
175
+ seenProjectIds.add(node.id);
176
+ projects.push(normalizeProjectSummary(node, owner));
177
+ return true;
178
+ };
179
+ let viewerProjectsCursor = null;
180
+ let hasMoreViewerProjects = true;
181
+ let viewerLogin = "";
182
+ while (hasMoreViewerProjects) {
183
+ if (!tryStartRequest()) {
184
+ break;
185
+ }
186
+ const data = await graphql(
187
+ client,
188
+ VIEWER_PROJECTS_PAGE_QUERY,
189
+ { cursor: viewerProjectsCursor }
73
190
  );
74
- }
75
- for (const orgNode of data.viewer.organizations?.nodes ?? []) {
76
- if (!orgNode) continue;
77
- for (const projNode of orgNode.projectsV2?.nodes ?? []) {
78
- if (!projNode) continue;
79
- projects.push(
80
- normalizeProjectSummary(projNode, {
81
- login: orgNode.login,
82
- type: "Organization"
83
- })
191
+ viewerLogin = data.viewer.login;
192
+ const projectPage = data.viewer.projectsV2;
193
+ for (const node of projectPage?.nodes ?? []) {
194
+ if (!node) continue;
195
+ if (!collectProject(node, { login: viewerLogin, type: "User" })) {
196
+ hasMoreViewerProjects = false;
197
+ break;
198
+ }
199
+ }
200
+ if (partial) {
201
+ break;
202
+ }
203
+ hasMoreViewerProjects = projectPage?.pageInfo?.hasNextPage ?? false;
204
+ viewerProjectsCursor = projectPage?.pageInfo?.endCursor ?? null;
205
+ }
206
+ let organizationsCursor = null;
207
+ let hasMoreOrganizations = true;
208
+ while (!partial && hasMoreOrganizations) {
209
+ if (!tryStartRequest()) {
210
+ break;
211
+ }
212
+ const data = await graphql(
213
+ client,
214
+ VIEWER_ORGANIZATIONS_PAGE_QUERY,
215
+ { cursor: organizationsCursor }
216
+ );
217
+ for (const orgNode of data.viewer.organizations?.nodes ?? []) {
218
+ if (!orgNode) continue;
219
+ orgLogins.push(orgNode.login);
220
+ }
221
+ hasMoreOrganizations = data.viewer.organizations?.pageInfo?.hasNextPage ?? false;
222
+ organizationsCursor = data.viewer.organizations?.pageInfo?.endCursor ?? null;
223
+ }
224
+ for (const orgLogin of orgLogins) {
225
+ let orgProjectsCursor = null;
226
+ let hasMoreOrgProjects = true;
227
+ while (!partial && hasMoreOrgProjects) {
228
+ if (!tryStartRequest()) {
229
+ break;
230
+ }
231
+ const data = await graphql(
232
+ client,
233
+ ORGANIZATION_PROJECTS_PAGE_QUERY,
234
+ { login: orgLogin, cursor: orgProjectsCursor }
84
235
  );
236
+ const projectPage = data.organization?.projectsV2 ?? null;
237
+ for (const node of projectPage?.nodes ?? []) {
238
+ if (!node) continue;
239
+ if (!collectProject(node, {
240
+ login: orgLogin,
241
+ type: "Organization"
242
+ })) {
243
+ hasMoreOrgProjects = false;
244
+ break;
245
+ }
246
+ }
247
+ if (partial) {
248
+ break;
249
+ }
250
+ hasMoreOrgProjects = projectPage?.pageInfo?.hasNextPage ?? false;
251
+ orgProjectsCursor = projectPage?.pageInfo?.endCursor ?? null;
85
252
  }
86
253
  }
87
- return projects;
254
+ return {
255
+ projects,
256
+ partial,
257
+ reason,
258
+ requests: requestCount
259
+ };
260
+ }
261
+ async function listUserProjects(client) {
262
+ return (await discoverUserProjects(client)).projects;
88
263
  }
89
264
  function normalizeProjectSummary(node, owner) {
90
265
  return {
@@ -230,11 +405,11 @@ async function graphql(client, query, variables) {
230
405
  }
231
406
  return payload.data;
232
407
  }
233
- var VIEWER_PROJECTS_QUERY = `
234
- query ViewerProjects {
408
+ var VIEWER_PROJECTS_PAGE_QUERY = `
409
+ query ViewerProjectsPage($cursor: String) {
235
410
  viewer {
236
411
  login
237
- projectsV2(first: 50) {
412
+ projectsV2(first: ${PROJECT_PAGE_SIZE}, after: $cursor) {
238
413
  nodes {
239
414
  id
240
415
  title
@@ -242,19 +417,43 @@ var VIEWER_PROJECTS_QUERY = `
242
417
  url
243
418
  items { totalCount }
244
419
  }
420
+ pageInfo {
421
+ endCursor
422
+ hasNextPage
423
+ }
245
424
  }
246
- organizations(first: 20) {
425
+ }
426
+ }
427
+ `;
428
+ var VIEWER_ORGANIZATIONS_PAGE_QUERY = `
429
+ query ViewerOrganizationsPage($cursor: String) {
430
+ viewer {
431
+ organizations(first: ${ORGANIZATION_PAGE_SIZE}, after: $cursor) {
247
432
  nodes {
248
433
  login
249
- projectsV2(first: 50) {
250
- nodes {
251
- id
252
- title
253
- shortDescription
254
- url
255
- items { totalCount }
256
- }
257
- }
434
+ }
435
+ pageInfo {
436
+ endCursor
437
+ hasNextPage
438
+ }
439
+ }
440
+ }
441
+ }
442
+ `;
443
+ var ORGANIZATION_PROJECTS_PAGE_QUERY = `
444
+ query OrganizationProjectsPage($login: String!, $cursor: String) {
445
+ organization(login: $login) {
446
+ projectsV2(first: ${PROJECT_PAGE_SIZE}, after: $cursor) {
447
+ nodes {
448
+ id
449
+ title
450
+ shortDescription
451
+ url
452
+ items { totalCount }
453
+ }
454
+ pageInfo {
455
+ endCursor
456
+ hasNextPage
258
457
  }
259
458
  }
260
459
  }
@@ -615,11 +814,15 @@ function parseScopes(output) {
615
814
  }
616
815
 
617
816
  export {
817
+ findLinkedRepository,
618
818
  GitHubApiError,
619
819
  GitHubScopeError,
820
+ GitHubRepositoryLookupError,
620
821
  createClient,
822
+ getRepositoryMetadata,
621
823
  validateToken,
622
824
  checkRequiredScopes,
825
+ discoverUserProjects,
623
826
  listUserProjects,
624
827
  getProjectDetail,
625
828
  REQUIRED_GH_SCOPES,
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  loadGlobalConfig,
4
4
  loadProjectConfig
5
- } from "./chunk-ROGRTUFI.js";
5
+ } from "./chunk-QIRE2VXS.js";
6
6
 
7
7
  // src/project-selection.ts
8
8
  import * as p from "@clack/prompts";
@@ -5,6 +5,7 @@ import {
5
5
  clearScreen,
6
6
  cyan,
7
7
  dim,
8
+ formatRepositoryDisplay,
8
9
  green,
9
10
  hideCursor,
10
11
  magenta,
@@ -12,14 +13,17 @@ import {
12
13
  showCursor,
13
14
  stripAnsi,
14
15
  yellow
15
- } from "./chunk-MVRF7BES.js";
16
+ } from "./chunk-36KYEDEO.js";
16
17
  import {
17
18
  resolveRuntimeRoot
18
- } from "./chunk-5NV3LSAJ.js";
19
+ } from "./chunk-IWFX2FMA.js";
20
+ import {
21
+ rejectRemovedProjectId
22
+ } from "./chunk-GDE6FYN4.js";
19
23
  import {
20
24
  handleMissingManagedProjectConfig,
21
25
  resolveManagedProjectConfig
22
- } from "./chunk-C7G7RJ4G.js";
26
+ } from "./chunk-DDL4BWSL.js";
23
27
 
24
28
  // src/commands/status.ts
25
29
  import { readFile } from "fs/promises";
@@ -212,7 +216,9 @@ function renderDashboard(snapshots, options) {
212
216
  const hasActiveRuns = snap.activeRuns.length > 0;
213
217
  const hasRetries = snap.retryQueue.length > 0;
214
218
  if (!hasActiveRuns && !hasRetries) continue;
215
- lines.push(sectionDivider(snap.slug, width, c));
219
+ lines.push(
220
+ sectionDivider(formatRepositoryDisplay(snap), width, c)
221
+ );
216
222
  if (hasActiveRuns) {
217
223
  lines.push(tableHeaderRow(c));
218
224
  for (const rawRun of snap.activeRuns) {
@@ -280,7 +286,7 @@ function resolveProjectTokenDelta(snapshot) {
280
286
  function renderLegacyStatus(snapshot, noColor) {
281
287
  const apply = noColor ? (s) => stripAnsi(s) : (s) => s;
282
288
  const lines = [];
283
- const headerTitle = `gh-symphony \u2219 ${snapshot.slug}`;
289
+ const headerTitle = `gh-symphony \u2219 ${formatRepositoryDisplay(snapshot)}`;
284
290
  const headerWidth = 45;
285
291
  const headerPadding = Math.max(
286
292
  0,
@@ -363,16 +369,6 @@ function parseStatusArgs(args) {
363
369
  parsed.watch = true;
364
370
  continue;
365
371
  }
366
- if (arg === "--project" || arg === "--project-id") {
367
- const value = args[i + 1];
368
- if (!value || value.startsWith("-")) {
369
- parsed.error = `Option '${arg}' argument missing`;
370
- return parsed;
371
- }
372
- parsed.projectId = value;
373
- i += 1;
374
- continue;
375
- }
376
372
  if (arg?.startsWith("-")) {
377
373
  parsed.error = `Unknown option '${arg}'`;
378
374
  return parsed;
@@ -381,33 +377,33 @@ function parseStatusArgs(args) {
381
377
  return parsed;
382
378
  }
383
379
  async function readStatusSnapshot(runtimeRoot, projectId) {
384
- try {
385
- const statusPath = join(
386
- runtimeRoot,
387
- "projects",
388
- projectId,
389
- "status.json"
390
- );
391
- const content = await readFile(statusPath, "utf-8");
392
- return JSON.parse(content);
393
- } catch {
394
- return null;
380
+ for (const statusPath of [
381
+ join(runtimeRoot, "status.json"),
382
+ join(runtimeRoot, "projects", projectId, "status.json")
383
+ ]) {
384
+ try {
385
+ const content = await readFile(statusPath, "utf-8");
386
+ return JSON.parse(content);
387
+ } catch {
388
+ }
395
389
  }
390
+ return null;
396
391
  }
397
392
  var handler = async (args, options) => {
393
+ if (rejectRemovedProjectId(args)) {
394
+ return;
395
+ }
398
396
  const parsed = parseStatusArgs(args);
399
397
  if (parsed.error) {
400
398
  process.stderr.write(`${parsed.error}
401
399
  `);
402
- process.stderr.write(
403
- "Usage: gh-symphony status [--project-id <project-id>] [--watch]\n"
404
- );
400
+ process.stderr.write("Usage: gh-symphony status [--watch]\n");
405
401
  process.exitCode = 2;
406
402
  return;
407
403
  }
408
404
  const projectConfig = await resolveManagedProjectConfig({
409
405
  configDir: options.configDir,
410
- requestedProjectId: parsed.projectId
406
+ requestedProjectId: void 0
411
407
  });
412
408
  if (!projectConfig) {
413
409
  handleMissingManagedProjectConfig();