moshcode 0.24.3 → 0.25.1

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/README.md CHANGED
@@ -76,8 +76,8 @@ use this only in an isolated container, VM, or workspace you trust:
76
76
 
77
77
  ```sh
78
78
  moshcode agents claude # claude agents --dangerously-skip-permissions (agent view)
79
- moshcode agents opencode # opencode agent list (agent view)
80
- moshcode agents privacycode # privacycode agent list (agent view)
79
+ moshcode agents opencode # opencode --auto (autonomous)
80
+ moshcode agents privacycode # privacycode --auto (autonomous)
81
81
  moshcode agents codex # codex --dangerously-bypass-approvals-and-sandbox (autonomous)
82
82
  moshcode agents gemini # gemini --approval-mode=yolo (autonomous)
83
83
  moshcode agents kimi # kimi --yolo (autonomous)
package/bin/moshcode.mjs CHANGED
@@ -546,7 +546,15 @@ async function main() {
546
546
  } catch (e) { console.error(String(e.message || e)); process.exitCode = 1; }
547
547
  return;
548
548
  }
549
- if (cmd === "whoami") { await whoami(); return; }
549
+ if (cmd === "whoami") {
550
+ if (rest.length > 1 || (rest.length === 1 && rest[0] !== "--json")) {
551
+ console.error("usage: moshcode whoami [--json]");
552
+ process.exitCode = 1;
553
+ return;
554
+ }
555
+ await whoami({ json: rest[0] === "--json" });
556
+ return;
557
+ }
550
558
  if (cmd === "logout") { logout(); return; }
551
559
  if (cmd === "run") {
552
560
  let max = 3, dryRun = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moshcode",
3
- "version": "0.24.3",
3
+ "version": "0.25.1",
4
4
  "type": "module",
5
5
  "description": "moshcode — a metal wrapper for coding engines and native UGig/CoinPay workflow CLIs, with OpenPRD and moshscript",
6
6
  "repository": {
@@ -12,8 +12,8 @@
12
12
  "url": "https://github.com/moshcoder/moshcode/issues"
13
13
  },
14
14
  "bin": {
15
- "moshcode": "./bin/moshcode.mjs",
16
- "moshscript": "./bin/moshscript.mjs"
15
+ "moshcode": "bin/moshcode.mjs",
16
+ "moshscript": "bin/moshscript.mjs"
17
17
  },
18
18
  "scripts": {
19
19
  "start": "node bin/moshcode.mjs",
package/src/auth.mjs CHANGED
@@ -167,22 +167,88 @@ export async function loginAuto({ device = false, browser = false } = {}) {
167
167
  }
168
168
 
169
169
  /** Print who is logged in (verified against the app). */
170
- export async function whoami() {
170
+ export async function whoami({ json = false } = {}) {
171
171
  const creds = loadCreds();
172
- if (!creds?.token) { console.log("not logged in — run: moshcode login"); return; }
172
+ if (!creds?.token) {
173
+ if (json) {
174
+ console.log(JSON.stringify({
175
+ status: "not_logged_in",
176
+ verified: false,
177
+ api: API(),
178
+ user: null,
179
+ }, null, 2));
180
+ } else {
181
+ console.log("not logged in — run: moshcode login");
182
+ }
183
+ return;
184
+ }
185
+ const api = creds.api || API();
186
+ const localUser = {
187
+ id: creds.id ?? null,
188
+ email: creds.email ?? null,
189
+ name: null,
190
+ credits: null,
191
+ };
192
+ const printJson = (value) => console.log(JSON.stringify(value, null, 2));
173
193
  try {
174
- const res = await fetch(`${creds.api || API()}/api/me`, { headers: { authorization: `Bearer ${creds.token}` } });
175
- if (res.status === 401) { console.log("session expired — run: moshcode login"); return; }
194
+ const res = await fetch(`${api}/api/me`, { headers: { authorization: `Bearer ${creds.token}` } });
195
+ if (res.status === 401) {
196
+ if (json) {
197
+ printJson({
198
+ status: "expired",
199
+ verified: false,
200
+ api,
201
+ user: localUser,
202
+ error: { type: "auth", status: 401 },
203
+ });
204
+ }
205
+ else console.log("session expired — run: moshcode login");
206
+ return;
207
+ }
176
208
  // Any other error status still has a body, and it isn't an account — reading
177
209
  // it as one prints a made-up identity for a session the app just refused.
178
210
  if (!res.ok) {
179
- console.log(`${creds.email || "logged in"} @ ${creds.api || API()} (couldn't verify — the app returned ${res.status})`);
211
+ if (json) {
212
+ printJson({
213
+ status: "unverified",
214
+ verified: false,
215
+ api,
216
+ user: localUser,
217
+ error: { type: "http", status: res.status },
218
+ });
219
+ } else {
220
+ console.log(`${creds.email || "logged in"} @ ${api} (couldn't verify — the app returned ${res.status})`);
221
+ }
180
222
  return;
181
223
  }
182
224
  const me = await res.json();
183
- console.log(`${me.email || me.name || "moshcoder"} 🤘 (${me.credits ?? "?"} credits) @ ${creds.api || API()}`);
225
+ if (json) {
226
+ printJson({
227
+ status: "authenticated",
228
+ verified: true,
229
+ api,
230
+ user: {
231
+ id: me.id ?? creds.id ?? null,
232
+ email: me.email ?? creds.email ?? null,
233
+ name: me.name ?? null,
234
+ credits: me.credits ?? null,
235
+ },
236
+ });
237
+ } else {
238
+ console.log(`${me.email || me.name || "moshcoder"} 🤘 (${me.credits ?? "?"} credits) @ ${api}`);
239
+ }
184
240
  } catch {
185
- console.log(`${creds.email || "logged in"} @ ${creds.api || API()} (couldn't reach the app to verify)`);
241
+ if (json) {
242
+ printJson({
243
+ status: "unreachable",
244
+ verified: false,
245
+ api,
246
+ user: localUser,
247
+ error: { type: "network" },
248
+ });
249
+ } else {
250
+ console.log(`${creds.email || "logged in"} @ ${api} (couldn't reach the app to verify)`);
251
+ }
186
252
  }
187
253
  }
188
254
 
@@ -161,7 +161,9 @@ export const CORE_CLI_COMMANDS = [
161
161
  name: "whoami",
162
162
  group: "account",
163
163
  description: "show the logged-in account",
164
- synopsis: [["moshcode whoami", ""]],
164
+ synopsis: [["moshcode whoami [--json]", ""]],
165
+ flags: [["--json", "print account status as machine-readable JSON", ""]],
166
+ examples: [["moshcode whoami --json", "inspect the current session from a script"]],
165
167
  seeAlso: ["login", "logout"],
166
168
  },
167
169
  {
@@ -215,7 +215,7 @@ Register-ArgumentCompleter -Native -CommandName moshcode -ScriptBlock {
215
215
  }
216
216
  }
217
217
  'login' { if ($wordToComplete.StartsWith('-')) { $choices = $script:MoshcodeCompletionLogin } }
218
- { $_ -in @('engines', 'tools', 'commands') } {
218
+ { $_ -in @('whoami', 'engines', 'tools', 'commands') } {
219
219
  if ($wordToComplete.StartsWith('-')) { $choices = $script:MoshcodeCompletionJson }
220
220
  }
221
221
  'run' { if ($wordToComplete.StartsWith('-')) { $choices = $script:MoshcodeCompletionRun } }
@@ -321,7 +321,7 @@ _moshcode_completion() {
321
321
  login)
322
322
  [[ "$cur" == -* ]] && choices="--browser -b --device -d"
323
323
  ;;
324
- engines|tools|commands)
324
+ whoami|engines|tools|commands)
325
325
  [[ "$cur" == -* ]] && choices="--json"
326
326
  ;;
327
327
  run)
@@ -464,7 +464,7 @@ _moshcode() {
464
464
  login)
465
465
  _values "login option" --browser -b --device -d
466
466
  ;;
467
- engines|tools|commands)
467
+ whoami|engines|tools|commands)
468
468
  _values "option" --json
469
469
  ;;
470
470
  run)
@@ -556,7 +556,7 @@ complete -c moshcode -n '__moshcode_nested_is mcp list' -l json -d 'print JSON'
556
556
  complete -c moshcode -n '__moshcode_nested_is skill list; or __moshcode_nested_is skills list' -l json -d 'print JSON'
557
557
  complete -c moshcode -n '__moshcode_command_is login' -l browser -s b -d 'use browser authentication'
558
558
  complete -c moshcode -n '__moshcode_command_is login' -l device -s d -d 'use device-code authentication'
559
- complete -c moshcode -n '${atSecondToken("agents engines tools commands")}' -l json -d 'print JSON'
559
+ complete -c moshcode -n '${atSecondToken("agents whoami engines tools commands")}' -l json -d 'print JSON'
560
560
  complete -c moshcode -n '__moshcode_command_is run' -l dry-run -d 'show actions without executing'
561
561
  complete -c moshcode -n '__moshcode_command_is run' -l max -s n -r -d 'maximum loop count'
562
562
  complete -c moshcode -n '__moshcode_command_is uninstall remove' -l yes -s y -d 'confirm deleting a binary'
package/src/engines.mjs CHANGED
@@ -5,11 +5,11 @@
5
5
  //
6
6
  // `agentsView` (optional) is the exact argv that opens the engine's native
7
7
  // agent list/view — used by `/agents <name>` when the engine actually has one
8
- // (claude, opencode). It's the FULL leading args (subcommand + any flags that
9
- // subcommand accepts), because not every agents-subcommand takes the engine's
10
- // bypass flag (e.g. `opencode agent list` takes none). Engines without an
11
- // `agentsView` fall back to `agentArgs` an autonomous session with native
12
- // approvals bypassed/auto-approved.
8
+ // (currently claude). It's the FULL leading args (subcommand + any flags that
9
+ // subcommand accepts). Engines without an `agentsView` fall back to
10
+ // `agentArgs` an autonomous session with native approvals
11
+ // bypassed/auto-approved. Do not use a machine-readable, one-shot list command
12
+ // as an agents view: `/agents` promises to hand the terminal to a live session.
13
13
  import { spawn } from "node:child_process";
14
14
  import { existsSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
15
15
  import { homedir, tmpdir } from "node:os";
@@ -22,17 +22,19 @@ export const ENGINES = {
22
22
  desc: "opencode — the open-source coding agent (SST/anomalyco)",
23
23
  bin: "opencode",
24
24
  agentArgs: ["--auto"],
25
- agentsView: ["agent", "list"], // `opencode agent list` — lists agents; the `agent` subcommand takes no bypass flag
26
25
  install: { cmd: "bash", args: ["-c", "curl -fsSL https://opencode.ai/install | bash"] },
27
26
  upgrade: { cmd: "opencode", args: ["upgrade"] },
27
+ // The installer appends this directory to a shell profile. The moshcode
28
+ // process that ran it cannot see that PATH change, so search it directly.
29
+ binDirs: [path.join(homedir(), ".opencode", "bin")],
28
30
  },
29
31
  privacycode: {
30
32
  desc: "privacycode — privacy-first coding agent (profullstack)",
31
33
  bin: "privacycode",
32
34
  // An opencode derivative, so it speaks the same flags/subcommands.
33
35
  agentArgs: ["--auto"],
34
- agentsView: ["agent", "list"],
35
36
  install: { cmd: "sh", args: ["-c", "curl -fsSL https://getprivacycode.com/install | sh"] },
37
+ binDirs: [path.join(homedir(), ".privacycode", "bin")],
36
38
  // Deliberately no native updater. `privacycode upgrade` is opencode's, and
37
39
  // it works out how to update itself by recognising where it was installed —
38
40
  // it knows opencode's own locations, not this fork's ~/.privacycode/bin. It
@@ -263,9 +265,10 @@ export function pickAiEngine(preferred) {
263
265
 
264
266
  /** Engine entries annotated with install status. */
265
267
  export function engineStatus() {
266
- // Search each engine's own install dir as well as PATH — kimi's installer only
267
- // adds ~/.kimi-code/bin to your shell rc, so PATH alone reports it missing in
268
- // the very session that installed it. (Inert for engines without binDirs.)
268
+ // Search each engine's own install dir as well as PATH — several curl-based
269
+ // installers only add their bin directory to a shell rc, so PATH alone
270
+ // reports them missing in the very session that installed them. (Inert for
271
+ // engines without binDirs.)
269
272
  return Object.entries(ENGINES).map(([key, e]) => ({ key, ...e, installed: isInstalled(e.bin, e.binDirs) }));
270
273
  }
271
274
 
package/src/tui.mjs CHANGED
@@ -539,7 +539,14 @@ export async function tui() {
539
539
  catch (e) { console.log(err(String(e.message || e))); }
540
540
  continue;
541
541
  }
542
- if (cmd === "whoami") { await whoami(); continue; }
542
+ if (cmd === "whoami") {
543
+ if (rest.length > 1 || (rest.length === 1 && rest[0] !== "--json")) {
544
+ console.log(err("usage: /whoami [--json]"));
545
+ continue;
546
+ }
547
+ await whoami({ json: rest[0] === "--json" });
548
+ continue;
549
+ }
543
550
  if (cmd === "logout") { logout(); continue; }
544
551
  if (cmd === "run") {
545
552
  await runFile(rest);