moshcode 0.24.0
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/LICENSE +21 -0
- package/README.md +580 -0
- package/bin/moshcode.mjs +674 -0
- package/bin/moshscript.mjs +29 -0
- package/examples/alive.mosh +6 -0
- package/examples/scripting-the-cli.mosh +21 -0
- package/examples/team-secrets.mosh +20 -0
- package/examples/templates/bun-caddy-sqlite/.env.example +14 -0
- package/examples/templates/bun-caddy-sqlite/Caddyfile +18 -0
- package/examples/templates/bun-caddy-sqlite/README.md +97 -0
- package/examples/templates/bun-caddy-sqlite/deploy/moshcode-dns.service +39 -0
- package/examples/templates/bun-caddy-sqlite/deploy/moshpit-service.service +38 -0
- package/examples/templates/bun-caddy-sqlite/package.json +15 -0
- package/examples/templates/bun-caddy-sqlite/src/db.ts +47 -0
- package/examples/templates/bun-caddy-sqlite/src/server.ts +44 -0
- package/examples/templates/bun-caddy-sqlite/template.json +10 -0
- package/examples/templates/caddy-proxy/Caddyfile +36 -0
- package/examples/templates/caddy-proxy/README.md +104 -0
- package/examples/templates/caddy-proxy/deploy/moshcode-dns.service +39 -0
- package/examples/templates/caddy-proxy/template.json +8 -0
- package/examples/templates/caddy-static/Caddyfile +16 -0
- package/examples/templates/caddy-static/README.md +90 -0
- package/examples/templates/caddy-static/deploy/moshcode-dns.service +39 -0
- package/examples/templates/caddy-static/site/index.html +11 -0
- package/examples/templates/caddy-static/template.json +8 -0
- package/install.sh +194 -0
- package/package.json +28 -0
- package/prd/0000-template.md +49 -0
- package/prd/0001-wrap-ugig-and-coinpay-clis.md +121 -0
- package/prd/0002-separate-agent-and-raw-engine-launches.md +113 -0
- package/prd/0003-cross-engine-mcp-and-skill-installation.md +165 -0
- package/prd/0004-moshscript-run-programmable-moshcode.md +344 -0
- package/prd/0005-hosted-moshpit-resolver.md +192 -0
- package/prd/0006-help.md +359 -0
- package/prd/0007-profullstack-site-init.md +1183 -0
- package/prd/README.md +26 -0
- package/src/ads.mjs +58 -0
- package/src/auth.mjs +193 -0
- package/src/cli-schema.mjs +533 -0
- package/src/cli.mjs +118 -0
- package/src/commands.mjs +259 -0
- package/src/completion.mjs +594 -0
- package/src/console.mjs +244 -0
- package/src/dns-system.mjs +404 -0
- package/src/dns.mjs +2872 -0
- package/src/doh-server.mjs +256 -0
- package/src/doh.mjs +218 -0
- package/src/engines.mjs +385 -0
- package/src/escalate.mjs +85 -0
- package/src/help.mjs +443 -0
- package/src/integrations.mjs +265 -0
- package/src/mcp-catalog.mjs +50 -0
- package/src/mcp.mjs +155 -0
- package/src/mirror.mjs +187 -0
- package/src/notify.mjs +86 -0
- package/src/open-url.mjs +34 -0
- package/src/parking-http.mjs +65 -0
- package/src/pins.mjs +190 -0
- package/src/pit-url.mjs +13 -0
- package/src/prd.mjs +341 -0
- package/src/pty.mjs +176 -0
- package/src/pwd.mjs +103 -0
- package/src/registry.mjs +37 -0
- package/src/release-install.mjs +191 -0
- package/src/runtime.mjs +161 -0
- package/src/selfupdate.mjs +215 -0
- package/src/serve.mjs +502 -0
- package/src/skills.mjs +93 -0
- package/src/tabs.mjs +144 -0
- package/src/templates.mjs +456 -0
- package/src/tools.mjs +231 -0
- package/src/trade.mjs +137 -0
- package/src/trust.mjs +712 -0
- package/src/tui.mjs +736 -0
- package/src/ui.mjs +49 -0
- package/src/uninstall.mjs +113 -0
- package/src/upgrade.mjs +217 -0
package/src/commands.mjs
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// The moshscript command vocabulary β the verbs a .mosh script can call.
|
|
2
|
+
//
|
|
3
|
+
// moshscript is, more or less, the moshcode CLI scripted. Each command is
|
|
4
|
+
// { name, summary, run(ctx, ...args) }
|
|
5
|
+
// and gets injected as a global of the same name by the runtime (src/runtime.mjs),
|
|
6
|
+
// so scripts call them bare: `mosh()`, `notify("shipping")`, `agents("claude")`.
|
|
7
|
+
//
|
|
8
|
+
// Two kinds of verb:
|
|
9
|
+
// 1. CLI verbs β the bulk. `agents("claude")` just runs `moshcode agents claude`
|
|
10
|
+
// (see cliVerb / src/cli.mjs). One implementation of every capability (the
|
|
11
|
+
// CLI); moshscript is a second caller. To expose a new CLI capability to
|
|
12
|
+
// scripts, add one cliVerb line below.
|
|
13
|
+
// 2. Local verbs β moshscript-only flavor/helpers with no CLI equivalent
|
|
14
|
+
// (mosh, code, notify, say, sleep, stop, repeat). `mosh()` is the worked
|
|
15
|
+
// example of the local command shape.
|
|
16
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
17
|
+
|
|
18
|
+
import { createRegistry } from "./registry.mjs";
|
|
19
|
+
import { cliVerb, aiVerb } from "./cli.mjs";
|
|
20
|
+
import { ingestApproval, pollApproval } from "./notify.mjs";
|
|
21
|
+
|
|
22
|
+
// The moshcoding pit-anthem playlist. mosh() blasts this URL and, on a desktop
|
|
23
|
+
// with a GUI, tries to open it in the default browser.
|
|
24
|
+
const MOSH_PLAYLIST =
|
|
25
|
+
process.env.MOSHCODE_PLAYLIST ||
|
|
26
|
+
"https://open.spotify.com/playlist/2FrXlq6ChSIFJ6CyGS0PGI";
|
|
27
|
+
|
|
28
|
+
/** True when we look like a desktop with a GUI the OS can open a browser on. */
|
|
29
|
+
function hasDesktop() {
|
|
30
|
+
if (process.platform === "darwin" || process.platform === "win32") return true;
|
|
31
|
+
// Linux/BSD: only if a display server is present (skip headless/CI/servers).
|
|
32
|
+
return Boolean(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Fire-and-forget open of a URL in the OS default browser. Never throws.
|
|
37
|
+
*
|
|
38
|
+
* Returns whether the open was *attempted*, not whether it worked: a missing
|
|
39
|
+
* opener surfaces as an async 'error' event on the child, long after this has
|
|
40
|
+
* returned, so the catch below only ever sees a synchronous spawn failure.
|
|
41
|
+
* Callers must not phrase the result as a browser that definitely opened.
|
|
42
|
+
*/
|
|
43
|
+
function openBrowser(url) {
|
|
44
|
+
const [cmd, args] =
|
|
45
|
+
process.platform === "darwin" ? ["open", [url]]
|
|
46
|
+
: process.platform === "win32" ? ["cmd", ["/c", "start", "", url]]
|
|
47
|
+
: ["xdg-open", [url]];
|
|
48
|
+
try {
|
|
49
|
+
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
50
|
+
child.on("error", () => {}); // no opener installed β stay quiet
|
|
51
|
+
child.unref();
|
|
52
|
+
return true;
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function expectNoArgs(name, args) {
|
|
59
|
+
if (args.length > 0) {
|
|
60
|
+
throw new Error(`moshscript: ${name}() does not take arguments`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The vocabulary, in registration order. mosh() is the worked example of the
|
|
65
|
+
// command shape; the rest follow the same pattern.
|
|
66
|
+
const COMMANDS = [
|
|
67
|
+
{
|
|
68
|
+
name: "code",
|
|
69
|
+
summary: "compile features (no bugs)",
|
|
70
|
+
usage: "code()",
|
|
71
|
+
detail: "narrates a build step; takes no arguments",
|
|
72
|
+
run(ctx, ...args) {
|
|
73
|
+
expectNoArgs("code", args);
|
|
74
|
+
ctx.out(" β¨ code() β compiling features (no bugs)β¦");
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "mosh",
|
|
79
|
+
summary: "open the pit + blast the moshcoding playlist",
|
|
80
|
+
usage: "mosh()",
|
|
81
|
+
detail: "opens the pit and starts the playlist",
|
|
82
|
+
run(ctx, ...args) {
|
|
83
|
+
expectNoArgs("mosh", args);
|
|
84
|
+
ctx.out(" π€ mosh() β opening the pit");
|
|
85
|
+
ctx.out(` π§ ${MOSH_PLAYLIST}`);
|
|
86
|
+
if (ctx.dryRun) return;
|
|
87
|
+
// Only ever an attempt β see openBrowser. dns.mjs ("opening <url>") and
|
|
88
|
+
// auth.mjs ("opening your browserβ¦") word their own opens the same way,
|
|
89
|
+
// and the playlist URL is already on the line above to fall back to.
|
|
90
|
+
if (hasDesktop() && openBrowser(MOSH_PLAYLIST)) {
|
|
91
|
+
ctx.out(" β opening it in your browser β crank it π");
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "notify",
|
|
97
|
+
summary: "ping the operator via app.moshcode.sh (email/SMS/Slack/Telegram/push)",
|
|
98
|
+
usage: "notify(...message)",
|
|
99
|
+
detail: "returns { id, url } β fire and forget, no reply awaited",
|
|
100
|
+
// Fire-and-forget. Posts the approval to the app, which fans it out to the
|
|
101
|
+
// operator's channels. Returns { id, url } so a script can hand the link off.
|
|
102
|
+
async run(ctx, ...args) {
|
|
103
|
+
const msg = args.length ? args.join(" ") : "moshcode ping π€";
|
|
104
|
+
ctx.out(` π notify() β ${msg}`);
|
|
105
|
+
if (ctx.dryRun) return { dryRun: true };
|
|
106
|
+
const r = await ingestApproval({ message: msg, kind: "notify", script: "moshscript", iter: ctx.iter });
|
|
107
|
+
if (!r.ok) { ctx.out(` ! notify failed (${r.error || r.status}) β run \`moshcode login\``); return null; }
|
|
108
|
+
ctx.out(` π ${r.url}`);
|
|
109
|
+
if (r.warning) ctx.out(` β ${r.warning}`);
|
|
110
|
+
return { id: r.id, url: r.url };
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "ask",
|
|
115
|
+
summary: "notify + BLOCK until the human approves/instructs at app.moshcode.sh",
|
|
116
|
+
usage: "ask(...prompt)",
|
|
117
|
+
detail: "BLOCKS until a human answers at app.moshcode.sh; returns their reply or null. needs await",
|
|
118
|
+
// The human-in-the-loop gate. Posts the approval to the app, then waits for
|
|
119
|
+
// the operator to open app.moshcode.sh/approve/:id, read the context, and
|
|
120
|
+
// submit. Resolves with their instructions (or null). Requires `await`.
|
|
121
|
+
// const task = await ask("what next?");
|
|
122
|
+
async run(ctx, ...args) {
|
|
123
|
+
const prompt = args.length ? args.join(" ") : "moshcode needs a human π€";
|
|
124
|
+
ctx.out(` π ask() β ${prompt}`);
|
|
125
|
+
if (ctx.dryRun) {
|
|
126
|
+
ctx.out(" (dry run β would block here for a human reply)");
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const r = await ingestApproval({ message: prompt, kind: "ask", script: "moshscript", iter: ctx.iter });
|
|
130
|
+
if (!r.ok) { ctx.out(` ! ask failed (${r.error || r.status}) β run \`moshcode login\``); return null; }
|
|
131
|
+
ctx.out(` π approve/instruct: ${r.url}`);
|
|
132
|
+
ctx.out(" β³ waiting for a humanβ¦");
|
|
133
|
+
const reply = await pollApproval(r.id);
|
|
134
|
+
ctx.out(reply == null ? " β no reply β moving on" : ` β
got it: ${reply}`);
|
|
135
|
+
return reply;
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: "repeat",
|
|
140
|
+
summary: "back to the top of the loop",
|
|
141
|
+
usage: "repeat()",
|
|
142
|
+
detail: "jumps back to the top of the loop",
|
|
143
|
+
run(ctx, ...args) {
|
|
144
|
+
expectNoArgs("repeat", args);
|
|
145
|
+
ctx.out(" β» repeat() β back to the top");
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "say",
|
|
150
|
+
summary: "print a line",
|
|
151
|
+
usage: "say(...parts)",
|
|
152
|
+
detail: "prints one line",
|
|
153
|
+
run(ctx, ...args) {
|
|
154
|
+
ctx.out(` π¬ ${args.join(" ")}`);
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: "sleep",
|
|
159
|
+
summary: "pause for N milliseconds (blocking)",
|
|
160
|
+
usage: "sleep(ms)",
|
|
161
|
+
detail: "blocks for ms milliseconds",
|
|
162
|
+
// Synchronous/blocking so it pauses inline in the simple no-`await` style:
|
|
163
|
+
// `while (alive) { work(); sleep(1000); }` actually waits each iteration.
|
|
164
|
+
run(ctx, ...args) {
|
|
165
|
+
const raw = args[0] ?? 0;
|
|
166
|
+
const ms = Number(raw);
|
|
167
|
+
if (!Number.isFinite(ms) || ms < 0) {
|
|
168
|
+
throw new Error(`moshscript: sleep(ms) requires a finite non-negative number, got ${JSON.stringify(raw)}`);
|
|
169
|
+
}
|
|
170
|
+
if (ctx.dryRun) {
|
|
171
|
+
ctx.out(` β± sleep(${ms}) β would pause for ${ms}ms`);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (ms > 0) Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: "stop",
|
|
179
|
+
summary: "end the loop (alive = false)",
|
|
180
|
+
usage: "stop()",
|
|
181
|
+
detail: "ends the loop (alive = false)",
|
|
182
|
+
run(ctx, ...args) {
|
|
183
|
+
expectNoArgs("stop", args);
|
|
184
|
+
ctx.stop();
|
|
185
|
+
ctx.out(" βΉ stop() β alive = false");
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
{
|
|
190
|
+
name: "shell",
|
|
191
|
+
summary: "run a shell command (blocking, cmd.exe on Windows or $SHELL -c elsewhere)",
|
|
192
|
+
usage: "shell(cmd)",
|
|
193
|
+
detail: "runs cmd in $SHELL; returns { ok, code, signal }",
|
|
194
|
+
// The moshscript system verb for arbitrary shell commands. Blocking
|
|
195
|
+
// (spawnSync + inherited stdio) so it runs inline in the no-`await` style,
|
|
196
|
+
// and the child owns the terminal for interactive commands. Returns
|
|
197
|
+
// { ok, code } so scripts can branch on the exit status:
|
|
198
|
+
// const r = shell("npm test"); if (!r.ok) say("tests failed");
|
|
199
|
+
run(ctx, ...args) {
|
|
200
|
+
const cmd = args.join(" ");
|
|
201
|
+
if (!cmd) throw new Error("moshscript: shell() requires a command string");
|
|
202
|
+
if (ctx.dryRun) {
|
|
203
|
+
ctx.out(` βΆ shell(${JSON.stringify(cmd)}) β would run: $SHELL -c ${JSON.stringify(cmd)}`);
|
|
204
|
+
// Same R8 contract as the comment above: `code` is always present, so a
|
|
205
|
+
// script branching on the exit status behaves the same under --dry-run.
|
|
206
|
+
return { ok: true, code: 0, dryRun: true };
|
|
207
|
+
}
|
|
208
|
+
const sh = process.platform === "win32"
|
|
209
|
+
? (process.env.COMSPEC || "cmd.exe")
|
|
210
|
+
: (process.env.SHELL || "/bin/sh");
|
|
211
|
+
const shArgs = process.platform === "win32" ? ["/d", "/s", "/c", cmd] : ["-c", cmd];
|
|
212
|
+
ctx.out(` βΆ shell: ${cmd}`);
|
|
213
|
+
const res = spawnSync(sh, shArgs, { stdio: "inherit" });
|
|
214
|
+
if (res.error) throw res.error;
|
|
215
|
+
const code = res.status ?? 1;
|
|
216
|
+
if (code !== 0) {
|
|
217
|
+
ctx.out(` β shell() exited ${res.signal || code}`);
|
|
218
|
+
return { ok: false, code, signal: res.signal || null };
|
|
219
|
+
}
|
|
220
|
+
return { ok: true, code: 0 };
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
// CLI verbs β each is `moshcode <name> ...args`. This is the whole point:
|
|
225
|
+
// scripting the CLI. Add a capability by adding a line here.
|
|
226
|
+
//
|
|
227
|
+
// `run` composes scripts: run("setup.mosh") is `moshcode run setup.mosh`, so a
|
|
228
|
+
// .mosh file can pull in other .mosh files. It blocks until the included script
|
|
229
|
+
// finishes (spawnSync), so they run in order.
|
|
230
|
+
cliVerb("run", "run another .mosh file (include)"),
|
|
231
|
+
// shortcut: ai() runs an engine headlessly and RETURNS its output (see PRD R17)
|
|
232
|
+
aiVerb,
|
|
233
|
+
cliVerb("agents", "launch an autonomous agent session (moshcode agents <engine>)"),
|
|
234
|
+
cliVerb("start", "raw-launch an engine (moshcode start <engine>)"),
|
|
235
|
+
cliVerb("install", "install an engine or workflow tool"),
|
|
236
|
+
cliVerb("upgrade", "upgrade moshcode, engines, and tools"),
|
|
237
|
+
cliVerb("mcp", "register/fan out an MCP server across engines"),
|
|
238
|
+
cliVerb("skill", "install a skill across engines"),
|
|
239
|
+
cliVerb("prd", "publish/author an OpenPRD doc"),
|
|
240
|
+
cliVerb("ugig", "drive the ugig workflow CLI"),
|
|
241
|
+
cliVerb("coinpay", "drive the coinpay workflow CLI"),
|
|
242
|
+
cliVerb("c0mpute", "drive the c0mpute workflow CLI"),
|
|
243
|
+
cliVerb("secrets", "manage/view team secrets via logicsrc (login, teams, credentials)"),
|
|
244
|
+
cliVerb("railway", "drive the Railway CLI (deploys, services, env vars)"),
|
|
245
|
+
cliVerb("gh", "drive the GitHub CLI (repos, PRs, issues, releases)"),
|
|
246
|
+
cliVerb("supabase", "drive the Supabase CLI (local stack, migrations, functions)"),
|
|
247
|
+
cliVerb("doppler", "drive the Doppler CLI (secrets, env injection)"),
|
|
248
|
+
cliVerb("doctl", "drive the DigitalOcean CLI (droplets, apps, databases)"),
|
|
249
|
+
cliVerb("turso", "drive the Turso CLI (auth, databases, replicas)"),
|
|
250
|
+
cliVerb("tailscale", "drive the Tailscale CLI (mesh VPN: up, status, ssh, serve)"),
|
|
251
|
+
cliVerb("alpaca", "drive the native Alpaca trading CLI"),
|
|
252
|
+
cliVerb("trade", "look up tickers, inspect markets, and preview/place Alpaca orders"),
|
|
253
|
+
cliVerb("pwd", "print the current repo/location"),
|
|
254
|
+
];
|
|
255
|
+
|
|
256
|
+
/** A fresh registry preloaded with the built-in vocabulary. */
|
|
257
|
+
export function moshVocabulary() {
|
|
258
|
+
return createRegistry(COMMANDS);
|
|
259
|
+
}
|