@gh-symphony/cli 0.0.5 → 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 +13 -13
- package/dist/commands/config-cmd.js +9 -9
- package/dist/commands/help.js +12 -12
- package/dist/commands/init.d.ts +4 -19
- package/dist/commands/init.js +28 -65
- 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/commands/tenant.js +18 -83
- package/dist/config.d.ts +18 -25
- package/dist/config.js +29 -28
- 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 +12 -27
- 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:
|
|
@@ -61,15 +61,15 @@ The interactive wizard will:
|
|
|
61
61
|
1. Authenticate via `gh` CLI
|
|
62
62
|
2. Let you select a **GitHub Project**
|
|
63
63
|
3. Select repositories to orchestrate
|
|
64
|
-
4.
|
|
65
|
-
5.
|
|
66
|
-
6. Write
|
|
64
|
+
4. Optionally limit processing to issues assigned to the authenticated user
|
|
65
|
+
5. Configure the workspace root directory
|
|
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
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { GlobalOptions } from "../index.js";
|
|
2
2
|
import { type ProjectDetail, type ProjectStatusField, type LinkedRepository } from "../github/client.js";
|
|
3
|
-
import { type StateMapping } from "../config.js";
|
|
4
3
|
export declare function abortIfCancelled<T>(input: T | Promise<T>): Promise<Exclude<T, symbol>>;
|
|
5
4
|
declare const handler: (args: string[], options: GlobalOptions) => Promise<void>;
|
|
6
5
|
export default handler;
|
|
@@ -14,7 +13,7 @@ type EcosystemOptions = {
|
|
|
14
13
|
};
|
|
15
14
|
export type EcosystemResult = {
|
|
16
15
|
projectId: string;
|
|
17
|
-
|
|
16
|
+
githubProjectTitle: string;
|
|
18
17
|
runtime: string;
|
|
19
18
|
skillsDir: string | null;
|
|
20
19
|
contextYamlWritten: boolean;
|
|
@@ -24,26 +23,12 @@ export type EcosystemResult = {
|
|
|
24
23
|
};
|
|
25
24
|
export declare function writeEcosystem(opts: EcosystemOptions): Promise<EcosystemResult>;
|
|
26
25
|
type WriteConfigInput = {
|
|
27
|
-
|
|
26
|
+
projectId: string;
|
|
28
27
|
project: ProjectDetail;
|
|
29
28
|
repos: LinkedRepository[];
|
|
30
|
-
|
|
31
|
-
id: string;
|
|
32
|
-
name: string;
|
|
33
|
-
options: Array<{
|
|
34
|
-
id: string;
|
|
35
|
-
name: string;
|
|
36
|
-
color?: string | null;
|
|
37
|
-
}>;
|
|
38
|
-
};
|
|
39
|
-
mappings: Record<string, StateMapping>;
|
|
40
|
-
runtime: string;
|
|
41
|
-
agentCommand?: string;
|
|
42
|
-
workerCommand?: string;
|
|
43
|
-
pollIntervalMs?: number;
|
|
44
|
-
concurrency?: number;
|
|
29
|
+
workspaceDir: string;
|
|
45
30
|
maxAttempts?: number;
|
|
46
31
|
assignedOnly?: boolean;
|
|
47
32
|
};
|
|
48
33
|
export declare function writeConfig(configDir: string, input: WriteConfigInput): Promise<void>;
|
|
49
|
-
export declare function
|
|
34
|
+
export declare function generateProjectId(githubProjectTitle: string, uniqueKey: string): string;
|
package/dist/commands/init.js
CHANGED
|
@@ -2,11 +2,10 @@ import * as p from "@clack/prompts";
|
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { mkdir, rename, writeFile } from "node:fs/promises";
|
|
4
4
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
5
|
import { createClient, validateToken, checkRequiredScopes, listUserProjects, getProjectDetail, GitHubScopeError, } from "../github/client.js";
|
|
7
6
|
import { inferAllStateRoles, toWorkflowLifecycleConfig, validateStateMapping, } from "../mapping/smart-defaults.js";
|
|
8
7
|
import { generateWorkflowMarkdown } from "../workflow/generate-workflow-md.js";
|
|
9
|
-
import { loadGlobalConfig, saveGlobalConfig,
|
|
8
|
+
import { loadGlobalConfig, saveGlobalConfig, saveProjectConfig, } from "../config.js";
|
|
10
9
|
import { getGhToken, ensureGhAuth, GhAuthError } from "../github/gh-auth.js";
|
|
11
10
|
import { detectEnvironment } from "../detection/environment-detector.js";
|
|
12
11
|
import { buildContextYaml, writeContextYaml, } from "../context/generate-context-yaml.js";
|
|
@@ -121,7 +120,7 @@ export async function writeEcosystem(opts) {
|
|
|
121
120
|
const result = await writeAllSkills(cwd, runtime, ALL_SKILL_TEMPLATES, {
|
|
122
121
|
runtime,
|
|
123
122
|
projectId: projectDetail.id,
|
|
124
|
-
|
|
123
|
+
githubProjectTitle: projectDetail.title,
|
|
125
124
|
repositories: projectDetail.linkedRepositories.map((r) => ({
|
|
126
125
|
owner: r.owner,
|
|
127
126
|
name: r.name,
|
|
@@ -140,7 +139,7 @@ export async function writeEcosystem(opts) {
|
|
|
140
139
|
}
|
|
141
140
|
return {
|
|
142
141
|
projectId: projectDetail.id,
|
|
143
|
-
|
|
142
|
+
githubProjectTitle: projectDetail.title,
|
|
144
143
|
runtime,
|
|
145
144
|
skillsDir,
|
|
146
145
|
contextYamlWritten,
|
|
@@ -154,7 +153,7 @@ function printEcosystemSummary(result, workflowPath, opts) {
|
|
|
154
153
|
const cwd = process.cwd();
|
|
155
154
|
const relWorkflow = relative(cwd, workflowPath) || "WORKFLOW.md";
|
|
156
155
|
const lines = [];
|
|
157
|
-
lines.push(`Project ${result.
|
|
156
|
+
lines.push(`GitHub Project ${result.githubProjectTitle} (${result.projectId})`);
|
|
158
157
|
lines.push(`Runtime ${result.runtime}`);
|
|
159
158
|
lines.push("");
|
|
160
159
|
lines.push("Generated files");
|
|
@@ -218,7 +217,7 @@ async function runNonInteractive(flags, options) {
|
|
|
218
217
|
}
|
|
219
218
|
// Find project
|
|
220
219
|
const projects = await listUserProjects(client);
|
|
221
|
-
let
|
|
220
|
+
let githubProject;
|
|
222
221
|
if (flags.project) {
|
|
223
222
|
const match = projects.find((proj) => proj.id === flags.project || proj.url === flags.project);
|
|
224
223
|
if (!match) {
|
|
@@ -226,10 +225,10 @@ async function runNonInteractive(flags, options) {
|
|
|
226
225
|
process.exitCode = 1;
|
|
227
226
|
return;
|
|
228
227
|
}
|
|
229
|
-
|
|
228
|
+
githubProject = await getProjectDetail(client, match.id);
|
|
230
229
|
}
|
|
231
230
|
else if (projects.length === 1) {
|
|
232
|
-
|
|
231
|
+
githubProject = await getProjectDetail(client, projects[0].id);
|
|
233
232
|
}
|
|
234
233
|
else {
|
|
235
234
|
process.stderr.write("Error: --project is required when multiple projects exist.\n");
|
|
@@ -237,8 +236,8 @@ async function runNonInteractive(flags, options) {
|
|
|
237
236
|
return;
|
|
238
237
|
}
|
|
239
238
|
// Auto-map with smart defaults
|
|
240
|
-
const statusField =
|
|
241
|
-
|
|
239
|
+
const statusField = githubProject.statusFields.find((f) => f.name.toLowerCase() === "status") ??
|
|
240
|
+
githubProject.statusFields[0];
|
|
242
241
|
if (!statusField) {
|
|
243
242
|
process.stderr.write("Error: No status field found on the project.\n");
|
|
244
243
|
process.exitCode = 1;
|
|
@@ -261,7 +260,7 @@ async function runNonInteractive(flags, options) {
|
|
|
261
260
|
const lifecycleConfig = toWorkflowLifecycleConfig(statusField.name, mappings);
|
|
262
261
|
const outputPath = resolve(flags.output ?? "WORKFLOW.md");
|
|
263
262
|
const workflowMd = generateWorkflowMarkdown({
|
|
264
|
-
projectId:
|
|
263
|
+
projectId: githubProject.id,
|
|
265
264
|
stateFieldName: statusField.name,
|
|
266
265
|
mappings,
|
|
267
266
|
lifecycle: lifecycleConfig,
|
|
@@ -270,7 +269,7 @@ async function runNonInteractive(flags, options) {
|
|
|
270
269
|
await writeFile(outputPath, workflowMd, "utf8");
|
|
271
270
|
const ecosystemResult = await writeEcosystem({
|
|
272
271
|
cwd: process.cwd(),
|
|
273
|
-
projectDetail:
|
|
272
|
+
projectDetail: githubProject,
|
|
274
273
|
statusField,
|
|
275
274
|
runtime: "codex",
|
|
276
275
|
skipSkills: flags.skipSkills,
|
|
@@ -282,7 +281,7 @@ async function runNonInteractive(flags, options) {
|
|
|
282
281
|
else {
|
|
283
282
|
printEcosystemSummary(ecosystemResult, outputPath, {
|
|
284
283
|
interactive: false,
|
|
285
|
-
nextSteps: "Run 'gh-symphony
|
|
284
|
+
nextSteps: "Run 'gh-symphony project add' to register a project.",
|
|
286
285
|
});
|
|
287
286
|
}
|
|
288
287
|
}
|
|
@@ -347,8 +346,8 @@ async function runInteractiveStandalone(_options) {
|
|
|
347
346
|
process.exitCode = 1;
|
|
348
347
|
return;
|
|
349
348
|
}
|
|
350
|
-
const
|
|
351
|
-
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:",
|
|
352
351
|
options: projects.map((proj) => ({
|
|
353
352
|
value: proj.id,
|
|
354
353
|
label: `${proj.owner.login}/${proj.title}`,
|
|
@@ -360,7 +359,7 @@ async function runInteractiveStandalone(_options) {
|
|
|
360
359
|
s2d.start("Loading project details...");
|
|
361
360
|
let projectDetail;
|
|
362
361
|
try {
|
|
363
|
-
projectDetail = await getProjectDetail(client,
|
|
362
|
+
projectDetail = await getProjectDetail(client, selectedGithubProjectId);
|
|
364
363
|
s2d.stop(`Loaded: ${projectDetail.title}`);
|
|
365
364
|
}
|
|
366
365
|
catch (error) {
|
|
@@ -433,32 +432,15 @@ async function runInteractiveStandalone(_options) {
|
|
|
433
432
|
});
|
|
434
433
|
printEcosystemSummary(ecosystemResult, outputPath, {
|
|
435
434
|
interactive: true,
|
|
436
|
-
nextSteps: "Run 'gh-symphony
|
|
435
|
+
nextSteps: "Run 'gh-symphony project add' to register a project.",
|
|
437
436
|
});
|
|
438
437
|
}
|
|
439
|
-
function resolveWorkerCommand() {
|
|
440
|
-
try {
|
|
441
|
-
const url = import.meta.resolve("@gh-symphony/worker/dist/index.js");
|
|
442
|
-
return `node ${fileURLToPath(url)}`;
|
|
443
|
-
}
|
|
444
|
-
catch {
|
|
445
|
-
return undefined;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
438
|
export async function writeConfig(configDir, input) {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
lifecycle: lifecycleConfig,
|
|
455
|
-
};
|
|
456
|
-
await saveWorkflowMapping(configDir, input.tenantId, mappingConfig);
|
|
457
|
-
// Save tenant config (OrchestratorTenantConfig shape)
|
|
458
|
-
const runtimeDir = `${configDir}/tenants/${input.tenantId}/runtime`;
|
|
459
|
-
await saveTenantConfig(configDir, input.tenantId, {
|
|
460
|
-
tenantId: input.tenantId,
|
|
461
|
-
slug: input.tenantId,
|
|
439
|
+
await saveProjectConfig(configDir, input.projectId, {
|
|
440
|
+
projectId: input.projectId,
|
|
441
|
+
slug: input.projectId,
|
|
442
|
+
displayName: input.project.title,
|
|
443
|
+
workspaceDir: input.workspaceDir,
|
|
462
444
|
repositories: input.repos.map((r) => ({
|
|
463
445
|
owner: r.owner,
|
|
464
446
|
name: r.name,
|
|
@@ -472,43 +454,24 @@ export async function writeConfig(configDir, input) {
|
|
|
472
454
|
...(input.assignedOnly ? { assignedOnly: true } : {}),
|
|
473
455
|
},
|
|
474
456
|
},
|
|
475
|
-
runtime: {
|
|
476
|
-
driver: "local",
|
|
477
|
-
workspaceRuntimeDir: runtimeDir,
|
|
478
|
-
projectRoot: process.cwd(),
|
|
479
|
-
workerCommand: input.workerCommand ?? resolveWorkerCommand(),
|
|
480
|
-
},
|
|
481
|
-
workflowMapping: mappingConfig,
|
|
482
457
|
});
|
|
483
458
|
// Save/update global config
|
|
484
459
|
const existing = await loadGlobalConfig(configDir);
|
|
485
460
|
const globalConfig = {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
...(existing?.
|
|
489
|
-
input.
|
|
461
|
+
activeProject: input.projectId,
|
|
462
|
+
projects: [
|
|
463
|
+
...(existing?.projects ?? []).filter((t) => t !== input.projectId),
|
|
464
|
+
input.projectId,
|
|
490
465
|
],
|
|
491
466
|
};
|
|
492
467
|
await saveGlobalConfig(configDir, globalConfig);
|
|
493
|
-
// Generate WORKFLOW.md for tenant-level fallback
|
|
494
|
-
const workflowMd = generateWorkflowMarkdown({
|
|
495
|
-
projectId: input.project.id,
|
|
496
|
-
stateFieldName: input.statusField.name,
|
|
497
|
-
mappings: input.mappings,
|
|
498
|
-
lifecycle: lifecycleConfig,
|
|
499
|
-
runtime: input.agentCommand ?? input.runtime,
|
|
500
|
-
pollIntervalMs: input.pollIntervalMs,
|
|
501
|
-
concurrency: input.concurrency,
|
|
502
|
-
});
|
|
503
|
-
const workflowMdPath = join(configDir, "tenants", input.tenantId, "WORKFLOW.md");
|
|
504
|
-
await writeFile(workflowMdPath, workflowMd, "utf8");
|
|
505
468
|
}
|
|
506
|
-
export function
|
|
507
|
-
const slug =
|
|
469
|
+
export function generateProjectId(githubProjectTitle, uniqueKey) {
|
|
470
|
+
const slug = githubProjectTitle
|
|
508
471
|
.toLowerCase()
|
|
509
472
|
.replace(/[^a-z0-9]+/g, "-")
|
|
510
473
|
.replace(/^-|-$/g, "")
|
|
511
474
|
.slice(0, 32);
|
|
512
475
|
const suffix = createHash("sha1").update(uniqueKey).digest("hex").slice(0, 8);
|
|
513
|
-
return [slug || "
|
|
476
|
+
return [slug || "project", suffix].join("-");
|
|
514
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
|
+
}
|