@gh-symphony/cli 0.0.14 → 0.0.16

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 (102) hide show
  1. package/dist/chunk-5NV3LSAJ.js +11 -0
  2. package/dist/chunk-6HBZC3BE.js +468 -0
  3. package/dist/chunk-76QPITKI.js +109 -0
  4. package/dist/chunk-EFMFGOWM.js +3575 -0
  5. package/dist/chunk-IWR4UQEJ.js +2250 -0
  6. package/dist/chunk-JO3AXHQI.js +130 -0
  7. package/dist/chunk-MHIWAIVD.js +876 -0
  8. package/dist/chunk-MVRF7BES.js +68 -0
  9. package/dist/chunk-ROGRTUFI.js +108 -0
  10. package/dist/chunk-TF3QNWNC.js +1121 -0
  11. package/dist/chunk-TH5QPO3Y.js +67 -0
  12. package/dist/config-cmd-AZ7POMAA.js +110 -0
  13. package/dist/index.d.ts +5 -4
  14. package/dist/index.js +568 -356
  15. package/dist/init-EZXQAXZM.js +17 -0
  16. package/dist/logs-6LNGT2GF.js +188 -0
  17. package/dist/project-557FE2GD.js +679 -0
  18. package/dist/recover-LVBI2TGH.js +131 -0
  19. package/dist/repo-R3XBIVAX.js +121 -0
  20. package/dist/run-WITYAYFZ.js +108 -0
  21. package/dist/start-JUFKNL3N.js +16 -0
  22. package/dist/status-3WK5BWRZ.js +11 -0
  23. package/dist/stop-AA3AP5M6.js +9 -0
  24. package/dist/version-VBB62JWI.js +30 -0
  25. package/dist/worker-entry.js +1828 -0
  26. package/package.json +9 -4
  27. package/dist/ansi.d.ts +0 -15
  28. package/dist/ansi.js +0 -53
  29. package/dist/commands/config-cmd.d.ts +0 -3
  30. package/dist/commands/config-cmd.js +0 -90
  31. package/dist/commands/help.d.ts +0 -3
  32. package/dist/commands/help.js +0 -55
  33. package/dist/commands/init.d.ts +0 -34
  34. package/dist/commands/init.js +0 -477
  35. package/dist/commands/logs.d.ts +0 -3
  36. package/dist/commands/logs.js +0 -184
  37. package/dist/commands/project.d.ts +0 -3
  38. package/dist/commands/project.js +0 -649
  39. package/dist/commands/recover.d.ts +0 -3
  40. package/dist/commands/recover.js +0 -119
  41. package/dist/commands/repo.d.ts +0 -3
  42. package/dist/commands/repo.js +0 -103
  43. package/dist/commands/run.d.ts +0 -3
  44. package/dist/commands/run.js +0 -95
  45. package/dist/commands/start.d.ts +0 -20
  46. package/dist/commands/start.js +0 -344
  47. package/dist/commands/status-refresh.d.ts +0 -9
  48. package/dist/commands/status-refresh.js +0 -27
  49. package/dist/commands/status.d.ts +0 -3
  50. package/dist/commands/status.js +0 -237
  51. package/dist/commands/stop.d.ts +0 -3
  52. package/dist/commands/stop.js +0 -92
  53. package/dist/commands/version.d.ts +0 -3
  54. package/dist/commands/version.js +0 -21
  55. package/dist/completion.d.ts +0 -1
  56. package/dist/completion.js +0 -204
  57. package/dist/config.d.ts +0 -38
  58. package/dist/config.js +0 -82
  59. package/dist/context/context-types.d.ts +0 -36
  60. package/dist/context/context-types.js +0 -1
  61. package/dist/context/generate-context-yaml.d.ts +0 -15
  62. package/dist/context/generate-context-yaml.js +0 -129
  63. package/dist/dashboard/renderer.d.ts +0 -9
  64. package/dist/dashboard/renderer.js +0 -220
  65. package/dist/detection/environment-detector.d.ts +0 -11
  66. package/dist/detection/environment-detector.js +0 -140
  67. package/dist/github/client.d.ts +0 -71
  68. package/dist/github/client.js +0 -348
  69. package/dist/github/gh-auth.d.ts +0 -34
  70. package/dist/github/gh-auth.js +0 -110
  71. package/dist/mapping/smart-defaults.d.ts +0 -17
  72. package/dist/mapping/smart-defaults.js +0 -86
  73. package/dist/orchestrator-runtime.d.ts +0 -1
  74. package/dist/orchestrator-runtime.js +0 -4
  75. package/dist/orchestrator-status-endpoint.d.ts +0 -5
  76. package/dist/orchestrator-status-endpoint.js +0 -27
  77. package/dist/project-selection.d.ts +0 -8
  78. package/dist/project-selection.js +0 -56
  79. package/dist/skills/skill-writer.d.ts +0 -14
  80. package/dist/skills/skill-writer.js +0 -62
  81. package/dist/skills/templates/commit.d.ts +0 -2
  82. package/dist/skills/templates/commit.js +0 -45
  83. package/dist/skills/templates/document.d.ts +0 -7
  84. package/dist/skills/templates/document.js +0 -16
  85. package/dist/skills/templates/gh-project.d.ts +0 -2
  86. package/dist/skills/templates/gh-project.js +0 -88
  87. package/dist/skills/templates/gh-symphony.d.ts +0 -2
  88. package/dist/skills/templates/gh-symphony.js +0 -125
  89. package/dist/skills/templates/index.d.ts +0 -8
  90. package/dist/skills/templates/index.js +0 -28
  91. package/dist/skills/templates/land.d.ts +0 -2
  92. package/dist/skills/templates/land.js +0 -59
  93. package/dist/skills/templates/pull.d.ts +0 -2
  94. package/dist/skills/templates/pull.js +0 -41
  95. package/dist/skills/templates/push.d.ts +0 -2
  96. package/dist/skills/templates/push.js +0 -36
  97. package/dist/skills/types.d.ts +0 -23
  98. package/dist/skills/types.js +0 -1
  99. package/dist/workflow/generate-reference-workflow.d.ts +0 -9
  100. package/dist/workflow/generate-reference-workflow.js +0 -261
  101. package/dist/workflow/generate-workflow-md.d.ts +0 -12
  102. package/dist/workflow/generate-workflow-md.js +0 -134
@@ -0,0 +1,679 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ GitHubScopeError,
4
+ abortIfCancelled,
5
+ checkRequiredScopes,
6
+ createClient,
7
+ generateProjectId,
8
+ getProjectDetail,
9
+ listUserProjects,
10
+ validateToken,
11
+ writeConfig
12
+ } from "./chunk-IWR4UQEJ.js";
13
+ import {
14
+ start_default
15
+ } from "./chunk-MHIWAIVD.js";
16
+ import {
17
+ GhAuthError,
18
+ ensureGhAuth,
19
+ getGhToken
20
+ } from "./chunk-JO3AXHQI.js";
21
+ import {
22
+ stop_default
23
+ } from "./chunk-76QPITKI.js";
24
+ import {
25
+ status_default
26
+ } from "./chunk-6HBZC3BE.js";
27
+ import {
28
+ stripAnsi
29
+ } from "./chunk-MVRF7BES.js";
30
+ import "./chunk-EFMFGOWM.js";
31
+ import "./chunk-TF3QNWNC.js";
32
+ import {
33
+ resolveRuntimeRoot
34
+ } from "./chunk-5NV3LSAJ.js";
35
+ import "./chunk-TH5QPO3Y.js";
36
+ import {
37
+ daemonPidPath,
38
+ httpStatusPath,
39
+ loadGlobalConfig,
40
+ loadProjectConfig,
41
+ projectConfigDir,
42
+ saveGlobalConfig
43
+ } from "./chunk-ROGRTUFI.js";
44
+
45
+ // src/commands/project.ts
46
+ import * as p from "@clack/prompts";
47
+ import { execFile as execFileCallback } from "child_process";
48
+ import { promisify } from "util";
49
+ import { readFile } from "fs/promises";
50
+ import { join } from "path";
51
+ var execFile = promisify(execFileCallback);
52
+ var KNOWN_REQUIRED_SCOPES = ["repo", "read:org", "project"];
53
+ function displayScopeError(error, retryCommand) {
54
+ const plural = error.requiredScopes.length === 1 ? "" : "s";
55
+ p.log.error(
56
+ `Token is missing required scope${plural}: ${error.requiredScopes.join(", ")}`
57
+ );
58
+ const currentSet = new Set(error.currentScopes.map((s) => s.toLowerCase()));
59
+ const scopesToAdd = KNOWN_REQUIRED_SCOPES.filter((s) => !currentSet.has(s));
60
+ const scopeArg = scopesToAdd.length > 0 ? scopesToAdd.join(",") : error.requiredScopes.join(",");
61
+ p.note(
62
+ `gh auth refresh --scopes ${scopeArg}
63
+
64
+ Then re-run: ${retryCommand}`,
65
+ "Fix missing scope"
66
+ );
67
+ }
68
+ function parseProjectAddFlags(args) {
69
+ const flags = { nonInteractive: false, assignedOnly: false };
70
+ for (let i = 0; i < args.length; i += 1) {
71
+ const arg = args[i];
72
+ const next = args[i + 1];
73
+ switch (arg) {
74
+ case "--non-interactive":
75
+ flags.nonInteractive = true;
76
+ break;
77
+ case "--project":
78
+ flags.project = next;
79
+ i += 1;
80
+ break;
81
+ case "--workspace-dir":
82
+ flags.workspaceDir = next;
83
+ i += 1;
84
+ break;
85
+ case "--assigned-only":
86
+ flags.assignedOnly = true;
87
+ break;
88
+ }
89
+ }
90
+ return flags;
91
+ }
92
+ var handler = async (args, options) => {
93
+ const [subcommand, ...rest] = args;
94
+ switch (subcommand) {
95
+ case "add":
96
+ await projectAdd(rest, options);
97
+ return;
98
+ case "list":
99
+ await projectList(options);
100
+ return;
101
+ case "remove":
102
+ await projectRemove(rest, options);
103
+ return;
104
+ case "start":
105
+ await start_default(rest, options);
106
+ return;
107
+ case "stop":
108
+ await stop_default(rest, options);
109
+ return;
110
+ case "switch":
111
+ await projectSwitch(options);
112
+ return;
113
+ case "status":
114
+ await status_default(rest, options);
115
+ return;
116
+ default:
117
+ process.stdout.write(
118
+ "Usage: gh-symphony project <add|list|remove|start|stop|switch|status>\n"
119
+ );
120
+ }
121
+ };
122
+ var project_default = handler;
123
+ function relativeTimeFromNow(isoString, now = /* @__PURE__ */ new Date()) {
124
+ const then = new Date(isoString);
125
+ if (!Number.isFinite(then.getTime())) {
126
+ return "-";
127
+ }
128
+ const diffMs = Math.max(0, now.getTime() - then.getTime());
129
+ const diffS = Math.floor(diffMs / 1e3);
130
+ const diffM = Math.floor(diffS / 60);
131
+ const diffH = Math.floor(diffM / 60);
132
+ const diffD = Math.floor(diffH / 24);
133
+ if (diffS < 60) return `${diffS}s ago`;
134
+ if (diffM < 60) return `${diffM}m ago`;
135
+ if (diffH < 24) return `${diffH}h ago`;
136
+ return `${diffD}d ago`;
137
+ }
138
+ function formatDuration(seconds) {
139
+ if (seconds < 60) return `${seconds}s`;
140
+ const days = Math.floor(seconds / 86400);
141
+ const hours = Math.floor(seconds % 86400 / 3600);
142
+ const minutes = Math.floor(seconds % 3600 / 60);
143
+ if (days > 0) {
144
+ return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
145
+ }
146
+ if (hours > 0) {
147
+ return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
148
+ }
149
+ return `${minutes}m`;
150
+ }
151
+ function parsePsElapsedTime(raw) {
152
+ const value = raw.trim();
153
+ if (value.length === 0) {
154
+ return null;
155
+ }
156
+ const [dayPart, timePart] = value.includes("-") ? value.split("-", 2) : [null, value];
157
+ const timeSegments = timePart.split(":").map((segment) => Number.parseInt(segment, 10));
158
+ if (timeSegments.some((segment) => !Number.isFinite(segment))) {
159
+ return null;
160
+ }
161
+ let seconds = 0;
162
+ if (timeSegments.length === 3) {
163
+ seconds += timeSegments[0] * 3600;
164
+ seconds += timeSegments[1] * 60;
165
+ seconds += timeSegments[2];
166
+ } else if (timeSegments.length === 2) {
167
+ seconds += timeSegments[0] * 60;
168
+ seconds += timeSegments[1];
169
+ } else {
170
+ return null;
171
+ }
172
+ if (dayPart !== null) {
173
+ const days = Number.parseInt(dayPart, 10);
174
+ if (!Number.isFinite(days)) {
175
+ return null;
176
+ }
177
+ seconds += days * 86400;
178
+ }
179
+ return seconds;
180
+ }
181
+ async function readPid(configDir, projectId) {
182
+ try {
183
+ const raw = await readFile(daemonPidPath(configDir, projectId), "utf8");
184
+ const pid = Number.parseInt(raw.trim(), 10);
185
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
186
+ } catch {
187
+ return null;
188
+ }
189
+ }
190
+ function isProcessRunning(pid) {
191
+ try {
192
+ process.kill(pid, 0);
193
+ return true;
194
+ } catch (error) {
195
+ if (error instanceof Error && "code" in error && error.code === "EPERM") {
196
+ return true;
197
+ }
198
+ return false;
199
+ }
200
+ }
201
+ async function readPersistedSnapshot(configDir, projectId) {
202
+ try {
203
+ const runtimeRoot = resolveRuntimeRoot(configDir);
204
+ const content = await readFile(
205
+ join(runtimeRoot, "projects", projectId, "status.json"),
206
+ "utf8"
207
+ );
208
+ return JSON.parse(content);
209
+ } catch {
210
+ return null;
211
+ }
212
+ }
213
+ async function fetchProjectSnapshot(configDir, projectId) {
214
+ return readPersistedSnapshot(configDir, projectId);
215
+ }
216
+ async function readHttpEndpoint(configDir, projectId) {
217
+ try {
218
+ const content = await readFile(httpStatusPath(configDir, projectId), "utf8");
219
+ const state = JSON.parse(content);
220
+ return typeof state.endpoint === "string" && state.endpoint.length > 0 ? state.endpoint : null;
221
+ } catch {
222
+ return null;
223
+ }
224
+ }
225
+ async function readProcessUptime(pid) {
226
+ if (process.platform === "win32") {
227
+ return "-";
228
+ }
229
+ try {
230
+ const { stdout } = await execFile("ps", ["-o", "etime=", "-p", String(pid)]);
231
+ const seconds = parsePsElapsedTime(stdout);
232
+ return seconds === null ? "-" : formatDuration(seconds);
233
+ } catch {
234
+ return "-";
235
+ }
236
+ }
237
+ function defaultProjectName(config, projectId) {
238
+ return config?.displayName ?? config?.slug ?? projectId;
239
+ }
240
+ function isCombiningCodePoint(codePoint) {
241
+ return codePoint >= 768 && codePoint <= 879 || codePoint >= 6832 && codePoint <= 6911 || codePoint >= 7616 && codePoint <= 7679 || codePoint >= 8400 && codePoint <= 8447 || codePoint >= 65056 && codePoint <= 65071;
242
+ }
243
+ function isWideCodePoint(codePoint) {
244
+ return codePoint >= 4352 && (codePoint <= 4447 || codePoint === 9001 || codePoint === 9002 || codePoint >= 11904 && codePoint <= 42191 && codePoint !== 12351 || codePoint >= 44032 && codePoint <= 55203 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 65040 && codePoint <= 65049 || codePoint >= 65072 && codePoint <= 65135 || codePoint >= 65280 && codePoint <= 65376 || codePoint >= 65504 && codePoint <= 65510 || codePoint >= 127744 && codePoint <= 128591 || codePoint >= 129280 && codePoint <= 129535 || codePoint >= 131072 && codePoint <= 262141);
245
+ }
246
+ function stringDisplayWidth(value) {
247
+ const visible = stripAnsi(value);
248
+ let width = 0;
249
+ for (const char of visible) {
250
+ const codePoint = char.codePointAt(0);
251
+ if (codePoint === void 0) {
252
+ continue;
253
+ }
254
+ if (codePoint === 0 || codePoint < 32 || codePoint >= 127 && codePoint < 160 || isCombiningCodePoint(codePoint)) {
255
+ continue;
256
+ }
257
+ width += isWideCodePoint(codePoint) ? 2 : 1;
258
+ }
259
+ return width;
260
+ }
261
+ async function collectProjectListRows(configDir, global) {
262
+ return Promise.all(
263
+ global.projects.map(async (projectId) => {
264
+ const config = await loadProjectConfig(configDir, projectId);
265
+ const pid = await readPid(configDir, projectId);
266
+ const running = pid !== null && isProcessRunning(pid);
267
+ const snapshot = running ? await fetchProjectSnapshot(configDir, projectId) : null;
268
+ return {
269
+ id: projectId,
270
+ name: defaultProjectName(config, projectId),
271
+ status: running ? "running" : "stopped",
272
+ health: snapshot?.health ?? "-",
273
+ activeRuns: snapshot?.summary.activeRuns ?? null,
274
+ lastTick: snapshot?.lastTickAt ? relativeTimeFromNow(snapshot.lastTickAt) : "-",
275
+ uptime: pid !== null && running ? await readProcessUptime(pid) : "-",
276
+ endpoint: running ? await readHttpEndpoint(configDir, projectId) : null,
277
+ active: global.activeProject === projectId
278
+ };
279
+ })
280
+ );
281
+ }
282
+ function renderTable(headers, rows) {
283
+ const widths = headers.map(
284
+ (header, index) => Math.max(
285
+ stringDisplayWidth(header),
286
+ ...rows.map((row) => stringDisplayWidth(row[index] ?? ""))
287
+ )
288
+ );
289
+ const formatRow = (left, sep, right, values) => left + values.map((value, index) => {
290
+ const width = widths[index];
291
+ const displayWidth = stringDisplayWidth(value);
292
+ return ` ${value}${" ".repeat(width - displayWidth)} `;
293
+ }).join(sep) + right;
294
+ const border = (left, middle, right) => left + widths.map((width) => "\u2500".repeat(width + 2)).join(middle) + right;
295
+ return [
296
+ border("\u250C", "\u252C", "\u2510"),
297
+ formatRow("\u2502", "\u2502", "\u2502", headers),
298
+ border("\u251C", "\u253C", "\u2524"),
299
+ ...rows.map((row) => formatRow("\u2502", "\u2502", "\u2502", row)),
300
+ border("\u2514", "\u2534", "\u2518")
301
+ ].join("\n");
302
+ }
303
+ async function projectAdd(args, options) {
304
+ const flags = parseProjectAddFlags(args);
305
+ if (flags.nonInteractive) {
306
+ await projectAddNonInteractive(flags, options);
307
+ return;
308
+ }
309
+ await projectAddInteractive(options);
310
+ }
311
+ async function projectAddNonInteractive(flags, options) {
312
+ let token;
313
+ try {
314
+ token = getGhToken();
315
+ } catch {
316
+ process.stderr.write(
317
+ "Error: GitHub token not found. Run 'gh auth login --scopes repo,read:org,project' or set GITHUB_GRAPHQL_TOKEN.\n"
318
+ );
319
+ process.exitCode = 1;
320
+ return;
321
+ }
322
+ const client = createClient(token);
323
+ let viewer;
324
+ try {
325
+ viewer = await validateToken(client);
326
+ } catch {
327
+ process.stderr.write("Error: Invalid GitHub token.\n");
328
+ process.exitCode = 1;
329
+ return;
330
+ }
331
+ const scopeCheck = checkRequiredScopes(viewer.scopes);
332
+ if (!scopeCheck.valid) {
333
+ process.stderr.write(
334
+ `Error: Missing required PAT scopes: ${scopeCheck.missing.join(", ")}
335
+ `
336
+ );
337
+ process.exitCode = 1;
338
+ return;
339
+ }
340
+ const projects = await listUserProjects(client);
341
+ let project;
342
+ if (flags.project) {
343
+ const match = projects.find(
344
+ (entry) => entry.id === flags.project || entry.url === flags.project
345
+ );
346
+ if (!match) {
347
+ process.stderr.write(`Error: Project not found: ${flags.project}
348
+ `);
349
+ process.exitCode = 1;
350
+ return;
351
+ }
352
+ project = await getProjectDetail(client, match.id);
353
+ } else if (projects.length === 1) {
354
+ project = await getProjectDetail(client, projects[0].id);
355
+ } else {
356
+ process.stderr.write(
357
+ "Error: --project is required when multiple projects exist.\n"
358
+ );
359
+ process.exitCode = 1;
360
+ return;
361
+ }
362
+ const projectId = generateProjectId(project.title, project.id);
363
+ const workspaceDir = flags.workspaceDir ?? join(options.configDir, "workspaces");
364
+ await writeConfig(options.configDir, {
365
+ projectId,
366
+ project,
367
+ repos: project.linkedRepositories,
368
+ workspaceDir,
369
+ assignedOnly: flags.assignedOnly
370
+ });
371
+ if (options.json) {
372
+ process.stdout.write(
373
+ JSON.stringify({ projectId, status: "created" }) + "\n"
374
+ );
375
+ } else {
376
+ process.stdout.write(`Project created: ${projectId}
377
+ `);
378
+ process.stdout.write(`Run 'gh-symphony start' to begin orchestration.
379
+ `);
380
+ }
381
+ }
382
+ async function projectAddInteractive(options) {
383
+ p.intro("gh-symphony - Project Setup");
384
+ const defaultWorkspaceDir = join(options.configDir, "workspaces");
385
+ const existingConfig = await loadGlobalConfig(options.configDir);
386
+ if (existingConfig) {
387
+ const action = await abortIfCancelled(
388
+ p.select({
389
+ message: "Existing configuration detected. What would you like to do?",
390
+ options: [
391
+ { value: "add", label: "Add a new project" },
392
+ { value: "overwrite", label: "Start fresh (overwrite)" }
393
+ ]
394
+ })
395
+ );
396
+ if (action === "overwrite") {
397
+ }
398
+ }
399
+ const s1 = p.spinner();
400
+ s1.start("Checking gh CLI authentication...");
401
+ let login;
402
+ let client;
403
+ try {
404
+ const { login: ghLogin, token } = ensureGhAuth();
405
+ login = ghLogin;
406
+ client = createClient(token);
407
+ s1.stop(`Authenticated as ${login}`);
408
+ } catch (error) {
409
+ s1.stop("Authentication failed.");
410
+ if (error instanceof GhAuthError) {
411
+ if (error.code === "not_installed") {
412
+ p.log.error(
413
+ "gh CLI\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. https://cli.github.com \uC5D0\uC11C \uC124\uCE58\uD558\uC138\uC694."
414
+ );
415
+ } else if (error.code === "not_authenticated") {
416
+ p.log.error(
417
+ "gh auth login --scopes repo,read:org,project \uB97C \uC2E4\uD589\uD558\uC138\uC694."
418
+ );
419
+ } else if (error.code === "missing_scopes") {
420
+ p.log.error(
421
+ "gh auth refresh --scopes repo,read:org,project \uB97C \uC2E4\uD589\uD558\uC138\uC694."
422
+ );
423
+ } else {
424
+ p.log.error(error.message);
425
+ }
426
+ } else {
427
+ p.log.error(error instanceof Error ? error.message : "Unknown error");
428
+ }
429
+ process.exitCode = 1;
430
+ return;
431
+ }
432
+ const s2 = p.spinner();
433
+ s2.start("Loading GitHub Project boards...");
434
+ let projects;
435
+ try {
436
+ projects = await listUserProjects(client);
437
+ s2.stop(
438
+ `Found ${projects.length} project${projects.length === 1 ? "" : "s"}`
439
+ );
440
+ } catch (error) {
441
+ s2.stop("Failed to load projects.");
442
+ if (error instanceof GitHubScopeError) {
443
+ displayScopeError(error, "gh-symphony project add");
444
+ } else {
445
+ p.log.error(error instanceof Error ? error.message : "Unknown error");
446
+ }
447
+ process.exitCode = 1;
448
+ return;
449
+ }
450
+ if (projects.length === 0) {
451
+ p.log.error(
452
+ "No GitHub Projects found. Create a project at https://github.com/orgs/YOUR_ORG/projects and re-run."
453
+ );
454
+ process.exitCode = 1;
455
+ return;
456
+ }
457
+ const selectedProjectId = await abortIfCancelled(
458
+ p.select({
459
+ message: "Step 1/2 - Select a GitHub Project board:",
460
+ options: projects.map((project) => ({
461
+ value: project.id,
462
+ label: `${project.owner.login}/${project.title}`,
463
+ hint: `${project.openItemCount} items`
464
+ })),
465
+ maxItems: 15
466
+ })
467
+ );
468
+ const s2d = p.spinner();
469
+ s2d.start("Loading project details...");
470
+ let projectDetail;
471
+ try {
472
+ projectDetail = await getProjectDetail(client, selectedProjectId);
473
+ s2d.stop(`Loaded: ${projectDetail.title}`);
474
+ } catch (error) {
475
+ s2d.stop("Failed to load project details.");
476
+ p.log.error(error instanceof Error ? error.message : "Unknown error");
477
+ process.exitCode = 1;
478
+ return;
479
+ }
480
+ if (projectDetail.linkedRepositories.length === 0) {
481
+ p.log.warn(
482
+ "No linked repositories found in this project. Add issues from repositories to the project first."
483
+ );
484
+ process.exitCode = 1;
485
+ return;
486
+ }
487
+ const assignedOnly = await abortIfCancelled(
488
+ p.confirm({
489
+ message: "Step 2/2 - Only process issues assigned to the authenticated GitHub user?",
490
+ initialValue: false
491
+ })
492
+ );
493
+ const customizeAdvancedOptions = await abortIfCancelled(
494
+ p.confirm({
495
+ message: "Customize advanced options? (default: No)",
496
+ initialValue: false
497
+ })
498
+ );
499
+ let selectedRepos = projectDetail.linkedRepositories;
500
+ let workspaceDir = defaultWorkspaceDir;
501
+ if (customizeAdvancedOptions) {
502
+ const filterRepositories = await abortIfCancelled(
503
+ p.confirm({
504
+ message: "Filter specific repositories? (default: No)",
505
+ initialValue: false
506
+ })
507
+ );
508
+ if (filterRepositories) {
509
+ selectedRepos = await abortIfCancelled(
510
+ p.multiselect({
511
+ message: "Select repositories to orchestrate:",
512
+ options: projectDetail.linkedRepositories.map((repo) => ({
513
+ value: repo,
514
+ label: `${repo.owner}/${repo.name}`
515
+ })),
516
+ required: true
517
+ })
518
+ );
519
+ }
520
+ workspaceDir = await abortIfCancelled(
521
+ p.text({
522
+ message: "Workspace root directory:",
523
+ placeholder: defaultWorkspaceDir,
524
+ defaultValue: defaultWorkspaceDir,
525
+ validate(value) {
526
+ return value.trim().length > 0 ? void 0 : "Workspace directory is required.";
527
+ }
528
+ })
529
+ );
530
+ }
531
+ const repoSummary = selectedRepos.length === projectDetail.linkedRepositories.length ? `${selectedRepos.map((repo) => `${repo.owner}/${repo.name}`).join(", ")} (all ${selectedRepos.length} linked)` : `${selectedRepos.map((repo) => `${repo.owner}/${repo.name}`).join(", ")} (${selectedRepos.length} of ${projectDetail.linkedRepositories.length} linked)`;
532
+ p.note(
533
+ [
534
+ `User: ${login}`,
535
+ `Project: ${projectDetail.title}`,
536
+ `Repos: ${repoSummary}`,
537
+ `Assigned: ${assignedOnly ? `Only issues assigned to ${login}` : "All project issues"}`,
538
+ `Workspace: ${workspaceDir}`
539
+ ].join("\n"),
540
+ "Configuration Summary"
541
+ );
542
+ const confirmed = await abortIfCancelled(
543
+ p.confirm({ message: "Apply this configuration?" })
544
+ );
545
+ if (!confirmed) {
546
+ p.cancel("Setup cancelled.");
547
+ process.exitCode = 130;
548
+ return;
549
+ }
550
+ const projectId = generateProjectId(projectDetail.title, projectDetail.id);
551
+ const s6 = p.spinner();
552
+ s6.start("Writing configuration...");
553
+ try {
554
+ await writeConfig(options.configDir, {
555
+ projectId,
556
+ project: projectDetail,
557
+ repos: selectedRepos,
558
+ workspaceDir,
559
+ assignedOnly
560
+ });
561
+ s6.stop("Configuration saved.");
562
+ } catch (error) {
563
+ s6.stop("Failed to write configuration.");
564
+ p.log.error(error instanceof Error ? error.message : "Unknown error");
565
+ process.exitCode = 1;
566
+ return;
567
+ }
568
+ p.outro(
569
+ `Project "${projectId}" created.
570
+ Run 'gh-symphony start' to begin orchestration.`
571
+ );
572
+ }
573
+ async function projectList(options) {
574
+ const global = await loadGlobalConfig(options.configDir);
575
+ if (!global?.projects?.length) {
576
+ process.stdout.write("No projects configured.\n");
577
+ return;
578
+ }
579
+ const rows = await collectProjectListRows(options.configDir, global);
580
+ if (options.json) {
581
+ process.stdout.write(JSON.stringify(rows, null, 2) + "\n");
582
+ return;
583
+ }
584
+ const showEndpointColumn = rows.some((row) => row.endpoint !== null);
585
+ const headers = [
586
+ "ID",
587
+ "Name",
588
+ "Status",
589
+ "Health",
590
+ "Active Runs",
591
+ "Last Tick",
592
+ "Uptime",
593
+ ...showEndpointColumn ? ["Endpoint"] : []
594
+ ];
595
+ const table = renderTable(
596
+ headers,
597
+ rows.map((row) => [
598
+ row.id,
599
+ row.name,
600
+ row.status,
601
+ row.health,
602
+ row.activeRuns === null ? "-" : String(row.activeRuns),
603
+ row.lastTick,
604
+ row.uptime,
605
+ ...showEndpointColumn ? [row.endpoint ?? "-"] : []
606
+ ])
607
+ );
608
+ process.stdout.write(`${table}
609
+ `);
610
+ }
611
+ async function projectRemove(args, options) {
612
+ const projectId = args[0];
613
+ if (!projectId) {
614
+ process.stderr.write("Usage: gh-symphony project remove <project-id>\n");
615
+ process.exitCode = 1;
616
+ return;
617
+ }
618
+ const global = await loadGlobalConfig(options.configDir);
619
+ if (!global) {
620
+ process.stderr.write("No configuration found.\n");
621
+ process.exitCode = 1;
622
+ return;
623
+ }
624
+ const updatedProjects = global.projects.filter((entry) => entry !== projectId);
625
+ if (updatedProjects.length === global.projects.length) {
626
+ process.stderr.write(`Project "${projectId}" not found.
627
+ `);
628
+ process.exitCode = 1;
629
+ return;
630
+ }
631
+ const updatedConfig = {
632
+ ...global,
633
+ projects: updatedProjects,
634
+ activeProject: global.activeProject === projectId ? null : global.activeProject
635
+ };
636
+ await saveGlobalConfig(options.configDir, updatedConfig);
637
+ const { rm } = await import("fs/promises");
638
+ try {
639
+ await rm(projectConfigDir(options.configDir, projectId), {
640
+ recursive: true,
641
+ force: true
642
+ });
643
+ } catch {
644
+ }
645
+ process.stdout.write(`Project "${projectId}" removed.
646
+ `);
647
+ }
648
+ async function projectSwitch(options) {
649
+ const global = await loadGlobalConfig(options.configDir);
650
+ if (!global || global.projects.length === 0) {
651
+ process.stderr.write("No projects configured. Run 'gh-symphony init'.\n");
652
+ process.exitCode = 1;
653
+ return;
654
+ }
655
+ if (global.projects.length === 1) {
656
+ process.stdout.write(`Only one project exists: ${global.projects[0]}
657
+ `);
658
+ return;
659
+ }
660
+ const selected = await p.select({
661
+ message: "Select project to activate:",
662
+ options: global.projects.map((projectId) => ({
663
+ value: projectId,
664
+ label: projectId,
665
+ hint: projectId === global.activeProject ? "current" : void 0
666
+ }))
667
+ });
668
+ if (p.isCancel(selected)) {
669
+ p.cancel("Cancelled.");
670
+ return;
671
+ }
672
+ global.activeProject = selected;
673
+ await saveGlobalConfig(options.configDir, global);
674
+ process.stdout.write(`Switched to project: ${selected}
675
+ `);
676
+ }
677
+ export {
678
+ project_default as default
679
+ };