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
5 changes: 4 additions & 1 deletion bin/moshcode.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,10 @@ async function main() {
return;
}
if (cmd === "agents") {
if (!rest.length) { printEngineStatus(); return; }
if (!rest.length || (rest.length === 1 && rest[0] === "--json")) {
printEngineStatus(rest[0] === "--json");
return;
}
const resolved = resolveEngine(rest[0]);
if (!resolved) {
console.error(`unknown engine "${rest[0]}". try: ${Object.keys(ENGINES).join(", ")}`);
Expand Down
5 changes: 5 additions & 0 deletions src/cli-schema.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,15 @@ export const CORE_CLI_COMMANDS = [
description: "list engines or launch one autonomously",
synopsis: [
["moshcode agents", "list engines and their install status"],
["moshcode agents --json", "list engine status as machine-readable JSON"],
["moshcode agents <engine> [args…]", "open the engine's agent view, or start it autonomously"],
],
flags: [
["--json", "print engine status as JSON", ""],
],
examples: [
["moshcode agents", "which engines are here"],
["moshcode agents --json", "pipe engine status into a script"],
["moshcode agents claude", "claude's agent list"],
],
seeAlso: ["start", "engines", "install"],
Expand Down
29 changes: 25 additions & 4 deletions src/completion.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,11 @@ Register-ArgumentCompleter -Native -CommandName moshcode -ScriptBlock {
} else {
switch ($command) {
{ $_ -in @('agents', 'start') } {
if ($argumentIndex -eq 2) { $choices = $script:MoshcodeCompletionEngines }
if ($argumentIndex -eq 2) {
$choices = if ($command -eq 'agents' -and $wordToComplete.StartsWith('-')) {
$script:MoshcodeCompletionJson
} else { $script:MoshcodeCompletionEngines }
}
}
'install' {
if ($argumentIndex -eq 2) { $choices = $script:MoshcodeCompletionInstall }
Expand Down Expand Up @@ -262,7 +266,12 @@ _moshcode_completion() {
choices="${names(model.top)}"
else
case "$subcommand" in
agents|start)
agents)
if (( COMP_CWORD == 2 )); then
if [[ "$cur" == -* ]]; then choices="--json"; else choices="${names(model.engines)}"; fi
fi
;;
start)
(( COMP_CWORD == 2 )) && choices="${names(model.engines)}"
;;
install)
Expand Down Expand Up @@ -365,7 +374,19 @@ _moshcode() {
fi

case "\${words[2]}" in
agents|start)
agents)
if (( CURRENT == 3 )); then
if [[ "$PREFIX" == -* ]]; then
_values "agent option" --json
else
choices=(${zshValues(model.engines)})
_describe "engine" choices
fi
else
_files
fi
;;
start)
if (( CURRENT == 3 )); then
choices=(${zshValues(model.engines)})
_describe "engine" choices
Expand Down Expand Up @@ -535,7 +556,7 @@ complete -c moshcode -n '__moshcode_nested_is mcp list' -l json -d 'print JSON'
complete -c moshcode -n '__moshcode_nested_is skill list; or __moshcode_nested_is skills list' -l json -d 'print JSON'
complete -c moshcode -n '__moshcode_command_is login' -l browser -s b -d 'use browser authentication'
complete -c moshcode -n '__moshcode_command_is login' -l device -s d -d 'use device-code authentication'
complete -c moshcode -n '__moshcode_command_is engines tools commands' -l json -d 'print JSON'
complete -c moshcode -n '${atSecondToken("agents engines tools commands")}' -l json -d 'print JSON'
complete -c moshcode -n '__moshcode_command_is run' -l dry-run -d 'show actions without executing'
complete -c moshcode -n '__moshcode_command_is run' -l max -s n -r -d 'maximum loop count'
complete -c moshcode -n '__moshcode_command_is uninstall remove' -l yes -s y -d 'confirm deleting a binary'
Expand Down
16 changes: 14 additions & 2 deletions src/tui.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,16 @@ function commandRemainder(line) {
return firstWord ? String(line).slice(firstWord[0].length).trim() : "";
}

function printEngines() {
function printEngines(json = false) {
if (json) {
console.log(JSON.stringify(engineStatus().map(({ key, desc, bin, installed }) => ({
name: key,
description: desc,
binary: bin,
installed,
})), null, 2));
return;
}
console.log(bone(" engines") + ash(" — autonomous ") + acid("/agents <name>") + ash(" · raw ") + acid("/start <name>"));
for (const e of engineStatus()) {
const dot = e.installed ? acid("●") : ash("○");
Expand Down Expand Up @@ -582,7 +591,10 @@ export async function tui() {
continue;
}
if (cmd === "agents" || cmd === "agent" || cmd === "engines") {
if (!rest[0]) { printEngines(); continue; }
if (!rest[0] || (rest.length === 1 && rest[0] === "--json")) {
printEngines(rest[0] === "--json");
continue;
}
const resolved = resolveEngine(rest[0]);
if (!resolved) { console.log(err(`unknown engine "${rest[0]}". try: ${Object.keys(ENGINES).join(", ")}`)); continue; }
const [key, engine] = resolved;
Expand Down
14 changes: 13 additions & 1 deletion test/cli.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ for (const command of ["help", "--help", "-h"]) {
});
}

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

test("moshcode agents preserves engine arguments named --json", () => {
const result = spawnSync(
process.execPath,
[BIN, "agents", "nonexistent-engine-for-passthrough-test", "--json"],
{ encoding: "utf8" },
);

assert.equal(result.status, 1);
assert.equal(result.stdout, "");
assert.match(result.stderr, /unknown engine "nonexistent-engine-for-passthrough-test"/);
});

test("moshcode commands --json prints the machine-readable vocabulary", () => {
const result = spawnSync(process.execPath, [BIN, "commands", "--json"], {
encoding: "utf8",
Expand Down
2 changes: 2 additions & 0 deletions test/completion.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ for (const [shell, args] of [
test("bash completion respects argument depth and preserves file fallbacks", () => {
assert.ok(bashCompletions(["moshcode", "cl"]).includes("claude"));
assert.ok(bashCompletions(["moshcode", "agents", ""]).includes("cc"));
assert.deepEqual(bashCompletions(["moshcode", "agents", "--"]), ["--json"]);
assert.deepEqual(bashCompletions(["moshcode", "agents", "claude", "--"]), []);
assert.ok(bashCompletions(["moshcode", "install", ""]).includes("claude"));
assert.deepEqual(bashCompletions(["moshcode", "install", "claude", ""]), []);

Expand Down
18 changes: 18 additions & 0 deletions test/tui.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,24 @@ test("TUI command parsing rejects incomplete quoting", () => {
assert.throws(() => splitCommandLine("/ugig trailing\\"), /trailing escape/);
});

test("TUI /agents --json prints machine-readable engine status", async () => {
const result = await runTui("/agents --json\n/quit\n");

assert.equal(result.status, 0);
const json = result.stdout.match(/\[\s*\{[\s\S]*?\}\s*\]/);
assert.ok(json, "expected a JSON status array");
const statuses = JSON.parse(json[0]);
assert.ok(statuses.some(({ name }) => name === "claude"));
for (const status of statuses) {
assert.deepEqual(Object.keys(status), ["name", "description", "binary", "installed"]);
assert.equal(typeof status.name, "string");
assert.equal(typeof status.description, "string");
assert.equal(typeof status.binary, "string");
assert.equal(typeof status.installed, "boolean");
}
assert.doesNotMatch(result.stdout, /unknown engine "--json"/);
});

test("TUI /run rejects unknown options before reading a script file", async () => {
const result = await runTui("/run --dryrun\n/quit\n");

Expand Down
Loading