Skip to content

Commit 89b0a01

Browse files
author
Sorra
authored
Merge pull request #240 from TheWizardsCode/copilot/legacy-cli-cutover-and-removal
Remove legacy CLI dual-dispatch and wire per-command yargs handlers
2 parents 6b15bb1 + c812661 commit 89b0a01

14 files changed

Lines changed: 569 additions & 115 deletions

File tree

src/cli.core.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ import { renderSequence } from "./sequence/renderer.js";
8181
import type { SequenceDefinition } from "./sequence/schema.js";
8282

8383
/** Parse command-line arguments into a structured map. */
84-
function parseArgs(argv: string[]): {
84+
export function parseArgs(argv: string[]): {
8585
command: string | undefined;
8686
subcommand: string | undefined;
8787
flags: Record<string, string | boolean>;
@@ -1284,8 +1284,30 @@ function formatClassificationBatchTable(
12841284
}
12851285

12861286
/** Main CLI entry point. Exported for testability. */
1287-
export async function main(argv: string[] = process.argv): Promise<number> {
1288-
const { command, subcommand, flags, layers } = parseArgs(argv);
1287+
/**
1288+
* Dispatch a command using pre-parsed arguments.
1289+
*
1290+
* yargs (or any other caller) should perform argument parsing and pass the
1291+
* structured result here. `coreMain.main()` is now a thin wrapper around this
1292+
* function so that the legacy entry-point remains available.
1293+
*
1294+
* @param command - Top-level command name (e.g. "generate", "stack"), or
1295+
* `undefined` for global flags only (--help, --version).
1296+
* @param subcommand - Subcommand name (e.g. "render" for `stack render`), or
1297+
* `undefined` when no subcommand is present.
1298+
* @param flags - Parsed flag values keyed by their hyphenated CLI name
1299+
* (e.g. `"seed-range"`, `"keep-top"`). Boolean flags have
1300+
* value `true`; option values are stored as strings so that
1301+
* the command handlers can perform their own validation.
1302+
* @param layers - Ordered list of `--layer` inline spec strings (stack cmd).
1303+
* @returns Promise resolving to a numeric exit code (0 = success).
1304+
*/
1305+
export async function dispatchCommand(
1306+
command: string | undefined,
1307+
subcommand: string | undefined,
1308+
flags: Record<string, string | boolean>,
1309+
layers: string[],
1310+
): Promise<number> {
12891311
const jsonMode = flags["json"] === true;
12901312

12911313
// Enable profiling when --profile flag is set
@@ -4217,6 +4239,15 @@ export async function main(argv: string[] = process.argv): Promise<number> {
42174239
}
42184240
}
42194241

4242+
/**
4243+
* Legacy entry point — parses raw argv then delegates to dispatchCommand.
4244+
* Retained for backwards compatibility (bin scripts, tests that import main).
4245+
*/
4246+
export async function main(argv: string[] = process.argv): Promise<number> {
4247+
const { command, subcommand, flags, layers } = parseArgs(argv);
4248+
return dispatchCommand(command, subcommand, flags, layers);
4249+
}
4250+
42204251
// Run when executed directly (via ./bin/dev-cli.js or tf/toneforge commands).
42214252
// The path check uses /cli.ts and /cli.js (with separator) to avoid
42224253
// matching bin/dev-cli.js which invokes main() itself.

src/cli.yargs.ts

Lines changed: 357 additions & 100 deletions
Large diffs are not rendered by default.

src/cli/commands/analyze.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export const command = "analyze";
2+
export const desc = "Analyze a sound's acoustic properties";
3+
4+
export function builder(yargs: any) {
5+
return yargs
6+
.option("recipe", { type: "string", describe: "Recipe name" })
7+
.option("seed", { type: "number", describe: "Seed for rendering" })
8+
.option("input", { type: "string", describe: "Path to input WAV file" })
9+
.option("output", { type: "string", describe: "Output directory for analysis files" })
10+
.option("format", { type: "string", describe: "Output format (json or table)" })
11+
.option("json", { type: "boolean", describe: "Output JSON" });
12+
}

src/cli/commands/classify.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export const command = "classify";
2+
export const desc = "Classify a sound by category, intensity, and texture";
3+
4+
export function builder(yargs: any) {
5+
return yargs
6+
.option("recipe", { type: "string", describe: "Recipe name" })
7+
.option("seed", { type: "number", describe: "Seed for rendering" })
8+
.option("input", { type: "string", describe: "Path to input WAV file" })
9+
.option("analysis", { type: "string", describe: "Path to analysis directory" })
10+
.option("output", { type: "string", describe: "Output directory" })
11+
.option("format", { type: "string", describe: "Output format (json or table)" })
12+
.option("json", { type: "boolean", describe: "Output JSON" });
13+
}

src/cli/commands/explore.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
export const command = "explore";
2+
export const desc = "Explore and discover sounds through sweep, mutate, and promote workflows";
3+
4+
export function builder(yargs: any) {
5+
return yargs
6+
.command("sweep", "Sweep a seed range and rank candidates", (y: any) => {
7+
y.option("recipe", { type: "string", describe: "Recipe name" })
8+
.option("seed-range", { type: "string", describe: "Seed range (start:end)" })
9+
.option("keep-top", { type: "number", default: 5, describe: "Number of top candidates to keep" })
10+
.option("rank-by", { type: "string", describe: "Metric to rank by" })
11+
.option("clusters", { type: "number", default: 3, describe: "Number of clusters" })
12+
.option("concurrency", { type: "number", default: 4, describe: "Concurrency level" })
13+
.option("output", { type: "string", describe: "Output directory" })
14+
.option("json", { type: "boolean", describe: "Output JSON" });
15+
})
16+
.command("mutate", "Mutate a seed to explore nearby sounds", (y: any) => {
17+
y.option("recipe", { type: "string", describe: "Recipe name" })
18+
.option("seed", { type: "number", describe: "Seed to mutate" })
19+
.option("jitter", { type: "number", default: 0.1, describe: "Jitter amount (0-1)" })
20+
.option("count", { type: "number", default: 20, describe: "Number of mutations" })
21+
.option("rank-by", { type: "string", describe: "Metric to rank by" })
22+
.option("output", { type: "string", describe: "Output directory" })
23+
.option("json", { type: "boolean", describe: "Output JSON" });
24+
})
25+
.command("promote", "Promote a candidate to the library", (y: any) => {
26+
y.option("run", { type: "string", describe: "Run ID" })
27+
.option("latest", { type: "boolean", describe: "Use the latest run" })
28+
.option("id", { type: "string", describe: "Candidate ID to promote" })
29+
.option("category", { type: "string", describe: "Override category" })
30+
.option("json", { type: "boolean", describe: "Output JSON" });
31+
})
32+
.command("show", "Show details of an exploration run", (y: any) => {
33+
y.option("run", { type: "string", describe: "Run ID" })
34+
.option("latest", { type: "boolean", describe: "Use the latest run" })
35+
.option("json", { type: "boolean", describe: "Output JSON" });
36+
})
37+
.command("runs", "List all exploration runs", (y: any) => {
38+
y.option("json", { type: "boolean", describe: "Output JSON" });
39+
});
40+
}

src/cli/commands/generate.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,25 @@ export const desc = "Render and export procedural sounds";
1010

1111
export function builder(yargs: any) {
1212
return yargs
13-
.option("recipe", { type: "string", demandOption: true })
14-
.option("seed", { type: "number" })
15-
.option("output", { type: "string" })
16-
.option("json", { type: "boolean" });
13+
.option("recipe", {
14+
type: "string",
15+
describe: "Recipe name (e.g. ui-scifi-confirm)",
16+
})
17+
.option("seed", {
18+
type: "string",
19+
describe: "Integer seed for deterministic rendering",
20+
})
21+
.option("seed-range", {
22+
type: "string",
23+
describe: "Seed range for batch generation (e.g. 1:10)",
24+
})
25+
.option("output", {
26+
type: "string",
27+
describe: "Output WAV path or directory (for --seed-range)",
28+
})
29+
.option("json", { type: "boolean", describe: "Output JSON" })
30+
.example("generate --recipe ui-scifi-confirm --seed 42", "Render a specific seed")
31+
.example("generate --recipe ui-scifi-confirm", "Render with a random seed");
1732
}
1833

1934
export async function handler(argv: Arguments) {

src/cli/commands/library.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
export const command = "library";
2+
export const desc = "Manage the ToneForge sound library";
3+
4+
export function builder(yargs: any) {
5+
return yargs
6+
.command("list", "List library entries", (y: any) => {
7+
y.option("category", { type: "string", describe: "Filter by category" })
8+
.option("json", { type: "boolean", describe: "Output JSON" });
9+
})
10+
.command("search", "Search library entries", (y: any) => {
11+
y.option("category", { type: "string", describe: "Filter by category" })
12+
.option("intensity", { type: "string", describe: "Filter by intensity" })
13+
.option("texture", { type: "string", describe: "Filter by texture" })
14+
.option("tags", { type: "string", describe: "Filter by tags" })
15+
.option("json", { type: "boolean", describe: "Output JSON" });
16+
})
17+
.command("similar", "Find similar library entries", (y: any) => {
18+
y.option("id", { type: "string", describe: "Entry ID to compare" })
19+
.option("limit", { type: "number", default: 10, describe: "Maximum results" })
20+
.option("json", { type: "boolean", describe: "Output JSON" });
21+
})
22+
.command("export", "Export library entries to WAV files", (y: any) => {
23+
y.option("output", { type: "string", describe: "Output directory" })
24+
.option("category", { type: "string", describe: "Filter by category" })
25+
.option("format", { type: "string", default: "wav", describe: "Output format" })
26+
.option("json", { type: "boolean", describe: "Output JSON" });
27+
})
28+
.command("regenerate", "Regenerate a library entry", (y: any) => {
29+
y.option("id", { type: "string", describe: "Entry ID to regenerate" })
30+
.option("json", { type: "boolean", describe: "Output JSON" });
31+
});
32+
}

src/cli/commands/list.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ export const desc = "List available resources";
77

88
export function builder(yargs: any) {
99
return yargs
10-
.positional("resource", { type: "string", default: "recipes" })
11-
.option("search", { type: "string" })
12-
.option("category", { type: "string" })
13-
.option("tags", { type: "string" })
14-
.option("json", { type: "boolean" });
10+
.positional("resource", {
11+
type: "string",
12+
describe: "Resource type to list (e.g. recipes)",
13+
default: "recipes",
14+
})
15+
.option("search", { type: "string", describe: "Search filter" })
16+
.option("category", { type: "string", describe: "Filter by category" })
17+
.option("tags", { type: "string", describe: "Filter by tags" })
18+
.option("json", { type: "boolean", describe: "Output JSON" });
1519
}
1620

1721
export async function handler(argv: Arguments) {

src/cli/commands/play.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ export const command = "play <file>";
88
export const desc = "Play a WAV file";
99

1010
export function builder(yargs: any) {
11-
return yargs.positional("file", { type: "string" }).option("json", { type: "boolean" });
11+
return yargs
12+
.positional("file", { type: "string", describe: "Path to WAV file to play" })
13+
.option("json", { type: "boolean", describe: "Output JSON" });
1214
}
1315

1416
export async function handler(argv: Arguments) {

src/cli/commands/sequence.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
export const command = "sequence";
2+
export const desc = "Work with sound sequences defined by preset files";
3+
4+
export function builder(yargs: any) {
5+
return yargs
6+
.command("generate", "Render a sequence to audio", (y: any) => {
7+
y.option("preset", { type: "string", describe: "Path to sequence preset JSON" })
8+
.option("seed", { type: "number", describe: "Seed for rendering" })
9+
.option("output", { type: "string", describe: "Output WAV path" })
10+
.option("duration", { type: "number", describe: "Duration override in seconds" })
11+
.option("json", { type: "boolean", describe: "Output JSON" });
12+
})
13+
.command("simulate", "Simulate a sequence and show event schedule", (y: any) => {
14+
y.option("preset", { type: "string", describe: "Path to sequence preset JSON" })
15+
.option("seed", { type: "number", describe: "Seed for simulation" })
16+
.option("json", { type: "boolean", describe: "Output JSON" });
17+
})
18+
.command("inspect", "Inspect a sequence preset structure", (y: any) => {
19+
y.option("preset", { type: "string", describe: "Path to sequence preset JSON" })
20+
.option("json", { type: "boolean", describe: "Output JSON" });
21+
});
22+
}

0 commit comments

Comments
 (0)