Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ to upgrade, `… | sh -s -- remove` to uninstall.

```sh
moshcode engines # list installable engines
moshcode engines --json # machine-readable install status for automation
moshcode install opencode # install opencode (curl … | bash)
moshcode install claude # npm i -g @anthropic-ai/claude-code
moshcode install codex # npm i -g @openai/codex
Expand Down Expand Up @@ -62,6 +63,7 @@ installs them and passes control through without reimplementing their APIs.

```sh
moshcode tools # list tools and native install status
moshcode tools --json # machine-readable install status for automation
moshcode install ugig # npm install -g ugig
moshcode install coinpay # npm install -g @profullstack/coinpay

Expand Down
29 changes: 20 additions & 9 deletions bin/moshcode.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,25 @@ function propagateExit(code, signal) {
process.exitCode = code ?? 0;
}

function printEngineStatus() {
for (const engine of engineStatus()) {
console.log(`${engine.installed ? "●" : "○"} ${engine.key.padEnd(10)} ${engine.desc}`);
function printStatus(entries, json = false) {
if (json) {
console.log(JSON.stringify(entries.map(({ key, desc, bin, installed }) => ({
name: key,
description: desc,
binary: bin,
installed,
})), null, 2));
return;
}
for (const entry of entries) {
console.log(`${entry.installed ? "●" : "○"} ${entry.key.padEnd(10)} ${entry.desc}`);
}
}

function printEngineStatus(json = false) {
printStatus(engineStatus(), json);
}

async function launchEngine(key, engine, args, { agentMode = false } = {}) {
if (agentMode) {
console.error(`⚠ agent mode: ${key} ${agentLaunchArgs(engine).join(" ")}${engine.agentsView ? " — opening its agent view" : " — native approvals/permissions are bypassed or auto-approved"}.`);
Expand Down Expand Up @@ -133,8 +146,8 @@ usage:
code flow) so notify()/ask() reach you
moshcode whoami | logout show / clear the logged-in account
moshcode pwd show the current dir + git repo/branch/origin
moshcode engines list engines + install status
moshcode tools list workflow tools + install status
moshcode engines [--json] list engines + install status
moshcode tools [--json] list workflow tools + install status
moshcode commands list built-in moshscript commands
moshcode help this

Expand Down Expand Up @@ -181,7 +194,7 @@ async function main() {
}

if (cmd === "engines") {
printEngineStatus();
printEngineStatus(rest.includes("--json"));
return;
}
if (cmd === "agents") {
Expand Down Expand Up @@ -211,9 +224,7 @@ async function main() {
return launchEngine(key, engine, rest.slice(1));
}
if (cmd === "tools") {
for (const tool of toolStatus()) {
console.log(`${tool.installed ? "●" : "○"} ${tool.key.padEnd(10)} ${tool.desc}`);
}
printStatus(toolStatus(), rest.includes("--json"));
return;
}
if (cmd === "mcp") {
Expand Down
24 changes: 23 additions & 1 deletion test/cli.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,38 @@ import { moshVocabulary } from "../src/commands.mjs";
import { runScript } from "../src/runtime.mjs";
import { createRegistry } from "../src/registry.mjs";

const BIN = fileURLToPath(new URL("../bin/moshcode.mjs", import.meta.url));

test("moshcode --version prints the package version", () => {
const expected = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
const result = spawnSync(process.execPath, [fileURLToPath(new URL("../bin/moshcode.mjs", import.meta.url)), "--version"], {
const result = spawnSync(process.execPath, [BIN, "--version"], {
encoding: "utf8",
});
assert.equal(result.status, 0);
assert.equal(result.stdout.trim(), expected);
assert.equal(result.stderr, "");
});

for (const command of ["engines", "tools"]) {
test(`moshcode ${command} --json prints machine-readable install status`, () => {
const result = spawnSync(process.execPath, [BIN, command, "--json"], {
encoding: "utf8",
});

assert.equal(result.status, 0);
assert.equal(result.stderr, "");
const entries = JSON.parse(result.stdout);
assert.ok(entries.length > 0);
for (const entry of entries) {
assert.deepEqual(Object.keys(entry), ["name", "description", "binary", "installed"]);
assert.equal(typeof entry.name, "string");
assert.equal(typeof entry.description, "string");
assert.equal(typeof entry.binary, "string");
assert.equal(typeof entry.installed, "boolean");
}
});
}

function dryCtx() {
return { dryRun: true, lines: [], out(l) { this.lines.push(l); } };
}
Expand Down
Loading