@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/dist/index.js CHANGED
@@ -1,44 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { realpathSync } from "node:fs";
3
3
  import { pathToFileURL } from "node:url";
4
+ import { Command, CommanderError, InvalidArgumentError, Option, } from "commander";
4
5
  import { resolveConfigDir } from "./config.js";
5
- export function parseGlobalOptions(argv) {
6
- const globalFlags = {
7
- configDir: resolveConfigDir(),
8
- verbose: false,
9
- json: false,
10
- noColor: false,
11
- };
12
- const remaining = [];
13
- let i = 0;
14
- while (i < argv.length) {
15
- const arg = argv[i];
16
- if (arg === "--config" || arg === "--config-dir") {
17
- globalFlags.configDir = resolveConfigDir(argv[i + 1]);
18
- i += 2;
19
- continue;
20
- }
21
- if (arg === "--verbose" || arg === "-v") {
22
- globalFlags.verbose = true;
23
- i += 1;
24
- continue;
25
- }
26
- if (arg === "--json") {
27
- globalFlags.json = true;
28
- i += 1;
29
- continue;
30
- }
31
- if (arg === "--no-color") {
32
- globalFlags.noColor = true;
33
- i += 1;
34
- continue;
35
- }
36
- remaining.push(arg);
37
- i += 1;
38
- }
39
- const [command = "help", ...args] = remaining;
40
- return { options: globalFlags, command, args };
41
- }
6
+ import { renderCompletionScript } from "./completion.js";
42
7
  const COMMANDS = {
43
8
  init: () => import("./commands/init.js"),
44
9
  start: () => import("./commands/start.js"),
@@ -49,34 +14,356 @@ const COMMANDS = {
49
14
  logs: () => import("./commands/logs.js"),
50
15
  project: () => import("./commands/project.js"),
51
16
  repo: () => import("./commands/repo.js"),
52
- tenant: () => import("./commands/tenant.js"),
53
17
  config: () => import("./commands/config-cmd.js"),
54
- help: () => import("./commands/help.js"),
55
18
  version: () => import("./commands/version.js"),
56
19
  };
57
- export async function runCli(argv) {
58
- const { options, command, args } = parseGlobalOptions(argv);
20
+ function addGlobalOptions(command) {
21
+ return command
22
+ .option("--config <dir>", "Config directory")
23
+ .addOption(new Option("--config-dir <dir>").hideHelp())
24
+ .option("-v, --verbose", "Enable verbose output")
25
+ .option("--json", "Output in JSON format")
26
+ .option("--no-color", "Disable color output");
27
+ }
28
+ function resolveGlobalOptions(values) {
29
+ const configInput = typeof values.config === "string"
30
+ ? values.config
31
+ : typeof values.configDir === "string"
32
+ ? values.configDir
33
+ : undefined;
34
+ const options = {
35
+ configDir: resolveConfigDir(configInput),
36
+ verbose: Boolean(values.verbose),
37
+ json: Boolean(values.json),
38
+ noColor: Boolean(values.noColor),
39
+ };
59
40
  if (options.noColor) {
60
41
  process.env.NO_COLOR = "1";
61
42
  }
62
- if (command === "--help" || command === "-h") {
63
- const helpModule = await COMMANDS.help();
64
- await helpModule.default(args, options);
43
+ return options;
44
+ }
45
+ function resolveProjectId(values) {
46
+ return values.projectId ?? values.project;
47
+ }
48
+ function pushOption(args, flag, value) {
49
+ if (typeof value === "string" && value.length > 0) {
50
+ args.push(flag, value);
65
51
  return;
66
52
  }
67
- if (command === "--version" || command === "-V") {
53
+ if (value === true) {
54
+ args.push(flag);
55
+ }
56
+ }
57
+ async function invokeHandler(key, args, values) {
58
+ const module = await COMMANDS[key]();
59
+ await module.default(args, resolveGlobalOptions(values));
60
+ }
61
+ function shellArgument(value) {
62
+ if (value === "bash" || value === "zsh" || value === "fish") {
63
+ return value;
64
+ }
65
+ throw new InvalidArgumentError("Shell must be one of: bash, zsh, fish");
66
+ }
67
+ function hasVersionFlag(argv) {
68
+ return argv.some((arg) => arg === "--version" || arg === "-V");
69
+ }
70
+ function resolveVersionOptions(argv) {
71
+ const options = {
72
+ configDir: resolveConfigDir(),
73
+ verbose: argv.some((arg) => arg === "--verbose" || arg === "-v"),
74
+ json: argv.includes("--json"),
75
+ noColor: argv.includes("--no-color"),
76
+ };
77
+ if (options.noColor) {
78
+ process.env.NO_COLOR = "1";
79
+ }
80
+ return options;
81
+ }
82
+ function createProgram() {
83
+ let actionInvoked = false;
84
+ const markInvoked = () => {
85
+ actionInvoked = true;
86
+ };
87
+ const program = addGlobalOptions(new Command()
88
+ .name("gh-symphony")
89
+ .description("AI Coding Agent Orchestrator")
90
+ .exitOverride()
91
+ .helpOption("-h, --help", "Show help")
92
+ .addHelpCommand("help [command]", "Show help for command")
93
+ .showHelpAfterError("(run with --help for usage)")
94
+ .option("-V, --version", "Show version"));
95
+ addGlobalOptions(program
96
+ .command("init")
97
+ .description("Interactive project setup wizard")
98
+ .allowExcessArguments(false)).action(async function () {
99
+ markInvoked();
100
+ await invokeHandler("init", [], this.optsWithGlobals());
101
+ });
102
+ addGlobalOptions(program
103
+ .command("start")
104
+ .description("Start the orchestrator")
105
+ .option("-d, --daemon", "Start in daemon mode")
106
+ .option("--project-id <projectId>", "Project identifier")
107
+ .addOption(new Option("--project <projectId>").hideHelp())
108
+ .allowExcessArguments(false)).action(async function () {
109
+ markInvoked();
110
+ const values = this.optsWithGlobals();
111
+ const args = [];
112
+ pushOption(args, "--project-id", resolveProjectId(values));
113
+ pushOption(args, "--daemon", values.daemon);
114
+ await invokeHandler("start", args, values);
115
+ });
116
+ addGlobalOptions(program
117
+ .command("stop")
118
+ .description("Stop the background orchestrator")
119
+ .option("--force", "Force stop with SIGKILL")
120
+ .option("--project-id <projectId>", "Project identifier")
121
+ .addOption(new Option("--project <projectId>").hideHelp())
122
+ .allowExcessArguments(false)).action(async function () {
123
+ markInvoked();
124
+ const values = this.optsWithGlobals();
125
+ const args = [];
126
+ pushOption(args, "--project-id", resolveProjectId(values));
127
+ pushOption(args, "--force", values.force);
128
+ await invokeHandler("stop", args, values);
129
+ });
130
+ addGlobalOptions(program
131
+ .command("status")
132
+ .description("Show orchestrator status")
133
+ .option("-w, --watch", "Watch status continuously")
134
+ .option("--project-id <projectId>", "Project identifier")
135
+ .addOption(new Option("--project <projectId>").hideHelp())
136
+ .allowExcessArguments(false)).action(async function () {
137
+ markInvoked();
138
+ const values = this.optsWithGlobals();
139
+ const args = [];
140
+ pushOption(args, "--project-id", resolveProjectId(values));
141
+ pushOption(args, "--watch", values.watch);
142
+ await invokeHandler("status", args, values);
143
+ });
144
+ addGlobalOptions(program
145
+ .command("run")
146
+ .description("Dispatch a single issue")
147
+ .argument("<issue>", "Issue identifier")
148
+ .option("-w, --watch", "Watch status after dispatch")
149
+ .option("--project-id <projectId>", "Project identifier")
150
+ .addOption(new Option("--project <projectId>").hideHelp())
151
+ .allowExcessArguments(false)).action(async function (issue) {
152
+ markInvoked();
153
+ const values = this.optsWithGlobals();
154
+ const args = [issue];
155
+ pushOption(args, "--project-id", resolveProjectId(values));
156
+ pushOption(args, "--watch", values.watch);
157
+ await invokeHandler("run", args, values);
158
+ });
159
+ addGlobalOptions(program
160
+ .command("recover")
161
+ .description("Recover stalled runs")
162
+ .option("--dry-run", "Show recoverable runs without recovering")
163
+ .option("--project-id <projectId>", "Project identifier")
164
+ .addOption(new Option("--project <projectId>").hideHelp())
165
+ .allowExcessArguments(false)).action(async function () {
166
+ markInvoked();
167
+ const values = this.optsWithGlobals();
168
+ const args = [];
169
+ pushOption(args, "--project-id", resolveProjectId(values));
170
+ pushOption(args, "--dry-run", values.dryRun);
171
+ await invokeHandler("recover", args, values);
172
+ });
173
+ addGlobalOptions(program
174
+ .command("logs")
175
+ .description("View orchestrator logs")
176
+ .option("-f, --follow", "Follow new log lines")
177
+ .option("--issue <issue>", "Filter by issue identifier")
178
+ .option("--run <runId>", "Read events for a specific run")
179
+ .option("--level <level>", "Filter by log level")
180
+ .option("--project-id <projectId>", "Project identifier")
181
+ .addOption(new Option("--project <projectId>").hideHelp())
182
+ .allowExcessArguments(false)).action(async function () {
183
+ markInvoked();
184
+ const values = this.optsWithGlobals();
185
+ const args = [];
186
+ pushOption(args, "--project-id", resolveProjectId(values));
187
+ pushOption(args, "--follow", values.follow);
188
+ pushOption(args, "--issue", values.issue);
189
+ pushOption(args, "--run", values.run);
190
+ pushOption(args, "--level", values.level);
191
+ await invokeHandler("logs", args, values);
192
+ });
193
+ const project = addGlobalOptions(program.command("project").description("Manage configured projects"));
194
+ project.action(async function () {
195
+ markInvoked();
196
+ await invokeHandler("project", [], this.optsWithGlobals());
197
+ });
198
+ addGlobalOptions(project
199
+ .command("add")
200
+ .description("Add a new project")
201
+ .option("--non-interactive", "Run without prompts")
202
+ .option("--project <id>", "GitHub Project ID")
203
+ .option("--workspace-dir <path>", "Workspace directory")
204
+ .option("--assigned-only", "Limit processing to assigned issues")
205
+ .allowExcessArguments(false)).action(async function () {
206
+ markInvoked();
207
+ const values = this.optsWithGlobals();
208
+ const args = [];
209
+ pushOption(args, "--non-interactive", values.nonInteractive);
210
+ pushOption(args, "--project", values.project);
211
+ pushOption(args, "--workspace-dir", values.workspaceDir);
212
+ pushOption(args, "--assigned-only", values.assignedOnly);
213
+ await invokeHandler("project", ["add", ...args], values);
214
+ });
215
+ addGlobalOptions(project.command("list").description("List configured projects")).action(async function () {
216
+ markInvoked();
217
+ const values = this.optsWithGlobals();
218
+ await invokeHandler("project", ["list"], values);
219
+ });
220
+ addGlobalOptions(project
221
+ .command("remove")
222
+ .description("Remove a project")
223
+ .argument("<projectId>", "Project identifier")
224
+ .allowExcessArguments(false)).action(async function (projectId) {
225
+ markInvoked();
226
+ await invokeHandler("project", ["remove", projectId], this.optsWithGlobals());
227
+ });
228
+ addGlobalOptions(project
229
+ .command("start")
230
+ .description("Start a specific project")
231
+ .option("-d, --daemon", "Start in daemon mode")
232
+ .option("--project-id <projectId>", "Project identifier")
233
+ .addOption(new Option("--project <projectId>").hideHelp())
234
+ .allowExcessArguments(false)).action(async function () {
235
+ markInvoked();
236
+ const values = this.optsWithGlobals();
237
+ const args = ["start"];
238
+ pushOption(args, "--project-id", resolveProjectId(values));
239
+ pushOption(args, "--daemon", values.daemon);
240
+ await invokeHandler("project", args, values);
241
+ });
242
+ addGlobalOptions(project
243
+ .command("stop")
244
+ .description("Stop a specific project")
245
+ .option("--force", "Force stop with SIGKILL")
246
+ .option("--project-id <projectId>", "Project identifier")
247
+ .addOption(new Option("--project <projectId>").hideHelp())
248
+ .allowExcessArguments(false)).action(async function () {
249
+ markInvoked();
250
+ const values = this.optsWithGlobals();
251
+ const args = ["stop"];
252
+ pushOption(args, "--project-id", resolveProjectId(values));
253
+ pushOption(args, "--force", values.force);
254
+ await invokeHandler("project", args, values);
255
+ });
256
+ addGlobalOptions(project.command("switch").description("Switch the active project")).action(async function () {
257
+ markInvoked();
258
+ await invokeHandler("project", ["switch"], this.optsWithGlobals());
259
+ });
260
+ addGlobalOptions(project
261
+ .command("status")
262
+ .description("Show status for a specific project")
263
+ .option("-w, --watch", "Watch status continuously")
264
+ .option("--project-id <projectId>", "Project identifier")
265
+ .addOption(new Option("--project <projectId>").hideHelp())
266
+ .allowExcessArguments(false)).action(async function () {
267
+ markInvoked();
268
+ const values = this.optsWithGlobals();
269
+ const args = ["status"];
270
+ pushOption(args, "--project-id", resolveProjectId(values));
271
+ pushOption(args, "--watch", values.watch);
272
+ await invokeHandler("project", args, values);
273
+ });
274
+ const repo = addGlobalOptions(program
275
+ .command("repo")
276
+ .description("Manage repositories in the active project"));
277
+ repo.action(async function () {
278
+ markInvoked();
279
+ await invokeHandler("repo", [], this.optsWithGlobals());
280
+ });
281
+ addGlobalOptions(repo.command("list").description("List repositories")).action(async function () {
282
+ markInvoked();
283
+ await invokeHandler("repo", ["list"], this.optsWithGlobals());
284
+ });
285
+ addGlobalOptions(repo
286
+ .command("add")
287
+ .description("Add a repository")
288
+ .argument("<owner/name>", "Repository spec")
289
+ .allowExcessArguments(false)).action(async function (repoSpec) {
290
+ markInvoked();
291
+ await invokeHandler("repo", ["add", repoSpec], this.optsWithGlobals());
292
+ });
293
+ addGlobalOptions(repo
294
+ .command("remove")
295
+ .description("Remove a repository")
296
+ .argument("<owner/name>", "Repository spec")
297
+ .allowExcessArguments(false)).action(async function (repoSpec) {
298
+ markInvoked();
299
+ await invokeHandler("repo", ["remove", repoSpec], this.optsWithGlobals());
300
+ });
301
+ const config = addGlobalOptions(program.command("config").description("Manage CLI configuration"));
302
+ config.action(async function () {
303
+ markInvoked();
304
+ await invokeHandler("config", [], this.optsWithGlobals());
305
+ });
306
+ addGlobalOptions(config.command("show").description("Show configuration")).action(async function () {
307
+ markInvoked();
308
+ await invokeHandler("config", ["show"], this.optsWithGlobals());
309
+ });
310
+ addGlobalOptions(config
311
+ .command("set")
312
+ .description("Set a configuration value")
313
+ .argument("<key>", "Configuration key")
314
+ .argument("<value>", "Configuration value")
315
+ .allowExcessArguments(false)).action(async function (key, value) {
316
+ markInvoked();
317
+ await invokeHandler("config", ["set", key, value], this.optsWithGlobals());
318
+ });
319
+ addGlobalOptions(config.command("edit").description("Open config in $EDITOR")).action(async function () {
320
+ markInvoked();
321
+ await invokeHandler("config", ["edit"], this.optsWithGlobals());
322
+ });
323
+ addGlobalOptions(program
324
+ .command("completion")
325
+ .description("Print shell completion script")
326
+ .argument("<shell>", "Shell name", shellArgument)
327
+ .allowExcessArguments(false)).action(async function (shell) {
328
+ markInvoked();
329
+ process.stdout.write(renderCompletionScript(shell));
330
+ });
331
+ program
332
+ .command("version")
333
+ .description("Show version")
334
+ .allowExcessArguments(false)
335
+ .action(async function () {
336
+ markInvoked();
337
+ await invokeHandler("version", [], this.optsWithGlobals());
338
+ });
339
+ return { program, wasInvoked: () => actionInvoked };
340
+ }
341
+ export async function runCli(argv) {
342
+ const { program, wasInvoked } = createProgram();
343
+ if (hasVersionFlag(argv)) {
68
344
  const versionModule = await COMMANDS.version();
69
- await versionModule.default(args, options);
345
+ await versionModule.default([], resolveVersionOptions(argv));
70
346
  return;
71
347
  }
72
- const loader = COMMANDS[command];
73
- if (!loader) {
74
- process.stderr.write(`Unknown command: ${command}\nRun 'gh-symphony help' for usage.\n`);
75
- process.exitCode = 2;
76
- return;
348
+ try {
349
+ await program.parseAsync(["node", "gh-symphony", ...argv], {
350
+ from: "node",
351
+ });
352
+ if (!wasInvoked()) {
353
+ program.outputHelp();
354
+ }
355
+ }
356
+ catch (error) {
357
+ if (error instanceof CommanderError &&
358
+ error.code === "commander.helpDisplayed") {
359
+ return;
360
+ }
361
+ if (error instanceof CommanderError) {
362
+ process.exitCode = error.exitCode;
363
+ return;
364
+ }
365
+ throw error;
77
366
  }
78
- const module = await loader();
79
- await module.default(args, options);
80
367
  }
81
368
  async function main() {
82
369
  await runCli(process.argv.slice(2));
@@ -1,5 +1,5 @@
1
- import { type CliTenantConfig } from "./config.js";
1
+ import { type CliProjectConfig } from "./config.js";
2
2
  export declare function resolveRuntimeRoot(configDir: string): string;
3
- export declare function resolveTenantConfig(configDir: string, requestedTenantId?: string): Promise<CliTenantConfig | null>;
4
- export declare function orchestratorTenantConfigPath(runtimeRoot: string, tenantId: string): string;
5
- export declare function syncTenantToRuntime(configDir: string, tenantConfig: CliTenantConfig): Promise<string>;
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>;
@@ -1,26 +1,26 @@
1
1
  import { mkdir, writeFile } from "node:fs/promises";
2
2
  import { dirname, join, resolve } from "node:path";
3
- import { loadGlobalConfig, loadTenantConfig, } from "./config.js";
3
+ import { loadGlobalConfig, loadProjectConfig, } from "./config.js";
4
4
  export function resolveRuntimeRoot(configDir) {
5
5
  return resolve(configDir);
6
6
  }
7
- export async function resolveTenantConfig(configDir, requestedTenantId) {
8
- if (requestedTenantId) {
9
- return loadTenantConfig(configDir, requestedTenantId);
7
+ export async function resolveProjectConfig(configDir, requestedProjectId) {
8
+ if (requestedProjectId) {
9
+ return loadProjectConfig(configDir, requestedProjectId);
10
10
  }
11
11
  const global = await loadGlobalConfig(configDir);
12
- if (!global?.activeTenant) {
12
+ if (!global?.activeProject) {
13
13
  return null;
14
14
  }
15
- return loadTenantConfig(configDir, global.activeTenant);
15
+ return loadProjectConfig(configDir, global.activeProject);
16
16
  }
17
- export function orchestratorTenantConfigPath(runtimeRoot, tenantId) {
18
- return join(runtimeRoot, "orchestrator", "tenants", tenantId, "config.json");
17
+ export function orchestratorProjectConfigPath(runtimeRoot, projectId) {
18
+ return join(runtimeRoot, "orchestrator", "projects", projectId, "config.json");
19
19
  }
20
- export async function syncTenantToRuntime(configDir, tenantConfig) {
20
+ export async function syncProjectToRuntime(configDir, projectConfig) {
21
21
  const runtimeRoot = resolveRuntimeRoot(configDir);
22
- const configPath = orchestratorTenantConfigPath(runtimeRoot, tenantConfig.tenantId);
22
+ const configPath = orchestratorProjectConfigPath(runtimeRoot, projectConfig.projectId);
23
23
  await mkdir(dirname(configPath), { recursive: true });
24
- await writeFile(configPath, JSON.stringify(tenantConfig, null, 2) + "\n");
24
+ await writeFile(configPath, JSON.stringify(projectConfig, null, 2) + "\n");
25
25
  return runtimeRoot;
26
26
  }
@@ -0,0 +1,5 @@
1
+ export declare function resolveProjectOrchestratorStatusBaseUrl(input: {
2
+ configDir: string;
3
+ projectId: string;
4
+ env?: NodeJS.ProcessEnv;
5
+ }): Promise<string | null>;
@@ -0,0 +1,27 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { orchestratorPortPath } from "./config.js";
3
+ export async function resolveProjectOrchestratorStatusBaseUrl(input) {
4
+ const env = input.env ?? process.env;
5
+ const explicitBaseUrl = env.ORCHESTRATOR_STATUS_BASE_URL;
6
+ if (explicitBaseUrl) {
7
+ return explicitBaseUrl;
8
+ }
9
+ const host = env.ORCHESTRATOR_STATUS_HOST ?? "127.0.0.1";
10
+ const port = env.ORCHESTRATOR_STATUS_PORT ??
11
+ (await readProjectStatusPort(input.configDir, input.projectId));
12
+ if (!port) {
13
+ return null;
14
+ }
15
+ const urlHost = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
16
+ return `http://${urlHost}:${port}`;
17
+ }
18
+ async function readProjectStatusPort(configDir, projectId) {
19
+ try {
20
+ const raw = await readFile(orchestratorPortPath(configDir, projectId), "utf8");
21
+ const port = raw.trim();
22
+ return port.length > 0 ? port : null;
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
@@ -0,0 +1,8 @@
1
+ import { type CliProjectConfig } from "./config.js";
2
+ type ResolveProjectSelectionInput = {
3
+ configDir: string;
4
+ requestedProjectId?: string;
5
+ };
6
+ export declare function resolveManagedProjectConfig(input: ResolveProjectSelectionInput): Promise<CliProjectConfig | null>;
7
+ export declare function handleMissingManagedProjectConfig(): void;
8
+ export {};
@@ -0,0 +1,56 @@
1
+ import * as p from "@clack/prompts";
2
+ import { loadGlobalConfig, loadProjectConfig, } from "./config.js";
3
+ function isInteractiveTerminal() {
4
+ return process.stdin.isTTY === true && process.stdout.isTTY === true;
5
+ }
6
+ function explicitProjectRequiredMessage() {
7
+ return "Multiple projects are configured. Re-run with --project-id in non-interactive environments.\n";
8
+ }
9
+ export async function resolveManagedProjectConfig(input) {
10
+ if (input.requestedProjectId) {
11
+ return loadProjectConfig(input.configDir, input.requestedProjectId);
12
+ }
13
+ const global = await loadGlobalConfig(input.configDir);
14
+ const projectIds = global?.projects ?? [];
15
+ if (projectIds.length === 0) {
16
+ return null;
17
+ }
18
+ if (projectIds.length === 1) {
19
+ return loadProjectConfig(input.configDir, projectIds[0]);
20
+ }
21
+ if (!isInteractiveTerminal()) {
22
+ process.stderr.write(explicitProjectRequiredMessage());
23
+ process.exitCode = 1;
24
+ return null;
25
+ }
26
+ const projects = await Promise.all(projectIds.map(async (projectId) => ({
27
+ projectId,
28
+ config: await loadProjectConfig(input.configDir, projectId),
29
+ })));
30
+ const selected = await p.select({
31
+ message: "Select a project:",
32
+ options: projects.map(({ projectId, config }) => ({
33
+ value: projectId,
34
+ label: config?.displayName ?? config?.slug ?? projectId,
35
+ hint: projectId === global?.activeProject
36
+ ? "current"
37
+ : config && config.displayName && config.displayName !== projectId
38
+ ? projectId
39
+ : undefined,
40
+ })),
41
+ maxItems: 10,
42
+ });
43
+ if (p.isCancel(selected)) {
44
+ p.cancel("Cancelled.");
45
+ process.exitCode = 130;
46
+ return null;
47
+ }
48
+ return loadProjectConfig(input.configDir, selected);
49
+ }
50
+ export function handleMissingManagedProjectConfig() {
51
+ if (process.exitCode) {
52
+ return;
53
+ }
54
+ process.stderr.write("No project configured. Run 'gh-symphony project add' first.\n");
55
+ process.exitCode = 1;
56
+ }
@@ -2,7 +2,7 @@ export type SkillRuntime = "claude-code" | "codex";
2
2
  export type SkillTemplateContext = {
3
3
  runtime: SkillRuntime | string;
4
4
  projectId: string;
5
- projectTitle: string;
5
+ githubProjectTitle: string;
6
6
  repositories: Array<{
7
7
  owner: string;
8
8
  name: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gh-symphony/cli",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "license": "MIT",
5
5
  "author": "hojinzs",
6
6
  "description": "Interactive CLI for GitHub Symphony orchestration",
@@ -36,10 +36,11 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@clack/prompts": "^0.9.1",
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"
39
+ "commander": "^14.0.1",
40
+ "@gh-symphony/core": "0.0.8",
41
+ "@gh-symphony/orchestrator": "0.0.8",
42
+ "@gh-symphony/tracker-github": "0.0.8",
43
+ "@gh-symphony/worker": "0.0.8"
43
44
  },
44
45
  "scripts": {
45
46
  "build": "tsc -p tsconfig.json",