@gh-symphony/cli 0.0.18 → 0.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -15,8 +15,10 @@ import {
15
15
 
16
16
  // src/completion.ts
17
17
  var TOP_LEVEL_COMMANDS = [
18
- "init",
18
+ "workflow",
19
+ "setup",
19
20
  "doctor",
21
+ "upgrade",
20
22
  "start",
21
23
  "stop",
22
24
  "status",
@@ -45,7 +47,35 @@ var GLOBAL_OPTIONS = [
45
47
  var GLOBAL_OPTIONS_WITH_VALUES = ["--config", "--config-dir"];
46
48
  var COMMAND_OPTIONS = {
47
49
  completion: ["bash", "zsh", "fish"],
50
+ workflow: ["init", "validate", "preview"],
51
+ "workflow:init": [
52
+ "--non-interactive",
53
+ "--project",
54
+ "--output",
55
+ "--skip-skills",
56
+ "--skip-context",
57
+ "--dry-run",
58
+ ...GLOBAL_OPTIONS
59
+ ],
60
+ "workflow:validate": ["--file", ...GLOBAL_OPTIONS],
61
+ "workflow:preview": [
62
+ "--file",
63
+ "--sample",
64
+ "--attempt",
65
+ ...GLOBAL_OPTIONS
66
+ ],
67
+ setup: [
68
+ "--non-interactive",
69
+ "--project",
70
+ "--workspace-dir",
71
+ "--assigned-only",
72
+ "--output",
73
+ "--skip-skills",
74
+ "--skip-context",
75
+ ...GLOBAL_OPTIONS
76
+ ],
48
77
  doctor: ["--project-id", "--project", ...GLOBAL_OPTIONS],
78
+ upgrade: [...GLOBAL_OPTIONS],
49
79
  start: ["--project-id", "--project", "--daemon", "-d", ...GLOBAL_OPTIONS],
50
80
  stop: ["--project-id", "--project", "--force", ...GLOBAL_OPTIONS],
51
81
  status: ["--project-id", "--project", "--watch", "-w", ...GLOBAL_OPTIONS],
@@ -87,10 +117,11 @@ var COMMAND_OPTIONS = {
87
117
  "-w",
88
118
  ...GLOBAL_OPTIONS
89
119
  ],
90
- repo: ["list", "add", "remove"],
120
+ repo: ["list", "add", "remove", "sync"],
91
121
  "repo:list": [...GLOBAL_OPTIONS],
92
122
  "repo:add": [...GLOBAL_OPTIONS],
93
123
  "repo:remove": [...GLOBAL_OPTIONS],
124
+ "repo:sync": ["--dry-run", "--prune", ...GLOBAL_OPTIONS],
94
125
  config: ["show", "set", "edit"],
95
126
  "config:show": [...GLOBAL_OPTIONS],
96
127
  "config:set": [...GLOBAL_OPTIONS],
@@ -109,7 +140,7 @@ function renderBashCasePatterns() {
109
140
  return
110
141
  ;;`;
111
142
  }
112
- if (command === "project" || command === "repo" || command === "config") {
143
+ if (command === "workflow" || command === "project" || command === "repo" || command === "config") {
113
144
  return ` ${command})
114
145
  COMPREPLY=( $(compgen -W "${quoteWords(values)}" -- "$cur") )
115
146
  return
@@ -150,6 +181,11 @@ function renderFishLines() {
150
181
  `complete -c gh-symphony -f -n '__fish_seen_subcommand_from config' -a '${subcommand}'`
151
182
  );
152
183
  }
184
+ for (const subcommand of COMMAND_OPTIONS.workflow ?? []) {
185
+ lines.push(
186
+ `complete -c gh-symphony -f -n '__fish_seen_subcommand_from workflow' -a '${subcommand}'`
187
+ );
188
+ }
153
189
  for (const shell of COMMAND_OPTIONS.completion ?? []) {
154
190
  lines.push(
155
191
  `complete -c gh-symphony -f -n '__fish_seen_subcommand_from completion' -a '${shell}'`
@@ -219,7 +255,7 @@ _gh_symphony_completion() {
219
255
  return
220
256
  fi
221
257
 
222
- if [[ "\${path}" == "project" || "\${path}" == "repo" || "\${path}" == "config" || "\${path}" == "completion" ]]; then
258
+ if [[ "\${path}" == "workflow" || "\${path}" == "project" || "\${path}" == "repo" || "\${path}" == "config" || "\${path}" == "completion" ]]; then
223
259
  if [[ -n "\${GH_SYMPHONY_SUBCOMMAND}" ]]; then
224
260
  path="\${path}:\${GH_SYMPHONY_SUBCOMMAND}"
225
261
  fi
@@ -242,18 +278,21 @@ ${bashFunction}complete -F _gh_symphony_completion gh-symphony
242
278
 
243
279
  // src/index.ts
244
280
  var COMMANDS = {
245
- init: () => import("./init-E432UZ32.js"),
246
- doctor: () => import("./doctor-3QT5CZN4.js"),
247
- start: () => import("./start-5JGGJIMC.js"),
281
+ workflow: () => import("./workflow-TBIFY5MO.js"),
282
+ init: () => import("./init-KZT6YNOH.js"),
283
+ setup: () => import("./setup-VWB7RZUQ.js"),
284
+ doctor: () => import("./doctor-IYHCFXOZ.js"),
285
+ upgrade: () => import("./upgrade-3YNF3VKY.js"),
286
+ start: () => import("./start-ENFLZUI6.js"),
248
287
  stop: () => import("./stop-7MFCBQVW.js"),
249
288
  status: () => import("./status-QSCFVGRQ.js"),
250
- run: () => import("./run-5H2R6CHB.js"),
251
- recover: () => import("./recover-UGUTQTWA.js"),
289
+ run: () => import("./run-ETC5UTRA.js"),
290
+ recover: () => import("./recover-5KQI7WH5.js"),
252
291
  logs: () => import("./logs-6JKKYDGJ.js"),
253
- project: () => import("./project-O57C32WF.js"),
254
- repo: () => import("./repo-R3XBIVAX.js"),
255
- config: () => import("./config-cmd-AZ7POMAA.js"),
256
- version: () => import("./version-N7YXKG6V.js")
292
+ project: () => import("./project-UUVHS3ZR.js"),
293
+ repo: () => import("./repo-HDDE7OUI.js"),
294
+ config: () => import("./config-cmd-DNXNL26Z.js"),
295
+ version: () => import("./version-NUBTTOG7.js")
257
296
  };
258
297
  function addGlobalOptions(command) {
259
298
  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");
@@ -317,28 +356,104 @@ function createProgram() {
317
356
  new Command().name("gh-symphony").description("AI Coding Agent Orchestrator").exitOverride().helpOption("-h, --help", "Show help").addHelpCommand("help [command]", "Show help for command").showHelpAfterError("(run with --help for usage)").option("-V, --version", "Show version")
318
357
  );
319
358
  addGlobalOptions(
320
- program.command("init").description("Interactive project setup wizard").allowExcessArguments(false)
359
+ program.command("init", { hidden: true }).description("Alias for 'gh-symphony workflow init'").option("--non-interactive", "Run without prompts").option("--project <id>", "GitHub Project ID or URL").option("--output <path>", "Write WORKFLOW.md to a custom path").option("--skip-skills", "Skip runtime skill generation").option("--skip-context", "Skip .gh-symphony/context.yaml generation").option("--dry-run", "Preview generated files without writing them").allowExcessArguments(false)
360
+ ).action(async function() {
361
+ markInvoked();
362
+ const values = this.optsWithGlobals();
363
+ const args = ["init"];
364
+ pushOption(args, "--non-interactive", values.nonInteractive);
365
+ pushOption(args, "--project", values.project);
366
+ pushOption(args, "--output", values.output);
367
+ pushOption(args, "--skip-skills", values.skipSkills);
368
+ pushOption(args, "--skip-context", values.skipContext);
369
+ pushOption(args, "--dry-run", values.dryRun);
370
+ await invokeHandler("workflow", args, values);
371
+ });
372
+ const workflow = addGlobalOptions(
373
+ program.command("workflow").description("Manage WORKFLOW.md authoring")
374
+ );
375
+ workflow.action(async function() {
376
+ markInvoked();
377
+ await invokeHandler(
378
+ "workflow",
379
+ [],
380
+ this.optsWithGlobals()
381
+ );
382
+ });
383
+ addGlobalOptions(
384
+ workflow.command("init").description("Generate WORKFLOW.md and workflow support files").option("--non-interactive", "Run without prompts").option("--project <id>", "GitHub Project ID or URL").option("--output <path>", "Write WORKFLOW.md to a custom path").option("--skip-skills", "Skip runtime skill generation").option("--skip-context", "Skip .gh-symphony/context.yaml generation").option("--dry-run", "Preview generated files without writing them").allowExcessArguments(false)
385
+ ).action(async function() {
386
+ markInvoked();
387
+ const values = this.optsWithGlobals();
388
+ const args = ["init"];
389
+ pushOption(args, "--non-interactive", values.nonInteractive);
390
+ pushOption(args, "--project", values.project);
391
+ pushOption(args, "--output", values.output);
392
+ pushOption(args, "--skip-skills", values.skipSkills);
393
+ pushOption(args, "--skip-context", values.skipContext);
394
+ pushOption(args, "--dry-run", values.dryRun);
395
+ await invokeHandler("workflow", args, values);
396
+ });
397
+ addGlobalOptions(
398
+ workflow.command("validate").description("Parse and strictly validate a WORKFLOW.md file").option("--file <path>", "Validate a custom WORKFLOW.md path").allowExcessArguments(false)
399
+ ).action(async function() {
400
+ markInvoked();
401
+ const values = this.optsWithGlobals();
402
+ const args = ["validate"];
403
+ pushOption(args, "--file", values.file);
404
+ await invokeHandler("workflow", args, values);
405
+ });
406
+ addGlobalOptions(
407
+ workflow.command("preview").description("Render the final worker prompt from a sample issue").option("--file <path>", "Read a custom WORKFLOW.md path").option("--sample <json>", "Read sample issue JSON from a file").option("--attempt <n>", "Render as retry attempt n").allowExcessArguments(false)
408
+ ).action(async function() {
409
+ markInvoked();
410
+ const values = this.optsWithGlobals();
411
+ const args = ["preview"];
412
+ pushOption(args, "--file", values.file);
413
+ pushOption(args, "--sample", values.sample);
414
+ pushOption(args, "--attempt", values.attempt);
415
+ await invokeHandler("workflow", args, values);
416
+ });
417
+ addGlobalOptions(
418
+ program.command("setup").description("Run the one-command first-run setup flow").option("--non-interactive", "Run without prompts").option("--project <id>", "GitHub Project ID or URL").option("--workspace-dir <path>", "Workspace directory").option("--assigned-only", "Limit processing to assigned issues").option("--output <path>", "Write WORKFLOW.md to a custom path").option("--skip-skills", "Skip runtime skill generation").option("--skip-context", "Skip .gh-symphony/context.yaml generation").allowExcessArguments(false)
321
419
  ).action(async function() {
322
420
  markInvoked();
323
- await invokeHandler("init", [], this.optsWithGlobals());
421
+ const values = this.optsWithGlobals();
422
+ const args = [];
423
+ pushOption(args, "--non-interactive", values.nonInteractive);
424
+ pushOption(args, "--project", values.project);
425
+ pushOption(args, "--workspace-dir", values.workspaceDir);
426
+ pushOption(args, "--assigned-only", values.assignedOnly);
427
+ pushOption(args, "--output", values.output);
428
+ pushOption(args, "--skip-skills", values.skipSkills);
429
+ pushOption(args, "--skip-context", values.skipContext);
430
+ await invokeHandler("setup", args, values);
324
431
  });
325
432
  addGlobalOptions(
326
- program.command("doctor").description("Run first-run diagnostics").option("--project-id <projectId>", "Project identifier").addOption(new Option("--project <projectId>").hideHelp()).allowExcessArguments(false)
433
+ program.command("doctor").description("Run diagnostics and optional first-run remediation").option("--project-id <projectId>", "Project identifier").option("--fix", "Apply safe remediation steps and print manual follow-ups").addOption(new Option("--project <projectId>").hideHelp()).allowExcessArguments(false)
327
434
  ).action(async function() {
328
435
  markInvoked();
329
436
  const values = this.optsWithGlobals();
330
437
  const args = [];
331
438
  pushOption(args, "--project-id", resolveProjectId(values));
439
+ pushOption(args, "--fix", values.fix);
332
440
  await invokeHandler("doctor", args, values);
333
441
  });
334
442
  addGlobalOptions(
335
- program.command("start").description("Start the orchestrator").option("-d, --daemon", "Start in daemon mode").option("--http [port]", "Expose dashboard and refresh endpoints over HTTP").option("--log-level <level>", "Orchestrator lifecycle log level").option("--project-id <projectId>", "Project identifier").addOption(new Option("--project <projectId>").hideHelp()).allowExcessArguments(false)
443
+ program.command("upgrade").description("Upgrade the CLI to the latest published version").allowExcessArguments(false)
444
+ ).action(async function() {
445
+ markInvoked();
446
+ await invokeHandler("upgrade", [], this.optsWithGlobals());
447
+ });
448
+ addGlobalOptions(
449
+ program.command("start").description("Start the orchestrator").option("-d, --daemon", "Start in daemon mode").option("--once", "Run a single orchestration tick and exit").option("--http [port]", "Expose dashboard and refresh endpoints over HTTP").option("--log-level <level>", "Orchestrator lifecycle log level").option("--project-id <projectId>", "Project identifier").addOption(new Option("--project <projectId>").hideHelp()).allowExcessArguments(false)
336
450
  ).action(async function() {
337
451
  markInvoked();
338
452
  const values = this.optsWithGlobals();
339
453
  const args = [];
340
454
  pushOption(args, "--project-id", resolveProjectId(values));
341
455
  pushOption(args, "--daemon", values.daemon);
456
+ pushOption(args, "--once", values.once);
342
457
  pushOption(args, "--http", values.http);
343
458
  pushOption(args, "--log-level", values.logLevel);
344
459
  await invokeHandler("start", args, values);
@@ -434,13 +549,14 @@ function createProgram() {
434
549
  );
435
550
  });
436
551
  addGlobalOptions(
437
- project.command("start").description("Start a specific project").option("-d, --daemon", "Start in daemon mode").option("--http [port]", "Expose dashboard and refresh endpoints over HTTP").option("--log-level <level>", "Orchestrator lifecycle log level").option("--project-id <projectId>", "Project identifier").addOption(new Option("--project <projectId>").hideHelp()).allowExcessArguments(false)
552
+ project.command("start").description("Start a specific project").option("-d, --daemon", "Start in daemon mode").option("--once", "Run a single orchestration tick and exit").option("--http [port]", "Expose dashboard and refresh endpoints over HTTP").option("--log-level <level>", "Orchestrator lifecycle log level").option("--project-id <projectId>", "Project identifier").addOption(new Option("--project <projectId>").hideHelp()).allowExcessArguments(false)
438
553
  ).action(async function() {
439
554
  markInvoked();
440
555
  const values = this.optsWithGlobals();
441
556
  const args = ["start"];
442
557
  pushOption(args, "--project-id", resolveProjectId(values));
443
558
  pushOption(args, "--daemon", values.daemon);
559
+ pushOption(args, "--once", values.once);
444
560
  pushOption(args, "--http", values.http);
445
561
  pushOption(args, "--log-level", values.logLevel);
446
562
  await invokeHandler("project", args, values);
@@ -512,6 +628,16 @@ function createProgram() {
512
628
  this.optsWithGlobals()
513
629
  );
514
630
  });
631
+ addGlobalOptions(
632
+ repo.command("sync").description("Sync repositories from the active GitHub Project").option("--dry-run", "Preview repository changes without writing config").option("--prune", "Remove local repositories that are no longer linked").allowExcessArguments(false)
633
+ ).action(async function() {
634
+ markInvoked();
635
+ const values = this.optsWithGlobals();
636
+ const args = ["sync"];
637
+ pushOption(args, "--dry-run", values.dryRun);
638
+ pushOption(args, "--prune", values.prune);
639
+ await invokeHandler("repo", args, values);
640
+ });
515
641
  const config = addGlobalOptions(
516
642
  program.command("config").description("Manage CLI configuration")
517
643
  );
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ abortIfCancelled,
4
+ buildAutomaticStateMappings,
5
+ buildDryRunJsonResult,
6
+ generateProjectId,
7
+ init_default,
8
+ planEcosystem,
9
+ planWorkflowArtifacts,
10
+ promptStateMappings,
11
+ renderDryRunPreview,
12
+ resolveStatusField,
13
+ writeConfig,
14
+ writeEcosystem,
15
+ writeWorkflowPlan
16
+ } from "./chunk-RN2PACNV.js";
17
+ import "./chunk-TILHWBP6.js";
18
+ import "./chunk-ROGRTUFI.js";
19
+ export {
20
+ abortIfCancelled,
21
+ buildAutomaticStateMappings,
22
+ buildDryRunJsonResult,
23
+ init_default as default,
24
+ generateProjectId,
25
+ planEcosystem,
26
+ planWorkflowArtifacts,
27
+ promptStateMappings,
28
+ renderDryRunPreview,
29
+ resolveStatusField,
30
+ writeConfig,
31
+ writeEcosystem,
32
+ writeWorkflowPlan
33
+ };
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ project_default,
4
+ promptProjectRegistrationOptions,
5
+ renderProjectRegistrationSummary
6
+ } from "./chunk-3AWF54PI.js";
7
+ import "./chunk-RN2PACNV.js";
8
+ import "./chunk-EKKT5USP.js";
9
+ import "./chunk-HZVDTAPS.js";
10
+ import "./chunk-M3IFVLQS.js";
11
+ import "./chunk-TILHWBP6.js";
12
+ import "./chunk-XN5ABWZ6.js";
13
+ import "./chunk-MVRF7BES.js";
14
+ import "./chunk-5NV3LSAJ.js";
15
+ import "./chunk-Y6TYJMNT.js";
16
+ import "./chunk-C7G7RJ4G.js";
17
+ import "./chunk-ROGRTUFI.js";
18
+ export {
19
+ project_default as default,
20
+ promptProjectRegistrationOptions,
21
+ renderProjectRegistrationSummary
22
+ };
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "./chunk-LZE6YUSB.js";
5
- import "./chunk-OL73UN2X.js";
4
+ } from "./chunk-HZVDTAPS.js";
5
+ import "./chunk-M3IFVLQS.js";
6
6
  import {
7
7
  resolveRuntimeRoot
8
8
  } from "./chunk-5NV3LSAJ.js";
@@ -0,0 +1,321 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ GitHubScopeError,
4
+ checkRequiredScopes,
5
+ createClient,
6
+ getGhToken,
7
+ getProjectDetail,
8
+ validateToken
9
+ } from "./chunk-TILHWBP6.js";
10
+ import {
11
+ loadActiveProjectConfig,
12
+ loadGlobalConfig,
13
+ saveProjectConfig
14
+ } from "./chunk-ROGRTUFI.js";
15
+
16
+ // src/commands/repo.ts
17
+ var handler = async (args, options) => {
18
+ const [subcommand, ...rest] = args;
19
+ switch (subcommand) {
20
+ case "list":
21
+ await repoList(options);
22
+ break;
23
+ case "add":
24
+ await repoAdd(rest, options);
25
+ break;
26
+ case "remove":
27
+ await repoRemove(rest, options);
28
+ break;
29
+ case "sync":
30
+ await repoSync(rest, options);
31
+ break;
32
+ default:
33
+ process.stderr.write(
34
+ "Usage: gh-symphony repo <list|add|remove|sync> [repo]\n"
35
+ );
36
+ process.exitCode = 2;
37
+ }
38
+ };
39
+ var repo_default = handler;
40
+ function repoKey(repo) {
41
+ return `${repo.owner}/${repo.name}`.toLowerCase();
42
+ }
43
+ function toRepoConfigEntry(repo) {
44
+ return {
45
+ owner: repo.owner,
46
+ name: repo.name,
47
+ cloneUrl: repo.cloneUrl
48
+ };
49
+ }
50
+ function parseRepoSyncFlags(args) {
51
+ const flags = { dryRun: false, prune: false };
52
+ for (const arg of args) {
53
+ if (arg === "--dry-run") {
54
+ flags.dryRun = true;
55
+ } else if (arg === "--prune") {
56
+ flags.prune = true;
57
+ }
58
+ }
59
+ return flags;
60
+ }
61
+ function displayScopeError(error) {
62
+ const plural = error.requiredScopes.length === 1 ? "" : "s";
63
+ process.stderr.write(
64
+ `Token is missing required scope${plural}: ${error.requiredScopes.join(", ")}
65
+ `
66
+ );
67
+ const currentSet = new Set(error.currentScopes.map((scope) => scope.toLowerCase()));
68
+ const scopesToAdd = ["repo", "read:org", "project"].filter(
69
+ (scope) => !currentSet.has(scope)
70
+ );
71
+ const scopeArg = scopesToAdd.length > 0 ? scopesToAdd.join(",") : error.requiredScopes.join(",");
72
+ process.stderr.write(
73
+ `Run 'gh auth refresh --scopes ${scopeArg}' and try again.
74
+ `
75
+ );
76
+ }
77
+ function formatRepoSpec(repo) {
78
+ return `${repo.owner}/${repo.name}`;
79
+ }
80
+ function sortRepos(repos) {
81
+ return [...repos].sort(
82
+ (left, right) => formatRepoSpec(left).localeCompare(formatRepoSpec(right))
83
+ );
84
+ }
85
+ function renderRepoGroup(label, repos) {
86
+ if (repos.length === 0) {
87
+ return [`${label}: none`];
88
+ }
89
+ return [label, ...sortRepos(repos).map((repo) => ` ${formatRepoSpec(repo)}`)];
90
+ }
91
+ function buildSyncedRepositories(currentRepos, linkedMap, linkedRepositories, prune) {
92
+ const retained = currentRepos.filter((repo) => linkedMap.has(repoKey(repo)) || !prune).map((repo) => {
93
+ const linked = linkedMap.get(repoKey(repo));
94
+ return linked ? toRepoConfigEntry(linked) : { ...repo };
95
+ });
96
+ const currentKeys = new Set(currentRepos.map((repo) => repoKey(repo)));
97
+ const additions = sortRepos(
98
+ linkedRepositories.filter((repo) => !currentKeys.has(repoKey(repo))).map(toRepoConfigEntry)
99
+ );
100
+ return [...retained, ...additions];
101
+ }
102
+ function writeRepoSummary(summary, options) {
103
+ if (options.json) {
104
+ process.stdout.write(JSON.stringify(summary, null, 2) + "\n");
105
+ return;
106
+ }
107
+ process.stdout.write(
108
+ [
109
+ `Repository sync ${summary.dryRun ? "preview" : "complete"} for ${summary.projectId}`,
110
+ `Mode: ${summary.prune ? "prune" : "additive"}`,
111
+ ...renderRepoGroup("Added", summary.added),
112
+ ...renderRepoGroup("Removed", summary.removed),
113
+ ...renderRepoGroup("Unchanged", summary.unchanged),
114
+ summary.dryRun ? "No config changes written." : "Configuration updated."
115
+ ].join("\n") + "\n"
116
+ );
117
+ }
118
+ async function repoList(options) {
119
+ const ws = await loadActiveProjectConfig(options.configDir);
120
+ if (!ws) {
121
+ process.stderr.write("No project configured.\n");
122
+ process.exitCode = 1;
123
+ return;
124
+ }
125
+ if (options.json) {
126
+ process.stdout.write(JSON.stringify(ws.repositories, null, 2) + "\n");
127
+ return;
128
+ }
129
+ process.stdout.write("Repositories:\n");
130
+ for (const repo of ws.repositories) {
131
+ process.stdout.write(` ${repo.owner}/${repo.name}
132
+ `);
133
+ }
134
+ }
135
+ async function repoAdd(args, options) {
136
+ const [repoSpec] = args;
137
+ if (!repoSpec || !repoSpec.includes("/")) {
138
+ process.stderr.write("Usage: gh-symphony repo add <owner/name>\n");
139
+ process.exitCode = 2;
140
+ return;
141
+ }
142
+ const global = await loadGlobalConfig(options.configDir);
143
+ if (!global?.activeProject) {
144
+ process.stderr.write("No active project.\n");
145
+ process.exitCode = 1;
146
+ return;
147
+ }
148
+ const ws = await loadActiveProjectConfig(options.configDir);
149
+ if (!ws) {
150
+ process.stderr.write("Project config missing.\n");
151
+ process.exitCode = 1;
152
+ return;
153
+ }
154
+ const [owner, name] = repoSpec.split("/");
155
+ if (!owner || !name) {
156
+ process.stderr.write("Invalid repo format. Use: owner/name\n");
157
+ process.exitCode = 2;
158
+ return;
159
+ }
160
+ if (ws.repositories.some(
161
+ (r) => r.owner === owner && r.name === name
162
+ )) {
163
+ process.stdout.write(`Repository ${repoSpec} is already configured.
164
+ `);
165
+ return;
166
+ }
167
+ ws.repositories.push({
168
+ owner,
169
+ name,
170
+ cloneUrl: `https://github.com/${owner}/${name}.git`
171
+ });
172
+ await saveProjectConfig(options.configDir, global.activeProject, ws);
173
+ process.stdout.write(`Added repository: ${repoSpec}
174
+ `);
175
+ }
176
+ async function repoRemove(args, options) {
177
+ const [repoSpec] = args;
178
+ if (!repoSpec || !repoSpec.includes("/")) {
179
+ process.stderr.write("Usage: gh-symphony repo remove <owner/name>\n");
180
+ process.exitCode = 2;
181
+ return;
182
+ }
183
+ const global = await loadGlobalConfig(options.configDir);
184
+ if (!global?.activeProject) {
185
+ process.stderr.write("No active project.\n");
186
+ process.exitCode = 1;
187
+ return;
188
+ }
189
+ const ws = await loadActiveProjectConfig(options.configDir);
190
+ if (!ws) {
191
+ process.stderr.write("Project config missing.\n");
192
+ process.exitCode = 1;
193
+ return;
194
+ }
195
+ const [owner, name] = repoSpec.split("/");
196
+ const idx = ws.repositories.findIndex(
197
+ (r) => r.owner === owner && r.name === name
198
+ );
199
+ if (idx === -1) {
200
+ process.stderr.write(`Repository ${repoSpec} is not configured.
201
+ `);
202
+ process.exitCode = 1;
203
+ return;
204
+ }
205
+ ws.repositories.splice(idx, 1);
206
+ await saveProjectConfig(options.configDir, global.activeProject, ws);
207
+ process.stdout.write(`Removed repository: ${repoSpec}
208
+ `);
209
+ }
210
+ async function repoSync(args, options) {
211
+ const flags = parseRepoSyncFlags(args);
212
+ const global = await loadGlobalConfig(options.configDir);
213
+ if (!global?.activeProject) {
214
+ process.stderr.write("No active project.\n");
215
+ process.exitCode = 1;
216
+ return;
217
+ }
218
+ const ws = await loadActiveProjectConfig(options.configDir);
219
+ if (!ws) {
220
+ process.stderr.write("Project config missing.\n");
221
+ process.exitCode = 1;
222
+ return;
223
+ }
224
+ const projectBindingId = typeof ws.tracker.settings?.projectId === "string" ? ws.tracker.settings.projectId : ws.tracker.bindingId;
225
+ if (!projectBindingId) {
226
+ process.stderr.write(
227
+ "Active project is missing its GitHub Project binding. Re-run 'gh-symphony project add'.\n"
228
+ );
229
+ process.exitCode = 1;
230
+ return;
231
+ }
232
+ let token;
233
+ try {
234
+ token = getGhToken();
235
+ } catch {
236
+ process.stderr.write(
237
+ "Error: GitHub token not found. Run 'gh auth login --scopes repo,read:org,project' or set GITHUB_GRAPHQL_TOKEN.\n"
238
+ );
239
+ process.exitCode = 1;
240
+ return;
241
+ }
242
+ const client = createClient(token);
243
+ try {
244
+ const viewer = await validateToken(client);
245
+ const scopeCheck = checkRequiredScopes(viewer.scopes);
246
+ if (!scopeCheck.valid) {
247
+ process.stderr.write(
248
+ `Error: Missing required PAT scopes: ${scopeCheck.missing.join(", ")}
249
+ `
250
+ );
251
+ process.exitCode = 1;
252
+ return;
253
+ }
254
+ } catch {
255
+ process.stderr.write("Error: Invalid GitHub token.\n");
256
+ process.exitCode = 1;
257
+ return;
258
+ }
259
+ let projectDetail;
260
+ try {
261
+ projectDetail = await getProjectDetail(client, projectBindingId);
262
+ } catch (error) {
263
+ if (error instanceof GitHubScopeError) {
264
+ displayScopeError(error);
265
+ } else {
266
+ process.stderr.write(
267
+ `${error instanceof Error ? error.message : "Failed to load linked repositories."}
268
+ `
269
+ );
270
+ }
271
+ process.exitCode = 1;
272
+ return;
273
+ }
274
+ const currentRepos = ws.repositories;
275
+ const currentMap = new Map(
276
+ currentRepos.map((repo) => [repoKey(repo), repo])
277
+ );
278
+ const linkedMap = new Map(
279
+ projectDetail.linkedRepositories.map((repo) => [
280
+ repoKey(repo),
281
+ repo
282
+ ])
283
+ );
284
+ const added = projectDetail.linkedRepositories.filter((repo) => !currentMap.has(repoKey(repo))).map(toRepoConfigEntry);
285
+ const removed = flags.prune ? currentRepos.filter((repo) => !linkedMap.has(repoKey(repo))).map((repo) => ({ ...repo })) : [];
286
+ const unchanged = flags.prune ? currentRepos.filter((repo) => linkedMap.has(repoKey(repo))).map((repo) => {
287
+ const linked = linkedMap.get(repoKey(repo));
288
+ return linked ? toRepoConfigEntry(linked) : { ...repo };
289
+ }) : currentRepos.map((repo) => {
290
+ const linked = linkedMap.get(repoKey(repo));
291
+ return linked ? toRepoConfigEntry(linked) : { ...repo };
292
+ });
293
+ const nextRepositories = buildSyncedRepositories(
294
+ currentRepos,
295
+ linkedMap,
296
+ projectDetail.linkedRepositories,
297
+ flags.prune
298
+ );
299
+ if (!flags.dryRun) {
300
+ await saveProjectConfig(options.configDir, global.activeProject, {
301
+ ...ws,
302
+ repositories: nextRepositories
303
+ });
304
+ }
305
+ writeRepoSummary(
306
+ {
307
+ projectId: global.activeProject,
308
+ githubProjectId: projectBindingId,
309
+ dryRun: flags.dryRun,
310
+ prune: flags.prune,
311
+ added,
312
+ removed,
313
+ unchanged,
314
+ repositories: nextRepositories
315
+ },
316
+ options
317
+ );
318
+ }
319
+ export {
320
+ repo_default as default
321
+ };
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "./chunk-LZE6YUSB.js";
5
- import "./chunk-OL73UN2X.js";
4
+ } from "./chunk-HZVDTAPS.js";
5
+ import "./chunk-M3IFVLQS.js";
6
6
  import {
7
7
  resolveRuntimeRoot
8
8
  } from "./chunk-5NV3LSAJ.js";