@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/dist/commands/status.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { resolveRuntimeRoot,
|
|
3
|
+
import { resolveRuntimeRoot, resolveProjectConfig, syncProjectToRuntime, } from "../orchestrator-runtime.js";
|
|
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 { resolveProjectOrchestratorStatusBaseUrl } from "../orchestrator-status-endpoint.js";
|
|
7
8
|
import { requestOrchestratorRefresh } from "./status-refresh.js";
|
|
9
|
+
import { parseCliArgs } from "./parse-cli-args.js";
|
|
8
10
|
const WATCH_REFRESH_TIMEOUT_MS = 1_500;
|
|
9
11
|
function healthIcon(health) {
|
|
10
12
|
switch (health) {
|
|
@@ -107,22 +109,22 @@ function renderLegacyStatus(snapshot, noColor) {
|
|
|
107
109
|
return lines.join("\n");
|
|
108
110
|
}
|
|
109
111
|
function parseStatusArgs(args) {
|
|
110
|
-
const parsed = {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
112
|
+
const parsed = parseCliArgs(args, {
|
|
113
|
+
watch: { type: "boolean", short: "w" },
|
|
114
|
+
project: { type: "string" },
|
|
115
|
+
"project-id": { type: "string" },
|
|
116
|
+
});
|
|
117
|
+
if ("error" in parsed) {
|
|
118
|
+
return { watch: false, error: parsed.error };
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
watch: Boolean(parsed.values.watch),
|
|
122
|
+
projectId: (parsed.values["project-id"] ?? parsed.values.project),
|
|
123
|
+
};
|
|
122
124
|
}
|
|
123
|
-
async function readStatusSnapshot(runtimeRoot,
|
|
125
|
+
async function readStatusSnapshot(runtimeRoot, projectId) {
|
|
124
126
|
try {
|
|
125
|
-
const statusPath = join(runtimeRoot, "orchestrator", "
|
|
127
|
+
const statusPath = join(runtimeRoot, "orchestrator", "projects", projectId, "status.json");
|
|
126
128
|
const content = await readFile(statusPath, "utf-8");
|
|
127
129
|
return JSON.parse(content);
|
|
128
130
|
}
|
|
@@ -130,56 +132,47 @@ async function readStatusSnapshot(runtimeRoot, tenantId) {
|
|
|
130
132
|
return null;
|
|
131
133
|
}
|
|
132
134
|
}
|
|
133
|
-
async function readAllStatusSnapshots(runtimeRoot) {
|
|
134
|
-
try {
|
|
135
|
-
const tenantsDir = join(runtimeRoot, "orchestrator", "tenants");
|
|
136
|
-
const { readdir } = await import("node:fs/promises");
|
|
137
|
-
const entries = await readdir(tenantsDir, { withFileTypes: true });
|
|
138
|
-
const snapshots = [];
|
|
139
|
-
for (const entry of entries) {
|
|
140
|
-
if (!entry.isDirectory())
|
|
141
|
-
continue;
|
|
142
|
-
const statusPath = join(tenantsDir, entry.name, "status.json");
|
|
143
|
-
try {
|
|
144
|
-
const content = await readFile(statusPath, "utf-8");
|
|
145
|
-
snapshots.push(JSON.parse(content));
|
|
146
|
-
}
|
|
147
|
-
catch {
|
|
148
|
-
// skip missing/invalid files
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return snapshots;
|
|
152
|
-
}
|
|
153
|
-
catch {
|
|
154
|
-
return [];
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
135
|
const handler = async (args, options) => {
|
|
158
136
|
const parsed = parseStatusArgs(args);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
process.stderr.write("
|
|
137
|
+
if (parsed.error) {
|
|
138
|
+
process.stderr.write(`${parsed.error}\n`);
|
|
139
|
+
process.stderr.write("Usage: gh-symphony status [--project-id <project-id>] [--watch]\n");
|
|
140
|
+
process.exitCode = 2;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const projectConfig = await resolveProjectConfig(options.configDir, parsed.projectId);
|
|
144
|
+
if (!projectConfig) {
|
|
145
|
+
process.stderr.write("No project configured. Run 'gh-symphony project add' first.\n");
|
|
162
146
|
process.exitCode = 1;
|
|
163
147
|
return;
|
|
164
148
|
}
|
|
165
149
|
const runtimeRoot = resolveRuntimeRoot(options.configDir);
|
|
166
|
-
const
|
|
167
|
-
await
|
|
150
|
+
const projectId = projectConfig.projectId;
|
|
151
|
+
await syncProjectToRuntime(options.configDir, projectConfig);
|
|
168
152
|
if (parsed.watch) {
|
|
169
153
|
const isTTY = process.stdout.isTTY === true;
|
|
170
154
|
let terminalWidth = process.stdout.columns ?? 115;
|
|
171
155
|
let runPromise = null;
|
|
172
156
|
const run = async () => {
|
|
157
|
+
const baseUrl = await resolveProjectOrchestratorStatusBaseUrl({
|
|
158
|
+
configDir: options.configDir,
|
|
159
|
+
projectId,
|
|
160
|
+
});
|
|
173
161
|
await requestOrchestratorRefresh({
|
|
162
|
+
baseUrl,
|
|
174
163
|
timeoutMs: WATCH_REFRESH_TIMEOUT_MS,
|
|
175
164
|
});
|
|
176
|
-
const
|
|
165
|
+
const snapshot = await readStatusSnapshot(runtimeRoot, projectId);
|
|
177
166
|
if (options.json || !isTTY) {
|
|
178
|
-
process.stdout.write(JSON.stringify(
|
|
167
|
+
process.stdout.write(JSON.stringify(snapshot, null, 2) + "\n");
|
|
179
168
|
}
|
|
180
169
|
else {
|
|
170
|
+
if (!snapshot) {
|
|
171
|
+
process.stdout.write(clearScreen() + "Unable to read status snapshot.\n");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
181
174
|
process.stdout.write(clearScreen() +
|
|
182
|
-
renderDashboard(
|
|
175
|
+
renderDashboard([snapshot], {
|
|
183
176
|
terminalWidth,
|
|
184
177
|
noColor: options.noColor,
|
|
185
178
|
}) +
|
|
@@ -213,7 +206,7 @@ const handler = async (args, options) => {
|
|
|
213
206
|
await new Promise(() => { });
|
|
214
207
|
}
|
|
215
208
|
// Single status query
|
|
216
|
-
const snapshot = await readStatusSnapshot(runtimeRoot,
|
|
209
|
+
const snapshot = await readStatusSnapshot(runtimeRoot, projectId);
|
|
217
210
|
if (snapshot) {
|
|
218
211
|
if (options.json) {
|
|
219
212
|
process.stdout.write(JSON.stringify(snapshot, null, 2) + "\n");
|
package/dist/commands/stop.js
CHANGED
|
@@ -1,17 +1,43 @@
|
|
|
1
1
|
import { readFile, rm } from "node:fs/promises";
|
|
2
|
-
import { daemonPidPath } from "../config.js";
|
|
2
|
+
import { daemonPidPath, orchestratorPortPath } from "../config.js";
|
|
3
|
+
import { parseCliArgs } from "./parse-cli-args.js";
|
|
3
4
|
function parseStopArgs(args) {
|
|
4
|
-
|
|
5
|
+
const parsed = parseCliArgs(args, {
|
|
6
|
+
force: { type: "boolean" },
|
|
7
|
+
project: { type: "string" },
|
|
8
|
+
"project-id": { type: "string" },
|
|
9
|
+
});
|
|
10
|
+
if ("error" in parsed) {
|
|
11
|
+
return { force: false, error: parsed.error };
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
force: Boolean(parsed.values.force),
|
|
15
|
+
projectId: (parsed.values["project-id"] ?? parsed.values.project),
|
|
16
|
+
};
|
|
5
17
|
}
|
|
6
18
|
const handler = async (args, options) => {
|
|
7
|
-
const
|
|
8
|
-
|
|
19
|
+
const parsed = parseStopArgs(args);
|
|
20
|
+
if (parsed.error) {
|
|
21
|
+
process.stderr.write(`${parsed.error}\n`);
|
|
22
|
+
process.stderr.write("Usage: gh-symphony stop --project-id <project-id> [--force]\n");
|
|
23
|
+
process.exitCode = 2;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (!parsed.projectId) {
|
|
27
|
+
process.stderr.write("Usage: gh-symphony stop --project-id <project-id> [--force]\n");
|
|
28
|
+
process.exitCode = 2;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const resolvedForce = parsed.force;
|
|
32
|
+
const resolvedProjectId = parsed.projectId;
|
|
33
|
+
const pidPath = daemonPidPath(options.configDir, resolvedProjectId);
|
|
34
|
+
const portPath = orchestratorPortPath(options.configDir, resolvedProjectId);
|
|
9
35
|
let pidStr;
|
|
10
36
|
try {
|
|
11
37
|
pidStr = await readFile(pidPath, "utf8");
|
|
12
38
|
}
|
|
13
39
|
catch {
|
|
14
|
-
process.stderr.write(
|
|
40
|
+
process.stderr.write(`No running daemon found for project "${resolvedProjectId}" (PID file missing).\n`);
|
|
15
41
|
process.exitCode = 1;
|
|
16
42
|
return;
|
|
17
43
|
}
|
|
@@ -26,11 +52,12 @@ const handler = async (args, options) => {
|
|
|
26
52
|
process.kill(pid, 0);
|
|
27
53
|
}
|
|
28
54
|
catch {
|
|
29
|
-
process.stdout.write(`Daemon (PID ${pid}) is not running. Cleaning up PID file.\n`);
|
|
55
|
+
process.stdout.write(`Daemon for project "${resolvedProjectId}" (PID ${pid}) is not running. Cleaning up PID file.\n`);
|
|
30
56
|
await rm(pidPath, { force: true });
|
|
57
|
+
await rm(portPath, { force: true });
|
|
31
58
|
return;
|
|
32
59
|
}
|
|
33
|
-
const signal =
|
|
60
|
+
const signal = resolvedForce ? "SIGKILL" : "SIGTERM";
|
|
34
61
|
try {
|
|
35
62
|
process.kill(pid, signal);
|
|
36
63
|
process.stdout.write(`Sent ${signal} to orchestrator (PID ${pid}).\n`);
|
|
@@ -41,6 +68,9 @@ const handler = async (args, options) => {
|
|
|
41
68
|
return;
|
|
42
69
|
}
|
|
43
70
|
await rm(pidPath, { force: true });
|
|
71
|
+
if (resolvedForce) {
|
|
72
|
+
await rm(portPath, { force: true });
|
|
73
|
+
}
|
|
44
74
|
process.stdout.write("Daemon stopped.\n");
|
|
45
75
|
};
|
|
46
76
|
export default handler;
|
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 ───────────────────────────────────────────────────────
|
|
@@ -29,8 +28,8 @@ 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;
|
|
36
35
|
case "--assigned-only":
|
|
@@ -115,37 +114,13 @@ async function tenantAddNonInteractive(flags, options) {
|
|
|
115
114
|
process.exitCode = 1;
|
|
116
115
|
return;
|
|
117
116
|
}
|
|
118
|
-
// Auto-map with smart defaults
|
|
119
|
-
const statusField = project.statusFields.find((f) => f.name.toLowerCase() === "status") ??
|
|
120
|
-
project.statusFields[0];
|
|
121
|
-
if (!statusField) {
|
|
122
|
-
process.stderr.write("Error: No status field found on the project.\n");
|
|
123
|
-
process.exitCode = 1;
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
const columnNames = statusField.options.map((o) => o.name);
|
|
127
|
-
const inferred = inferAllStateRoles(columnNames);
|
|
128
|
-
const mappings = {};
|
|
129
|
-
for (const mapping of inferred) {
|
|
130
|
-
if (mapping.role) {
|
|
131
|
-
mappings[mapping.columnName] = { role: mapping.role };
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
const validation = validateStateMapping(mappings);
|
|
135
|
-
if (!validation.valid) {
|
|
136
|
-
process.stderr.write(`Error: Cannot auto-map columns. ${validation.errors.join("; ")}\nRun without --non-interactive for manual mapping.\n`);
|
|
137
|
-
process.exitCode = 1;
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
const runtime = flags.runtime ?? "codex";
|
|
141
117
|
const tenantId = generateTenantId(project.title, project.id);
|
|
118
|
+
const workspaceDir = flags.workspaceDir ?? `${options.configDir}/workspaces`;
|
|
142
119
|
await writeConfig(options.configDir, {
|
|
143
120
|
tenantId,
|
|
144
121
|
project,
|
|
145
122
|
repos: project.linkedRepositories,
|
|
146
|
-
|
|
147
|
-
mappings,
|
|
148
|
-
runtime,
|
|
123
|
+
workspaceDir,
|
|
149
124
|
assignedOnly: flags.assignedOnly,
|
|
150
125
|
});
|
|
151
126
|
if (options.json) {
|
|
@@ -253,7 +228,7 @@ async function tenantAddInteractive(options) {
|
|
|
253
228
|
process.exitCode = 1;
|
|
254
229
|
return;
|
|
255
230
|
}
|
|
256
|
-
// ── Step
|
|
231
|
+
// ── Step 2: Repository selection ────────────────────────────────────────────
|
|
257
232
|
if (projectDetail.linkedRepositories.length === 0) {
|
|
258
233
|
p.log.warn("No linked repositories found in this project. Add issues from repositories to the project first.");
|
|
259
234
|
process.exitCode = 1;
|
|
@@ -267,60 +242,28 @@ async function tenantAddInteractive(options) {
|
|
|
267
242
|
})),
|
|
268
243
|
required: true,
|
|
269
244
|
}));
|
|
270
|
-
// ── Step
|
|
271
|
-
const statusField = projectDetail.statusFields.find((f) => f.name.toLowerCase() === "status") ??
|
|
272
|
-
projectDetail.statusFields[0];
|
|
273
|
-
if (!statusField) {
|
|
274
|
-
p.log.error("No status field found on the project. The project needs a single-select 'Status' field.");
|
|
275
|
-
process.exitCode = 1;
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
const columnNames = statusField.options.map((o) => o.name);
|
|
279
|
-
const inferred = inferAllStateRoles(columnNames);
|
|
280
|
-
const mappings = {};
|
|
281
|
-
for (const mapping of inferred) {
|
|
282
|
-
if (mapping.role) {
|
|
283
|
-
mappings[mapping.columnName] = { role: mapping.role };
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
const validation = validateStateMapping(mappings);
|
|
287
|
-
if (!validation.valid) {
|
|
288
|
-
p.log.error(`Cannot auto-map status columns: ${validation.errors.join("; ")}\nRun 'gh-symphony init' to manually configure WORKFLOW.md.`);
|
|
289
|
-
process.exitCode = 1;
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
const lifecycleConfig = toWorkflowLifecycleConfig(statusField.name, mappings);
|
|
293
|
-
p.log.info(`Auto-detected workflow: Active=[${lifecycleConfig.activeStates.join(", ")}] Terminal=[${lifecycleConfig.terminalStates.join(", ")}]`);
|
|
294
|
-
// ── Step 4: Assignment filter ────────────────────────────────────────────────
|
|
245
|
+
// ── Step 3: Assignment filter ────────────────────────────────────────────────
|
|
295
246
|
const assignedOnly = await abortIfCancelled(p.confirm({
|
|
296
|
-
message:
|
|
247
|
+
message: "Step 3/4 — Only process issues assigned to the authenticated GitHub user?",
|
|
297
248
|
initialValue: false,
|
|
298
249
|
}));
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
+
},
|
|
307
259
|
}));
|
|
308
|
-
let agentCommand;
|
|
309
|
-
if (runtime === "custom") {
|
|
310
|
-
agentCommand = await abortIfCancelled(p.text({
|
|
311
|
-
message: "Custom agent command:",
|
|
312
|
-
placeholder: "bash -lc my-agent",
|
|
313
|
-
}));
|
|
314
|
-
}
|
|
315
260
|
// ── Confirmation ─────────────────────────────────────────────────────────────
|
|
316
261
|
p.note([
|
|
317
262
|
`User: ${login}`,
|
|
318
263
|
`Project: ${projectDetail.title}`,
|
|
319
264
|
`Repos: ${selectedRepos.map((r) => `${r.owner}/${r.name}`).join(", ")}`,
|
|
320
265
|
`Assigned: ${assignedOnly ? `Only issues assigned to ${login}` : "All project issues"}`,
|
|
321
|
-
`
|
|
322
|
-
`Active: ${lifecycleConfig.activeStates.join(", ")}`,
|
|
323
|
-
`Terminal: ${lifecycleConfig.terminalStates.join(", ")}`,
|
|
266
|
+
`Workspace: ${workspaceDir}`,
|
|
324
267
|
].join("\n"), "Configuration Summary");
|
|
325
268
|
const confirmed = await abortIfCancelled(p.confirm({ message: "Apply this configuration?" }));
|
|
326
269
|
if (!confirmed) {
|
|
@@ -337,14 +280,7 @@ async function tenantAddInteractive(options) {
|
|
|
337
280
|
tenantId,
|
|
338
281
|
project: projectDetail,
|
|
339
282
|
repos: selectedRepos,
|
|
340
|
-
|
|
341
|
-
id: statusField.id,
|
|
342
|
-
name: statusField.name,
|
|
343
|
-
options: statusField.options,
|
|
344
|
-
},
|
|
345
|
-
mappings,
|
|
346
|
-
runtime,
|
|
347
|
-
agentCommand,
|
|
283
|
+
workspaceDir,
|
|
348
284
|
assignedOnly,
|
|
349
285
|
});
|
|
350
286
|
s6.stop("Configuration saved.");
|
|
@@ -355,7 +291,6 @@ async function tenantAddInteractive(options) {
|
|
|
355
291
|
process.exitCode = 1;
|
|
356
292
|
return;
|
|
357
293
|
}
|
|
358
|
-
p.log.info(`WORKFLOW.md generated at ${tenantId}/WORKFLOW.md — edit it to customize your team policy.`);
|
|
359
294
|
p.outro(`Tenant "${tenantId}" created!\n Run 'gh-symphony start' to begin orchestration.`);
|
|
360
295
|
}
|
|
361
296
|
// ── tenant list ───────────────────────────────────────────────────────────────
|
package/dist/config.d.ts
CHANGED
|
@@ -1,44 +1,37 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { OrchestratorProjectConfig } 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";
|
|
5
|
-
export declare const
|
|
5
|
+
export declare const ORCHESTRATOR_LOG_FILE = "orchestrator.log";
|
|
6
|
+
export declare const ORCHESTRATOR_PORT_FILE = "port";
|
|
6
7
|
export type CliGlobalConfig = {
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
activeProject: string | null;
|
|
9
|
+
projects: string[];
|
|
9
10
|
};
|
|
10
|
-
export type
|
|
11
|
+
export type CliProjectTrackerSettings = Record<string, string | boolean> & {
|
|
11
12
|
projectId?: string;
|
|
12
13
|
assignedOnly?: boolean;
|
|
13
14
|
};
|
|
14
|
-
export type
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
export type CliProjectConfig = Omit<OrchestratorProjectConfig, "tracker"> & {
|
|
16
|
+
displayName?: string;
|
|
17
|
+
tracker: Omit<OrchestratorProjectConfig["tracker"], "settings"> & {
|
|
18
|
+
settings?: CliProjectTrackerSettings;
|
|
17
19
|
};
|
|
18
|
-
workflowMapping?: WorkflowStateConfig;
|
|
19
20
|
};
|
|
20
21
|
export type StateRole = "active" | "wait" | "terminal";
|
|
21
22
|
export type StateMapping = {
|
|
22
23
|
role: StateRole;
|
|
23
24
|
goal?: string;
|
|
24
25
|
};
|
|
25
|
-
export type WorkflowStateConfig = {
|
|
26
|
-
stateFieldName: string;
|
|
27
|
-
mappings: Record<string, StateMapping>;
|
|
28
|
-
lifecycle: WorkflowLifecycleConfig;
|
|
29
|
-
};
|
|
30
26
|
export declare function resolveConfigDir(override?: string): string;
|
|
31
27
|
export declare function configFilePath(configDir: string): string;
|
|
32
|
-
export declare function
|
|
33
|
-
export declare function
|
|
34
|
-
export declare function
|
|
35
|
-
export declare function
|
|
36
|
-
export declare function
|
|
37
|
-
export declare function orchestratorLogPath(configDir: string): string;
|
|
28
|
+
export declare function projectConfigDir(configDir: string, projectId: string): string;
|
|
29
|
+
export declare function projectConfigPath(configDir: string, projectId: string): string;
|
|
30
|
+
export declare function daemonPidPath(configDir: string, projectId: string): string;
|
|
31
|
+
export declare function orchestratorLogPath(configDir: string, projectId: string): string;
|
|
32
|
+
export declare function orchestratorPortPath(configDir: string, projectId: string): string;
|
|
38
33
|
export declare function loadGlobalConfig(configDir: string): Promise<CliGlobalConfig | null>;
|
|
39
34
|
export declare function saveGlobalConfig(configDir: string, config: CliGlobalConfig): Promise<void>;
|
|
40
|
-
export declare function
|
|
41
|
-
export declare function
|
|
42
|
-
export declare function
|
|
43
|
-
export declare function saveWorkflowMapping(configDir: string, tenantId: string, mapping: WorkflowStateConfig): Promise<void>;
|
|
44
|
-
export declare function loadActiveTenantConfig(configDir: string): Promise<CliTenantConfig | null>;
|
|
35
|
+
export declare function loadProjectConfig(configDir: string, projectId: string): Promise<CliProjectConfig | null>;
|
|
36
|
+
export declare function saveProjectConfig(configDir: string, projectId: string, config: CliProjectConfig): Promise<void>;
|
|
37
|
+
export declare function loadActiveProjectConfig(configDir: string): Promise<CliProjectConfig | null>;
|
package/dist/config.js
CHANGED
|
@@ -4,55 +4,56 @@ import { homedir } from "node:os";
|
|
|
4
4
|
export const DEFAULT_CONFIG_DIR = join(homedir(), ".gh-symphony");
|
|
5
5
|
export const CONFIG_FILE = "config.json";
|
|
6
6
|
export const DAEMON_PID_FILE = "daemon.pid";
|
|
7
|
-
export const
|
|
7
|
+
export const ORCHESTRATOR_LOG_FILE = "orchestrator.log";
|
|
8
|
+
export const ORCHESTRATOR_PORT_FILE = "port";
|
|
8
9
|
export function resolveConfigDir(override) {
|
|
9
10
|
return override ?? process.env.GH_SYMPHONY_CONFIG_DIR ?? DEFAULT_CONFIG_DIR;
|
|
10
11
|
}
|
|
11
12
|
export function configFilePath(configDir) {
|
|
12
13
|
return join(configDir, CONFIG_FILE);
|
|
13
14
|
}
|
|
14
|
-
export function
|
|
15
|
-
return join(configDir, "
|
|
15
|
+
export function projectConfigDir(configDir, projectId) {
|
|
16
|
+
return join(configDir, "projects", projectId);
|
|
16
17
|
}
|
|
17
|
-
export function
|
|
18
|
-
return join(
|
|
18
|
+
export function projectConfigPath(configDir, projectId) {
|
|
19
|
+
return join(projectConfigDir(configDir, projectId), "project.json");
|
|
19
20
|
}
|
|
20
|
-
export function
|
|
21
|
-
return join(
|
|
21
|
+
export function daemonPidPath(configDir, projectId) {
|
|
22
|
+
return join(projectConfigDir(configDir, projectId), DAEMON_PID_FILE);
|
|
22
23
|
}
|
|
23
|
-
export function
|
|
24
|
-
return join(configDir,
|
|
24
|
+
export function orchestratorLogPath(configDir, projectId) {
|
|
25
|
+
return join(projectConfigDir(configDir, projectId), ORCHESTRATOR_LOG_FILE);
|
|
25
26
|
}
|
|
26
|
-
export function
|
|
27
|
-
return join(configDir,
|
|
28
|
-
}
|
|
29
|
-
export function orchestratorLogPath(configDir) {
|
|
30
|
-
return join(logsDir(configDir), "orchestrator.log");
|
|
27
|
+
export function orchestratorPortPath(configDir, projectId) {
|
|
28
|
+
return join(projectConfigDir(configDir, projectId), ORCHESTRATOR_PORT_FILE);
|
|
31
29
|
}
|
|
32
30
|
export async function loadGlobalConfig(configDir) {
|
|
33
|
-
|
|
31
|
+
const raw = await readJsonFile(configFilePath(configDir));
|
|
32
|
+
if (!raw) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
activeProject: typeof raw.activeProject === "string" ? raw.activeProject : null,
|
|
37
|
+
projects: Array.isArray(raw.projects)
|
|
38
|
+
? raw.projects.filter((projectId) => typeof projectId === "string")
|
|
39
|
+
: [],
|
|
40
|
+
};
|
|
34
41
|
}
|
|
35
42
|
export async function saveGlobalConfig(configDir, config) {
|
|
36
43
|
await writeJsonFile(configFilePath(configDir), config);
|
|
37
44
|
}
|
|
38
|
-
export async function
|
|
39
|
-
return readJsonFile(
|
|
40
|
-
}
|
|
41
|
-
export async function saveTenantConfig(configDir, tenantId, config) {
|
|
42
|
-
await writeJsonFile(tenantConfigPath(configDir, tenantId), config);
|
|
43
|
-
}
|
|
44
|
-
export async function loadWorkflowMapping(configDir, tenantId) {
|
|
45
|
-
return readJsonFile(workflowMappingPath(configDir, tenantId));
|
|
45
|
+
export async function loadProjectConfig(configDir, projectId) {
|
|
46
|
+
return readJsonFile(projectConfigPath(configDir, projectId));
|
|
46
47
|
}
|
|
47
|
-
export async function
|
|
48
|
-
await writeJsonFile(
|
|
48
|
+
export async function saveProjectConfig(configDir, projectId, config) {
|
|
49
|
+
await writeJsonFile(projectConfigPath(configDir, projectId), config);
|
|
49
50
|
}
|
|
50
|
-
export async function
|
|
51
|
+
export async function loadActiveProjectConfig(configDir) {
|
|
51
52
|
const global = await loadGlobalConfig(configDir);
|
|
52
|
-
if (!global?.
|
|
53
|
+
if (!global?.activeProject) {
|
|
53
54
|
return null;
|
|
54
55
|
}
|
|
55
|
-
return
|
|
56
|
+
return loadProjectConfig(configDir, global.activeProject);
|
|
56
57
|
}
|
|
57
58
|
async function readJsonFile(path) {
|
|
58
59
|
try {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ProjectStatusSnapshot } from "@gh-symphony/core";
|
|
2
2
|
export type DashboardOptions = {
|
|
3
3
|
terminalWidth: number;
|
|
4
4
|
noColor: boolean;
|
|
@@ -6,4 +6,4 @@ export type DashboardOptions = {
|
|
|
6
6
|
/** Override Date.now() for deterministic testing */
|
|
7
7
|
now?: number;
|
|
8
8
|
};
|
|
9
|
-
export declare function renderDashboard(snapshots:
|
|
9
|
+
export declare function renderDashboard(snapshots: ProjectStatusSnapshot[], options: DashboardOptions): string;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { bold, dim, green, red, yellow, cyan, magenta, blue, stripAnsi, } from "../ansi.js";
|
|
3
3
|
// ── Column widths (from Elixir spec) ─────────────────────────────────────────
|
|
4
4
|
const COL_ID = 24;
|
|
5
|
-
const
|
|
5
|
+
const COL_STATUS = 14;
|
|
6
6
|
const COL_PID = 8;
|
|
7
7
|
const COL_AGE_TURN = 12;
|
|
8
8
|
const COL_TOKENS = 10;
|
|
@@ -81,7 +81,7 @@ const COL_SEPARATORS = 6;
|
|
|
81
81
|
function eventColWidth(termWidth) {
|
|
82
82
|
const fixed = 2 +
|
|
83
83
|
COL_ID_HEADER +
|
|
84
|
-
|
|
84
|
+
COL_STATUS +
|
|
85
85
|
COL_PID +
|
|
86
86
|
COL_AGE_TURN +
|
|
87
87
|
COL_TOKENS +
|
|
@@ -140,7 +140,7 @@ function buildSummaryLines(snapshots, options, c) {
|
|
|
140
140
|
function tableHeaderRow(c) {
|
|
141
141
|
const cols = [
|
|
142
142
|
pad("ID", COL_ID_HEADER),
|
|
143
|
-
pad("
|
|
143
|
+
pad("STATUS", COL_STATUS),
|
|
144
144
|
pad("PID", COL_PID),
|
|
145
145
|
pad("AGE/TURN", COL_AGE_TURN),
|
|
146
146
|
pad("TOKENS", COL_TOKENS),
|
|
@@ -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
|
|
155
|
+
const status = pad(run.issueState ?? run.executionPhase ?? "\u2014", COL_STATUS);
|
|
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;
|
|
@@ -161,7 +161,7 @@ function activeRunRow(run, now, evtWidth, c) {
|
|
|
161
161
|
const sessionId = run.runtimeSession?.sessionId ?? run.runtimeSession?.threadId ?? null;
|
|
162
162
|
const session = pad(compactSessionId(sessionId), COL_SESSION);
|
|
163
163
|
const event = pad(run.lastEvent ?? "\u2014", evtWidth);
|
|
164
|
-
const columns = [id,
|
|
164
|
+
const columns = [id, status, pid, ageTurn, tokens, session, event].join(" ");
|
|
165
165
|
return ` ${dot} ${columns}`;
|
|
166
166
|
}
|
|
167
167
|
function retryRow(entry, snapshot, now, c) {
|
package/dist/index.js
CHANGED
|
@@ -49,7 +49,6 @@ const COMMANDS = {
|
|
|
49
49
|
logs: () => import("./commands/logs.js"),
|
|
50
50
|
project: () => import("./commands/project.js"),
|
|
51
51
|
repo: () => import("./commands/repo.js"),
|
|
52
|
-
tenant: () => import("./commands/tenant.js"),
|
|
53
52
|
config: () => import("./commands/config-cmd.js"),
|
|
54
53
|
help: () => import("./commands/help.js"),
|
|
55
54
|
version: () => import("./commands/version.js"),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type CliProjectConfig } from "./config.js";
|
|
2
2
|
export declare function resolveRuntimeRoot(configDir: string): string;
|
|
3
|
-
export declare function
|
|
4
|
-
export declare function
|
|
5
|
-
export declare function
|
|
3
|
+
export declare function resolveProjectConfig(configDir: string, requestedProjectId?: string): Promise<CliProjectConfig | null>;
|
|
4
|
+
export declare function orchestratorProjectConfigPath(runtimeRoot: string, projectId: string): string;
|
|
5
|
+
export declare function syncProjectToRuntime(configDir: string, projectConfig: CliProjectConfig): Promise<string>;
|