@gh-symphony/cli 0.0.6 → 0.0.7
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 +11 -11
- package/dist/commands/config-cmd.js +9 -9
- package/dist/commands/help.js +12 -12
- package/dist/commands/init.d.ts +3 -3
- package/dist/commands/init.js +27 -26
- package/dist/commands/logs.js +11 -5
- package/dist/commands/parse-cli-args.d.ts +6 -0
- package/dist/commands/parse-cli-args.js +20 -0
- package/dist/commands/project.js +592 -62
- package/dist/commands/recover.js +13 -13
- package/dist/commands/repo.js +13 -13
- package/dist/commands/run.js +15 -15
- package/dist/commands/start.d.ts +11 -0
- package/dist/commands/start.js +162 -129
- package/dist/commands/status-refresh.d.ts +1 -0
- package/dist/commands/status-refresh.js +7 -1
- package/dist/commands/status.js +41 -48
- package/dist/commands/stop.js +37 -7
- package/dist/config.d.ts +18 -16
- package/dist/config.js +29 -19
- package/dist/dashboard/renderer.d.ts +2 -2
- package/dist/dashboard/renderer.js +5 -5
- package/dist/index.js +0 -1
- package/dist/orchestrator-runtime.d.ts +4 -4
- package/dist/orchestrator-runtime.js +11 -11
- package/dist/orchestrator-status-endpoint.d.ts +5 -0
- package/dist/orchestrator-status-endpoint.js +27 -0
- package/dist/skills/types.d.ts +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -48,12 +48,12 @@ You can further customize the agent's behavior by editing `WORKFLOW.md` — this
|
|
|
48
48
|
|
|
49
49
|
> Currently supported runtimes: **Codex**, **Claude Code**
|
|
50
50
|
|
|
51
|
-
## 3. Set Orchestrator Runner (
|
|
51
|
+
## 3. Set Orchestrator Runner (Project)
|
|
52
52
|
|
|
53
|
-
On the machine where you want the orchestrator to run, register a
|
|
53
|
+
On the machine where you want the orchestrator to run, register a project:
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
|
-
gh-symphony
|
|
56
|
+
gh-symphony project add
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
The interactive wizard will:
|
|
@@ -63,13 +63,13 @@ The interactive wizard will:
|
|
|
63
63
|
3. Select repositories to orchestrate
|
|
64
64
|
4. Optionally limit processing to issues assigned to the authenticated user
|
|
65
65
|
5. Configure the workspace root directory
|
|
66
|
-
6. Write
|
|
66
|
+
6. Write project configuration to `~/.gh-symphony/`
|
|
67
67
|
|
|
68
|
-
###
|
|
68
|
+
### Project Management
|
|
69
69
|
|
|
70
70
|
```bash
|
|
71
|
-
gh-symphony
|
|
72
|
-
gh-symphony
|
|
71
|
+
gh-symphony project list # List all configured projects
|
|
72
|
+
gh-symphony project remove <id> # Remove a project
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
## 4. Run the Orchestrator
|
|
@@ -127,10 +127,10 @@ Orchestration:
|
|
|
127
127
|
recover Recover stalled runs
|
|
128
128
|
logs View orchestrator logs
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
Project Management:
|
|
131
|
+
project add Add a new project (interactive wizard)
|
|
132
|
+
project list List all configured projects
|
|
133
|
+
project remove Remove a project
|
|
134
134
|
|
|
135
135
|
Global Options:
|
|
136
136
|
--config <dir> Config directory (default: ~/.gh-symphony)
|
|
@@ -31,12 +31,12 @@ async function configShow(options) {
|
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
33
|
process.stdout.write(`Config: ${configFilePath(options.configDir)}\n\n`);
|
|
34
|
-
process.stdout.write(`Active
|
|
35
|
-
process.stdout.write(`
|
|
34
|
+
process.stdout.write(`Active project: ${config.activeProject ?? "none"}\n`);
|
|
35
|
+
process.stdout.write(`Projects: ${config.projects.join(", ") || "none"}\n`);
|
|
36
36
|
}
|
|
37
37
|
// ── 7.2: config set ──────────────────────────────────────────────────────────
|
|
38
38
|
const VALID_KEYS = {
|
|
39
|
-
"active-
|
|
39
|
+
"active-project": { type: "string" },
|
|
40
40
|
};
|
|
41
41
|
async function configSet(args, options) {
|
|
42
42
|
const [key, value] = args;
|
|
@@ -54,17 +54,17 @@ async function configSet(args, options) {
|
|
|
54
54
|
}
|
|
55
55
|
const config = (await loadGlobalConfig(options.configDir)) ??
|
|
56
56
|
{
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
activeProject: null,
|
|
58
|
+
projects: [],
|
|
59
59
|
};
|
|
60
60
|
switch (key) {
|
|
61
|
-
case "active-
|
|
62
|
-
if (!config.
|
|
63
|
-
process.stderr.write(`
|
|
61
|
+
case "active-project":
|
|
62
|
+
if (!config.projects.includes(value)) {
|
|
63
|
+
process.stderr.write(`Project "${value}" not found. Available: ${config.projects.join(", ")}\n`);
|
|
64
64
|
process.exitCode = 1;
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
67
|
-
config.
|
|
67
|
+
config.activeProject = value;
|
|
68
68
|
break;
|
|
69
69
|
}
|
|
70
70
|
await saveGlobalConfig(options.configDir, config);
|
package/dist/commands/help.js
CHANGED
|
@@ -4,7 +4,7 @@ gh-symphony — AI Coding Agent Orchestrator
|
|
|
4
4
|
Usage: gh-symphony <command> [options]
|
|
5
5
|
|
|
6
6
|
Setup:
|
|
7
|
-
init Interactive
|
|
7
|
+
init Interactive project setup wizard
|
|
8
8
|
config show Show current configuration
|
|
9
9
|
config set Set a configuration value
|
|
10
10
|
config edit Open config in $EDITOR
|
|
@@ -18,15 +18,15 @@ Orchestration:
|
|
|
18
18
|
recover Recover stalled runs
|
|
19
19
|
logs View orchestrator logs
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
Project Management:
|
|
22
|
+
project add Add a new project (interactive wizard)
|
|
23
|
+
project list List all configured projects
|
|
24
|
+
project remove Remove a project
|
|
25
25
|
|
|
26
26
|
Project / Repo:
|
|
27
|
-
project list List
|
|
28
|
-
project switch Switch active
|
|
29
|
-
project status Show
|
|
27
|
+
project list List projects
|
|
28
|
+
project switch Switch active project
|
|
29
|
+
project status Show orchestrator status for a project
|
|
30
30
|
repo list List configured repositories
|
|
31
31
|
repo add Add a repository
|
|
32
32
|
repo remove Remove a repository
|
|
@@ -40,10 +40,10 @@ Global Options:
|
|
|
40
40
|
--version, -V Show version
|
|
41
41
|
|
|
42
42
|
Examples:
|
|
43
|
-
gh-symphony
|
|
44
|
-
gh-symphony
|
|
45
|
-
gh-symphony
|
|
46
|
-
gh-symphony
|
|
43
|
+
gh-symphony project add # Add a project (interactive)
|
|
44
|
+
gh-symphony project add --non-interactive --project <id> --workspace-dir <path>
|
|
45
|
+
gh-symphony project list # List all projects
|
|
46
|
+
gh-symphony project remove <id> # Remove a project
|
|
47
47
|
gh-symphony start # Start orchestrator
|
|
48
48
|
gh-symphony start --daemon # Start in background
|
|
49
49
|
gh-symphony run org/repo#123 # Dispatch a specific issue
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ type EcosystemOptions = {
|
|
|
13
13
|
};
|
|
14
14
|
export type EcosystemResult = {
|
|
15
15
|
projectId: string;
|
|
16
|
-
|
|
16
|
+
githubProjectTitle: string;
|
|
17
17
|
runtime: string;
|
|
18
18
|
skillsDir: string | null;
|
|
19
19
|
contextYamlWritten: boolean;
|
|
@@ -23,7 +23,7 @@ export type EcosystemResult = {
|
|
|
23
23
|
};
|
|
24
24
|
export declare function writeEcosystem(opts: EcosystemOptions): Promise<EcosystemResult>;
|
|
25
25
|
type WriteConfigInput = {
|
|
26
|
-
|
|
26
|
+
projectId: string;
|
|
27
27
|
project: ProjectDetail;
|
|
28
28
|
repos: LinkedRepository[];
|
|
29
29
|
workspaceDir: string;
|
|
@@ -31,4 +31,4 @@ type WriteConfigInput = {
|
|
|
31
31
|
assignedOnly?: boolean;
|
|
32
32
|
};
|
|
33
33
|
export declare function writeConfig(configDir: string, input: WriteConfigInput): Promise<void>;
|
|
34
|
-
export declare function
|
|
34
|
+
export declare function generateProjectId(githubProjectTitle: string, uniqueKey: string): string;
|
package/dist/commands/init.js
CHANGED
|
@@ -5,7 +5,7 @@ import { basename, dirname, join, relative, resolve } from "node:path";
|
|
|
5
5
|
import { createClient, validateToken, checkRequiredScopes, listUserProjects, getProjectDetail, GitHubScopeError, } from "../github/client.js";
|
|
6
6
|
import { inferAllStateRoles, toWorkflowLifecycleConfig, validateStateMapping, } from "../mapping/smart-defaults.js";
|
|
7
7
|
import { generateWorkflowMarkdown } from "../workflow/generate-workflow-md.js";
|
|
8
|
-
import { loadGlobalConfig, saveGlobalConfig,
|
|
8
|
+
import { loadGlobalConfig, saveGlobalConfig, saveProjectConfig, } from "../config.js";
|
|
9
9
|
import { getGhToken, ensureGhAuth, GhAuthError } from "../github/gh-auth.js";
|
|
10
10
|
import { detectEnvironment } from "../detection/environment-detector.js";
|
|
11
11
|
import { buildContextYaml, writeContextYaml, } from "../context/generate-context-yaml.js";
|
|
@@ -120,7 +120,7 @@ export async function writeEcosystem(opts) {
|
|
|
120
120
|
const result = await writeAllSkills(cwd, runtime, ALL_SKILL_TEMPLATES, {
|
|
121
121
|
runtime,
|
|
122
122
|
projectId: projectDetail.id,
|
|
123
|
-
|
|
123
|
+
githubProjectTitle: projectDetail.title,
|
|
124
124
|
repositories: projectDetail.linkedRepositories.map((r) => ({
|
|
125
125
|
owner: r.owner,
|
|
126
126
|
name: r.name,
|
|
@@ -139,7 +139,7 @@ export async function writeEcosystem(opts) {
|
|
|
139
139
|
}
|
|
140
140
|
return {
|
|
141
141
|
projectId: projectDetail.id,
|
|
142
|
-
|
|
142
|
+
githubProjectTitle: projectDetail.title,
|
|
143
143
|
runtime,
|
|
144
144
|
skillsDir,
|
|
145
145
|
contextYamlWritten,
|
|
@@ -153,7 +153,7 @@ function printEcosystemSummary(result, workflowPath, opts) {
|
|
|
153
153
|
const cwd = process.cwd();
|
|
154
154
|
const relWorkflow = relative(cwd, workflowPath) || "WORKFLOW.md";
|
|
155
155
|
const lines = [];
|
|
156
|
-
lines.push(`Project ${result.
|
|
156
|
+
lines.push(`GitHub Project ${result.githubProjectTitle} (${result.projectId})`);
|
|
157
157
|
lines.push(`Runtime ${result.runtime}`);
|
|
158
158
|
lines.push("");
|
|
159
159
|
lines.push("Generated files");
|
|
@@ -217,7 +217,7 @@ async function runNonInteractive(flags, options) {
|
|
|
217
217
|
}
|
|
218
218
|
// Find project
|
|
219
219
|
const projects = await listUserProjects(client);
|
|
220
|
-
let
|
|
220
|
+
let githubProject;
|
|
221
221
|
if (flags.project) {
|
|
222
222
|
const match = projects.find((proj) => proj.id === flags.project || proj.url === flags.project);
|
|
223
223
|
if (!match) {
|
|
@@ -225,10 +225,10 @@ async function runNonInteractive(flags, options) {
|
|
|
225
225
|
process.exitCode = 1;
|
|
226
226
|
return;
|
|
227
227
|
}
|
|
228
|
-
|
|
228
|
+
githubProject = await getProjectDetail(client, match.id);
|
|
229
229
|
}
|
|
230
230
|
else if (projects.length === 1) {
|
|
231
|
-
|
|
231
|
+
githubProject = await getProjectDetail(client, projects[0].id);
|
|
232
232
|
}
|
|
233
233
|
else {
|
|
234
234
|
process.stderr.write("Error: --project is required when multiple projects exist.\n");
|
|
@@ -236,8 +236,8 @@ async function runNonInteractive(flags, options) {
|
|
|
236
236
|
return;
|
|
237
237
|
}
|
|
238
238
|
// Auto-map with smart defaults
|
|
239
|
-
const statusField =
|
|
240
|
-
|
|
239
|
+
const statusField = githubProject.statusFields.find((f) => f.name.toLowerCase() === "status") ??
|
|
240
|
+
githubProject.statusFields[0];
|
|
241
241
|
if (!statusField) {
|
|
242
242
|
process.stderr.write("Error: No status field found on the project.\n");
|
|
243
243
|
process.exitCode = 1;
|
|
@@ -260,7 +260,7 @@ async function runNonInteractive(flags, options) {
|
|
|
260
260
|
const lifecycleConfig = toWorkflowLifecycleConfig(statusField.name, mappings);
|
|
261
261
|
const outputPath = resolve(flags.output ?? "WORKFLOW.md");
|
|
262
262
|
const workflowMd = generateWorkflowMarkdown({
|
|
263
|
-
projectId:
|
|
263
|
+
projectId: githubProject.id,
|
|
264
264
|
stateFieldName: statusField.name,
|
|
265
265
|
mappings,
|
|
266
266
|
lifecycle: lifecycleConfig,
|
|
@@ -269,7 +269,7 @@ async function runNonInteractive(flags, options) {
|
|
|
269
269
|
await writeFile(outputPath, workflowMd, "utf8");
|
|
270
270
|
const ecosystemResult = await writeEcosystem({
|
|
271
271
|
cwd: process.cwd(),
|
|
272
|
-
projectDetail:
|
|
272
|
+
projectDetail: githubProject,
|
|
273
273
|
statusField,
|
|
274
274
|
runtime: "codex",
|
|
275
275
|
skipSkills: flags.skipSkills,
|
|
@@ -281,7 +281,7 @@ async function runNonInteractive(flags, options) {
|
|
|
281
281
|
else {
|
|
282
282
|
printEcosystemSummary(ecosystemResult, outputPath, {
|
|
283
283
|
interactive: false,
|
|
284
|
-
nextSteps: "Run 'gh-symphony
|
|
284
|
+
nextSteps: "Run 'gh-symphony project add' to register a project.",
|
|
285
285
|
});
|
|
286
286
|
}
|
|
287
287
|
}
|
|
@@ -346,8 +346,8 @@ async function runInteractiveStandalone(_options) {
|
|
|
346
346
|
process.exitCode = 1;
|
|
347
347
|
return;
|
|
348
348
|
}
|
|
349
|
-
const
|
|
350
|
-
message: "Step 1/2 — Select a GitHub Project:",
|
|
349
|
+
const selectedGithubProjectId = await abortIfCancelled(p.select({
|
|
350
|
+
message: "Step 1/2 — Select a GitHub Project board:",
|
|
351
351
|
options: projects.map((proj) => ({
|
|
352
352
|
value: proj.id,
|
|
353
353
|
label: `${proj.owner.login}/${proj.title}`,
|
|
@@ -359,7 +359,7 @@ async function runInteractiveStandalone(_options) {
|
|
|
359
359
|
s2d.start("Loading project details...");
|
|
360
360
|
let projectDetail;
|
|
361
361
|
try {
|
|
362
|
-
projectDetail = await getProjectDetail(client,
|
|
362
|
+
projectDetail = await getProjectDetail(client, selectedGithubProjectId);
|
|
363
363
|
s2d.stop(`Loaded: ${projectDetail.title}`);
|
|
364
364
|
}
|
|
365
365
|
catch (error) {
|
|
@@ -432,13 +432,14 @@ async function runInteractiveStandalone(_options) {
|
|
|
432
432
|
});
|
|
433
433
|
printEcosystemSummary(ecosystemResult, outputPath, {
|
|
434
434
|
interactive: true,
|
|
435
|
-
nextSteps: "Run 'gh-symphony
|
|
435
|
+
nextSteps: "Run 'gh-symphony project add' to register a project.",
|
|
436
436
|
});
|
|
437
437
|
}
|
|
438
438
|
export async function writeConfig(configDir, input) {
|
|
439
|
-
await
|
|
440
|
-
|
|
441
|
-
slug: input.
|
|
439
|
+
await saveProjectConfig(configDir, input.projectId, {
|
|
440
|
+
projectId: input.projectId,
|
|
441
|
+
slug: input.projectId,
|
|
442
|
+
displayName: input.project.title,
|
|
442
443
|
workspaceDir: input.workspaceDir,
|
|
443
444
|
repositories: input.repos.map((r) => ({
|
|
444
445
|
owner: r.owner,
|
|
@@ -457,20 +458,20 @@ export async function writeConfig(configDir, input) {
|
|
|
457
458
|
// Save/update global config
|
|
458
459
|
const existing = await loadGlobalConfig(configDir);
|
|
459
460
|
const globalConfig = {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
...(existing?.
|
|
463
|
-
input.
|
|
461
|
+
activeProject: input.projectId,
|
|
462
|
+
projects: [
|
|
463
|
+
...(existing?.projects ?? []).filter((t) => t !== input.projectId),
|
|
464
|
+
input.projectId,
|
|
464
465
|
],
|
|
465
466
|
};
|
|
466
467
|
await saveGlobalConfig(configDir, globalConfig);
|
|
467
468
|
}
|
|
468
|
-
export function
|
|
469
|
-
const slug =
|
|
469
|
+
export function generateProjectId(githubProjectTitle, uniqueKey) {
|
|
470
|
+
const slug = githubProjectTitle
|
|
470
471
|
.toLowerCase()
|
|
471
472
|
.replace(/[^a-z0-9]+/g, "-")
|
|
472
473
|
.replace(/^-|-$/g, "")
|
|
473
474
|
.slice(0, 32);
|
|
474
475
|
const suffix = createHash("sha1").update(uniqueKey).digest("hex").slice(0, 8);
|
|
475
|
-
return [slug || "
|
|
476
|
+
return [slug || "project", suffix].join("-");
|
|
476
477
|
}
|
package/dist/commands/logs.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readFile, readdir } from "node:fs/promises";
|
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { createReadStream } from "node:fs";
|
|
4
4
|
import { createInterface } from "node:readline";
|
|
5
|
-
import {
|
|
5
|
+
import { loadActiveProjectConfig, loadProjectConfig, orchestratorLogPath, } from "../config.js";
|
|
6
6
|
function parseLogsArgs(args) {
|
|
7
7
|
const parsed = { follow: false };
|
|
8
8
|
for (let i = 0; i < args.length; i += 1) {
|
|
@@ -22,14 +22,20 @@ function parseLogsArgs(args) {
|
|
|
22
22
|
parsed.level = args[i + 1];
|
|
23
23
|
i += 1;
|
|
24
24
|
}
|
|
25
|
+
if (arg === "--project" || arg === "--project-id") {
|
|
26
|
+
parsed.projectId = args[i + 1];
|
|
27
|
+
i += 1;
|
|
28
|
+
}
|
|
25
29
|
}
|
|
26
30
|
return parsed;
|
|
27
31
|
}
|
|
28
32
|
const handler = async (args, options) => {
|
|
29
33
|
const parsed = parseLogsArgs(args);
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
const projectConfig = parsed.projectId
|
|
35
|
+
? await loadProjectConfig(options.configDir, parsed.projectId)
|
|
36
|
+
: await loadActiveProjectConfig(options.configDir);
|
|
37
|
+
if (!projectConfig) {
|
|
38
|
+
process.stderr.write("No project configured. Run 'gh-symphony project add' first.\n");
|
|
33
39
|
process.exitCode = 1;
|
|
34
40
|
return;
|
|
35
41
|
}
|
|
@@ -56,7 +62,7 @@ const handler = async (args, options) => {
|
|
|
56
62
|
}
|
|
57
63
|
// Default: read orchestrator log or scan all events
|
|
58
64
|
if (parsed.follow) {
|
|
59
|
-
const logPath = orchestratorLogPath(options.configDir);
|
|
65
|
+
const logPath = orchestratorLogPath(options.configDir, projectConfig.projectId);
|
|
60
66
|
try {
|
|
61
67
|
const stream = createReadStream(logPath, { encoding: "utf8" });
|
|
62
68
|
const rl = createInterface({ input: stream });
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { parseArgs, type ParseArgsOptionsConfig } from "node:util";
|
|
2
|
+
type ParseCliArgsResult = ReturnType<typeof parseArgs> | {
|
|
3
|
+
error: string;
|
|
4
|
+
};
|
|
5
|
+
export declare function parseCliArgs(args: string[], options: ParseArgsOptionsConfig): ParseCliArgsResult;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
export function parseCliArgs(args, options) {
|
|
3
|
+
try {
|
|
4
|
+
return parseArgs({
|
|
5
|
+
args,
|
|
6
|
+
options,
|
|
7
|
+
allowPositionals: false,
|
|
8
|
+
strict: true,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
return { error: formatParseArgsError(error) };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function formatParseArgsError(error) {
|
|
16
|
+
if (error instanceof Error) {
|
|
17
|
+
return error.message;
|
|
18
|
+
}
|
|
19
|
+
return "Invalid arguments";
|
|
20
|
+
}
|