@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/README.md
CHANGED
|
@@ -8,10 +8,15 @@ The following tools must be installed before using the CLI:
|
|
|
8
8
|
|
|
9
9
|
- **[Node.js](https://nodejs.org/)** (v24+) with npm
|
|
10
10
|
- **[Git](https://git-scm.com/)**
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
- One GitHub auth source with required scopes (`repo`, `read:org`, `project`):
|
|
12
|
+
- **[GitHub CLI (`gh`)](https://cli.github.com/)**:
|
|
13
|
+
```bash
|
|
14
|
+
gh auth login --scopes repo,read:org,project
|
|
15
|
+
```
|
|
16
|
+
- Or `GITHUB_GRAPHQL_TOKEN` for CI or minimal shells:
|
|
17
|
+
```bash
|
|
18
|
+
export GITHUB_GRAPHQL_TOKEN=ghp_your_classic_token
|
|
19
|
+
```
|
|
15
20
|
|
|
16
21
|
## 1. Install Package
|
|
17
22
|
|
|
@@ -29,7 +34,9 @@ Validate the machine and repo prerequisites before first use:
|
|
|
29
34
|
|
|
30
35
|
```bash
|
|
31
36
|
gh-symphony doctor
|
|
37
|
+
gh-symphony doctor --fix
|
|
32
38
|
gh-symphony doctor --json
|
|
39
|
+
GITHUB_GRAPHQL_TOKEN=ghp_your_classic_token gh-symphony doctor --json
|
|
33
40
|
```
|
|
34
41
|
|
|
35
42
|
Enable shell completion:
|
|
@@ -51,19 +58,34 @@ autoload -Uz compinit && compinit
|
|
|
51
58
|
Navigate to the repository you want to orchestrate, then run:
|
|
52
59
|
|
|
53
60
|
```bash
|
|
54
|
-
gh-symphony init
|
|
61
|
+
gh-symphony workflow init
|
|
62
|
+
gh-symphony workflow init --dry-run
|
|
63
|
+
gh-symphony workflow validate
|
|
64
|
+
gh-symphony workflow preview
|
|
55
65
|
```
|
|
56
66
|
|
|
57
67
|
The interactive wizard will:
|
|
58
68
|
|
|
59
|
-
1. Authenticate via `gh` CLI
|
|
69
|
+
1. Authenticate via `GITHUB_GRAPHQL_TOKEN` or fall back to `gh` CLI
|
|
60
70
|
2. Let you select a **GitHub Project** to bind
|
|
61
71
|
3. Map project status columns to workflow phases (active / wait / terminal)
|
|
62
72
|
4. Generate `WORKFLOW.md` and supporting files in the repository
|
|
63
73
|
|
|
74
|
+
Token-only interactive setup is supported:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
export GITHUB_GRAPHQL_TOKEN=ghp_your_classic_token
|
|
78
|
+
gh-symphony workflow init
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Use `--dry-run` to preview the generated write plan first. The preview reports
|
|
82
|
+
whether `WORKFLOW.md`, `.gh-symphony/context.yaml`,
|
|
83
|
+
`.gh-symphony/reference-workflow.md`, and runtime skill files would be created,
|
|
84
|
+
updated, or left unchanged, and then exits without modifying the repository.
|
|
85
|
+
|
|
64
86
|
### Customizing Agent Behavior
|
|
65
87
|
|
|
66
|
-
`gh-symphony init` generates skill files under `.codex/skills/` (or `.claude/skills/` for Claude Code). These skills define how the AI agent handles commits, pushes, pulls, and project status transitions.
|
|
88
|
+
`gh-symphony workflow init` generates skill files under `.codex/skills/` (or `.claude/skills/` for Claude Code). These skills define how the AI agent handles commits, pushes, pulls, and project status transitions.
|
|
67
89
|
|
|
68
90
|
You can further customize the agent's behavior by editing `WORKFLOW.md` — this is the policy layer that controls what the agent does at each workflow phase.
|
|
69
91
|
|
|
@@ -111,26 +133,54 @@ gh-symphony project add
|
|
|
111
133
|
|
|
112
134
|
The interactive wizard will:
|
|
113
135
|
|
|
114
|
-
1. Authenticate via `gh` CLI
|
|
136
|
+
1. Authenticate via `GITHUB_GRAPHQL_TOKEN` or fall back to `gh` CLI
|
|
115
137
|
2. Let you select a **GitHub Project**
|
|
116
138
|
3. Optionally limit processing to issues assigned to the authenticated user
|
|
117
139
|
4. Optionally customize advanced settings for repository filtering and workspace root directory
|
|
118
140
|
5. Write project configuration to `~/.gh-symphony/`
|
|
119
141
|
|
|
142
|
+
Token-only non-interactive setup:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
GITHUB_GRAPHQL_TOKEN=ghp_your_classic_token \
|
|
146
|
+
gh-symphony workflow init --non-interactive --project PVT_xxx --output WORKFLOW.md
|
|
147
|
+
|
|
148
|
+
GITHUB_GRAPHQL_TOKEN=ghp_your_classic_token \
|
|
149
|
+
gh-symphony project add --non-interactive --project PVT_xxx --workspace-dir ~/.gh-symphony/workspaces
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Token-only project registration is also supported:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
export GITHUB_GRAPHQL_TOKEN=ghp_your_classic_token
|
|
156
|
+
gh-symphony project add
|
|
157
|
+
```
|
|
158
|
+
|
|
120
159
|
### Project Management
|
|
121
160
|
|
|
122
161
|
```bash
|
|
123
|
-
gh-symphony doctor # Validate auth, config, WORKFLOW.md, and runtime command
|
|
162
|
+
gh-symphony doctor # Validate local prerequisites, auth, config, WORKFLOW.md, and runtime command
|
|
163
|
+
gh-symphony doctor --fix # Apply safe fixes and guide/launch follow-up recovery commands
|
|
124
164
|
gh-symphony project list # List all configured projects
|
|
125
165
|
gh-symphony project remove <id> # Remove a project
|
|
166
|
+
gh-symphony repo sync # Add newly linked repositories from the GitHub Project
|
|
167
|
+
gh-symphony repo sync --dry-run # Preview linked repository drift
|
|
168
|
+
gh-symphony repo sync --prune # Remove local repositories no longer linked
|
|
126
169
|
```
|
|
127
170
|
|
|
171
|
+
Use `gh-symphony repo sync` when the GitHub Project board has gained or lost
|
|
172
|
+
linked repositories since the project was first added locally. Default sync is
|
|
173
|
+
additive; `--prune` switches to strict alignment, and `--json` prints the added,
|
|
174
|
+
removed, unchanged, and final repository sets.
|
|
175
|
+
|
|
128
176
|
## 4. Run the Orchestrator
|
|
129
177
|
|
|
130
178
|
### Foreground
|
|
131
179
|
|
|
132
180
|
```bash
|
|
133
181
|
gh-symphony start
|
|
182
|
+
gh-symphony start --once # Run startup cleanup + one orchestration tick, then exit
|
|
183
|
+
gh-symphony project start --once # Same one-shot flow for an explicit project
|
|
134
184
|
```
|
|
135
185
|
|
|
136
186
|
### Background (daemon)
|
|
@@ -140,6 +190,8 @@ gh-symphony start --daemon # Start in background
|
|
|
140
190
|
gh-symphony stop # Stop the daemon
|
|
141
191
|
```
|
|
142
192
|
|
|
193
|
+
Use `start --once` for the first real managed-project run or a CI smoke check. It reuses the configured GitHub Project binding and `WORKFLOW.md` and performs exactly one poll/reconcile/dispatch cycle instead of entering the long-running orchestration loop. `--daemon --once` is rejected because the modes conflict. If you add `--http`, the dashboard/API remains available after that one-shot tick completes, and the process stays up until you interrupt it with `Ctrl+C`.
|
|
194
|
+
|
|
143
195
|
### Monitor
|
|
144
196
|
|
|
145
197
|
```bash
|
|
@@ -164,32 +216,50 @@ gh-symphony recover --dry-run # Preview what would be recovered
|
|
|
164
216
|
|
|
165
217
|
## Diagnostics
|
|
166
218
|
|
|
167
|
-
`gh-symphony doctor` validates the most common first-run prerequisites in one pass:
|
|
219
|
+
`gh-symphony doctor` validates the most common first-run prerequisites in one pass. `gh-symphony doctor --fix` extends that flow with safe remediation and guided follow-up:
|
|
220
|
+
|
|
221
|
+
- creates missing config/runtime/workspace directories
|
|
222
|
+
- launches `gh auth login` or `gh auth refresh` when a TTY is available, otherwise prints the exact command to run
|
|
223
|
+
- launches `gh-symphony init` when `WORKFLOW.md` is missing or invalid
|
|
224
|
+
- launches `gh-symphony project add` when managed project setup or GitHub Project binding must be repaired
|
|
225
|
+
- prints concrete runtime install guidance when the configured command is missing on `PATH`
|
|
168
226
|
|
|
169
|
-
|
|
227
|
+
The diagnostic checks cover:
|
|
228
|
+
|
|
229
|
+
- the active GitHub auth source (`GITHUB_GRAPHQL_TOKEN` first, otherwise `gh`) and required scopes
|
|
230
|
+
- Node.js runtime version against the documented minimum (`v24+`) and the current `process.version`
|
|
231
|
+
- Git installation availability on `PATH`, including `git --version` when available
|
|
232
|
+
- GitHub authentication via `GITHUB_GRAPHQL_TOKEN` or `gh`, including required scopes
|
|
170
233
|
- managed project selection plus GitHub Project binding resolution
|
|
171
234
|
- config/runtime/workspace path writability
|
|
172
235
|
- repository `WORKFLOW.md` presence and parse validity
|
|
173
236
|
- runtime command availability on `PATH`
|
|
174
237
|
|
|
175
|
-
Use JSON output for scripts and CI smoke checks
|
|
238
|
+
Use JSON output for scripts and CI smoke checks. `--fix --json` includes a remediation section where each step is reported as `applied`, `skipped`, or `manual`.
|
|
176
239
|
|
|
177
240
|
```bash
|
|
178
241
|
gh-symphony doctor --json
|
|
242
|
+
gh-symphony doctor --fix --json
|
|
243
|
+
gh-symphony start --once
|
|
179
244
|
```
|
|
180
245
|
|
|
246
|
+
JSON output includes the resolved auth source as `env` or `gh`.
|
|
247
|
+
|
|
181
248
|
## Command Reference
|
|
182
249
|
|
|
183
250
|
```
|
|
184
251
|
Setup:
|
|
185
|
-
init
|
|
186
|
-
|
|
252
|
+
workflow init Interactive repository setup wizard
|
|
253
|
+
workflow validate Parse and strictly validate WORKFLOW.md
|
|
254
|
+
workflow preview Render the final worker prompt from a sample issue
|
|
255
|
+
doctor Run diagnostics and optional first-run remediation
|
|
187
256
|
config show Show current configuration
|
|
188
257
|
config set Set a configuration value
|
|
189
258
|
config edit Open config in $EDITOR
|
|
190
259
|
|
|
191
260
|
Orchestration:
|
|
192
261
|
start Start the orchestrator (foreground)
|
|
262
|
+
start --once Run a single orchestration tick and exit
|
|
193
263
|
start --daemon Start the orchestrator (background)
|
|
194
264
|
stop Stop the background orchestrator
|
|
195
265
|
status Show orchestrator status
|
|
@@ -202,6 +272,7 @@ Project Management:
|
|
|
202
272
|
project add Add a new project (interactive wizard)
|
|
203
273
|
project list List all configured projects
|
|
204
274
|
project remove Remove a project
|
|
275
|
+
repo sync Refresh repositories from the linked GitHub Project
|
|
205
276
|
|
|
206
277
|
Global Options:
|
|
207
278
|
--config <dir> Config directory (default: ~/.gh-symphony)
|
|
@@ -3,38 +3,33 @@ import {
|
|
|
3
3
|
abortIfCancelled,
|
|
4
4
|
generateProjectId,
|
|
5
5
|
writeConfig
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-RN2PACNV.js";
|
|
7
7
|
import {
|
|
8
|
+
start_default
|
|
9
|
+
} from "./chunk-EKKT5USP.js";
|
|
10
|
+
import {
|
|
11
|
+
GhAuthError,
|
|
8
12
|
GitHubScopeError,
|
|
9
13
|
checkRequiredScopes,
|
|
10
14
|
createClient,
|
|
15
|
+
getGhTokenWithSource,
|
|
11
16
|
getProjectDetail,
|
|
12
17
|
listUserProjects,
|
|
18
|
+
resolveGitHubAuth,
|
|
13
19
|
validateToken
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import {
|
|
16
|
-
start_default
|
|
17
|
-
} from "./chunk-ZYYY55WB.js";
|
|
18
|
-
import {
|
|
19
|
-
GhAuthError,
|
|
20
|
-
ensureGhAuth,
|
|
21
|
-
getGhToken
|
|
22
|
-
} from "./chunk-7UBUBSMH.js";
|
|
23
|
-
import {
|
|
24
|
-
stop_default
|
|
25
|
-
} from "./chunk-Y6TYJMNT.js";
|
|
20
|
+
} from "./chunk-TILHWBP6.js";
|
|
26
21
|
import {
|
|
27
22
|
status_default
|
|
28
23
|
} from "./chunk-XN5ABWZ6.js";
|
|
29
24
|
import {
|
|
30
25
|
stripAnsi
|
|
31
26
|
} from "./chunk-MVRF7BES.js";
|
|
32
|
-
import "./chunk-LZE6YUSB.js";
|
|
33
|
-
import "./chunk-OL73UN2X.js";
|
|
34
27
|
import {
|
|
35
28
|
resolveRuntimeRoot
|
|
36
29
|
} from "./chunk-5NV3LSAJ.js";
|
|
37
|
-
import
|
|
30
|
+
import {
|
|
31
|
+
stop_default
|
|
32
|
+
} from "./chunk-Y6TYJMNT.js";
|
|
38
33
|
import {
|
|
39
34
|
daemonPidPath,
|
|
40
35
|
httpStatusPath,
|
|
@@ -68,7 +63,7 @@ Then re-run: ${retryCommand}`,
|
|
|
68
63
|
);
|
|
69
64
|
}
|
|
70
65
|
function parseProjectAddFlags(args) {
|
|
71
|
-
const flags = { nonInteractive: false
|
|
66
|
+
const flags = { nonInteractive: false };
|
|
72
67
|
for (let i = 0; i < args.length; i += 1) {
|
|
73
68
|
const arg = args[i];
|
|
74
69
|
const next = args[i + 1];
|
|
@@ -308,12 +303,12 @@ async function projectAdd(args, options) {
|
|
|
308
303
|
await projectAddNonInteractive(flags, options);
|
|
309
304
|
return;
|
|
310
305
|
}
|
|
311
|
-
await projectAddInteractive(options);
|
|
306
|
+
await projectAddInteractive(flags, options);
|
|
312
307
|
}
|
|
313
308
|
async function projectAddNonInteractive(flags, options) {
|
|
314
309
|
let token;
|
|
315
310
|
try {
|
|
316
|
-
token =
|
|
311
|
+
token = getGhTokenWithSource().token;
|
|
317
312
|
} catch {
|
|
318
313
|
process.stderr.write(
|
|
319
314
|
"Error: GitHub token not found. Run 'gh auth login --scopes repo,read:org,project' or set GITHUB_GRAPHQL_TOKEN.\n"
|
|
@@ -381,7 +376,7 @@ async function projectAddNonInteractive(flags, options) {
|
|
|
381
376
|
`);
|
|
382
377
|
}
|
|
383
378
|
}
|
|
384
|
-
async function projectAddInteractive(options) {
|
|
379
|
+
async function projectAddInteractive(flags, options) {
|
|
385
380
|
p.intro("gh-symphony - Project Setup");
|
|
386
381
|
const defaultWorkspaceDir = join(options.configDir, "workspaces");
|
|
387
382
|
const existingConfig = await loadGlobalConfig(options.configDir);
|
|
@@ -399,32 +394,19 @@ async function projectAddInteractive(options) {
|
|
|
399
394
|
}
|
|
400
395
|
}
|
|
401
396
|
const s1 = p.spinner();
|
|
402
|
-
s1.start("Checking
|
|
397
|
+
s1.start("Checking GitHub authentication...");
|
|
403
398
|
let login;
|
|
404
399
|
let client;
|
|
405
400
|
try {
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
401
|
+
const auth = await resolveGitHubAuth();
|
|
402
|
+
const sourceLabel = auth.source === "env" ? "GITHUB_GRAPHQL_TOKEN" : "gh CLI";
|
|
403
|
+
login = auth.login;
|
|
404
|
+
client = createClient(auth.token);
|
|
405
|
+
s1.stop(`Authenticated via ${sourceLabel} as ${login}`);
|
|
410
406
|
} catch (error) {
|
|
411
407
|
s1.stop("Authentication failed.");
|
|
412
408
|
if (error instanceof GhAuthError) {
|
|
413
|
-
|
|
414
|
-
p.log.error(
|
|
415
|
-
"gh CLI\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. https://cli.github.com \uC5D0\uC11C \uC124\uCE58\uD558\uC138\uC694."
|
|
416
|
-
);
|
|
417
|
-
} else if (error.code === "not_authenticated") {
|
|
418
|
-
p.log.error(
|
|
419
|
-
"gh auth login --scopes repo,read:org,project \uB97C \uC2E4\uD589\uD558\uC138\uC694."
|
|
420
|
-
);
|
|
421
|
-
} else if (error.code === "missing_scopes") {
|
|
422
|
-
p.log.error(
|
|
423
|
-
"gh auth refresh --scopes repo,read:org,project \uB97C \uC2E4\uD589\uD558\uC138\uC694."
|
|
424
|
-
);
|
|
425
|
-
} else {
|
|
426
|
-
p.log.error(error.message);
|
|
427
|
-
}
|
|
409
|
+
p.log.error(error.message);
|
|
428
410
|
} else {
|
|
429
411
|
p.log.error(error instanceof Error ? error.message : "Unknown error");
|
|
430
412
|
}
|
|
@@ -486,10 +468,64 @@ async function projectAddInteractive(options) {
|
|
|
486
468
|
process.exitCode = 1;
|
|
487
469
|
return;
|
|
488
470
|
}
|
|
471
|
+
const {
|
|
472
|
+
assignedOnly: promptAssignedOnly,
|
|
473
|
+
selectedRepos,
|
|
474
|
+
workspaceDir
|
|
475
|
+
} = await promptProjectRegistrationOptions({
|
|
476
|
+
projectDetail,
|
|
477
|
+
defaultWorkspaceDir,
|
|
478
|
+
assignedOnlyMessage: "Step 2/2 - Only process issues assigned to the authenticated GitHub user?",
|
|
479
|
+
assignedOnlyInitialValue: flags.assignedOnly
|
|
480
|
+
});
|
|
481
|
+
const assignedOnly = flags.assignedOnly || promptAssignedOnly;
|
|
482
|
+
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)`;
|
|
483
|
+
p.note(
|
|
484
|
+
renderProjectRegistrationSummary({
|
|
485
|
+
login,
|
|
486
|
+
projectTitle: projectDetail.title,
|
|
487
|
+
repoSummary,
|
|
488
|
+
assignedOnly,
|
|
489
|
+
workspaceDir
|
|
490
|
+
}),
|
|
491
|
+
"Configuration Summary"
|
|
492
|
+
);
|
|
493
|
+
const confirmed = await abortIfCancelled(
|
|
494
|
+
p.confirm({ message: "Apply this configuration?" })
|
|
495
|
+
);
|
|
496
|
+
if (!confirmed) {
|
|
497
|
+
p.cancel("Setup cancelled.");
|
|
498
|
+
process.exitCode = 130;
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
const projectId = generateProjectId(projectDetail.title, projectDetail.id);
|
|
502
|
+
const s6 = p.spinner();
|
|
503
|
+
s6.start("Writing configuration...");
|
|
504
|
+
try {
|
|
505
|
+
await writeConfig(options.configDir, {
|
|
506
|
+
projectId,
|
|
507
|
+
project: projectDetail,
|
|
508
|
+
repos: selectedRepos,
|
|
509
|
+
workspaceDir,
|
|
510
|
+
assignedOnly
|
|
511
|
+
});
|
|
512
|
+
s6.stop("Configuration saved.");
|
|
513
|
+
} catch (error) {
|
|
514
|
+
s6.stop("Failed to write configuration.");
|
|
515
|
+
p.log.error(error instanceof Error ? error.message : "Unknown error");
|
|
516
|
+
process.exitCode = 1;
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
p.outro(
|
|
520
|
+
`Project "${projectId}" created.
|
|
521
|
+
Run 'gh-symphony start' to begin orchestration.`
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
async function promptProjectRegistrationOptions(input) {
|
|
489
525
|
const assignedOnly = await abortIfCancelled(
|
|
490
526
|
p.confirm({
|
|
491
|
-
message:
|
|
492
|
-
initialValue: false
|
|
527
|
+
message: input.assignedOnlyMessage ?? "Only process issues assigned to the authenticated GitHub user?",
|
|
528
|
+
initialValue: input.assignedOnlyInitialValue ?? false
|
|
493
529
|
})
|
|
494
530
|
);
|
|
495
531
|
const customizeAdvancedOptions = await abortIfCancelled(
|
|
@@ -498,8 +534,8 @@ async function projectAddInteractive(options) {
|
|
|
498
534
|
initialValue: false
|
|
499
535
|
})
|
|
500
536
|
);
|
|
501
|
-
let selectedRepos = projectDetail.linkedRepositories;
|
|
502
|
-
let workspaceDir = defaultWorkspaceDir;
|
|
537
|
+
let selectedRepos = input.projectDetail.linkedRepositories;
|
|
538
|
+
let workspaceDir = input.defaultWorkspaceDir;
|
|
503
539
|
if (customizeAdvancedOptions) {
|
|
504
540
|
const filterRepositories = await abortIfCancelled(
|
|
505
541
|
p.confirm({
|
|
@@ -511,7 +547,7 @@ async function projectAddInteractive(options) {
|
|
|
511
547
|
selectedRepos = await abortIfCancelled(
|
|
512
548
|
p.multiselect({
|
|
513
549
|
message: "Select repositories to orchestrate:",
|
|
514
|
-
options: projectDetail.linkedRepositories.map((repo) => ({
|
|
550
|
+
options: input.projectDetail.linkedRepositories.map((repo) => ({
|
|
515
551
|
value: repo,
|
|
516
552
|
label: `${repo.owner}/${repo.name}`
|
|
517
553
|
})),
|
|
@@ -522,55 +558,28 @@ async function projectAddInteractive(options) {
|
|
|
522
558
|
workspaceDir = await abortIfCancelled(
|
|
523
559
|
p.text({
|
|
524
560
|
message: "Workspace root directory:",
|
|
525
|
-
placeholder: defaultWorkspaceDir,
|
|
526
|
-
defaultValue: defaultWorkspaceDir,
|
|
561
|
+
placeholder: input.defaultWorkspaceDir,
|
|
562
|
+
defaultValue: input.defaultWorkspaceDir,
|
|
527
563
|
validate(value) {
|
|
528
564
|
return value.trim().length > 0 ? void 0 : "Workspace directory is required.";
|
|
529
565
|
}
|
|
530
566
|
})
|
|
531
567
|
);
|
|
532
568
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
);
|
|
547
|
-
if (!confirmed) {
|
|
548
|
-
p.cancel("Setup cancelled.");
|
|
549
|
-
process.exitCode = 130;
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
|
-
const projectId = generateProjectId(projectDetail.title, projectDetail.id);
|
|
553
|
-
const s6 = p.spinner();
|
|
554
|
-
s6.start("Writing configuration...");
|
|
555
|
-
try {
|
|
556
|
-
await writeConfig(options.configDir, {
|
|
557
|
-
projectId,
|
|
558
|
-
project: projectDetail,
|
|
559
|
-
repos: selectedRepos,
|
|
560
|
-
workspaceDir,
|
|
561
|
-
assignedOnly
|
|
562
|
-
});
|
|
563
|
-
s6.stop("Configuration saved.");
|
|
564
|
-
} catch (error) {
|
|
565
|
-
s6.stop("Failed to write configuration.");
|
|
566
|
-
p.log.error(error instanceof Error ? error.message : "Unknown error");
|
|
567
|
-
process.exitCode = 1;
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
|
-
p.outro(
|
|
571
|
-
`Project "${projectId}" created.
|
|
572
|
-
Run 'gh-symphony start' to begin orchestration.`
|
|
573
|
-
);
|
|
569
|
+
return {
|
|
570
|
+
assignedOnly,
|
|
571
|
+
selectedRepos,
|
|
572
|
+
workspaceDir
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
function renderProjectRegistrationSummary(input) {
|
|
576
|
+
return [
|
|
577
|
+
`User: ${input.login}`,
|
|
578
|
+
`Project: ${input.projectTitle}`,
|
|
579
|
+
`Repos: ${input.repoSummary}`,
|
|
580
|
+
`Assigned: ${input.assignedOnly ? `Only issues assigned to ${input.login}` : "All project issues"}`,
|
|
581
|
+
`Workspace: ${input.workspaceDir}`
|
|
582
|
+
].join("\n");
|
|
574
583
|
}
|
|
575
584
|
async function projectList(options) {
|
|
576
585
|
const global = await loadGlobalConfig(options.configDir);
|
|
@@ -650,7 +659,9 @@ async function projectRemove(args, options) {
|
|
|
650
659
|
async function projectSwitch(options) {
|
|
651
660
|
const global = await loadGlobalConfig(options.configDir);
|
|
652
661
|
if (!global || global.projects.length === 0) {
|
|
653
|
-
process.stderr.write(
|
|
662
|
+
process.stderr.write(
|
|
663
|
+
"No projects configured. Run 'gh-symphony workflow init'.\n"
|
|
664
|
+
);
|
|
654
665
|
process.exitCode = 1;
|
|
655
666
|
return;
|
|
656
667
|
}
|
|
@@ -676,6 +687,9 @@ async function projectSwitch(options) {
|
|
|
676
687
|
process.stdout.write(`Switched to project: ${selected}
|
|
677
688
|
`);
|
|
678
689
|
}
|
|
690
|
+
|
|
679
691
|
export {
|
|
680
|
-
project_default
|
|
692
|
+
project_default,
|
|
693
|
+
promptProjectRegistrationOptions,
|
|
694
|
+
renderProjectRegistrationSummary
|
|
681
695
|
};
|
|
@@ -1,23 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
getGhToken
|
|
4
|
-
} from "./chunk-7UBUBSMH.js";
|
|
5
|
-
import {
|
|
6
|
-
bold,
|
|
7
|
-
cyan,
|
|
8
|
-
dim,
|
|
9
|
-
green,
|
|
10
|
-
red,
|
|
11
|
-
setNoColor,
|
|
12
|
-
yellow
|
|
13
|
-
} from "./chunk-MVRF7BES.js";
|
|
14
2
|
import {
|
|
15
3
|
OrchestratorService,
|
|
16
4
|
acquireProjectLock,
|
|
17
5
|
createStore,
|
|
18
6
|
releaseProjectLock,
|
|
19
7
|
resolveOrchestratorLogLevel
|
|
20
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-HZVDTAPS.js";
|
|
21
9
|
import {
|
|
22
10
|
deriveIssueWorkspaceKeyFromIdentifier,
|
|
23
11
|
isFileMissing,
|
|
@@ -26,7 +14,19 @@ import {
|
|
|
26
14
|
parseRecentEvents,
|
|
27
15
|
readJsonFile,
|
|
28
16
|
safeReadDir
|
|
29
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-M3IFVLQS.js";
|
|
18
|
+
import {
|
|
19
|
+
getGhToken
|
|
20
|
+
} from "./chunk-TILHWBP6.js";
|
|
21
|
+
import {
|
|
22
|
+
bold,
|
|
23
|
+
cyan,
|
|
24
|
+
dim,
|
|
25
|
+
green,
|
|
26
|
+
red,
|
|
27
|
+
setNoColor,
|
|
28
|
+
yellow
|
|
29
|
+
} from "./chunk-MVRF7BES.js";
|
|
30
30
|
import {
|
|
31
31
|
resolveRuntimeRoot
|
|
32
32
|
} from "./chunk-5NV3LSAJ.js";
|
|
@@ -387,7 +387,8 @@ var DEFAULT_HTTP_PORT = 4680;
|
|
|
387
387
|
var HTTP_HOST = "0.0.0.0";
|
|
388
388
|
function parseStartArgs(args) {
|
|
389
389
|
const parsed = {
|
|
390
|
-
daemon: false
|
|
390
|
+
daemon: false,
|
|
391
|
+
once: false
|
|
391
392
|
};
|
|
392
393
|
for (let i = 0; i < args.length; i += 1) {
|
|
393
394
|
const arg = args[i];
|
|
@@ -395,6 +396,10 @@ function parseStartArgs(args) {
|
|
|
395
396
|
parsed.daemon = true;
|
|
396
397
|
continue;
|
|
397
398
|
}
|
|
399
|
+
if (arg === "--once") {
|
|
400
|
+
parsed.once = true;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
398
403
|
if (arg === "--http") {
|
|
399
404
|
const value = args[i + 1];
|
|
400
405
|
if (!value || value.startsWith("-")) {
|
|
@@ -649,11 +654,16 @@ var handler = async (args, options) => {
|
|
|
649
654
|
process.stderr.write(`${parsed.error}
|
|
650
655
|
`);
|
|
651
656
|
process.stderr.write(
|
|
652
|
-
"Usage: gh-symphony start --project-id <project-id> [--daemon] [--http [port]]\n"
|
|
657
|
+
"Usage: gh-symphony start --project-id <project-id> [--daemon] [--once] [--http [port]]\n"
|
|
653
658
|
);
|
|
654
659
|
process.exitCode = 2;
|
|
655
660
|
return;
|
|
656
661
|
}
|
|
662
|
+
if (parsed.daemon && parsed.once) {
|
|
663
|
+
process.stderr.write("Options '--daemon' and '--once' cannot be used together\n");
|
|
664
|
+
process.exitCode = 2;
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
657
667
|
const projectConfig = await resolveManagedProjectConfig({
|
|
658
668
|
configDir: options.configDir,
|
|
659
669
|
requestedProjectId: parsed.projectId
|
|
@@ -761,14 +771,20 @@ var handler = async (args, options) => {
|
|
|
761
771
|
`HTTP dashboard listening on ${httpServer.url}`
|
|
762
772
|
);
|
|
763
773
|
}
|
|
764
|
-
logLine(
|
|
774
|
+
logLine(
|
|
775
|
+
dim("\xB7"),
|
|
776
|
+
dim(parsed.once ? "Running one orchestration tick" : "Press Ctrl+C to stop")
|
|
777
|
+
);
|
|
765
778
|
let shuttingDown = false;
|
|
766
779
|
let shutdownPromise = null;
|
|
780
|
+
let keepHttpAliveResolve = null;
|
|
767
781
|
const shutdown = async () => {
|
|
768
782
|
if (shuttingDown) {
|
|
769
783
|
return shutdownPromise;
|
|
770
784
|
}
|
|
771
785
|
shuttingDown = true;
|
|
786
|
+
keepHttpAliveResolve?.();
|
|
787
|
+
keepHttpAliveResolve = null;
|
|
772
788
|
const heldLock = projectLock;
|
|
773
789
|
projectLock = null;
|
|
774
790
|
shutdownPromise = shutdownForegroundOrchestrator({
|
|
@@ -780,16 +796,31 @@ var handler = async (args, options) => {
|
|
|
780
796
|
});
|
|
781
797
|
return shutdownPromise;
|
|
782
798
|
};
|
|
783
|
-
|
|
799
|
+
const handleSigint = () => {
|
|
784
800
|
void shutdown();
|
|
785
|
-
}
|
|
786
|
-
|
|
801
|
+
};
|
|
802
|
+
const handleSigterm = () => {
|
|
787
803
|
void shutdown();
|
|
788
|
-
}
|
|
804
|
+
};
|
|
805
|
+
process.on("SIGINT", handleSigint);
|
|
806
|
+
process.on("SIGTERM", handleSigterm);
|
|
789
807
|
try {
|
|
790
808
|
while (!shuttingDown) {
|
|
791
809
|
try {
|
|
792
|
-
await service.run();
|
|
810
|
+
await service.run({ once: parsed.once });
|
|
811
|
+
if (parsed.once) {
|
|
812
|
+
if (httpServer) {
|
|
813
|
+
logLine(
|
|
814
|
+
cyan("\u25A1"),
|
|
815
|
+
"One-shot tick completed; HTTP dashboard remains available until Ctrl+C"
|
|
816
|
+
);
|
|
817
|
+
await new Promise((resolve2) => {
|
|
818
|
+
keepHttpAliveResolve = resolve2;
|
|
819
|
+
});
|
|
820
|
+
} else {
|
|
821
|
+
await shutdown();
|
|
822
|
+
}
|
|
823
|
+
}
|
|
793
824
|
break;
|
|
794
825
|
} catch (error) {
|
|
795
826
|
if (shuttingDown) {
|
|
@@ -798,12 +829,32 @@ var handler = async (args, options) => {
|
|
|
798
829
|
logLine(
|
|
799
830
|
red("\u2717"),
|
|
800
831
|
red(
|
|
801
|
-
|
|
832
|
+
`${parsed.once ? "One-shot run failed" : "Run loop error"}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
802
833
|
)
|
|
803
834
|
);
|
|
835
|
+
if (parsed.once) {
|
|
836
|
+
process.exitCode = 1;
|
|
837
|
+
await closeHttpServer(httpServer?.server).catch((closeError) => {
|
|
838
|
+
logLine(
|
|
839
|
+
yellow("\u26A0"),
|
|
840
|
+
`Failed to stop HTTP server: ${closeError instanceof Error ? closeError.message : "Unknown error"}`
|
|
841
|
+
);
|
|
842
|
+
});
|
|
843
|
+
await removeHttpBindingState(options.configDir, projectId).catch(
|
|
844
|
+
(removeError) => {
|
|
845
|
+
logLine(
|
|
846
|
+
yellow("\u26A0"),
|
|
847
|
+
`Failed to remove HTTP state: ${removeError instanceof Error ? removeError.message : "Unknown error"}`
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
);
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
804
853
|
}
|
|
805
854
|
}
|
|
806
855
|
} finally {
|
|
856
|
+
process.off("SIGINT", handleSigint);
|
|
857
|
+
process.off("SIGTERM", handleSigterm);
|
|
807
858
|
if (shutdownPromise) {
|
|
808
859
|
await shutdownPromise;
|
|
809
860
|
}
|