Skip to content

Commit e2174b5

Browse files
feat(cli): add JSON install status output (#50)
1 parent 8f2c0f5 commit e2174b5

3 files changed

Lines changed: 45 additions & 10 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ to upgrade, `… | sh -s -- remove` to uninstall.
1818

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

6364
```sh
6465
moshcode tools # list tools and native install status
66+
moshcode tools --json # machine-readable install status for automation
6567
moshcode install ugig # npm install -g ugig
6668
moshcode install coinpay # npm install -g @profullstack/coinpay
6769

bin/moshcode.mjs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,25 @@ function propagateExit(code, signal) {
7676
process.exitCode = code ?? 0;
7777
}
7878

79-
function printEngineStatus() {
80-
for (const engine of engineStatus()) {
81-
console.log(`${engine.installed ? "●" : "○"} ${engine.key.padEnd(10)} ${engine.desc}`);
79+
function printStatus(entries, json = false) {
80+
if (json) {
81+
console.log(JSON.stringify(entries.map(({ key, desc, bin, installed }) => ({
82+
name: key,
83+
description: desc,
84+
binary: bin,
85+
installed,
86+
})), null, 2));
87+
return;
88+
}
89+
for (const entry of entries) {
90+
console.log(`${entry.installed ? "●" : "○"} ${entry.key.padEnd(10)} ${entry.desc}`);
8291
}
8392
}
8493

94+
function printEngineStatus(json = false) {
95+
printStatus(engineStatus(), json);
96+
}
97+
8598
async function launchEngine(key, engine, args, { agentMode = false } = {}) {
8699
if (agentMode) {
87100
console.error(`⚠ agent mode: ${key} ${agentLaunchArgs(engine).join(" ")}${engine.agentsView ? " — opening its agent view" : " — native approvals/permissions are bypassed or auto-approved"}.`);
@@ -133,8 +146,8 @@ usage:
133146
code flow) so notify()/ask() reach you
134147
moshcode whoami | logout show / clear the logged-in account
135148
moshcode pwd show the current dir + git repo/branch/origin
136-
moshcode engines list engines + install status
137-
moshcode tools list workflow tools + install status
149+
moshcode engines [--json] list engines + install status
150+
moshcode tools [--json] list workflow tools + install status
138151
moshcode commands list built-in moshscript commands
139152
moshcode help this
140153
@@ -181,7 +194,7 @@ async function main() {
181194
}
182195

183196
if (cmd === "engines") {
184-
printEngineStatus();
197+
printEngineStatus(rest.includes("--json"));
185198
return;
186199
}
187200
if (cmd === "agents") {
@@ -211,9 +224,7 @@ async function main() {
211224
return launchEngine(key, engine, rest.slice(1));
212225
}
213226
if (cmd === "tools") {
214-
for (const tool of toolStatus()) {
215-
console.log(`${tool.installed ? "●" : "○"} ${tool.key.padEnd(10)} ${tool.desc}`);
216-
}
227+
printStatus(toolStatus(), rest.includes("--json"));
217228
return;
218229
}
219230
if (cmd === "mcp") {

test/cli.test.mjs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,38 @@ import { moshVocabulary } from "../src/commands.mjs";
1010
import { runScript } from "../src/runtime.mjs";
1111
import { createRegistry } from "../src/registry.mjs";
1212

13+
const BIN = fileURLToPath(new URL("../bin/moshcode.mjs", import.meta.url));
14+
1315
test("moshcode --version prints the package version", () => {
1416
const expected = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
15-
const result = spawnSync(process.execPath, [fileURLToPath(new URL("../bin/moshcode.mjs", import.meta.url)), "--version"], {
17+
const result = spawnSync(process.execPath, [BIN, "--version"], {
1618
encoding: "utf8",
1719
});
1820
assert.equal(result.status, 0);
1921
assert.equal(result.stdout.trim(), expected);
2022
assert.equal(result.stderr, "");
2123
});
2224

25+
for (const command of ["engines", "tools"]) {
26+
test(`moshcode ${command} --json prints machine-readable install status`, () => {
27+
const result = spawnSync(process.execPath, [BIN, command, "--json"], {
28+
encoding: "utf8",
29+
});
30+
31+
assert.equal(result.status, 0);
32+
assert.equal(result.stderr, "");
33+
const entries = JSON.parse(result.stdout);
34+
assert.ok(entries.length > 0);
35+
for (const entry of entries) {
36+
assert.deepEqual(Object.keys(entry), ["name", "description", "binary", "installed"]);
37+
assert.equal(typeof entry.name, "string");
38+
assert.equal(typeof entry.description, "string");
39+
assert.equal(typeof entry.binary, "string");
40+
assert.equal(typeof entry.installed, "boolean");
41+
}
42+
});
43+
}
44+
2345
function dryCtx() {
2446
return { dryRun: true, lines: [], out(l) { this.lines.push(l); } };
2547
}

0 commit comments

Comments
 (0)