@gh-symphony/cli 0.0.6 → 0.0.8
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 +28 -14
- 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 +19 -8
- 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 +17 -14
- package/dist/commands/repo.js +13 -13
- package/dist/commands/run.js +19 -16
- package/dist/commands/start.d.ts +11 -0
- package/dist/commands/start.js +165 -122
- package/dist/commands/status-refresh.d.ts +1 -0
- package/dist/commands/status-refresh.js +7 -1
- package/dist/commands/status.js +48 -40
- package/dist/commands/stop.js +53 -7
- package/dist/completion.d.ts +1 -0
- package/dist/completion.js +204 -0
- 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.d.ts +0 -5
- package/dist/index.js +340 -53
- 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/project-selection.d.ts +8 -0
- package/dist/project-selection.js +56 -0
- package/dist/skills/types.d.ts +1 -1
- package/package.json +6 -5
package/dist/commands/status.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { resolveRuntimeRoot,
|
|
3
|
+
import { resolveRuntimeRoot, syncProjectToRuntime, } from "../orchestrator-runtime.js";
|
|
4
|
+
import { handleMissingManagedProjectConfig, resolveManagedProjectConfig, } from "../project-selection.js";
|
|
4
5
|
import { bold, dim, green, red, yellow, cyan, stripAnsi } from "../ansi.js";
|
|
5
6
|
import { clearScreen, showCursor, hideCursor } from "../ansi.js";
|
|
6
7
|
import { renderDashboard } from "../dashboard/renderer.js";
|
|
8
|
+
import { resolveProjectOrchestratorStatusBaseUrl } from "../orchestrator-status-endpoint.js";
|
|
7
9
|
import { requestOrchestratorRefresh } from "./status-refresh.js";
|
|
8
10
|
const WATCH_REFRESH_TIMEOUT_MS = 1_500;
|
|
9
11
|
function healthIcon(health) {
|
|
@@ -107,22 +109,35 @@ function renderLegacyStatus(snapshot, noColor) {
|
|
|
107
109
|
return lines.join("\n");
|
|
108
110
|
}
|
|
109
111
|
function parseStatusArgs(args) {
|
|
110
|
-
const parsed = {
|
|
112
|
+
const parsed = {
|
|
113
|
+
watch: false,
|
|
114
|
+
};
|
|
111
115
|
for (let i = 0; i < args.length; i += 1) {
|
|
112
116
|
const arg = args[i];
|
|
113
117
|
if (arg === "--watch" || arg === "-w") {
|
|
114
118
|
parsed.watch = true;
|
|
119
|
+
continue;
|
|
115
120
|
}
|
|
116
|
-
if (arg === "--
|
|
117
|
-
|
|
121
|
+
if (arg === "--project" || arg === "--project-id") {
|
|
122
|
+
const value = args[i + 1];
|
|
123
|
+
if (!value || value.startsWith("-")) {
|
|
124
|
+
parsed.error = `Option '${arg}' argument missing`;
|
|
125
|
+
return parsed;
|
|
126
|
+
}
|
|
127
|
+
parsed.projectId = value;
|
|
118
128
|
i += 1;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (arg?.startsWith("-")) {
|
|
132
|
+
parsed.error = `Unknown option '${arg}'`;
|
|
133
|
+
return parsed;
|
|
119
134
|
}
|
|
120
135
|
}
|
|
121
136
|
return parsed;
|
|
122
137
|
}
|
|
123
|
-
async function readStatusSnapshot(runtimeRoot,
|
|
138
|
+
async function readStatusSnapshot(runtimeRoot, projectId) {
|
|
124
139
|
try {
|
|
125
|
-
const statusPath = join(runtimeRoot, "orchestrator", "
|
|
140
|
+
const statusPath = join(runtimeRoot, "orchestrator", "projects", projectId, "status.json");
|
|
126
141
|
const content = await readFile(statusPath, "utf-8");
|
|
127
142
|
return JSON.parse(content);
|
|
128
143
|
}
|
|
@@ -130,56 +145,49 @@ async function readStatusSnapshot(runtimeRoot, tenantId) {
|
|
|
130
145
|
return null;
|
|
131
146
|
}
|
|
132
147
|
}
|
|
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
148
|
const handler = async (args, options) => {
|
|
158
149
|
const parsed = parseStatusArgs(args);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
process.stderr.write("
|
|
162
|
-
process.exitCode =
|
|
150
|
+
if (parsed.error) {
|
|
151
|
+
process.stderr.write(`${parsed.error}\n`);
|
|
152
|
+
process.stderr.write("Usage: gh-symphony status [--project-id <project-id>] [--watch]\n");
|
|
153
|
+
process.exitCode = 2;
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const projectConfig = await resolveManagedProjectConfig({
|
|
157
|
+
configDir: options.configDir,
|
|
158
|
+
requestedProjectId: parsed.projectId,
|
|
159
|
+
});
|
|
160
|
+
if (!projectConfig) {
|
|
161
|
+
handleMissingManagedProjectConfig();
|
|
163
162
|
return;
|
|
164
163
|
}
|
|
165
164
|
const runtimeRoot = resolveRuntimeRoot(options.configDir);
|
|
166
|
-
const
|
|
167
|
-
await
|
|
165
|
+
const projectId = projectConfig.projectId;
|
|
166
|
+
await syncProjectToRuntime(options.configDir, projectConfig);
|
|
168
167
|
if (parsed.watch) {
|
|
169
168
|
const isTTY = process.stdout.isTTY === true;
|
|
170
169
|
let terminalWidth = process.stdout.columns ?? 115;
|
|
171
170
|
let runPromise = null;
|
|
172
171
|
const run = async () => {
|
|
172
|
+
const baseUrl = await resolveProjectOrchestratorStatusBaseUrl({
|
|
173
|
+
configDir: options.configDir,
|
|
174
|
+
projectId,
|
|
175
|
+
});
|
|
173
176
|
await requestOrchestratorRefresh({
|
|
177
|
+
baseUrl,
|
|
174
178
|
timeoutMs: WATCH_REFRESH_TIMEOUT_MS,
|
|
175
179
|
});
|
|
176
|
-
const
|
|
180
|
+
const snapshot = await readStatusSnapshot(runtimeRoot, projectId);
|
|
177
181
|
if (options.json || !isTTY) {
|
|
178
|
-
process.stdout.write(JSON.stringify(
|
|
182
|
+
process.stdout.write(JSON.stringify(snapshot, null, 2) + "\n");
|
|
179
183
|
}
|
|
180
184
|
else {
|
|
185
|
+
if (!snapshot) {
|
|
186
|
+
process.stdout.write(clearScreen() + "Unable to read status snapshot.\n");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
181
189
|
process.stdout.write(clearScreen() +
|
|
182
|
-
renderDashboard(
|
|
190
|
+
renderDashboard([snapshot], {
|
|
183
191
|
terminalWidth,
|
|
184
192
|
noColor: options.noColor,
|
|
185
193
|
}) +
|
|
@@ -213,7 +221,7 @@ const handler = async (args, options) => {
|
|
|
213
221
|
await new Promise(() => { });
|
|
214
222
|
}
|
|
215
223
|
// Single status query
|
|
216
|
-
const snapshot = await readStatusSnapshot(runtimeRoot,
|
|
224
|
+
const snapshot = await readStatusSnapshot(runtimeRoot, projectId);
|
|
217
225
|
if (snapshot) {
|
|
218
226
|
if (options.json) {
|
|
219
227
|
process.stdout.write(JSON.stringify(snapshot, null, 2) + "\n");
|
package/dist/commands/stop.js
CHANGED
|
@@ -1,17 +1,59 @@
|
|
|
1
1
|
import { readFile, rm } from "node:fs/promises";
|
|
2
|
-
import { daemonPidPath } from "../config.js";
|
|
2
|
+
import { daemonPidPath, orchestratorPortPath } from "../config.js";
|
|
3
|
+
import { handleMissingManagedProjectConfig, resolveManagedProjectConfig, } from "../project-selection.js";
|
|
3
4
|
function parseStopArgs(args) {
|
|
4
|
-
|
|
5
|
+
const parsed = {
|
|
6
|
+
force: false,
|
|
7
|
+
};
|
|
8
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
9
|
+
const arg = args[i];
|
|
10
|
+
if (arg === "--force") {
|
|
11
|
+
parsed.force = true;
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (arg === "--project" || arg === "--project-id") {
|
|
15
|
+
const value = args[i + 1];
|
|
16
|
+
if (!value || value.startsWith("-")) {
|
|
17
|
+
parsed.error = `Option '${arg}' argument missing`;
|
|
18
|
+
return parsed;
|
|
19
|
+
}
|
|
20
|
+
parsed.projectId = value;
|
|
21
|
+
i += 1;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (arg?.startsWith("-")) {
|
|
25
|
+
parsed.error = `Unknown option '${arg}'`;
|
|
26
|
+
return parsed;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return parsed;
|
|
5
30
|
}
|
|
6
31
|
const handler = async (args, options) => {
|
|
7
|
-
const
|
|
8
|
-
|
|
32
|
+
const parsed = parseStopArgs(args);
|
|
33
|
+
if (parsed.error) {
|
|
34
|
+
process.stderr.write(`${parsed.error}\n`);
|
|
35
|
+
process.stderr.write("Usage: gh-symphony stop --project-id <project-id> [--force]\n");
|
|
36
|
+
process.exitCode = 2;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const resolvedForce = parsed.force;
|
|
40
|
+
const projectConfig = await resolveManagedProjectConfig({
|
|
41
|
+
configDir: options.configDir,
|
|
42
|
+
requestedProjectId: parsed.projectId,
|
|
43
|
+
});
|
|
44
|
+
if (!projectConfig) {
|
|
45
|
+
handleMissingManagedProjectConfig();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const resolvedProjectId = projectConfig.projectId;
|
|
49
|
+
const pidPath = daemonPidPath(options.configDir, resolvedProjectId);
|
|
50
|
+
const portPath = orchestratorPortPath(options.configDir, resolvedProjectId);
|
|
9
51
|
let pidStr;
|
|
10
52
|
try {
|
|
11
53
|
pidStr = await readFile(pidPath, "utf8");
|
|
12
54
|
}
|
|
13
55
|
catch {
|
|
14
|
-
process.stderr.write(
|
|
56
|
+
process.stderr.write(`No running daemon found for project "${resolvedProjectId}" (PID file missing).\n`);
|
|
15
57
|
process.exitCode = 1;
|
|
16
58
|
return;
|
|
17
59
|
}
|
|
@@ -26,11 +68,12 @@ const handler = async (args, options) => {
|
|
|
26
68
|
process.kill(pid, 0);
|
|
27
69
|
}
|
|
28
70
|
catch {
|
|
29
|
-
process.stdout.write(`Daemon (PID ${pid}) is not running. Cleaning up PID file.\n`);
|
|
71
|
+
process.stdout.write(`Daemon for project "${resolvedProjectId}" (PID ${pid}) is not running. Cleaning up PID file.\n`);
|
|
30
72
|
await rm(pidPath, { force: true });
|
|
73
|
+
await rm(portPath, { force: true });
|
|
31
74
|
return;
|
|
32
75
|
}
|
|
33
|
-
const signal =
|
|
76
|
+
const signal = resolvedForce ? "SIGKILL" : "SIGTERM";
|
|
34
77
|
try {
|
|
35
78
|
process.kill(pid, signal);
|
|
36
79
|
process.stdout.write(`Sent ${signal} to orchestrator (PID ${pid}).\n`);
|
|
@@ -41,6 +84,9 @@ const handler = async (args, options) => {
|
|
|
41
84
|
return;
|
|
42
85
|
}
|
|
43
86
|
await rm(pidPath, { force: true });
|
|
87
|
+
if (resolvedForce) {
|
|
88
|
+
await rm(portPath, { force: true });
|
|
89
|
+
}
|
|
44
90
|
process.stdout.write("Daemon stopped.\n");
|
|
45
91
|
};
|
|
46
92
|
export default handler;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function renderCompletionScript(shell: "bash" | "zsh" | "fish"): string;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
const TOP_LEVEL_COMMANDS = [
|
|
2
|
+
"init",
|
|
3
|
+
"start",
|
|
4
|
+
"stop",
|
|
5
|
+
"status",
|
|
6
|
+
"run",
|
|
7
|
+
"recover",
|
|
8
|
+
"logs",
|
|
9
|
+
"project",
|
|
10
|
+
"repo",
|
|
11
|
+
"config",
|
|
12
|
+
"completion",
|
|
13
|
+
"help",
|
|
14
|
+
"version",
|
|
15
|
+
];
|
|
16
|
+
const GLOBAL_OPTIONS = [
|
|
17
|
+
"--config",
|
|
18
|
+
"--config-dir",
|
|
19
|
+
"--verbose",
|
|
20
|
+
"-v",
|
|
21
|
+
"--json",
|
|
22
|
+
"--no-color",
|
|
23
|
+
"--help",
|
|
24
|
+
"-h",
|
|
25
|
+
"--version",
|
|
26
|
+
"-V",
|
|
27
|
+
];
|
|
28
|
+
const GLOBAL_OPTIONS_WITH_VALUES = ["--config", "--config-dir"];
|
|
29
|
+
const COMMAND_OPTIONS = {
|
|
30
|
+
completion: ["bash", "zsh", "fish"],
|
|
31
|
+
start: ["--project-id", "--project", "--daemon", "-d", ...GLOBAL_OPTIONS],
|
|
32
|
+
stop: ["--project-id", "--project", "--force", ...GLOBAL_OPTIONS],
|
|
33
|
+
status: ["--project-id", "--project", "--watch", "-w", ...GLOBAL_OPTIONS],
|
|
34
|
+
run: ["--project-id", "--project", "--watch", "-w", ...GLOBAL_OPTIONS],
|
|
35
|
+
recover: ["--project-id", "--project", "--dry-run", ...GLOBAL_OPTIONS],
|
|
36
|
+
logs: [
|
|
37
|
+
"--project-id",
|
|
38
|
+
"--project",
|
|
39
|
+
"--follow",
|
|
40
|
+
"-f",
|
|
41
|
+
"--issue",
|
|
42
|
+
"--run",
|
|
43
|
+
"--level",
|
|
44
|
+
...GLOBAL_OPTIONS,
|
|
45
|
+
],
|
|
46
|
+
project: ["add", "list", "remove", "start", "stop", "switch", "status"],
|
|
47
|
+
"project:add": [
|
|
48
|
+
"--non-interactive",
|
|
49
|
+
"--project",
|
|
50
|
+
"--workspace-dir",
|
|
51
|
+
"--assigned-only",
|
|
52
|
+
...GLOBAL_OPTIONS,
|
|
53
|
+
],
|
|
54
|
+
"project:list": [...GLOBAL_OPTIONS],
|
|
55
|
+
"project:remove": [...GLOBAL_OPTIONS],
|
|
56
|
+
"project:start": [
|
|
57
|
+
"--project-id",
|
|
58
|
+
"--project",
|
|
59
|
+
"--daemon",
|
|
60
|
+
"-d",
|
|
61
|
+
...GLOBAL_OPTIONS,
|
|
62
|
+
],
|
|
63
|
+
"project:stop": ["--project-id", "--project", "--force", ...GLOBAL_OPTIONS],
|
|
64
|
+
"project:switch": [...GLOBAL_OPTIONS],
|
|
65
|
+
"project:status": [
|
|
66
|
+
"--project-id",
|
|
67
|
+
"--project",
|
|
68
|
+
"--watch",
|
|
69
|
+
"-w",
|
|
70
|
+
...GLOBAL_OPTIONS,
|
|
71
|
+
],
|
|
72
|
+
repo: ["list", "add", "remove"],
|
|
73
|
+
"repo:list": [...GLOBAL_OPTIONS],
|
|
74
|
+
"repo:add": [...GLOBAL_OPTIONS],
|
|
75
|
+
"repo:remove": [...GLOBAL_OPTIONS],
|
|
76
|
+
config: ["show", "set", "edit"],
|
|
77
|
+
"config:show": [...GLOBAL_OPTIONS],
|
|
78
|
+
"config:set": [...GLOBAL_OPTIONS],
|
|
79
|
+
"config:edit": [...GLOBAL_OPTIONS],
|
|
80
|
+
};
|
|
81
|
+
function quoteWords(values) {
|
|
82
|
+
return values.join(" ");
|
|
83
|
+
}
|
|
84
|
+
function renderBashCasePatterns() {
|
|
85
|
+
return Object.entries(COMMAND_OPTIONS)
|
|
86
|
+
.map(([key, values]) => {
|
|
87
|
+
const [command, subcommand] = key.split(":");
|
|
88
|
+
if (!subcommand) {
|
|
89
|
+
if (command === "completion") {
|
|
90
|
+
return ` completion)\n COMPREPLY=( $(compgen -W "${quoteWords(values)}" -- "$cur") )\n return\n ;;`;
|
|
91
|
+
}
|
|
92
|
+
if (command === "project" ||
|
|
93
|
+
command === "repo" ||
|
|
94
|
+
command === "config") {
|
|
95
|
+
return ` ${command})\n COMPREPLY=( $(compgen -W "${quoteWords(values)}" -- "$cur") )\n return\n ;;`;
|
|
96
|
+
}
|
|
97
|
+
return ` ${command})\n COMPREPLY=( $(compgen -W "${quoteWords(values)}" -- "$cur") )\n return\n ;;`;
|
|
98
|
+
}
|
|
99
|
+
return ` ${command}:${subcommand})\n COMPREPLY=( $(compgen -W "${quoteWords(values)}" -- "$cur") )\n return\n ;;`;
|
|
100
|
+
})
|
|
101
|
+
.join("\n");
|
|
102
|
+
}
|
|
103
|
+
function renderFishLines() {
|
|
104
|
+
const lines = GLOBAL_OPTIONS.map((option) => option.startsWith("--")
|
|
105
|
+
? `complete -c gh-symphony -f -l ${option.slice(2)}`
|
|
106
|
+
: `complete -c gh-symphony -f -s ${option.slice(1)}`);
|
|
107
|
+
for (const command of TOP_LEVEL_COMMANDS) {
|
|
108
|
+
lines.push(`complete -c gh-symphony -f -n '__fish_use_subcommand' -a '${command}'`);
|
|
109
|
+
}
|
|
110
|
+
for (const subcommand of COMMAND_OPTIONS.project ?? []) {
|
|
111
|
+
lines.push(`complete -c gh-symphony -f -n '__fish_seen_subcommand_from project' -a '${subcommand}'`);
|
|
112
|
+
}
|
|
113
|
+
for (const subcommand of COMMAND_OPTIONS.repo ?? []) {
|
|
114
|
+
lines.push(`complete -c gh-symphony -f -n '__fish_seen_subcommand_from repo' -a '${subcommand}'`);
|
|
115
|
+
}
|
|
116
|
+
for (const subcommand of COMMAND_OPTIONS.config ?? []) {
|
|
117
|
+
lines.push(`complete -c gh-symphony -f -n '__fish_seen_subcommand_from config' -a '${subcommand}'`);
|
|
118
|
+
}
|
|
119
|
+
for (const shell of COMMAND_OPTIONS.completion ?? []) {
|
|
120
|
+
lines.push(`complete -c gh-symphony -f -n '__fish_seen_subcommand_from completion' -a '${shell}'`);
|
|
121
|
+
}
|
|
122
|
+
return lines.join("\n");
|
|
123
|
+
}
|
|
124
|
+
export function renderCompletionScript(shell) {
|
|
125
|
+
if (shell === "fish") {
|
|
126
|
+
return `${renderFishLines()}\n`;
|
|
127
|
+
}
|
|
128
|
+
const bashFunction = `# shellcheck shell=bash
|
|
129
|
+
_gh_symphony_find_context() {
|
|
130
|
+
GH_SYMPHONY_COMMAND=""
|
|
131
|
+
GH_SYMPHONY_SUBCOMMAND=""
|
|
132
|
+
|
|
133
|
+
local idx=1
|
|
134
|
+
local token=""
|
|
135
|
+
local expects_value=0
|
|
136
|
+
|
|
137
|
+
while (( idx < COMP_CWORD )); do
|
|
138
|
+
token="\${COMP_WORDS[idx]}"
|
|
139
|
+
|
|
140
|
+
if (( expects_value )); then
|
|
141
|
+
expects_value=0
|
|
142
|
+
(( idx++ ))
|
|
143
|
+
continue
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
case "\${token}" in
|
|
147
|
+
${GLOBAL_OPTIONS_WITH_VALUES.map((option) => `${option}`).join("|")})
|
|
148
|
+
expects_value=1
|
|
149
|
+
;;
|
|
150
|
+
${GLOBAL_OPTIONS_WITH_VALUES.map((option) => `${option}=*`).join("|")})
|
|
151
|
+
;;
|
|
152
|
+
${GLOBAL_OPTIONS.filter((option) => !GLOBAL_OPTIONS_WITH_VALUES.includes(option)).join("|")})
|
|
153
|
+
;;
|
|
154
|
+
-*)
|
|
155
|
+
;;
|
|
156
|
+
*)
|
|
157
|
+
if [[ -z "\${GH_SYMPHONY_COMMAND}" ]]; then
|
|
158
|
+
GH_SYMPHONY_COMMAND="\${token}"
|
|
159
|
+
elif [[ -z "\${GH_SYMPHONY_SUBCOMMAND}" ]]; then
|
|
160
|
+
GH_SYMPHONY_SUBCOMMAND="\${token}"
|
|
161
|
+
fi
|
|
162
|
+
;;
|
|
163
|
+
esac
|
|
164
|
+
|
|
165
|
+
(( idx++ ))
|
|
166
|
+
done
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
_gh_symphony_completion() {
|
|
170
|
+
local cur prev path
|
|
171
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
172
|
+
prev=""
|
|
173
|
+
if (( COMP_CWORD > 0 )); then
|
|
174
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
_gh_symphony_find_context
|
|
178
|
+
path="\${GH_SYMPHONY_COMMAND}"
|
|
179
|
+
|
|
180
|
+
if [[ -z "\${path}" ]]; then
|
|
181
|
+
COMPREPLY=( $(compgen -W "${quoteWords(TOP_LEVEL_COMMANDS)} ${quoteWords(GLOBAL_OPTIONS)}" -- "$cur") )
|
|
182
|
+
return
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
if [[ "\${path}" == "project" || "\${path}" == "repo" || "\${path}" == "config" || "\${path}" == "completion" ]]; then
|
|
186
|
+
if [[ -n "\${GH_SYMPHONY_SUBCOMMAND}" ]]; then
|
|
187
|
+
path="\${path}:\${GH_SYMPHONY_SUBCOMMAND}"
|
|
188
|
+
fi
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
case "\${path}" in
|
|
192
|
+
${renderBashCasePatterns()}
|
|
193
|
+
esac
|
|
194
|
+
}
|
|
195
|
+
`;
|
|
196
|
+
if (shell === "zsh") {
|
|
197
|
+
return `autoload -Uz compinit && compinit
|
|
198
|
+
autoload -U +X bashcompinit && bashcompinit
|
|
199
|
+
${bashFunction}complete -F _gh_symphony_completion gh-symphony
|
|
200
|
+
`;
|
|
201
|
+
}
|
|
202
|
+
return `${bashFunction}complete -F _gh_symphony_completion gh-symphony
|
|
203
|
+
`;
|
|
204
|
+
}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
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
20
|
};
|
|
19
21
|
export type StateRole = "active" | "wait" | "terminal";
|
|
@@ -23,13 +25,13 @@ export type StateMapping = {
|
|
|
23
25
|
};
|
|
24
26
|
export declare function resolveConfigDir(override?: string): string;
|
|
25
27
|
export declare function configFilePath(configDir: string): string;
|
|
26
|
-
export declare function
|
|
27
|
-
export declare function
|
|
28
|
-
export declare function daemonPidPath(configDir: string): string;
|
|
29
|
-
export declare function
|
|
30
|
-
export declare function
|
|
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;
|
|
31
33
|
export declare function loadGlobalConfig(configDir: string): Promise<CliGlobalConfig | null>;
|
|
32
34
|
export declare function saveGlobalConfig(configDir: string, config: CliGlobalConfig): Promise<void>;
|
|
33
|
-
export declare function
|
|
34
|
-
export declare function
|
|
35
|
-
export declare function
|
|
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,46 +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 daemonPidPath(configDir) {
|
|
21
|
-
return join(configDir, DAEMON_PID_FILE);
|
|
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(
|
|
27
|
+
export function orchestratorPortPath(configDir, projectId) {
|
|
28
|
+
return join(projectConfigDir(configDir, projectId), ORCHESTRATOR_PORT_FILE);
|
|
28
29
|
}
|
|
29
30
|
export async function loadGlobalConfig(configDir) {
|
|
30
|
-
|
|
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
|
+
};
|
|
31
41
|
}
|
|
32
42
|
export async function saveGlobalConfig(configDir, config) {
|
|
33
43
|
await writeJsonFile(configFilePath(configDir), config);
|
|
34
44
|
}
|
|
35
|
-
export async function
|
|
36
|
-
return readJsonFile(
|
|
45
|
+
export async function loadProjectConfig(configDir, projectId) {
|
|
46
|
+
return readJsonFile(projectConfigPath(configDir, projectId));
|
|
37
47
|
}
|
|
38
|
-
export async function
|
|
39
|
-
await writeJsonFile(
|
|
48
|
+
export async function saveProjectConfig(configDir, projectId, config) {
|
|
49
|
+
await writeJsonFile(projectConfigPath(configDir, projectId), config);
|
|
40
50
|
}
|
|
41
|
-
export async function
|
|
51
|
+
export async function loadActiveProjectConfig(configDir) {
|
|
42
52
|
const global = await loadGlobalConfig(configDir);
|
|
43
|
-
if (!global?.
|
|
53
|
+
if (!global?.activeProject) {
|
|
44
54
|
return null;
|
|
45
55
|
}
|
|
46
|
-
return
|
|
56
|
+
return loadProjectConfig(configDir, global.activeProject);
|
|
47
57
|
}
|
|
48
58
|
async function readJsonFile(path) {
|
|
49
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.d.ts
CHANGED
|
@@ -5,10 +5,5 @@ export type GlobalOptions = {
|
|
|
5
5
|
json: boolean;
|
|
6
6
|
noColor: boolean;
|
|
7
7
|
};
|
|
8
|
-
export declare function parseGlobalOptions(argv: string[]): {
|
|
9
|
-
options: GlobalOptions;
|
|
10
|
-
command: string;
|
|
11
|
-
args: string[];
|
|
12
|
-
};
|
|
13
8
|
export type CommandHandler = (args: string[], options: GlobalOptions) => Promise<void>;
|
|
14
9
|
export declare function runCli(argv: string[]): Promise<void>;
|