@mushi-mushi/cli 0.36.1 → 0.36.2

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.
@@ -9,10 +9,10 @@ import {
9
9
  requireUuid,
10
10
  saveConfig,
11
11
  savedSdkKeyFor
12
- } from "./chunk-TFNFJXCK.js";
12
+ } from "./chunk-H6HZNVVT.js";
13
13
  import {
14
14
  MUSHI_MCP_PIN_SPEC
15
- } from "./chunk-R4ZRRD4B.js";
15
+ } from "./chunk-YYDQ3IYE.js";
16
16
  import {
17
17
  assertEndpoint,
18
18
  normalizeEndpoint
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var MUSHI_CLI_VERSION = true ? "0.36.1" : "0.0.0-dev";
2
+ var MUSHI_CLI_VERSION = true ? "0.36.2" : "0.0.0-dev";
3
3
  var MUSHI_MCP_PIN_SPEC = "@mushi-mushi/mcp@0.30.7";
4
4
 
5
5
  export {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  MUSHI_CLI_VERSION
4
- } from "./chunk-R4ZRRD4B.js";
4
+ } from "./chunk-YYDQ3IYE.js";
5
5
  import {
6
6
  CLOUD_API_ENDPOINT,
7
7
  normalizeEndpoint
@@ -14,6 +14,7 @@ var EXIT_CODE_MAP = {
14
14
  E_PROJECT_MISSING: 2,
15
15
  E_ENDPOINT_INVALID: 2,
16
16
  E_INVALID_INPUT: 2,
17
+ E_WRONG_REPO: 2,
17
18
  E_NOT_INTERACTIVE: 2,
18
19
  E_NETWORK: 3,
19
20
  E_TIMEOUT: 3,
@@ -8,10 +8,10 @@ import {
8
8
  requireConfig,
9
9
  requireUuid,
10
10
  tryLoadWorkerKey
11
- } from "./chunk-TFNFJXCK.js";
11
+ } from "./chunk-H6HZNVVT.js";
12
12
  import {
13
13
  MUSHI_CLI_VERSION
14
- } from "./chunk-R4ZRRD4B.js";
14
+ } from "./chunk-YYDQ3IYE.js";
15
15
 
16
16
  // src/worker/daemon.ts
17
17
  import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
@@ -405,6 +405,43 @@ ${run.stderr}`).trim();
405
405
  ${text.slice(-2e3)}` : text);
406
406
  }
407
407
 
408
+ // src/commands/work-repo.ts
409
+ function redactRemote(url) {
410
+ return url.trim().replace(/^([a-z][a-z0-9+.-]*:\/\/)[^@/]*@/i, "$1");
411
+ }
412
+ function normaliseRepoUrl(url) {
413
+ let s = url.trim();
414
+ if (!s) return null;
415
+ const proto = /^[a-z][a-z0-9+.-]*:\/\//i.exec(s);
416
+ if (proto) {
417
+ s = s.slice(proto[0].length).replace(/^[^@/]*@/, "");
418
+ s = s.replace(/^([^/:]+):\d+(?=\/|$)/, "$1");
419
+ } else {
420
+ s = s.replace(/^[^@/]*@/, "");
421
+ s = s.replace(/^([^/:]+):(?!\/)/, "$1/");
422
+ }
423
+ s = s.replace(/\/+$/, "").replace(/\.git$/i, "").replace(/\/+$/, "").toLowerCase();
424
+ if (!s) return null;
425
+ const parts = s.split("/").filter(Boolean);
426
+ if (parts.length === 2 && !parts[0].includes(".")) parts.unshift("github.com");
427
+ return parts.length ? parts.join("/") : null;
428
+ }
429
+ function originMatchesRepos(origin, repoUrls) {
430
+ if (!repoUrls.length) return true;
431
+ const o = origin ? normaliseRepoUrl(origin) : null;
432
+ if (!o) return false;
433
+ return repoUrls.some((u) => normaliseRepoUrl(u) === o);
434
+ }
435
+ function wrongRepoMessage(o) {
436
+ if (!o.repoUrls?.length || originMatchesRepos(o.origin, o.repoUrls)) return null;
437
+ const project = o.projectName ? `${o.projectName}${o.projectId ? ` (${o.projectId})` : ""}` : o.projectId ?? "this key's project";
438
+ const origin = o.origin ? redactRemote(o.origin) : "(no origin remote)";
439
+ return {
440
+ message: `Wrong repo: your Mushi key belongs to ${project}, which is linked to ${o.repoUrls.join(", ")}, but ${o.repoDir} has origin ${origin}. mushi work would apply that project's reports to this repo, so it stopped before claiming anything.`,
441
+ hint: "In this repo, run `mushi login --upgrade-scope --project-id <the project for this repo>`, or pass --repo-dir with a clone of the linked repo. For a monorepo or a fork, pass --any-repo to skip this check."
442
+ };
443
+ }
444
+
408
445
  // src/commands/work.ts
409
446
  import { spawn } from "child_process";
410
447
  import { createHash as createHash2 } from "crypto";
@@ -1096,10 +1133,29 @@ function missingWriteScope(scopes, projectId) {
1096
1133
  const have = scopes.length ? scopes.join(", ") : "no scopes";
1097
1134
  return `This CLI key has ${have}; mushi work also needs mcp:write to record the fix and queue its review. Run: mushi login --upgrade-scope${projectId ? ` --project-id ${projectId}` : ""}`;
1098
1135
  }
1099
- async function requireWriteScope(cfg) {
1136
+ async function readWhoami(cfg) {
1100
1137
  const res = await apiCall("/v1/sync/whoami", cfg);
1101
- const scopes = res.ok ? res.data?.scopes : void 0;
1102
- const problem = missingWriteScope(scopes, cfg.projectId);
1138
+ return res.ok ? res.data : void 0;
1139
+ }
1140
+ async function originOf(repoDir) {
1141
+ if (!existsSync(repoDir)) return null;
1142
+ const r = await runProc("git", ["remote", "get-url", "origin"], { cwd: repoDir, timeoutMs: 3e4 });
1143
+ const url = r.code === 0 ? r.stdout.trim() : "";
1144
+ return url || null;
1145
+ }
1146
+ async function requireRightRepo(whoami, repoDir) {
1147
+ if (!whoami?.repo_urls?.length) return;
1148
+ const problem = wrongRepoMessage({
1149
+ projectName: whoami.project_name,
1150
+ projectId: whoami.project_id,
1151
+ repoUrls: whoami.repo_urls,
1152
+ origin: await originOf(repoDir),
1153
+ repoDir
1154
+ });
1155
+ if (problem) throw new MushiCliError("E_WRONG_REPO", problem.message, problem.hint);
1156
+ }
1157
+ function requireWriteScope(cfg, whoami) {
1158
+ const problem = missingWriteScope(whoami?.scopes, cfg.projectId);
1103
1159
  if (problem) throw new MushiCliError("E_AUTH_MISSING", problem, `mushi login --upgrade-scope${cfg.projectId ? ` --project-id ${cfg.projectId}` : ""}`);
1104
1160
  }
1105
1161
  function stopLine(agent, left, what) {
@@ -1107,14 +1163,14 @@ function stopLine(agent, left, what) {
1107
1163
  return `Stopped: the ${spec.label} agent could not run (out of usage or signed out). ${left} ${what}${left === 1 ? "" : "s"} left untouched and can be picked up again; the run was closed without a retry cooldown. Check: ${spec.bin} ${spec.status.join(" ")}`;
1108
1164
  }
1109
1165
  function registerWorkCommands(program) {
1110
- program.command("work").description("Fix reports with a coding agent on this machine (the Cursor CLI or Claude Code); pushes and opens the draft PR with your git and gh").option("--repo-dir <path>", "A clone of the app repo (only worktrees are created in it); not with --daemon").option("--project <id>", "Project id (overrides MUSHI_PROJECT_ID)").option("--agent <name>", "Local agent: cursor (cursor-agent, default) or claude (Claude Code, claude -p); with --daemon, overrides the worker config's agent").option("--model <id>", "Model for the agent (cursor: cursor-agent --list-models; claude: opus, sonnet, haiku or a claude-\u2026 id)").option("--max <n>", "Reports per run", "3").option("--report <id>", "Work on this report only").option("--followups", 'Instead of new reports: fix the Opus "fix first" findings on PRs mushi work opened, and push to them').option("--keep", "Keep the worktree after the run").option("--dry-run", "Show what would run; change nothing").option("--json", "Machine-readable JSON output").option("--daemon", "Run as a local worker: register this machine, send a heartbeat every 60 s, take the console's lane settings and claim reports (set up with `mushi worker install`)").option("--worker-config <path>", "With --daemon: the lanes file (default ~/.config/mushi/worker.json)").action(async (opts) => {
1166
+ program.command("work").description("Fix reports with a coding agent on this machine (the Cursor CLI or Claude Code); pushes and opens the draft PR with your git and gh").option("--repo-dir <path>", "A clone of the app repo (only worktrees are created in it); not with --daemon").option("--project <id>", "Project id (overrides MUSHI_PROJECT_ID)").option("--agent <name>", "Local agent: cursor (cursor-agent, default) or claude (Claude Code, claude -p); with --daemon, overrides the worker config's agent").option("--model <id>", "Model for the agent (cursor: cursor-agent --list-models; claude: opus, sonnet, haiku or a claude-\u2026 id)").option("--max <n>", "Reports per run", "3").option("--report <id>", "Work on this report only").option("--followups", 'Instead of new reports: fix the Opus "fix first" findings on PRs mushi work opened, and push to them').option("--keep", "Keep the worktree after the run").option("--dry-run", "Show what would run; change nothing").option("--any-repo", "Skip the check that the clone is a repo the key's project is linked to (a monorepo or a fork)").option("--json", "Machine-readable JSON output").option("--daemon", "Run as a local worker: register this machine, send a heartbeat every 60 s, take the console's lane settings and claim reports (set up with `mushi worker install`)").option("--worker-config <path>", "With --daemon: the lanes file (default ~/.config/mushi/worker.json)").action(async (opts) => {
1111
1167
  const agent = parseLocalAgent(opts.agent);
1112
1168
  if (opts.daemon) {
1113
1169
  if (opts.repoDir || opts.report || opts.followups) {
1114
1170
  throw new MushiCliError("E_INVALID_INPUT", "--daemon takes its lanes and clones from the worker config, not from --repo-dir, --report or --followups.", "mushi worker install");
1115
1171
  }
1116
- const { runDaemon: runDaemon2 } = await import("./daemon-VC4WW5GU.js");
1117
- await runDaemon2({ configPath: opts.workerConfig, ...opts.agent !== void 0 ? { agent } : {} });
1172
+ const { runDaemon: runDaemon2 } = await import("./daemon-D3BEJ4ZR.js");
1173
+ await runDaemon2({ configPath: opts.workerConfig, ...opts.agent !== void 0 ? { agent } : {}, ...opts.anyRepo ? { anyRepo: true } : {} });
1118
1174
  return;
1119
1175
  }
1120
1176
  if (!opts.repoDir) throw new MushiCliError("E_INVALID_INPUT", "mushi work needs --repo-dir <clone>.", "mushi work --repo-dir ~/src/my-app");
@@ -1130,6 +1186,8 @@ function registerWorkCommands(program) {
1130
1186
  };
1131
1187
  const warn = (l) => process.stderr.write(`${l}
1132
1188
  `);
1189
+ const whoami = await readWhoami(cfg);
1190
+ if (!opts.anyRepo) await requireRightRepo(whoami, repoDir);
1133
1191
  if (opts.followups) {
1134
1192
  const listed = await api(`/v1/admin/projects/${cfg.projectId}/local-followups`);
1135
1193
  const due = listed.fixes.slice(0, max);
@@ -1141,7 +1199,7 @@ function registerWorkCommands(program) {
1141
1199
  return;
1142
1200
  }
1143
1201
  await preflight(repoDir, warn, agent);
1144
- await requireWriteScope(cfg);
1202
+ requireWriteScope(cfg, whoami);
1145
1203
  const results2 = [];
1146
1204
  for (const f of due) {
1147
1205
  if (interruptedBy()) break;
@@ -1172,7 +1230,7 @@ function registerWorkCommands(program) {
1172
1230
  return;
1173
1231
  }
1174
1232
  await preflight(repoDir, warn, agent);
1175
- await requireWriteScope(cfg);
1233
+ requireWriteScope(cfg, whoami);
1176
1234
  const results = [];
1177
1235
  for (const d of picked) {
1178
1236
  if (interruptedBy()) break;
@@ -2879,6 +2937,7 @@ async function runDaemon(opts = {}) {
2879
2937
  if (gh.code !== 0) process.stdout.write("worker: gh is not signed in on this machine; lanes take nothing until it is (run: gh auth login).\n");
2880
2938
  const repos = /* @__PURE__ */ new Map();
2881
2939
  const notReady = /* @__PURE__ */ new Map();
2940
+ const origins = /* @__PURE__ */ new Map();
2882
2941
  for (const lane of cfg.lanes) {
2883
2942
  const g = await runProc("git", ["rev-parse", "--git-dir"], { cwd: lane.repoDir, timeoutMs: 3e4 });
2884
2943
  if (g.code !== 0) {
@@ -2890,8 +2949,28 @@ async function runDaemon(opts = {}) {
2890
2949
  const origin = await runProc("git", ["remote", "get-url", "origin"], { cwd: lane.repoDir, timeoutMs: 3e4 });
2891
2950
  const repo = origin.code === 0 ? repoOfRemote(origin.stdout) : null;
2892
2951
  if (repo) repos.set(lane.name, repo);
2952
+ if (origin.code === 0 && origin.stdout.trim()) origins.set(lane.name, origin.stdout.trim());
2893
2953
  }
2894
2954
  const conf = { apiKey: key, endpoint };
2955
+ if (!opts.anyRepo) {
2956
+ const who = await apiCall("/v1/sync/whoami", conf, {}, { timeoutMs: 3e4 });
2957
+ if (who.ok) {
2958
+ for (const lane of cfg.lanes) {
2959
+ if (notReady.has(lane.name) || lane.projectId !== who.data.project_id) continue;
2960
+ const problem = wrongRepoMessage({
2961
+ projectName: who.data.project_name,
2962
+ projectId: who.data.project_id,
2963
+ repoUrls: who.data.repo_urls,
2964
+ origin: origins.get(lane.name) ?? null,
2965
+ repoDir: lane.repoDir
2966
+ });
2967
+ if (!problem) continue;
2968
+ process.stdout.write(`worker: lane ${lane.name}: ${problem.message} The lane stays out until it is fixed (or restart with --any-repo).
2969
+ `);
2970
+ notReady.set(lane.name, `Its clone is not a repo ${who.data.project_name || "this project"} is linked to (${who.data.repo_urls.join(", ")}); point the lane at a clone of one, or restart the worker with --any-repo`);
2971
+ }
2972
+ }
2973
+ }
2895
2974
  let wake = () => void 0;
2896
2975
  const readings = createHostReadings({
2897
2976
  ...cursorAccount ? { cursorAccount } : {},
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MUSHI_CLI_VERSION
3
- } from "./chunk-5DQLHMVZ.js";
3
+ } from "./chunk-76LVDUR5.js";
4
4
  import {
5
5
  assertEndpoint,
6
6
  normalizeEndpoint
@@ -415,6 +415,7 @@ var EXIT_CODE_MAP = {
415
415
  E_PROJECT_MISSING: 2,
416
416
  E_ENDPOINT_INVALID: 2,
417
417
  E_INVALID_INPUT: 2,
418
+ E_WRONG_REPO: 2,
418
419
  E_NOT_INTERACTIVE: 2,
419
420
  E_NETWORK: 3,
420
421
  E_TIMEOUT: 3,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/version.ts
4
- var MUSHI_CLI_VERSION = true ? "0.36.1" : "0.0.0-dev";
4
+ var MUSHI_CLI_VERSION = true ? "0.36.2" : "0.0.0-dev";
5
5
  var MUSHI_MCP_PIN_SPEC = "@mushi-mushi/mcp@0.30.7";
6
6
 
7
7
  export {
@@ -6,10 +6,10 @@ import {
6
6
  saveConfig,
7
7
  savedSdkKeyFor,
8
8
  waitForIngestReady
9
- } from "./chunk-MRQEXRAP.js";
9
+ } from "./chunk-TN2IZOY7.js";
10
10
  import {
11
11
  MUSHI_MCP_PIN_SPEC
12
- } from "./chunk-5DQLHMVZ.js";
12
+ } from "./chunk-76LVDUR5.js";
13
13
  import {
14
14
  detectFramework,
15
15
  envVarsToWrite,
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  resolveSdkEnvKey,
4
4
  runConnect
5
- } from "./chunk-GLA6OXI3.js";
5
+ } from "./chunk-2JOEQQOU.js";
6
6
  import "./chunk-KOBEOGNF.js";
7
- import "./chunk-TFNFJXCK.js";
8
- import "./chunk-R4ZRRD4B.js";
7
+ import "./chunk-H6HZNVVT.js";
8
+ import "./chunk-YYDQ3IYE.js";
9
9
  import "./chunk-LGGE3LH7.js";
10
10
  export {
11
11
  resolveSdkEnvKey,
@@ -10,9 +10,9 @@ import {
10
10
  readWorkerKey,
11
11
  runDaemon,
12
12
  workerHostId
13
- } from "./chunk-D3ABBB6H.js";
14
- import "./chunk-TFNFJXCK.js";
15
- import "./chunk-R4ZRRD4B.js";
13
+ } from "./chunk-OKT53MJ7.js";
14
+ import "./chunk-H6HZNVVT.js";
15
+ import "./chunk-YYDQ3IYE.js";
16
16
  import "./chunk-LGGE3LH7.js";
17
17
  export {
18
18
  assertWorkerKey,
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  sanitizeEndpoint,
23
23
  sanitizeProjectId,
24
24
  writeMcpServerEntry
25
- } from "./chunk-GLA6OXI3.js";
25
+ } from "./chunk-2JOEQQOU.js";
26
26
  import {
27
27
  FRAMEWORKS,
28
28
  detectFramework,
@@ -50,7 +50,7 @@ import {
50
50
  registerUxCommands,
51
51
  registerWorkCommands,
52
52
  validateWorkerConfig
53
- } from "./chunk-D3ABBB6H.js";
53
+ } from "./chunk-OKT53MJ7.js";
54
54
  import {
55
55
  CONFIG_PATH,
56
56
  MushiCliError,
@@ -89,11 +89,11 @@ import {
89
89
  tryLoadKeyFromKeychain,
90
90
  trySaveKeyToKeychain,
91
91
  trySaveWorkerKey
92
- } from "./chunk-TFNFJXCK.js";
92
+ } from "./chunk-H6HZNVVT.js";
93
93
  import {
94
94
  MUSHI_CLI_VERSION,
95
95
  MUSHI_MCP_PIN_SPEC
96
- } from "./chunk-R4ZRRD4B.js";
96
+ } from "./chunk-YYDQ3IYE.js";
97
97
  import {
98
98
  CLOUD_API_ENDPOINT,
99
99
  CLOUD_MCP_OAUTH_URL,
@@ -1609,7 +1609,7 @@ async function checkOnboardingStatus(config, _consoleBase, cwd, doFetch = global
1609
1609
  }
1610
1610
  async function checkVersionDrift(cwd, doFetch) {
1611
1611
  const checks = [];
1612
- const { MUSHI_CLI_VERSION: MUSHI_CLI_VERSION2 } = await import("./version-5GNUKBUB.js");
1612
+ const { MUSHI_CLI_VERSION: MUSHI_CLI_VERSION2 } = await import("./version-TOLAY73N.js");
1613
1613
  if (MUSHI_CLI_VERSION2 === "0.0.0-dev") return checks;
1614
1614
  const parse2 = (v) => {
1615
1615
  const m = v.replace(/^[~^]/, "").match(/^(\d+)\.(\d+)\.(\d+)/);
@@ -2582,7 +2582,7 @@ async function maybeOfferConnect(credentials, options, _consoleBase) {
2582
2582
  if (p.isCancel(answer) || !answer) return;
2583
2583
  const endpoint = resolveCloudEndpoint(options.endpoint);
2584
2584
  try {
2585
- const { runConnect: runConnect2 } = await import("./connect-PJ2KR2FI.js");
2585
+ const { runConnect: runConnect2 } = await import("./connect-YZMEO7Y5.js");
2586
2586
  const saved = loadConfig();
2587
2587
  const result = await runConnect2(
2588
2588
  {
package/dist/init.js CHANGED
@@ -25,10 +25,10 @@ import {
25
25
  saveConfig,
26
26
  savedSdkKeyFor,
27
27
  trySaveKeyToKeychain
28
- } from "./chunk-MRQEXRAP.js";
28
+ } from "./chunk-TN2IZOY7.js";
29
29
  import {
30
30
  MUSHI_CLI_VERSION
31
- } from "./chunk-5DQLHMVZ.js";
31
+ } from "./chunk-76LVDUR5.js";
32
32
  import {
33
33
  FRAMEWORKS,
34
34
  detectFramework,
@@ -2127,7 +2127,7 @@ async function maybeOfferConnect(credentials, options, _consoleBase) {
2127
2127
  if (p.isCancel(answer) || !answer) return;
2128
2128
  const endpoint = resolveCloudEndpoint(options.endpoint);
2129
2129
  try {
2130
- const { runConnect } = await import("./connect-X5OPJFCR.js");
2130
+ const { runConnect } = await import("./connect-W4VHAQIQ.js");
2131
2131
  const saved = loadConfig();
2132
2132
  const result = await runConnect(
2133
2133
  {
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  MUSHI_CLI_VERSION,
4
4
  MUSHI_MCP_PIN_SPEC
5
- } from "./chunk-R4ZRRD4B.js";
5
+ } from "./chunk-YYDQ3IYE.js";
6
6
  export {
7
7
  MUSHI_CLI_VERSION,
8
8
  MUSHI_MCP_PIN_SPEC
package/dist/version.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  MUSHI_CLI_VERSION,
3
3
  MUSHI_MCP_PIN_SPEC
4
- } from "./chunk-5DQLHMVZ.js";
4
+ } from "./chunk-76LVDUR5.js";
5
5
  export {
6
6
  MUSHI_CLI_VERSION,
7
7
  MUSHI_MCP_PIN_SPEC
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mushi-mushi/cli",
3
- "version": "0.36.1",
3
+ "version": "0.36.2",
4
4
  "license": "MIT",
5
5
  "description": "Mushi Mushi CLI: SDK setup, MCP wiring, triage. Bug fixing for AI-built apps: plain-English diagnosis + a ready fix, in your editor.",
6
6
  "bin": {