@gh-symphony/cli 0.0.4 → 0.0.6
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 +2 -2
- package/dist/commands/help.js +1 -1
- package/dist/commands/init.d.ts +2 -16
- package/dist/commands/init.js +3 -40
- package/dist/commands/start.js +3 -0
- package/dist/commands/status-refresh.d.ts +8 -0
- package/dist/commands/status-refresh.js +21 -0
- package/dist/commands/status.js +17 -2
- package/dist/commands/tenant.js +30 -84
- package/dist/config.d.ts +9 -11
- package/dist/config.js +0 -9
- package/dist/dashboard/renderer.js +2 -2
- package/dist/github/gh-auth.js +2 -2
- package/dist/orchestrator-runtime.js +1 -16
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -61,8 +61,8 @@ 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.
|
|
64
|
+
4. Optionally limit processing to issues assigned to the authenticated user
|
|
65
|
+
5. Configure the workspace root directory
|
|
66
66
|
6. Write tenant configuration to `~/.gh-symphony/`
|
|
67
67
|
|
|
68
68
|
### Tenant Management
|
package/dist/commands/help.js
CHANGED
|
@@ -41,7 +41,7 @@ Global Options:
|
|
|
41
41
|
|
|
42
42
|
Examples:
|
|
43
43
|
gh-symphony tenant add # Add a tenant (interactive)
|
|
44
|
-
gh-symphony tenant add --non-interactive --project <id>
|
|
44
|
+
gh-symphony tenant add --non-interactive --project <id> --workspace-dir <path>
|
|
45
45
|
gh-symphony tenant list # List all tenants
|
|
46
46
|
gh-symphony tenant remove <id> # Remove a tenant
|
|
47
47
|
gh-symphony start # Start orchestrator
|
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;
|
|
@@ -27,22 +26,9 @@ type WriteConfigInput = {
|
|
|
27
26
|
tenantId: 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;
|
|
31
|
+
assignedOnly?: boolean;
|
|
46
32
|
};
|
|
47
33
|
export declare function writeConfig(configDir: string, input: WriteConfigInput): Promise<void>;
|
|
48
34
|
export declare function generateTenantId(projectTitle: 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, saveTenantConfig,
|
|
8
|
+
import { loadGlobalConfig, saveGlobalConfig, saveTenantConfig, } 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";
|
|
@@ -436,29 +435,11 @@ async function runInteractiveStandalone(_options) {
|
|
|
436
435
|
nextSteps: "Run 'gh-symphony tenant add' to register a tenant.",
|
|
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
|
-
const lifecycleConfig = toWorkflowLifecycleConfig(input.statusField.name, input.mappings);
|
|
450
|
-
// Save workflow mapping
|
|
451
|
-
const mappingConfig = {
|
|
452
|
-
stateFieldName: input.statusField.name,
|
|
453
|
-
mappings: input.mappings,
|
|
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
439
|
await saveTenantConfig(configDir, input.tenantId, {
|
|
460
440
|
tenantId: input.tenantId,
|
|
461
441
|
slug: input.tenantId,
|
|
442
|
+
workspaceDir: input.workspaceDir,
|
|
462
443
|
repositories: input.repos.map((r) => ({
|
|
463
444
|
owner: r.owner,
|
|
464
445
|
name: r.name,
|
|
@@ -469,15 +450,9 @@ export async function writeConfig(configDir, input) {
|
|
|
469
450
|
bindingId: input.project.id,
|
|
470
451
|
settings: {
|
|
471
452
|
projectId: input.project.id,
|
|
453
|
+
...(input.assignedOnly ? { assignedOnly: true } : {}),
|
|
472
454
|
},
|
|
473
455
|
},
|
|
474
|
-
runtime: {
|
|
475
|
-
driver: "local",
|
|
476
|
-
workspaceRuntimeDir: runtimeDir,
|
|
477
|
-
projectRoot: process.cwd(),
|
|
478
|
-
workerCommand: input.workerCommand ?? resolveWorkerCommand(),
|
|
479
|
-
},
|
|
480
|
-
workflowMapping: mappingConfig,
|
|
481
456
|
});
|
|
482
457
|
// Save/update global config
|
|
483
458
|
const existing = await loadGlobalConfig(configDir);
|
|
@@ -489,18 +464,6 @@ export async function writeConfig(configDir, input) {
|
|
|
489
464
|
],
|
|
490
465
|
};
|
|
491
466
|
await saveGlobalConfig(configDir, globalConfig);
|
|
492
|
-
// Generate WORKFLOW.md for tenant-level fallback
|
|
493
|
-
const workflowMd = generateWorkflowMarkdown({
|
|
494
|
-
projectId: input.project.id,
|
|
495
|
-
stateFieldName: input.statusField.name,
|
|
496
|
-
mappings: input.mappings,
|
|
497
|
-
lifecycle: lifecycleConfig,
|
|
498
|
-
runtime: input.agentCommand ?? input.runtime,
|
|
499
|
-
pollIntervalMs: input.pollIntervalMs,
|
|
500
|
-
concurrency: input.concurrency,
|
|
501
|
-
});
|
|
502
|
-
const workflowMdPath = join(configDir, "tenants", input.tenantId, "WORKFLOW.md");
|
|
503
|
-
await writeFile(workflowMdPath, workflowMd, "utf8");
|
|
504
467
|
}
|
|
505
468
|
export function generateTenantId(projectTitle, uniqueKey) {
|
|
506
469
|
const slug = projectTitle
|
package/dist/commands/start.js
CHANGED
|
@@ -151,6 +151,9 @@ const handler = async (args, options) => {
|
|
|
151
151
|
return snapshot ?? null;
|
|
152
152
|
},
|
|
153
153
|
},
|
|
154
|
+
onRefresh: async () => {
|
|
155
|
+
await service.runOnce({ tenantId });
|
|
156
|
+
},
|
|
154
157
|
});
|
|
155
158
|
logLine(green("\u25B2"), `Starting orchestrator for tenant: ${bold(tenantId)}`);
|
|
156
159
|
logLine(dim("\u00B7"), dim("Press Ctrl+C to stop"));
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type RefreshRequestOptions = {
|
|
2
|
+
fetchImpl?: typeof fetch;
|
|
3
|
+
timeoutMs?: number;
|
|
4
|
+
env?: NodeJS.ProcessEnv;
|
|
5
|
+
};
|
|
6
|
+
export declare function resolveOrchestratorStatusBaseUrl(env?: NodeJS.ProcessEnv): string;
|
|
7
|
+
export declare function requestOrchestratorRefresh(options?: RefreshRequestOptions): Promise<boolean>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function resolveOrchestratorStatusBaseUrl(env = process.env) {
|
|
2
|
+
const host = env.ORCHESTRATOR_STATUS_HOST ?? "127.0.0.1";
|
|
3
|
+
const port = env.ORCHESTRATOR_STATUS_PORT ?? "4680";
|
|
4
|
+
const urlHost = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
|
|
5
|
+
return `http://${urlHost}:${port}`;
|
|
6
|
+
}
|
|
7
|
+
export async function requestOrchestratorRefresh(options = {}) {
|
|
8
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
9
|
+
const timeoutMs = options.timeoutMs ?? 5_000;
|
|
10
|
+
const signal = AbortSignal.timeout(timeoutMs);
|
|
11
|
+
try {
|
|
12
|
+
const response = await fetchImpl(`${resolveOrchestratorStatusBaseUrl(options.env)}/api/v1/refresh`, {
|
|
13
|
+
method: "POST",
|
|
14
|
+
signal,
|
|
15
|
+
});
|
|
16
|
+
return response.ok;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
package/dist/commands/status.js
CHANGED
|
@@ -4,6 +4,8 @@ import { resolveRuntimeRoot, resolveTenantConfig, syncTenantToRuntime, } from ".
|
|
|
4
4
|
import { bold, dim, green, red, yellow, cyan, stripAnsi } from "../ansi.js";
|
|
5
5
|
import { clearScreen, showCursor, hideCursor } from "../ansi.js";
|
|
6
6
|
import { renderDashboard } from "../dashboard/renderer.js";
|
|
7
|
+
import { requestOrchestratorRefresh } from "./status-refresh.js";
|
|
8
|
+
const WATCH_REFRESH_TIMEOUT_MS = 1_500;
|
|
7
9
|
function healthIcon(health) {
|
|
8
10
|
switch (health) {
|
|
9
11
|
case "idle":
|
|
@@ -166,7 +168,11 @@ const handler = async (args, options) => {
|
|
|
166
168
|
if (parsed.watch) {
|
|
167
169
|
const isTTY = process.stdout.isTTY === true;
|
|
168
170
|
let terminalWidth = process.stdout.columns ?? 115;
|
|
171
|
+
let runPromise = null;
|
|
169
172
|
const run = async () => {
|
|
173
|
+
await requestOrchestratorRefresh({
|
|
174
|
+
timeoutMs: WATCH_REFRESH_TIMEOUT_MS,
|
|
175
|
+
});
|
|
170
176
|
const snapshots = await readAllStatusSnapshots(runtimeRoot);
|
|
171
177
|
if (options.json || !isTTY) {
|
|
172
178
|
process.stdout.write(JSON.stringify(snapshots, null, 2) + "\n");
|
|
@@ -180,11 +186,20 @@ const handler = async (args, options) => {
|
|
|
180
186
|
"\n");
|
|
181
187
|
}
|
|
182
188
|
};
|
|
189
|
+
const tick = () => {
|
|
190
|
+
if (runPromise) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
runPromise = run().finally(() => {
|
|
194
|
+
runPromise = null;
|
|
195
|
+
});
|
|
196
|
+
};
|
|
183
197
|
if (isTTY) {
|
|
184
198
|
process.stdout.write(hideCursor());
|
|
185
199
|
}
|
|
186
|
-
|
|
187
|
-
|
|
200
|
+
tick();
|
|
201
|
+
await runPromise;
|
|
202
|
+
const interval = setInterval(tick, 2000);
|
|
188
203
|
process.on("SIGWINCH", () => {
|
|
189
204
|
terminalWidth = process.stdout.columns ?? terminalWidth;
|
|
190
205
|
});
|
package/dist/commands/tenant.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
2
|
import { createClient, validateToken, checkRequiredScopes, listUserProjects, getProjectDetail, GitHubScopeError, } from "../github/client.js";
|
|
3
3
|
import { ensureGhAuth, getGhToken, GhAuthError } from "../github/gh-auth.js";
|
|
4
|
-
import { inferAllStateRoles, toWorkflowLifecycleConfig, validateStateMapping, } from "../mapping/smart-defaults.js";
|
|
5
4
|
import { loadGlobalConfig, saveGlobalConfig, loadTenantConfig, tenantConfigDir, } from "../config.js";
|
|
6
5
|
import { writeConfig, generateTenantId, abortIfCancelled } from "./init.js";
|
|
7
6
|
// ── Scope error display ───────────────────────────────────────────────────────
|
|
@@ -17,7 +16,7 @@ function displayScopeError(error, retryCommand) {
|
|
|
17
16
|
p.note(`gh auth refresh --scopes ${scopeArg}\n\nThen re-run: ${retryCommand}`, "Fix missing scope");
|
|
18
17
|
}
|
|
19
18
|
function parseTenantAddFlags(args) {
|
|
20
|
-
const flags = { nonInteractive: false };
|
|
19
|
+
const flags = { nonInteractive: false, assignedOnly: false };
|
|
21
20
|
for (let i = 0; i < args.length; i += 1) {
|
|
22
21
|
const arg = args[i];
|
|
23
22
|
const next = args[i + 1];
|
|
@@ -29,10 +28,13 @@ function parseTenantAddFlags(args) {
|
|
|
29
28
|
flags.project = next;
|
|
30
29
|
i += 1;
|
|
31
30
|
break;
|
|
32
|
-
case "--
|
|
33
|
-
flags.
|
|
31
|
+
case "--workspace-dir":
|
|
32
|
+
flags.workspaceDir = next;
|
|
34
33
|
i += 1;
|
|
35
34
|
break;
|
|
35
|
+
case "--assigned-only":
|
|
36
|
+
flags.assignedOnly = true;
|
|
37
|
+
break;
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
return flags;
|
|
@@ -112,37 +114,14 @@ async function tenantAddNonInteractive(flags, options) {
|
|
|
112
114
|
process.exitCode = 1;
|
|
113
115
|
return;
|
|
114
116
|
}
|
|
115
|
-
// Auto-map with smart defaults
|
|
116
|
-
const statusField = project.statusFields.find((f) => f.name.toLowerCase() === "status") ??
|
|
117
|
-
project.statusFields[0];
|
|
118
|
-
if (!statusField) {
|
|
119
|
-
process.stderr.write("Error: No status field found on the project.\n");
|
|
120
|
-
process.exitCode = 1;
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
const columnNames = statusField.options.map((o) => o.name);
|
|
124
|
-
const inferred = inferAllStateRoles(columnNames);
|
|
125
|
-
const mappings = {};
|
|
126
|
-
for (const mapping of inferred) {
|
|
127
|
-
if (mapping.role) {
|
|
128
|
-
mappings[mapping.columnName] = { role: mapping.role };
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
const validation = validateStateMapping(mappings);
|
|
132
|
-
if (!validation.valid) {
|
|
133
|
-
process.stderr.write(`Error: Cannot auto-map columns. ${validation.errors.join("; ")}\nRun without --non-interactive for manual mapping.\n`);
|
|
134
|
-
process.exitCode = 1;
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
const runtime = flags.runtime ?? "codex";
|
|
138
117
|
const tenantId = generateTenantId(project.title, project.id);
|
|
118
|
+
const workspaceDir = flags.workspaceDir ?? `${options.configDir}/workspaces`;
|
|
139
119
|
await writeConfig(options.configDir, {
|
|
140
120
|
tenantId,
|
|
141
121
|
project,
|
|
142
122
|
repos: project.linkedRepositories,
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
runtime,
|
|
123
|
+
workspaceDir,
|
|
124
|
+
assignedOnly: flags.assignedOnly,
|
|
146
125
|
});
|
|
147
126
|
if (options.json) {
|
|
148
127
|
process.stdout.write(JSON.stringify({ tenantId, status: "created" }) + "\n");
|
|
@@ -228,7 +207,7 @@ async function tenantAddInteractive(options) {
|
|
|
228
207
|
return;
|
|
229
208
|
}
|
|
230
209
|
const selectedProjectId = await abortIfCancelled(p.select({
|
|
231
|
-
message: "Step 1/
|
|
210
|
+
message: "Step 1/4 — Select a GitHub Project:",
|
|
232
211
|
options: projects.map((proj) => ({
|
|
233
212
|
value: proj.id,
|
|
234
213
|
label: `${proj.owner.login}/${proj.title}`,
|
|
@@ -249,68 +228,42 @@ async function tenantAddInteractive(options) {
|
|
|
249
228
|
process.exitCode = 1;
|
|
250
229
|
return;
|
|
251
230
|
}
|
|
252
|
-
// ── Step
|
|
231
|
+
// ── Step 2: Repository selection ────────────────────────────────────────────
|
|
253
232
|
if (projectDetail.linkedRepositories.length === 0) {
|
|
254
233
|
p.log.warn("No linked repositories found in this project. Add issues from repositories to the project first.");
|
|
255
234
|
process.exitCode = 1;
|
|
256
235
|
return;
|
|
257
236
|
}
|
|
258
237
|
const selectedRepos = await abortIfCancelled(p.multiselect({
|
|
259
|
-
message: "Step 2/
|
|
238
|
+
message: "Step 2/4 — Select repositories to orchestrate:",
|
|
260
239
|
options: projectDetail.linkedRepositories.map((repo) => ({
|
|
261
240
|
value: repo,
|
|
262
241
|
label: `${repo.owner}/${repo.name}`,
|
|
263
242
|
})),
|
|
264
243
|
required: true,
|
|
265
244
|
}));
|
|
266
|
-
// ── Step
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
const validation = validateStateMapping(mappings);
|
|
283
|
-
if (!validation.valid) {
|
|
284
|
-
p.log.error(`Cannot auto-map status columns: ${validation.errors.join("; ")}\nRun 'gh-symphony init' to manually configure WORKFLOW.md.`);
|
|
285
|
-
process.exitCode = 1;
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
const lifecycleConfig = toWorkflowLifecycleConfig(statusField.name, mappings);
|
|
289
|
-
p.log.info(`Auto-detected workflow: Active=[${lifecycleConfig.activeStates.join(", ")}] Terminal=[${lifecycleConfig.terminalStates.join(", ")}]`);
|
|
290
|
-
// ── Step 4: Runtime selection ────────────────────────────────────────────────
|
|
291
|
-
const runtime = await abortIfCancelled(p.select({
|
|
292
|
-
message: "Step 3/3 — Select AI runtime:",
|
|
293
|
-
options: [
|
|
294
|
-
{ value: "codex", label: "OpenAI Codex", hint: "recommended" },
|
|
295
|
-
{ value: "claude-code", label: "Claude Code" },
|
|
296
|
-
{ value: "custom", label: "Custom command" },
|
|
297
|
-
],
|
|
245
|
+
// ── Step 3: Assignment filter ────────────────────────────────────────────────
|
|
246
|
+
const assignedOnly = await abortIfCancelled(p.confirm({
|
|
247
|
+
message: "Step 3/4 — Only process issues assigned to the authenticated GitHub user?",
|
|
248
|
+
initialValue: false,
|
|
249
|
+
}));
|
|
250
|
+
const workspaceDir = await abortIfCancelled(p.text({
|
|
251
|
+
message: "Step 4/4 — Workspace root directory:",
|
|
252
|
+
placeholder: `${options.configDir}/workspaces`,
|
|
253
|
+
defaultValue: `${options.configDir}/workspaces`,
|
|
254
|
+
validate(value) {
|
|
255
|
+
return value.trim().length > 0
|
|
256
|
+
? undefined
|
|
257
|
+
: "Workspace directory is required.";
|
|
258
|
+
},
|
|
298
259
|
}));
|
|
299
|
-
let agentCommand;
|
|
300
|
-
if (runtime === "custom") {
|
|
301
|
-
agentCommand = await abortIfCancelled(p.text({
|
|
302
|
-
message: "Custom agent command:",
|
|
303
|
-
placeholder: "bash -lc my-agent",
|
|
304
|
-
}));
|
|
305
|
-
}
|
|
306
260
|
// ── Confirmation ─────────────────────────────────────────────────────────────
|
|
307
261
|
p.note([
|
|
308
262
|
`User: ${login}`,
|
|
309
263
|
`Project: ${projectDetail.title}`,
|
|
310
264
|
`Repos: ${selectedRepos.map((r) => `${r.owner}/${r.name}`).join(", ")}`,
|
|
311
|
-
`
|
|
312
|
-
`
|
|
313
|
-
`Terminal: ${lifecycleConfig.terminalStates.join(", ")}`,
|
|
265
|
+
`Assigned: ${assignedOnly ? `Only issues assigned to ${login}` : "All project issues"}`,
|
|
266
|
+
`Workspace: ${workspaceDir}`,
|
|
314
267
|
].join("\n"), "Configuration Summary");
|
|
315
268
|
const confirmed = await abortIfCancelled(p.confirm({ message: "Apply this configuration?" }));
|
|
316
269
|
if (!confirmed) {
|
|
@@ -327,14 +280,8 @@ async function tenantAddInteractive(options) {
|
|
|
327
280
|
tenantId,
|
|
328
281
|
project: projectDetail,
|
|
329
282
|
repos: selectedRepos,
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
name: statusField.name,
|
|
333
|
-
options: statusField.options,
|
|
334
|
-
},
|
|
335
|
-
mappings,
|
|
336
|
-
runtime,
|
|
337
|
-
agentCommand,
|
|
283
|
+
workspaceDir,
|
|
284
|
+
assignedOnly,
|
|
338
285
|
});
|
|
339
286
|
s6.stop("Configuration saved.");
|
|
340
287
|
}
|
|
@@ -344,7 +291,6 @@ async function tenantAddInteractive(options) {
|
|
|
344
291
|
process.exitCode = 1;
|
|
345
292
|
return;
|
|
346
293
|
}
|
|
347
|
-
p.log.info(`WORKFLOW.md generated at ${tenantId}/WORKFLOW.md — edit it to customize your team policy.`);
|
|
348
294
|
p.outro(`Tenant "${tenantId}" created!\n Run 'gh-symphony start' to begin orchestration.`);
|
|
349
295
|
}
|
|
350
296
|
// ── tenant list ───────────────────────────────────────────────────────────────
|
package/dist/config.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OrchestratorTenantConfig
|
|
1
|
+
import type { OrchestratorTenantConfig } from "@gh-symphony/core";
|
|
2
2
|
export declare const DEFAULT_CONFIG_DIR: string;
|
|
3
3
|
export declare const CONFIG_FILE = "config.json";
|
|
4
4
|
export declare const DAEMON_PID_FILE = "daemon.pid";
|
|
@@ -7,24 +7,24 @@ export type CliGlobalConfig = {
|
|
|
7
7
|
activeTenant: string | null;
|
|
8
8
|
tenants: string[];
|
|
9
9
|
};
|
|
10
|
-
export type
|
|
11
|
-
|
|
10
|
+
export type CliTenantTrackerSettings = Record<string, string | boolean> & {
|
|
11
|
+
projectId?: string;
|
|
12
|
+
assignedOnly?: boolean;
|
|
13
|
+
};
|
|
14
|
+
export type CliTenantConfig = Omit<OrchestratorTenantConfig, "tracker"> & {
|
|
15
|
+
tracker: Omit<OrchestratorTenantConfig["tracker"], "settings"> & {
|
|
16
|
+
settings?: CliTenantTrackerSettings;
|
|
17
|
+
};
|
|
12
18
|
};
|
|
13
19
|
export type StateRole = "active" | "wait" | "terminal";
|
|
14
20
|
export type StateMapping = {
|
|
15
21
|
role: StateRole;
|
|
16
22
|
goal?: string;
|
|
17
23
|
};
|
|
18
|
-
export type WorkflowStateConfig = {
|
|
19
|
-
stateFieldName: string;
|
|
20
|
-
mappings: Record<string, StateMapping>;
|
|
21
|
-
lifecycle: WorkflowLifecycleConfig;
|
|
22
|
-
};
|
|
23
24
|
export declare function resolveConfigDir(override?: string): string;
|
|
24
25
|
export declare function configFilePath(configDir: string): string;
|
|
25
26
|
export declare function tenantConfigDir(configDir: string, tenantId: string): string;
|
|
26
27
|
export declare function tenantConfigPath(configDir: string, tenantId: string): string;
|
|
27
|
-
export declare function workflowMappingPath(configDir: string, tenantId: string): string;
|
|
28
28
|
export declare function daemonPidPath(configDir: string): string;
|
|
29
29
|
export declare function logsDir(configDir: string): string;
|
|
30
30
|
export declare function orchestratorLogPath(configDir: string): string;
|
|
@@ -32,6 +32,4 @@ export declare function loadGlobalConfig(configDir: string): Promise<CliGlobalCo
|
|
|
32
32
|
export declare function saveGlobalConfig(configDir: string, config: CliGlobalConfig): Promise<void>;
|
|
33
33
|
export declare function loadTenantConfig(configDir: string, tenantId: string): Promise<CliTenantConfig | null>;
|
|
34
34
|
export declare function saveTenantConfig(configDir: string, tenantId: string, config: CliTenantConfig): Promise<void>;
|
|
35
|
-
export declare function loadWorkflowMapping(configDir: string, tenantId: string): Promise<WorkflowStateConfig | null>;
|
|
36
|
-
export declare function saveWorkflowMapping(configDir: string, tenantId: string, mapping: WorkflowStateConfig): Promise<void>;
|
|
37
35
|
export declare function loadActiveTenantConfig(configDir: string): Promise<CliTenantConfig | null>;
|
package/dist/config.js
CHANGED
|
@@ -17,9 +17,6 @@ export function tenantConfigDir(configDir, tenantId) {
|
|
|
17
17
|
export function tenantConfigPath(configDir, tenantId) {
|
|
18
18
|
return join(tenantConfigDir(configDir, tenantId), "tenant.json");
|
|
19
19
|
}
|
|
20
|
-
export function workflowMappingPath(configDir, tenantId) {
|
|
21
|
-
return join(tenantConfigDir(configDir, tenantId), "workflow-mapping.json");
|
|
22
|
-
}
|
|
23
20
|
export function daemonPidPath(configDir) {
|
|
24
21
|
return join(configDir, DAEMON_PID_FILE);
|
|
25
22
|
}
|
|
@@ -41,12 +38,6 @@ export async function loadTenantConfig(configDir, tenantId) {
|
|
|
41
38
|
export async function saveTenantConfig(configDir, tenantId, config) {
|
|
42
39
|
await writeJsonFile(tenantConfigPath(configDir, tenantId), config);
|
|
43
40
|
}
|
|
44
|
-
export async function loadWorkflowMapping(configDir, tenantId) {
|
|
45
|
-
return readJsonFile(workflowMappingPath(configDir, tenantId));
|
|
46
|
-
}
|
|
47
|
-
export async function saveWorkflowMapping(configDir, tenantId, mapping) {
|
|
48
|
-
await writeJsonFile(workflowMappingPath(configDir, tenantId), mapping);
|
|
49
|
-
}
|
|
50
41
|
export async function loadActiveTenantConfig(configDir) {
|
|
51
42
|
const global = await loadGlobalConfig(configDir);
|
|
52
43
|
if (!global?.activeTenant) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ── Dashboard Renderer (Elixir-parity) ──────────────────────────────────────
|
|
2
2
|
import { bold, dim, green, red, yellow, cyan, magenta, blue, stripAnsi, } from "../ansi.js";
|
|
3
3
|
// ── Column widths (from Elixir spec) ─────────────────────────────────────────
|
|
4
|
-
const COL_ID =
|
|
4
|
+
const COL_ID = 24;
|
|
5
5
|
const COL_STAGE = 14;
|
|
6
6
|
const COL_PID = 8;
|
|
7
7
|
const COL_AGE_TURN = 12;
|
|
@@ -152,7 +152,7 @@ function tableHeaderRow(c) {
|
|
|
152
152
|
function activeRunRow(run, now, evtWidth, c) {
|
|
153
153
|
const dot = statusDot(run, c);
|
|
154
154
|
const id = pad(run.issueIdentifier, COL_ID);
|
|
155
|
-
const stage = pad(run.issueState, COL_STAGE);
|
|
155
|
+
const stage = pad(run.issueState || "\u2014", COL_STAGE);
|
|
156
156
|
const pid = pad(run.processId != null ? String(run.processId) : "\u2014", COL_PID);
|
|
157
157
|
const age = fmtAge(run.startedAt, now);
|
|
158
158
|
const turn = run.turnCount ?? 0;
|
package/dist/github/gh-auth.js
CHANGED
|
@@ -31,7 +31,7 @@ export function checkGhAuthenticated(opts) {
|
|
|
31
31
|
if ((result.status ?? 1) !== 0) {
|
|
32
32
|
return { authenticated: false };
|
|
33
33
|
}
|
|
34
|
-
const login = parseLogin((result.
|
|
34
|
+
const login = parseLogin((result.stdout ?? "").toString());
|
|
35
35
|
return { authenticated: true, login };
|
|
36
36
|
}
|
|
37
37
|
export function checkGhScopes(opts) {
|
|
@@ -40,7 +40,7 @@ export function checkGhScopes(opts) {
|
|
|
40
40
|
encoding: "utf8",
|
|
41
41
|
stdio: ["pipe", "pipe", "pipe"],
|
|
42
42
|
});
|
|
43
|
-
const output = (result.
|
|
43
|
+
const output = (result.stdout ?? "").toString();
|
|
44
44
|
const scopes = parseScopes(output);
|
|
45
45
|
if (scopes.length === 0) {
|
|
46
46
|
return { valid: true, missing: [], scopes: [] };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { loadGlobalConfig, loadTenantConfig, } from "./config.js";
|
|
4
4
|
export function resolveRuntimeRoot(configDir) {
|
|
@@ -22,20 +22,5 @@ export async function syncTenantToRuntime(configDir, tenantConfig) {
|
|
|
22
22
|
const configPath = orchestratorTenantConfigPath(runtimeRoot, tenantConfig.tenantId);
|
|
23
23
|
await mkdir(dirname(configPath), { recursive: true });
|
|
24
24
|
await writeFile(configPath, JSON.stringify(tenantConfig, null, 2) + "\n");
|
|
25
|
-
// Copy tenant WORKFLOW.md to runtime if it exists
|
|
26
|
-
const workflowSrc = join(configDir, "tenants", tenantConfig.tenantId, "WORKFLOW.md");
|
|
27
|
-
const workflowDst = join(dirname(configPath), "WORKFLOW.md");
|
|
28
|
-
try {
|
|
29
|
-
await copyFile(workflowSrc, workflowDst);
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
// ENOENT is expected for tenants created before WORKFLOW.md scaffolding
|
|
33
|
-
if (!(error &&
|
|
34
|
-
typeof error === "object" &&
|
|
35
|
-
"code" in error &&
|
|
36
|
-
error.code === "ENOENT")) {
|
|
37
|
-
throw error;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
25
|
return runtimeRoot;
|
|
41
26
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gh-symphony/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "hojinzs",
|
|
6
6
|
"description": "Interactive CLI for GitHub Symphony orchestration",
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@clack/prompts": "^0.9.1",
|
|
39
|
-
"@gh-symphony/core": "0.0.
|
|
40
|
-
"@gh-symphony/orchestrator": "0.0.
|
|
41
|
-
"@gh-symphony/tracker-github": "0.0.
|
|
42
|
-
"@gh-symphony/worker": "0.0.
|
|
39
|
+
"@gh-symphony/core": "0.0.6",
|
|
40
|
+
"@gh-symphony/orchestrator": "0.0.6",
|
|
41
|
+
"@gh-symphony/tracker-github": "0.0.6",
|
|
42
|
+
"@gh-symphony/worker": "0.0.6"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
45
45
|
"build": "tsc -p tsconfig.json",
|