@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/README.md +85 -14
- package/dist/{project-O57C32WF.js → chunk-3AWF54PI.js} +104 -90
- package/dist/{chunk-ZYYY55WB.js → chunk-EKKT5USP.js} +74 -23
- package/dist/{chunk-LZE6YUSB.js → chunk-HZVDTAPS.js} +32 -72
- package/dist/{chunk-5YLETHMR.js → chunk-RN2PACNV.js} +345 -169
- package/dist/{chunk-62L6QQE6.js → chunk-TILHWBP6.js} +277 -1
- package/dist/{config-cmd-AZ7POMAA.js → config-cmd-DNXNL26Z.js} +3 -1
- package/dist/doctor-IYHCFXOZ.js +1126 -0
- package/dist/index.js +144 -18
- package/dist/init-KZT6YNOH.js +33 -0
- package/dist/project-UUVHS3ZR.js +22 -0
- package/dist/{recover-UGUTQTWA.js → recover-5KQI7WH5.js} +2 -2
- package/dist/repo-HDDE7OUI.js +321 -0
- package/dist/{run-5H2R6CHB.js → run-ETC5UTRA.js} +2 -2
- package/dist/setup-VWB7RZUQ.js +431 -0
- package/dist/{start-5JGGJIMC.js → start-ENFLZUI6.js} +4 -4
- package/dist/upgrade-3YNF3VKY.js +165 -0
- package/dist/{version-N7YXKG6V.js → version-NUBTTOG7.js} +1 -1
- package/dist/worker-entry.js +71 -193
- package/dist/workflow-TBIFY5MO.js +497 -0
- package/package.json +2 -2
- package/dist/chunk-7UBUBSMH.js +0 -134
- package/dist/doctor-3QT5CZN4.js +0 -532
- package/dist/init-E432UZ32.js +0 -18
- package/dist/repo-R3XBIVAX.js +0 -121
- package/dist/{chunk-OL73UN2X.js → chunk-M3IFVLQS.js} +77 -77
package/dist/index.js
CHANGED
|
@@ -15,8 +15,10 @@ import {
|
|
|
15
15
|
|
|
16
16
|
// src/completion.ts
|
|
17
17
|
var TOP_LEVEL_COMMANDS = [
|
|
18
|
-
"
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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-
|
|
251
|
-
recover: () => import("./recover-
|
|
289
|
+
run: () => import("./run-ETC5UTRA.js"),
|
|
290
|
+
recover: () => import("./recover-5KQI7WH5.js"),
|
|
252
291
|
logs: () => import("./logs-6JKKYDGJ.js"),
|
|
253
|
-
project: () => import("./project-
|
|
254
|
-
repo: () => import("./repo-
|
|
255
|
-
config: () => import("./config-cmd-
|
|
256
|
-
version: () => import("./version-
|
|
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("
|
|
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
|
-
|
|
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
|
|
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("
|
|
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
|
+
};
|
|
@@ -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
|
+
};
|