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/bin/moshcode.mjs
ADDED
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { runScript } from "../src/runtime.mjs";
|
|
6
|
+
import { moshVocabulary } from "../src/commands.mjs";
|
|
7
|
+
import {
|
|
8
|
+
agentLaunchArgs,
|
|
9
|
+
engineList,
|
|
10
|
+
ENGINES,
|
|
11
|
+
engineStatus,
|
|
12
|
+
openSession,
|
|
13
|
+
resolveEngine,
|
|
14
|
+
resolveExecutable,
|
|
15
|
+
runCmd,
|
|
16
|
+
} from "../src/engines.mjs";
|
|
17
|
+
import { TOOLS, toolList, toolStatus, resolveTool, openTool } from "../src/tools.mjs";
|
|
18
|
+
import { tradeArgs, tradeUsage } from "../src/trade.mjs";
|
|
19
|
+
import { runUpgrade } from "../src/upgrade.mjs";
|
|
20
|
+
import { selfUpdateCommand } from "../src/selfupdate.mjs";
|
|
21
|
+
import { describeUninstall, uninstallPlan } from "../src/uninstall.mjs";
|
|
22
|
+
import { mcpCommand, skillCommand } from "../src/integrations.mjs";
|
|
23
|
+
import { locate, tilde } from "../src/pwd.mjs";
|
|
24
|
+
import { createPrd, listPrds, authoringPrompt } from "../src/prd.mjs";
|
|
25
|
+
import { loginAuto, whoami, logout } from "../src/auth.mjs";
|
|
26
|
+
import { tui } from "../src/tui.mjs";
|
|
27
|
+
import { consoleCommand } from "../src/console.mjs";
|
|
28
|
+
import { dnsCommand } from "../src/dns.mjs";
|
|
29
|
+
import { templateCommand } from "../src/templates.mjs";
|
|
30
|
+
import { serveCommand } from "../src/serve.mjs";
|
|
31
|
+
import { createDohServer, nginxDohSite, parseDohPort, parseGuardArgs, DEFAULT_DOH_PORT, DOH_PATH } from "../src/doh-server.mjs";
|
|
32
|
+
import { completionScript } from "../src/completion.mjs";
|
|
33
|
+
import { CORE_CLI_COMMAND_NAMES } from "../src/cli-schema.mjs";
|
|
34
|
+
import {
|
|
35
|
+
findCommand, findVerb, helpModel, renderAll, renderCommand, renderOverview,
|
|
36
|
+
renderMarkdown, renderScriptVerb, suggest, wantsHelp, withoutHelp,
|
|
37
|
+
} from "../src/help.mjs";
|
|
38
|
+
import { moshcodeVersion } from "../src/ui.mjs";
|
|
39
|
+
|
|
40
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
41
|
+
const EXAMPLE = path.join(HERE, "..", "examples", "alive.mosh");
|
|
42
|
+
|
|
43
|
+
const DEFAULT_SCRIPT = `while (alive) {
|
|
44
|
+
code();
|
|
45
|
+
mosh();
|
|
46
|
+
notify();
|
|
47
|
+
repeat();
|
|
48
|
+
} // no bugs, only features
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
function readScript(arg) {
|
|
52
|
+
if (!arg || arg === "-") return fs.readFileSync(0, "utf8"); // stdin (paste)
|
|
53
|
+
try {
|
|
54
|
+
return fs.readFileSync(arg, "utf8");
|
|
55
|
+
} catch (e) {
|
|
56
|
+
throw new Error(`moshcode run: cannot read script ${JSON.stringify(arg)} (${e.code || e.message})`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseMax(value) {
|
|
61
|
+
if (value === undefined) throw new Error("moshcode run: --max requires a positive integer");
|
|
62
|
+
if (!/^\d+$/.test(String(value))) {
|
|
63
|
+
throw new Error(`moshcode run: --max must be a positive integer, got ${JSON.stringify(value)}`);
|
|
64
|
+
}
|
|
65
|
+
const max = Number(value);
|
|
66
|
+
if (!Number.isSafeInteger(max) || max < 1) {
|
|
67
|
+
throw new Error(`moshcode run: --max must be a positive integer, got ${JSON.stringify(value)}`);
|
|
68
|
+
}
|
|
69
|
+
return max;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// After a hand-off subcommand/engine session ends, capture its exit and drop
|
|
73
|
+
// back into the mosh shell instead of quitting to the OS shell β but only when
|
|
74
|
+
// interactive. Piped / non-TTY invocations (scripts, CI, `β¦ | moshcode run -`)
|
|
75
|
+
// keep the old behaviour: exit with the child's code.
|
|
76
|
+
function backToPit(label, code, signal) {
|
|
77
|
+
// A nested invocation (moshscript shim, or a CLI verb called from a script)
|
|
78
|
+
// must hand control back to its parent, not open a second mosh pit on the
|
|
79
|
+
// shared TTY.
|
|
80
|
+
if (!process.stdin.isTTY || process.env.MOSHCODE_NESTED === "1") process.exit(code ?? 0);
|
|
81
|
+
const how = signal ? ` (${signal})` : code != null ? ` (code ${code})` : "";
|
|
82
|
+
console.log(`\nβ© ${label} exited${how} β back in the mosh pit. /quit to leave.\n`);
|
|
83
|
+
return tui();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Direct workflow-tool calls are ordinary CLI passthroughs, not interactive
|
|
87
|
+
// engine sessions. Preserve the child's result for shells, scripts, and agents.
|
|
88
|
+
function propagateExit(code, signal) {
|
|
89
|
+
if (signal) {
|
|
90
|
+
try { process.kill(process.pid, signal); }
|
|
91
|
+
catch { process.exitCode = 1; }
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
process.exitCode = code ?? 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function printStatus(entries, json = false) {
|
|
98
|
+
if (json) {
|
|
99
|
+
console.log(JSON.stringify(entries.map(({ key, desc, bin, installed }) => ({
|
|
100
|
+
name: key,
|
|
101
|
+
description: desc,
|
|
102
|
+
binary: bin,
|
|
103
|
+
installed,
|
|
104
|
+
})), null, 2));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
console.log(`${entry.installed ? "β" : "β"} ${entry.key.padEnd(10)} ${entry.desc}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function printEngineStatus(json = false) {
|
|
113
|
+
printStatus(engineStatus(), json);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function launchEngine(key, engine, args, { agentMode = false } = {}) {
|
|
117
|
+
if (agentMode) {
|
|
118
|
+
const note = `agent mode: ${key} ${agentLaunchArgs(engine).join(" ")}`;
|
|
119
|
+
console.error(engine.agentsView
|
|
120
|
+
? `Β· ${note} β opening its agent view.`
|
|
121
|
+
: `β ${note} β native approvals/permissions are bypassed or auto-approved.`);
|
|
122
|
+
}
|
|
123
|
+
const result = await openSession(engine, agentMode ? agentLaunchArgs(engine, args) : args);
|
|
124
|
+
if (!result.ok) {
|
|
125
|
+
console.error(result.error?.code === "ENOENT"
|
|
126
|
+
? `${key} isn't installed (\`${engine.bin}\`). run: moshcode install ${key}`
|
|
127
|
+
: `launch failed: ${result.error?.message || result.error}`);
|
|
128
|
+
if (!process.stdin.isTTY) { process.exitCode = 1; return; }
|
|
129
|
+
return tui();
|
|
130
|
+
}
|
|
131
|
+
return backToPit(key, result.code, result.signal);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Help, on the right stream, from the schema (PRD 0006 R3, R5).
|
|
136
|
+
*
|
|
137
|
+
* \`write\` decides the stream and it is not cosmetic: help asked for is output,
|
|
138
|
+
* help printed because a verb was wrong is a diagnostic, and a diagnostic on
|
|
139
|
+
* stdout ends up inside whatever the caller was redirecting to β \`moshcode doh
|
|
140
|
+
* --nginx x > site.conf\` on a build without \`doh\` used to write this banner
|
|
141
|
+
* into an nginx config, and nginx then failed to start for reasons four layers
|
|
142
|
+
* from the typo.
|
|
143
|
+
*
|
|
144
|
+
* The 87-line template literal that used to live here is gone. It had already
|
|
145
|
+
* drifted β \`dns\` and \`version\` were dispatchable and missing from it β and
|
|
146
|
+
* every fix to it was a second place to remember.
|
|
147
|
+
*/
|
|
148
|
+
function helpContext() {
|
|
149
|
+
return {
|
|
150
|
+
engines: Object.keys(ENGINES),
|
|
151
|
+
tools: Object.keys(TOOLS),
|
|
152
|
+
version: moshcodeVersion() || "",
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function help(write = console.log, { all = false } = {}) {
|
|
157
|
+
write(all ? renderAll(helpContext()) : renderOverview(helpContext()));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Answer every form of "how does this work" before anything is parsed.
|
|
162
|
+
*
|
|
163
|
+
* Ahead of dispatch on purpose (PRD 0006 R1, R2). Asking for help used to be
|
|
164
|
+
* handled by each command's own argument parser, by accident and differently
|
|
165
|
+
* every time: `prd --help` read `--help` as the PRD's idea and *published a
|
|
166
|
+
* document*, `run --help` was an unknown-option error, `console --help` printed
|
|
167
|
+
* usage to stderr and exited 1. Recognising it here means no parser sees it and
|
|
168
|
+
* none of them can act on it.
|
|
169
|
+
*
|
|
170
|
+
* Returns true when it handled the invocation.
|
|
171
|
+
*
|
|
172
|
+
* Two boundaries it deliberately does not cross:
|
|
173
|
+
* - an engine or tool name. `moshcode gh --help` is gh's question and passes
|
|
174
|
+
* through byte-for-byte; `moshcode help gh` asks how moshcode wraps it.
|
|
175
|
+
* - the moshscript filename. `moshcode run --help` is the runner's; after the
|
|
176
|
+
* file, `--help` is the script's argv (PRD 0004 R13).
|
|
177
|
+
*/
|
|
178
|
+
function handledHelp(cmd, rest) {
|
|
179
|
+
const asJson = rest.includes("--json");
|
|
180
|
+
const topLevel = ["help", "--help", "-h"].includes(cmd);
|
|
181
|
+
|
|
182
|
+
if (topLevel) {
|
|
183
|
+
// `--markdown` is the generator behind README.md's command table (R13). A
|
|
184
|
+
// build-time convenience, deliberately not a docs pipeline: it emits one
|
|
185
|
+
// table, and the test that keeps README.md honest calls the same function.
|
|
186
|
+
if (rest.includes("--markdown")) {
|
|
187
|
+
console.log(renderMarkdown());
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
const args = withoutHelp(rest).filter((a) => a !== "--json" && a !== "--all");
|
|
191
|
+
// `moshcode help --help` asks about `help` itself, and R4 says every
|
|
192
|
+
// command answers for itself. Only the spelled-out verb: `moshcode --help`
|
|
193
|
+
// is the overview, which is what a bare flag has always meant.
|
|
194
|
+
if (cmd === "help" && !args.length && wantsHelp(rest)) args.push("help");
|
|
195
|
+
if (asJson && !args.length) {
|
|
196
|
+
console.log(JSON.stringify(helpModel(helpContext()), null, 2));
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
if (!args.length) {
|
|
200
|
+
help(console.log, { all: rest.includes("--all") });
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
// `moshcode help <topic> [verb]`
|
|
204
|
+
const command = findCommand(args[0]);
|
|
205
|
+
if (!command) {
|
|
206
|
+
// An engine or a tool is a legitimate topic: this is the only way to ask
|
|
207
|
+
// how moshcode wraps something whose own --help passes through.
|
|
208
|
+
if (resolveEngine(args[0])) {
|
|
209
|
+
console.log(`moshcode ${args[0]} [argsβ¦] β open ${args[0]} directly (passthrough).`);
|
|
210
|
+
console.log(`its own flags belong to it: \`moshcode ${args[0]} --help\` asks ${args[0]}, not moshcode.`);
|
|
211
|
+
console.log(`see also: moshcode help agents Β· moshcode help start`);
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
if (resolveTool(args[0])) {
|
|
215
|
+
console.log(`moshcode ${args[0]} [argsβ¦] β run ${args[0]}, with moshcode's auth and env (passthrough).`);
|
|
216
|
+
console.log(`its own flags belong to it: \`moshcode ${args[0]} --help\` asks ${args[0]}, not moshcode.`);
|
|
217
|
+
console.log(`see also: moshcode help tools Β· moshcode help install`);
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
// A moshscript verb is a fair thing to ask about β `ask()` is as much
|
|
221
|
+
// part of the interface as `moshcode prd` (R14).
|
|
222
|
+
const verbHelp = renderScriptVerb(moshVocabulary().get(args[0]));
|
|
223
|
+
if (verbHelp) {
|
|
224
|
+
console.log(verbHelp);
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
const near = suggest(args[0], [
|
|
228
|
+
...Object.keys(ENGINES), ...Object.keys(TOOLS), ...moshVocabulary().names(),
|
|
229
|
+
]);
|
|
230
|
+
console.error(`β no help for ${JSON.stringify(args[0])}${near ? ` β did you mean ${near}?` : ""}`);
|
|
231
|
+
console.error(" moshcode help list commands");
|
|
232
|
+
process.exitCode = 1;
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
const verb = args[1] ? findVerb(command, args[1]) : null;
|
|
236
|
+
if (args[1] && !verb) {
|
|
237
|
+
console.error(`β ${command.name} has no verb ${JSON.stringify(args[1])}`);
|
|
238
|
+
console.error(renderCommand(command));
|
|
239
|
+
process.exitCode = 1;
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
if (asJson) {
|
|
243
|
+
const model = helpModel(helpContext()).commands.find((c) => c.name === command.name);
|
|
244
|
+
console.log(JSON.stringify(verb ? model.verbs.find((v) => v.name === verb.name) : model, null, 2));
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
console.log(renderCommand(command, { verb }));
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// `moshcode <command> [verb] --help`, at any depth.
|
|
252
|
+
const command = findCommand(cmd);
|
|
253
|
+
if (!command) return false;
|
|
254
|
+
// `run` claims --help only before the script filename.
|
|
255
|
+
if (!wantsHelp(rest, { stopAt: command.name === "run" })) return false;
|
|
256
|
+
|
|
257
|
+
const verb = findVerb(command, withoutHelp(rest)[0]);
|
|
258
|
+
if (asJson) {
|
|
259
|
+
const model = helpModel(helpContext()).commands.find((c) => c.name === command.name);
|
|
260
|
+
console.log(JSON.stringify(verb ? model.verbs.find((v) => v.name === verb.name) : model, null, 2));
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
console.log(renderCommand(command, { verb }));
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function main() {
|
|
268
|
+
const [, , cmd, ...rest] = process.argv;
|
|
269
|
+
|
|
270
|
+
// No args β open the interactive TUI shell (/agents <engine>, etc.).
|
|
271
|
+
if (cmd === undefined) return tui();
|
|
272
|
+
|
|
273
|
+
// Before every parser, so none of them can act on --help. See handledHelp().
|
|
274
|
+
if (handledHelp(cmd, rest)) return;
|
|
275
|
+
|
|
276
|
+
if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
277
|
+
console.log(moshcodeVersion() || "unknown");
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (cmd === "engines") {
|
|
282
|
+
printEngineStatus(rest.includes("--json"));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (cmd === "agents") {
|
|
286
|
+
if (!rest.length || (rest.length === 1 && rest[0] === "--json")) {
|
|
287
|
+
printEngineStatus(rest[0] === "--json");
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const resolved = resolveEngine(rest[0]);
|
|
291
|
+
if (!resolved) {
|
|
292
|
+
console.error(`unknown engine "${rest[0]}". try: ${Object.keys(ENGINES).join(", ")}`);
|
|
293
|
+
process.exitCode = 1;
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const [key, engine] = resolved;
|
|
297
|
+
return launchEngine(key, engine, rest.slice(1), { agentMode: true });
|
|
298
|
+
}
|
|
299
|
+
if (cmd === "start") {
|
|
300
|
+
if (!rest.length) {
|
|
301
|
+
console.error(`usage: moshcode start <engine> [argsβ¦]\nengines:\n${engineList()}`);
|
|
302
|
+
process.exitCode = 1;
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const resolved = resolveEngine(rest[0]);
|
|
306
|
+
if (!resolved) {
|
|
307
|
+
console.error(`unknown engine "${rest[0]}". try: ${Object.keys(ENGINES).join(", ")}`);
|
|
308
|
+
process.exitCode = 1;
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const [key, engine] = resolved;
|
|
312
|
+
return launchEngine(key, engine, rest.slice(1));
|
|
313
|
+
}
|
|
314
|
+
if (cmd === "tools") {
|
|
315
|
+
const asJson = rest.includes("--json");
|
|
316
|
+
printStatus(toolStatus(), asJson);
|
|
317
|
+
// Never after --json: the note would corrupt output being piped into jq.
|
|
318
|
+
if (!asJson) {
|
|
319
|
+
console.log("\nthe primary development toolchain runs through `moshcode` as a");
|
|
320
|
+
console.log("dev.profullstack.com user β https://dev.profullstack.com/");
|
|
321
|
+
}
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (cmd === "trade") {
|
|
325
|
+
const translated = tradeArgs(rest);
|
|
326
|
+
if (translated.usage) {
|
|
327
|
+
console.log(tradeUsage());
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (translated.error) {
|
|
331
|
+
console.error(`${translated.error}\n\n${tradeUsage()}`);
|
|
332
|
+
process.exitCode = 1;
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const tool = TOOLS.alpaca;
|
|
336
|
+
const r = await openTool(tool, translated.args);
|
|
337
|
+
if (!r.ok) {
|
|
338
|
+
console.error(r.error?.code === "ENOENT"
|
|
339
|
+
? `alpaca isn't installed (\`${tool.bin}\`). run: moshcode install alpaca`
|
|
340
|
+
: `launch failed: ${r.error?.message || r.error}`);
|
|
341
|
+
process.exitCode = 1;
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
propagateExit(r.code, r.signal);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (cmd === "console") {
|
|
348
|
+
const code = await consoleCommand(rest);
|
|
349
|
+
if (code) process.exitCode = code;
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (cmd === "mcp") {
|
|
353
|
+
process.exitCode = (await mcpCommand(rest)) || 0;
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (cmd === "skill" || cmd === "skills") {
|
|
357
|
+
process.exitCode = (await skillCommand(rest)) || 0;
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (cmd === "install") {
|
|
361
|
+
const target = rest.find((a) => !a.startsWith("-"))?.toLowerCase();
|
|
362
|
+
// Own properties only β `install constructor` must print usage, not resolve
|
|
363
|
+
// to something off Object.prototype and crash on its missing install spec.
|
|
364
|
+
const entry = target
|
|
365
|
+
&& ((Object.hasOwn(ENGINES, target) && ENGINES[target]) || (Object.hasOwn(TOOLS, target) && TOOLS[target]));
|
|
366
|
+
if (!target || !entry) {
|
|
367
|
+
console.error(`usage: moshcode install <engine|tool>\nengines:\n${engineList()}\ntools:\n${toolList()}`);
|
|
368
|
+
process.exit(target ? 1 : 0);
|
|
369
|
+
}
|
|
370
|
+
const { install, desc, bin } = entry;
|
|
371
|
+
console.log(`πΈ installing ${target} β ${desc}\n$ ${install.cmd} ${install.args.join(" ")}\n`);
|
|
372
|
+
const result = await runCmd(install.cmd, install.args);
|
|
373
|
+
if (!result.ok) {
|
|
374
|
+
console.error(`install failed: ${result.error?.message || result.error || "unknown error"}`);
|
|
375
|
+
if (result.error?.code === "ENOENT" && entry.installHelp) console.error(entry.installHelp);
|
|
376
|
+
process.exitCode = 1;
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
if (result.code === 0) console.log(`\nβ ${target} installed. run it with \`${bin}\`. π€`);
|
|
380
|
+
return backToPit(`install ${target}`, result.code);
|
|
381
|
+
}
|
|
382
|
+
if (cmd === "uninstall" || cmd === "remove") {
|
|
383
|
+
const target = rest.find((a) => !a.startsWith("-"))?.toLowerCase();
|
|
384
|
+
const entry = target
|
|
385
|
+
&& ((Object.hasOwn(ENGINES, target) && ENGINES[target]) || (Object.hasOwn(TOOLS, target) && TOOLS[target]));
|
|
386
|
+
if (!target || !entry) {
|
|
387
|
+
console.error(`usage: moshcode uninstall <engine|tool>\nengines:\n${engineList()}\ntools:\n${toolList()}`);
|
|
388
|
+
process.exit(target ? 1 : 0);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const binPath = resolveExecutable(entry.bin, entry.binDirs);
|
|
392
|
+
const plan = uninstallPlan(entry, { binPath });
|
|
393
|
+
|
|
394
|
+
if (plan.kind === "absent" || plan.kind === "refused") {
|
|
395
|
+
for (const w of plan.warnings) console.error(w);
|
|
396
|
+
process.exitCode = plan.kind === "refused" ? 1 : 0;
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
console.log(`πΈ uninstalling ${target} β ${entry.desc}`);
|
|
401
|
+
console.log(describeUninstall(plan));
|
|
402
|
+
if (rest.includes("--dry-run")) return;
|
|
403
|
+
|
|
404
|
+
// Removing a binary is not something to do because a flag was left off.
|
|
405
|
+
// An npm uninstall is reversible with one command and does not ask.
|
|
406
|
+
if (plan.kind === "binary" && !rest.includes("--yes") && !rest.includes("-y")) {
|
|
407
|
+
console.error(`\nthis deletes ${binPath}. re-run with --yes to do it.`);
|
|
408
|
+
process.exitCode = 1;
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
for (const step of plan.steps) {
|
|
413
|
+
if (step.kind === "remove") {
|
|
414
|
+
try {
|
|
415
|
+
fs.rmSync(step.path, { force: true });
|
|
416
|
+
console.log(`\nβ removed ${step.path}`);
|
|
417
|
+
} catch (err) {
|
|
418
|
+
console.error(`could not remove ${step.path}: ${err.message}`);
|
|
419
|
+
process.exitCode = 1;
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
} else {
|
|
423
|
+
const result = await runCmd(step.command, step.args);
|
|
424
|
+
if (!result.ok || result.code !== 0) {
|
|
425
|
+
console.error(`uninstall failed: ${result.error?.message || `exit ${result.code}`}`);
|
|
426
|
+
process.exitCode = 1;
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
console.log(`\nβ ${target} uninstalled. π€`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return backToPit(`uninstall ${target}`, 0);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (cmd === "upgrade" || cmd === "update") {
|
|
436
|
+
// --check, --if-newer and --timer never reinstall blindly: a scheduled run
|
|
437
|
+
// that re-fetches Node, bun and the tarball to discover nothing changed is
|
|
438
|
+
// minutes of network and disk for no reason.
|
|
439
|
+
if (rest.some((a) => ["--check", "--if-newer", "--timer"].includes(a))) {
|
|
440
|
+
const { promises: fsp } = await import("node:fs");
|
|
441
|
+
const { execFile } = await import("node:child_process");
|
|
442
|
+
process.exitCode = (await selfUpdateCommand(rest, console.log, {
|
|
443
|
+
upgrade: async () => {
|
|
444
|
+
const results = await runUpgrade(rest.filter((a) => !a.startsWith("--")));
|
|
445
|
+
return results.filter((r) => !r.ok).length ? 1 : 0;
|
|
446
|
+
},
|
|
447
|
+
write: (path, body) => fsp.writeFile(path, body),
|
|
448
|
+
runner: (cmd2, args2) => new Promise((res) => execFile(cmd2, args2, (err) => res({ ok: !err }))),
|
|
449
|
+
})) || 0;
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
console.log("πΈ moshcode upgrade β updating moshcode + installed engines/tools π€");
|
|
453
|
+
const results = await runUpgrade(rest);
|
|
454
|
+
const failed = results.filter((r) => !r.ok).length;
|
|
455
|
+
return backToPit("upgrade", failed ? 1 : 0);
|
|
456
|
+
}
|
|
457
|
+
if (cmd === "dns") {
|
|
458
|
+
process.exitCode = (await dnsCommand(rest)) || 0;
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
if (cmd === "doh") {
|
|
462
|
+
const nameAt = rest.indexOf("--nginx");
|
|
463
|
+
if (nameAt >= 0) {
|
|
464
|
+
console.log(nginxDohSite({
|
|
465
|
+
name: rest[nameAt + 1] || "dns.example",
|
|
466
|
+
port: DEFAULT_DOH_PORT,
|
|
467
|
+
tls: rest.includes("--tls"),
|
|
468
|
+
}));
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const listenPort = parseDohPort(rest, DEFAULT_DOH_PORT);
|
|
472
|
+
if (!listenPort.ok) {
|
|
473
|
+
console.error(`--port needs a decimal integer from 1 to 65535, got ${JSON.stringify(listenPort.raw)}`);
|
|
474
|
+
process.exitCode = 1;
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const server = await createDohServer({
|
|
478
|
+
port: listenPort.port,
|
|
479
|
+
...parseGuardArgs(rest),
|
|
480
|
+
});
|
|
481
|
+
console.log(`DoH resolver on ${server.url}`);
|
|
482
|
+
console.log(server.guards.rateLimit
|
|
483
|
+
? ` guards: ${server.guards.rateLimit.perSecond}/s per client (burst ${server.guards.rateLimit.burst}), `
|
|
484
|
+
+ `bans double from ${Math.round(server.guards.ban.baseMs / 1000)}s, answers capped at ${server.guards.maxResponseBytes}B`
|
|
485
|
+
: " ! guards OFF (--no-guards) β do not expose this without something else limiting it");
|
|
486
|
+
console.log("TLS belongs to whatever holds 443 β see: moshcode doh --nginx <name>");
|
|
487
|
+
console.log("this must not be reachable directly; it has no TLS and trusts X-Forwarded-For");
|
|
488
|
+
return new Promise(() => {});
|
|
489
|
+
}
|
|
490
|
+
if (cmd === "site" || cmd === "serve") {
|
|
491
|
+
process.exitCode = (await serveCommand(rest)) || 0;
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (cmd === "template" || cmd === "templates") {
|
|
495
|
+
process.exitCode = (await templateCommand(rest)) || 0;
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (cmd === "pwd" || cmd === "where") {
|
|
500
|
+
const location = locate();
|
|
501
|
+
if (rest.includes("--json")) {
|
|
502
|
+
console.log(JSON.stringify({ cwd: location.cwd, git: location.git }, null, 2));
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
const { cwd, home, git } = location;
|
|
506
|
+
console.log(tilde(cwd, home));
|
|
507
|
+
if (git) {
|
|
508
|
+
console.log(`repo: ${git.name}${git.branch ? ` (${git.branch})` : ""}`);
|
|
509
|
+
console.log(`root: ${tilde(git.root, home)}`);
|
|
510
|
+
if (git.origin) console.log(`origin: ${git.origin}`);
|
|
511
|
+
} else {
|
|
512
|
+
console.log("(not a git repo)");
|
|
513
|
+
}
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
if (cmd === "commands") {
|
|
517
|
+
const commands = moshVocabulary().all();
|
|
518
|
+
if (rest.includes("--json")) {
|
|
519
|
+
console.log(JSON.stringify(commands.map(({ name, summary }) => ({
|
|
520
|
+
name,
|
|
521
|
+
description: summary,
|
|
522
|
+
})), null, 2));
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
console.log("built-in moshscript commands:");
|
|
526
|
+
for (const c of commands) {
|
|
527
|
+
console.log(` ${(`${c.name}()`).padEnd(12)} ${c.summary}`);
|
|
528
|
+
}
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
if (cmd === "completion") {
|
|
532
|
+
try {
|
|
533
|
+
process.stdout.write(completionScript(rest[0]));
|
|
534
|
+
} catch (e) {
|
|
535
|
+
console.error(`usage: moshcode completion <bash|zsh|fish|powershell>\n${e.message || e}`);
|
|
536
|
+
process.exitCode = 1;
|
|
537
|
+
}
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
if (cmd === "login") {
|
|
541
|
+
const device = rest.includes("--device") || rest.includes("-d") || !process.stdin.isTTY;
|
|
542
|
+
const browser = rest.includes("--browser") || rest.includes("-b");
|
|
543
|
+
try {
|
|
544
|
+
const { email } = await loginAuto({ device, browser });
|
|
545
|
+
console.log(`β logged in${email ? ` as ${email}` : ""} π€ β notify()/ask() will reach you now.`);
|
|
546
|
+
} catch (e) { console.error(String(e.message || e)); process.exitCode = 1; }
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
if (cmd === "whoami") { await whoami(); return; }
|
|
550
|
+
if (cmd === "logout") { logout(); return; }
|
|
551
|
+
if (cmd === "run") {
|
|
552
|
+
let max = 3, dryRun = false;
|
|
553
|
+
let optionsEnded = false;
|
|
554
|
+
const positional = []; // first is the file; the rest reach the script as argv
|
|
555
|
+
for (let k = 0; k < rest.length; k++) {
|
|
556
|
+
const a = rest[k];
|
|
557
|
+
if (!optionsEnded && a === "--") {
|
|
558
|
+
optionsEnded = true;
|
|
559
|
+
}
|
|
560
|
+
else if (!optionsEnded && (a === "--max" || a === "-n")) {
|
|
561
|
+
try { max = parseMax(rest[++k]); }
|
|
562
|
+
catch (e) { console.error(String(e.message || e)); process.exit(1); }
|
|
563
|
+
}
|
|
564
|
+
else if (!optionsEnded && a.startsWith("--max=")) {
|
|
565
|
+
try { max = parseMax(a.slice("--max=".length)); }
|
|
566
|
+
catch (e) { console.error(String(e.message || e)); process.exit(1); }
|
|
567
|
+
}
|
|
568
|
+
else if (!optionsEnded && a === "--dry-run") dryRun = true;
|
|
569
|
+
else if (!optionsEnded && a !== "-" && a.startsWith("-") && positional.length === 0) {
|
|
570
|
+
console.error(`moshcode run: unknown option ${a}`);
|
|
571
|
+
process.exit(1);
|
|
572
|
+
}
|
|
573
|
+
else positional.push(a);
|
|
574
|
+
}
|
|
575
|
+
const file = positional[0] || null;
|
|
576
|
+
const argv = positional.slice(1);
|
|
577
|
+
let src;
|
|
578
|
+
try {
|
|
579
|
+
src = file ? readScript(file) : (fs.existsSync(EXAMPLE) ? fs.readFileSync(EXAMPLE, "utf8") : DEFAULT_SCRIPT);
|
|
580
|
+
} catch (e) {
|
|
581
|
+
console.error(String(e.message || e));
|
|
582
|
+
process.exit(1);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
console.log(`πΈ moshcode β running moshscript${dryRun ? " (dry run)" : ""}\n`);
|
|
586
|
+
let result;
|
|
587
|
+
try {
|
|
588
|
+
result = await runScript(src, {
|
|
589
|
+
commands: moshVocabulary(),
|
|
590
|
+
max,
|
|
591
|
+
dryRun,
|
|
592
|
+
argv,
|
|
593
|
+
out: (s) => console.log(s),
|
|
594
|
+
});
|
|
595
|
+
} catch (e) {
|
|
596
|
+
console.error("\n" + String(e.message || e));
|
|
597
|
+
process.exit(1);
|
|
598
|
+
}
|
|
599
|
+
console.log(`\nβ ${result.iterations} loop(s) β no bugs, only features. π€`);
|
|
600
|
+
return backToPit("moshscript", 0);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (cmd === "prd") {
|
|
604
|
+
const listMode = !rest.length || rest[0] === "list" || rest[0] === "--json";
|
|
605
|
+
if (listMode) {
|
|
606
|
+
const listArgs = rest[0] === "list" ? rest.slice(1) : rest;
|
|
607
|
+
const unknown = listArgs.filter((arg) => arg !== "--json");
|
|
608
|
+
if (unknown.length) {
|
|
609
|
+
console.error(`usage: moshcode prd list [--json]\nunknown option: ${unknown[0]}`);
|
|
610
|
+
process.exitCode = 1;
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
const prds = listPrds();
|
|
614
|
+
if (listArgs.includes("--json")) {
|
|
615
|
+
console.log(JSON.stringify(prds.map(({ id, title, status, file }) => ({
|
|
616
|
+
id,
|
|
617
|
+
title,
|
|
618
|
+
status,
|
|
619
|
+
file,
|
|
620
|
+
})), null, 2));
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
if (!prds.length) { console.log("no PRDs yet β `moshcode prd <idea>` to start one."); return; }
|
|
624
|
+
for (const p of prds) console.log(`${p.id} ${p.status.padEnd(9)} ${p.title}`);
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
const idea = rest.join(" ");
|
|
628
|
+
const { id, slug, path: file, existed, bootstrapped } = createPrd(idea);
|
|
629
|
+
if (bootstrapped) console.log("bootstrapped prd/ β README + 0000-template.md");
|
|
630
|
+
console.log(existed
|
|
631
|
+
? `PRD ${id} exists β ${file}`
|
|
632
|
+
: `β published prd/${id}-${slug}.md (committed β status: Draft)`);
|
|
633
|
+
const st = engineStatus();
|
|
634
|
+
const chosen = st.find((e) => e.key === "claude" && e.installed) || st.find((e) => e.installed);
|
|
635
|
+
if (!chosen) { console.log("open an engine to author it β run: moshcode install claude"); return; }
|
|
636
|
+
console.log(`handing ${id} to ${chosen.key} to authorβ¦`);
|
|
637
|
+
const r = await openSession(ENGINES[chosen.key], [authoringPrompt({ path: file, idea: existed ? "" : idea })]);
|
|
638
|
+
return backToPit(chosen.key, r.code, r.signal);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// `moshcode <engine> [argsβ¦]` β open a passthrough session directly.
|
|
642
|
+
const resolved = resolveEngine(cmd);
|
|
643
|
+
if (resolved) {
|
|
644
|
+
const [key, engine] = resolved;
|
|
645
|
+
return launchEngine(key, engine, rest);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// `moshcode <tool> [argsβ¦]` is deliberately silent: the native CLI owns
|
|
649
|
+
// stdout/stderr so JSON and other pipelines remain byte-for-byte usable.
|
|
650
|
+
const resolvedTool = resolveTool(cmd);
|
|
651
|
+
if (resolvedTool) {
|
|
652
|
+
const [key, tool] = resolvedTool;
|
|
653
|
+
const r = await openTool(tool, rest);
|
|
654
|
+
if (!r.ok) {
|
|
655
|
+
console.error(r.error?.code === "ENOENT"
|
|
656
|
+
? `${key} isn't installed (\`${tool.bin}\`). run: moshcode install ${key}`
|
|
657
|
+
: `launch failed: ${r.error?.message || r.error}`);
|
|
658
|
+
process.exitCode = 1;
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
propagateExit(r.code, r.signal);
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Unknown. One line and a way forward, on stderr (PRD 0006 R3, R11) β this
|
|
666
|
+
// used to dump 127 lines to *stdout* and exit 1, which polluted every pipe
|
|
667
|
+
// that had a typo in it and buried the one fact the caller needed.
|
|
668
|
+
const near = suggest(cmd, [...Object.keys(ENGINES), ...Object.keys(TOOLS)]);
|
|
669
|
+
console.error(`β unknown command ${JSON.stringify(cmd)}${near ? ` β did you mean ${near}?` : ""}`);
|
|
670
|
+
console.error(" moshcode help list commands");
|
|
671
|
+
process.exit(1);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
main();
|