Skip to content

Commit bddd24e

Browse files
ralyodioclaude
andcommitted
feat(moshscript): re-architect /run as JS + CLI-scripting, add human-in-the-loop
Retire the custom-DSL interpreter; a .mosh file now executes as real JavaScript (AsyncFunction + with(Proxy scope)) with the command vocabulary injected as globals. `alive` is a getter that bounds while(alive) loops to --max (shared default 3); stop()/alive=false end the loop. - src/registry.mjs: single source of truth for the verb vocabulary - src/runtime.mjs: JS execution, shebang strip, alive/max, async drain - src/cli.mjs: CLI verbs shell out to `moshcode <cmd> ...args` (spawnSync, blocking/sequential/interactive) — moshscript IS the CLI, scripted - src/notify.mjs: notify() ping + approval link; ask() blocks on an app.moshcode.sh/approve/:id reply so a loop can pause for a human - run() includes other .mosh files; mosh() opens the moshcoding playlist - rewire bin + TUI /run to runScript; argv passthrough; #! shebang support - prd/0004: OpenPRD for the above (moshscript-only; web app is out of scope) 84/84 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent de8665f commit bddd24e

17 files changed

Lines changed: 1108 additions & 265 deletions

bin/moshcode.mjs

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import fs from "node:fs";
33
import { spawn } from "node:child_process";
44
import { fileURLToPath } from "node:url";
55
import path from "node:path";
6-
import { compile, run } from "../src/interpreter.mjs";
7-
import { defaultCommands } from "../src/commands.mjs";
6+
import { runScript } from "../src/runtime.mjs";
7+
import { moshVocabulary } from "../src/commands.mjs";
88
import {
99
ENGINES,
1010
agentLaunchArgs,
@@ -226,11 +226,15 @@ async function main() {
226226
return;
227227
}
228228
if (cmd === "commands") {
229-
console.log("built-in moshscript commands:\n " + Object.keys(defaultCommands()).map((c) => `${c}()`).join(" "));
229+
console.log("built-in moshscript commands:");
230+
for (const c of moshVocabulary().all()) {
231+
console.log(` ${(`${c.name}()`).padEnd(12)} ${c.summary}`);
232+
}
230233
return;
231234
}
232235
if (cmd === "run") {
233-
let max = 3, dryRun = false, file = null;
236+
let max = 3, dryRun = false;
237+
const positional = []; // first is the file; the rest reach the script as argv
234238
for (let k = 0; k < rest.length; k++) {
235239
const a = rest[k];
236240
if (a === "--max" || a === "-n") {
@@ -242,39 +246,37 @@ async function main() {
242246
catch (e) { console.error(String(e.message || e)); process.exit(1); }
243247
}
244248
else if (a === "--dry-run") dryRun = true;
245-
else if (a.startsWith("-")) {
249+
else if (a.startsWith("-") && positional.length === 0) {
246250
console.error(`moshcode run: unknown option ${a}`);
247251
process.exit(1);
248252
}
249-
else if (file) {
250-
console.error(`moshcode run: expected one script file, got ${JSON.stringify(file)} and ${JSON.stringify(a)}`);
251-
process.exit(1);
252-
}
253-
else file = a;
253+
else positional.push(a);
254254
}
255+
const file = positional[0] || null;
256+
const argv = positional.slice(1);
255257
let src;
256258
try {
257259
src = file ? readScript(file) : (fs.existsSync(EXAMPLE) ? fs.readFileSync(EXAMPLE, "utf8") : DEFAULT_SCRIPT);
258260
} catch (e) {
259261
console.error(String(e.message || e));
260262
process.exit(1);
261263
}
262-
let ast;
263-
try { ast = compile(src); }
264-
catch (e) { console.error(String(e.message || e)); process.exit(1); }
265264

266-
const ctx = {
267-
vars: { alive: true },
268-
iter: 0,
269-
maxIterations: max,
270-
dryRun,
271-
out: (s) => console.log(s),
272-
commands: defaultCommands(),
273-
};
274-
console.log("🎸 moshcode — running moshscript\n");
275-
try { await run(ast, ctx); }
276-
catch (e) { console.error("\n" + String(e.message || e)); process.exit(1); }
277-
console.log(`\n✓ ${ctx.iter} loop(s) — no bugs, only features. 🤘`);
265+
console.log(`🎸 moshcode — running moshscript${dryRun ? " (dry run)" : ""}\n`);
266+
let result;
267+
try {
268+
result = await runScript(src, {
269+
commands: moshVocabulary(),
270+
max,
271+
dryRun,
272+
argv,
273+
out: (s) => console.log(s),
274+
});
275+
} catch (e) {
276+
console.error("\n" + String(e.message || e));
277+
process.exit(1);
278+
}
279+
console.log(`\n✓ ${result.iterations} loop(s) — no bugs, only features. 🤘`);
278280
return backToPit("moshscript", 0);
279281
}
280282

examples/scripting-the-cli.mosh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env moshscript
2+
// scripting-the-cli.mosh — secretly all JS is legal, and every CLI verb is just
3+
// `moshcode <cmd> ...args`. Try it safely first:
4+
//
5+
// moshcode run examples/scripting-the-cli.mosh --dry-run
6+
// chmod +x examples/scripting-the-cli.mosh && ./examples/scripting-the-cli.mosh claude
7+
//
8+
// argv[0] is the engine to drop into at the end (default: claude).
9+
10+
const engine = argv[0] || "claude";
11+
const engines = ["claude", "codex"];
12+
13+
say(`🎸 setting up ${engines.length} engines`);
14+
for (const e of engines) {
15+
install(e); // → moshcode install <e>
16+
}
17+
18+
mcp("install", "https://mcp.sentry.dev/mcp"); // → moshcode mcp install ...
19+
notify(`moshcode ready — dropping into ${engine}`);
20+
21+
agents(engine); // → moshcode agents <engine>

0 commit comments

Comments
 (0)