New:Socket for Asana Is Now Available.Learn more
Get Started

moshcode

Package Overview
Dependencies
Maintainers
1
Versions
73
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

moshcode - npm Package Compare versions

Comparing version
0.63.0
to
0.64.0
+1
-1
package.json
{
"name": "moshcode",
"version": "0.63.0",
"version": "0.64.0",
"type": "module",

@@ -5,0 +5,0 @@ "description": "moshcode — a metal wrapper for coding engines and native UGig/CoinPay workflow CLIs, with OpenPRD and moshscript",

@@ -408,11 +408,33 @@ // Agentic-coding engines moshcode can install + wrap. `moshcode install <name>`

* upgrade to run engine installers/updaters.
*
* With `{ capture: true }` the child's stdout/stderr are piped and *echoed
* through* rather than inherited, and the combined text comes back as `output`.
* The terminal still sees exactly what it saw before — the tee exists so a
* caller can read the engine's own words about *why* it exited non-zero, which
* a bare exit code cannot tell apart (see `alreadyRegistered` in mcp.mjs).
* Inherit stays the default: piping costs a couple of streams, and every other
* caller runs installers whose output nobody needs to parse.
*
* stdin is inherited either way, so a child that prompts still reaches the user.
*/
export function runCmd(cmd, args = []) {
export function runCmd(cmd, args = [], { capture = false } = {}) {
return new Promise((resolve) => {
let child;
const spec = spawnSpec(cmd, args);
try { child = spawn(spec.cmd, spec.args, { stdio: "inherit" }); }
const stdio = capture ? ["inherit", "pipe", "pipe"] : "inherit";
try { child = spawn(spec.cmd, spec.args, { stdio }); }
catch (e) { resolve({ ok: false, error: e }); return; }
child.on("error", (e) => resolve({ ok: false, error: e }));
child.on("exit", (code, signal) => resolve({ ok: true, code, signal }));
let output = "";
if (capture) {
for (const [stream, sink] of [[child.stdout, process.stdout], [child.stderr, process.stderr]]) {
stream?.on("data", (chunk) => { output += chunk.toString(); sink.write(chunk); });
}
}
child.on("error", (e) => resolve({ ok: false, error: e, output }));
// "exit" fires as soon as the process is gone, which with pipes can leave
// the last chunk still queued — the one line we are trying to read. "close"
// waits for the streams too. With stdio inherited there are no streams, so
// the two are the same moment and existing callers are unaffected; the
// distinction is kept explicit so neither branch changes by accident.
child.on(capture ? "close" : "exit", (code, signal) => resolve({ ok: true, code, signal, output }));
});

@@ -419,0 +441,0 @@ }

@@ -108,2 +108,18 @@ // `/mcp` and `/skill` command flows, shared by the TUI and the CLI. Each parses

// A remote server is a URL and nothing else — every engine's builder pushes
// the target alone and discards `args`. So a leftover token here is not a
// command line, it is something the user typed that this command will silently
// throw away. `mcp install <url> --dry-run` is the case that matters: the flag
// does not exist, it lands here, and the install goes ahead and writes to
// every engine's config — the exact opposite of what the person typing it
// expected. Say so instead of dropping it on the floor.
if (!cmdParts && target && isRemoteTarget(target) && args.length) {
const extra = args[0];
return {
error: extra.startsWith("-")
? `unknown mcp flag "${extra}" — mcp takes --name, -t/--transport, -e/--env, and -H/--header, and has no --dry-run`
: `unexpected argument "${extra}" after a remote server URL — a URL server takes no command arguments`,
};
}
if (verb === "install" && !name) {

@@ -189,2 +205,5 @@ if (target && isRemoteTarget(target)) name = deriveName(target);

if (r.status === "added" || r.status === "installed" || r.status === "removed") console.log(line(r.key, ok(r.status)));
// Nothing to do and nothing wrong: grey, like the other "we didn't act"
// rows, rather than the green of a change we actually made.
else if (r.status === "already") console.log(line(r.key, ash("already registered")));
else if (r.status === "failed") console.log(line(r.key, err(`failed${r.code != null ? ` (code ${r.code})` : r.signal ? ` (${r.signal})` : ""}`)));

@@ -191,0 +210,0 @@ else if (r.status === "not-installed") console.log(line(r.key, ash("not installed — /install " + r.key)));

@@ -8,3 +8,3 @@ // Register MCP (Model Context Protocol) servers across every engine that

// Coding engines that can register MCP servers. Aider has no MCP support.
export const MCP_ENGINES = ["claude", "gemini", "codex", "opencode", "privacycode"];
export const MCP_ENGINES = ["claude", "gemini", "qwen", "codex", "opencode", "privacycode"];

@@ -75,3 +75,7 @@ /** Is this target a remote server URL (vs a local stdio command)? */

}
case "gemini": {
// Qwen Code is a Gemini CLI fork and kept the whole `mcp add` surface —
// same `-s/-t/-e/-H` flags, same "URL or command" positional. It shares the
// builder rather than getting a copy, so the two can only drift on purpose.
case "gemini":
case "qwen": {
const argv = ["mcp", "add", "-s", "user"];

@@ -144,4 +148,22 @@ if (remote) argv.push("-t", transport);

/**
* Did this engine exit non-zero only because the server was already there?
*
* Registering the same server twice is the normal way to re-run `mcp install`,
* and it is not a failure — but Claude Code and Gemini/Qwen exit 1 on it, so the
* fan-out summary painted `claude ✗ failed (code 1)` next to opencode's cheerful
* green box. Read from a box where four engines already had the server, that
* says "moshcode cannot register with Claude Code" — which is exactly the wrong
* conclusion, and the reason this function exists rather than a nicer exit code.
*
* Matched against the engine's own words, so it stays honest: an engine that
* fails for any *other* reason still comes back failed.
*/
const ALREADY_RE = /already (?:exists|configured|registered|added)|exists in (?:user|global|project) config/i;
export function alreadyRegistered(r) {
return ALREADY_RE.test(String(r?.output ?? ""));
}
/**
* Execute a plan: run each installed, non-skipped engine's `mcp add`. Returns
* results [{ key, status: "added"|"skipped"|"failed"|"not-installed", reason? }].
* results [{ key, status: "added"|"already"|"skipped"|"failed"|"not-installed", reason? }].
* `run` is injectable for tests; defaults to the real spawner.

@@ -154,6 +176,9 @@ */

if (!item.installed) { results.push({ key: item.key, status: "not-installed" }); continue; }
const r = await run(item.bin, item.argv);
results.push({ key: item.key, status: ranOk(r) ? "added" : "failed", code: r.code, signal: r.signal ?? null });
// capture so a non-zero exit can be read for "already exists" rather than
// reported as a failure; the child's output still reaches the terminal.
const r = await run(item.bin, item.argv, { capture: true });
const status = ranOk(r) ? "added" : alreadyRegistered(r) ? "already" : "failed";
results.push({ key: item.key, status, code: r.code, signal: r.signal ?? null });
}
return results;
}

Sorry, the diff of this file is too big to display