@mushi-mushi/cli 0.14.0 → 0.16.0

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
@@ -215,6 +215,77 @@ mushi fixes tail --report-id 11111111-2222-3333-4444-555555555555
215
215
  Exits automatically when the job reaches a terminal status (`completed`,
216
216
  `failed`, `cancelled`, `skipped`).
217
217
 
218
+ ### `mushi fixes merge <fixId>`
219
+
220
+ Squash-merge (or merge/rebase) the fix PR on GitHub and run the same post-merge
221
+ bookkeeping as the admin console: `merged_at`, report → **Fixed**, reporter
222
+ notification, `fix.applied` webhooks.
223
+
224
+ ```bash
225
+ mushi fixes merge 75199dde-f726-404a-b5f7-be17bf7a3a46
226
+ mushi fixes merge <fixId> --method squash # default
227
+ mushi fixes merge <fixId> --method merge
228
+ mushi fixes merge <fixId> --method rebase
229
+ mushi fixes merge <fixId> --json
230
+ ```
231
+
232
+ Requires an admin API key with `mcp:write` scope and a connected GitHub App or PAT.
233
+ Draft PRs are auto-readied via GraphQL before merge.
234
+
235
+ ### `mushi fixes refresh-ci <fixId>`
236
+
237
+ Pull the latest GitHub Actions check-run status on demand (same as **Refresh CI
238
+ status** in the console). Useful when webhooks drop or you just pushed to the PR
239
+ branch.
240
+
241
+ ```bash
242
+ mushi fixes refresh-ci <fixId>
243
+ mushi fixes refresh-ci <fixId> --json
244
+ ```
245
+
246
+ ---
247
+
248
+ ## QA coverage & TDD
249
+
250
+ ```bash
251
+ mushi stories map --url https://your-app.com --wait
252
+ mushi tdd gen <storyId> --mode review
253
+ mushi tdd pending
254
+ mushi tdd approve <qaStoryId>
255
+ mushi tdd improve
256
+ mushi qa stories
257
+ mushi qa runs <storyId>
258
+ mushi qa run <storyId>
259
+ mushi qa audit
260
+ ```
261
+
262
+ ---
263
+
264
+ ## Skill pipelines
265
+
266
+ ```bash
267
+ mushi skills list [--category workflow] [--search "fix"]
268
+ mushi skills show workflow-fix-and-ship
269
+ mushi skills sync
270
+
271
+ mushi pipeline start <reportId> --skill workflow-fix-and-ship [--mode cloud|handoff]
272
+ mushi pipeline watch <runId-or-prefix>
273
+ mushi pipeline checkin <runId-or-prefix> --step 0 --status passed
274
+ ```
275
+
276
+ ---
277
+
278
+ ## Integrations & BYOK
279
+
280
+ ```bash
281
+ mushi integrations list
282
+ mushi integrations test slack|sentry|github
283
+ mushi keys list
284
+ MUSHI_BYOK_KEY=sk-ant-... mushi keys add --provider anthropic --label "Backup"
285
+ mushi slack status
286
+ mushi slack test
287
+ ```
288
+
218
289
  ## Security notes
219
290
 
220
291
  - `~/.mushirc` is written with mode `0o600` on Unix. Legacy configs with looser permissions are tightened on load.
@@ -0,0 +1,6 @@
1
+ // src/version.ts
2
+ var MUSHI_CLI_VERSION = true ? "0.16.0" : "0.0.0-dev";
3
+
4
+ export {
5
+ MUSHI_CLI_VERSION
6
+ };
package/dist/index.js CHANGED
@@ -598,7 +598,7 @@ function getFrameworkFromPkg(pkg) {
598
598
  }
599
599
 
600
600
  // src/version.ts
601
- var MUSHI_CLI_VERSION = true ? "0.14.0" : "0.0.0-dev";
601
+ var MUSHI_CLI_VERSION = true ? "0.16.0" : "0.0.0-dev";
602
602
 
603
603
  // src/init.ts
604
604
  var ENV_FILES = [".env.local", ".env"];
@@ -1088,6 +1088,49 @@ function runMigrate(opts = {}) {
1088
1088
  return { matches };
1089
1089
  }
1090
1090
 
1091
+ // src/sanitize-config.ts
1092
+ var PROJECT_ID_RE = /^(?:proj_[A-Za-z0-9_-]{10,}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
1093
+ var API_KEY_RE = /^mushi_[A-Za-z0-9_-]{10,}$/;
1094
+ function sanitizeApiKey(raw) {
1095
+ const key = raw.replace(/[\r\n\0]/g, "");
1096
+ if (!API_KEY_RE.test(key)) {
1097
+ throw new Error(
1098
+ "Invalid API key in config \u2014 run `mushi login --api-key <key>` to refresh credentials."
1099
+ );
1100
+ }
1101
+ return key;
1102
+ }
1103
+ function sanitizeProjectId(raw) {
1104
+ const id = raw.trim();
1105
+ if (!PROJECT_ID_RE.test(id)) {
1106
+ throw new Error(
1107
+ "Invalid project ID in config \u2014 expected a UUID or proj_* slug from the admin console."
1108
+ );
1109
+ }
1110
+ return id;
1111
+ }
1112
+ function sanitizeEndpoint(raw) {
1113
+ return assertEndpoint(normalizeEndpoint(raw));
1114
+ }
1115
+ function sanitizeCliCredentials(config) {
1116
+ if (!config.endpoint || !config.apiKey || !config.projectId) {
1117
+ throw new Error("Missing endpoint, apiKey, or projectId");
1118
+ }
1119
+ return {
1120
+ endpoint: sanitizeEndpoint(config.endpoint),
1121
+ apiKey: sanitizeApiKey(config.apiKey),
1122
+ projectId: sanitizeProjectId(config.projectId)
1123
+ };
1124
+ }
1125
+ function apiKeyHeaders(apiKey, projectId) {
1126
+ const headers = {
1127
+ Authorization: `Bearer ${apiKey}`,
1128
+ "X-Mushi-Api-Key": apiKey
1129
+ };
1130
+ if (projectId) headers["X-Mushi-Project"] = projectId;
1131
+ return headers;
1132
+ }
1133
+
1091
1134
  // src/sourcemaps.ts
1092
1135
  import { createReadStream } from "fs";
1093
1136
  import { readFile, readdir } from "fs/promises";
@@ -1430,13 +1473,11 @@ var IngestSetupHttpError = class extends Error {
1430
1473
  };
1431
1474
  var NON_RETRYABLE_STATUSES = /* @__PURE__ */ new Set([401, 403, 404]);
1432
1475
  async function fetchIngestSetup(config, doFetch = globalThis.fetch) {
1433
- const safeKey = config.apiKey.replace(/[\r\n]/g, "");
1434
- const res = await doFetch(`${config.endpoint}/v1/sync/ingest-setup`, {
1435
- headers: {
1436
- Authorization: `Bearer ${safeKey}`,
1437
- "X-Mushi-Api-Key": safeKey,
1438
- ...config.projectId ? { "X-Mushi-Project": config.projectId } : {}
1439
- },
1476
+ const endpoint = sanitizeEndpoint(config.endpoint);
1477
+ const apiKey = sanitizeApiKey(config.apiKey);
1478
+ const projectId = config.projectId ? sanitizeProjectId(config.projectId) : void 0;
1479
+ const res = await doFetch(`${endpoint}/v1/sync/ingest-setup`, {
1480
+ headers: apiKeyHeaders(apiKey, projectId),
1440
1481
  signal: AbortSignal.timeout(8e3)
1441
1482
  });
1442
1483
  if (!res.ok) {
@@ -1511,13 +1552,14 @@ function checkCliConfig(config) {
1511
1552
  }
1512
1553
  async function checkEndpointReachability(endpoint, doFetch = globalThis.fetch) {
1513
1554
  try {
1514
- const res = await doFetch(`${endpoint}/health`, {
1555
+ const safeEndpoint = sanitizeEndpoint(endpoint);
1556
+ const res = await doFetch(`${safeEndpoint}/health`, {
1515
1557
  signal: AbortSignal.timeout(5e3)
1516
1558
  });
1517
1559
  return {
1518
1560
  name: "Endpoint reachable",
1519
1561
  ok: res.status === 200,
1520
- detail: `GET ${endpoint}/health \u2192 ${res.status}`
1562
+ detail: `GET ${safeEndpoint}/health \u2192 ${res.status}`
1521
1563
  };
1522
1564
  } catch (err) {
1523
1565
  const msg = err instanceof Error ? err.message : String(err);
@@ -1559,14 +1601,11 @@ async function checkServerPreflight(config, doFetch = globalThis.fetch) {
1559
1601
  ];
1560
1602
  }
1561
1603
  try {
1604
+ const { endpoint, apiKey, projectId } = sanitizeCliCredentials(config);
1562
1605
  const res = await doFetch(
1563
- `${config.endpoint}/v1/admin/projects/${config.projectId}/preflight`,
1606
+ `${endpoint}/v1/admin/projects/${projectId}/preflight`,
1564
1607
  {
1565
- headers: {
1566
- Authorization: `Bearer ${config.apiKey}`,
1567
- "X-Mushi-Api-Key": config.apiKey,
1568
- "X-Mushi-Project": config.projectId
1569
- },
1608
+ headers: apiKeyHeaders(apiKey, projectId),
1570
1609
  signal: AbortSignal.timeout(8e3)
1571
1610
  }
1572
1611
  );
@@ -1642,14 +1681,12 @@ async function checkQaStoriesHealth(config, doFetch = globalThis.fetch) {
1642
1681
  }
1643
1682
  const checks = [];
1644
1683
  try {
1684
+ const { endpoint, apiKey, projectId } = sanitizeCliCredentials(config);
1685
+ const headers = apiKeyHeaders(apiKey, projectId);
1645
1686
  const storiesRes = await doFetch(
1646
- `${config.endpoint}/v1/admin/projects/${config.projectId}/qa-coverage`,
1687
+ `${endpoint}/v1/admin/projects/${projectId}/qa-coverage`,
1647
1688
  {
1648
- headers: {
1649
- Authorization: `Bearer ${config.apiKey}`,
1650
- "X-Mushi-Api-Key": config.apiKey,
1651
- "X-Mushi-Project": config.projectId
1652
- },
1689
+ headers,
1653
1690
  signal: AbortSignal.timeout(8e3)
1654
1691
  }
1655
1692
  );
@@ -1670,14 +1707,10 @@ async function checkQaStoriesHealth(config, doFetch = globalThis.fetch) {
1670
1707
  detail: `${enabled.length} enabled story/stories configured`
1671
1708
  });
1672
1709
  const slackRes = await doFetch(
1673
- `${config.endpoint}/v1/admin/projects/${config.projectId}/integrations/probe/slack`,
1710
+ `${endpoint}/v1/admin/projects/${projectId}/integrations/probe/slack`,
1674
1711
  {
1675
1712
  method: "POST",
1676
- headers: {
1677
- Authorization: `Bearer ${config.apiKey}`,
1678
- "X-Mushi-Api-Key": config.apiKey,
1679
- "X-Mushi-Project": config.projectId
1680
- },
1713
+ headers,
1681
1714
  signal: AbortSignal.timeout(6e3)
1682
1715
  }
1683
1716
  );
@@ -1689,14 +1722,10 @@ async function checkQaStoriesHealth(config, doFetch = globalThis.fetch) {
1689
1722
  detail: slackOk ? "Slack connected \u2014 failures will notify your channel" : "Slack not connected \u2014 you won't be notified when stories fail. Visit /integrations \u2192 Add to Slack."
1690
1723
  });
1691
1724
  const fcRes = await doFetch(
1692
- `${config.endpoint}/v1/admin/projects/${config.projectId}/integrations/probe/firecrawl`,
1725
+ `${endpoint}/v1/admin/projects/${projectId}/integrations/probe/firecrawl`,
1693
1726
  {
1694
1727
  method: "POST",
1695
- headers: {
1696
- Authorization: `Bearer ${config.apiKey}`,
1697
- "X-Mushi-Api-Key": config.apiKey,
1698
- "X-Mushi-Project": config.projectId
1699
- },
1728
+ headers,
1700
1729
  signal: AbortSignal.timeout(6e3)
1701
1730
  }
1702
1731
  );
@@ -3273,6 +3302,79 @@ fixes.command("tail").description(
3273
3302
  }
3274
3303
  }
3275
3304
  });
3305
+ fixes.command("merge <fixId>").description(
3306
+ "Squash-merge the fix PR on GitHub and mark the report Fixed (same as console merge)"
3307
+ ).option(
3308
+ "--method <method>",
3309
+ "GitHub merge method: squash (default), merge, or rebase",
3310
+ "squash"
3311
+ ).option("--json", "Machine-readable JSON output").action(async (fixId, opts) => {
3312
+ const config = loadConfig();
3313
+ if (!config.apiKey) {
3314
+ console.error("Run `mushi login` first");
3315
+ process.exit(1);
3316
+ }
3317
+ if (!config.endpoint) {
3318
+ console.error("No endpoint configured. Run `mushi init`");
3319
+ process.exit(1);
3320
+ }
3321
+ const method = opts.method;
3322
+ if (!["squash", "merge", "rebase"].includes(method)) {
3323
+ console.error("--method must be squash, merge, or rebase");
3324
+ process.exit(1);
3325
+ }
3326
+ const result = await apiCall(`/v1/admin/fixes/${fixId}/merge`, config, {
3327
+ method: "POST",
3328
+ body: JSON.stringify({ mergeMethod: method })
3329
+ });
3330
+ if (!result.ok) {
3331
+ if (opts.json) {
3332
+ console.log(JSON.stringify({ ok: false, error: result.error }, null, 2));
3333
+ } else {
3334
+ console.error("Merge failed:", result.error.message);
3335
+ }
3336
+ process.exit(1);
3337
+ }
3338
+ const data = result.data ?? {};
3339
+ if (opts.json) {
3340
+ console.log(JSON.stringify({ ok: true, ...data }, null, 2));
3341
+ return;
3342
+ }
3343
+ if (data.alreadyMerged) {
3344
+ console.log(`PR was already merged. Report status: ${data.reportStatus ?? "unknown"}`);
3345
+ } else {
3346
+ console.log(`Merged successfully. Report status: ${data.reportStatus ?? "fixed"}`);
3347
+ }
3348
+ });
3349
+ fixes.command("refresh-ci <fixId>").description("Pull latest GitHub Actions check-run status for a fix attempt (on-demand ci-sync)").option("--json", "Machine-readable JSON output").action(async (fixId, opts) => {
3350
+ const config = loadConfig();
3351
+ if (!config.apiKey) {
3352
+ console.error("Run `mushi login` first");
3353
+ process.exit(1);
3354
+ }
3355
+ if (!config.endpoint) {
3356
+ console.error("No endpoint configured. Run `mushi init`");
3357
+ process.exit(1);
3358
+ }
3359
+ const result = await apiCall(`/v1/admin/fixes/${fixId}/refresh-ci`, config, { method: "POST" });
3360
+ if (!result.ok) {
3361
+ if (opts.json) {
3362
+ console.log(JSON.stringify({ ok: false, error: result.error }, null, 2));
3363
+ } else {
3364
+ console.error("CI refresh failed:", result.error.message);
3365
+ }
3366
+ process.exit(1);
3367
+ }
3368
+ const data = result.data ?? {};
3369
+ if (opts.json) {
3370
+ console.log(JSON.stringify({ ok: true, ...data }, null, 2));
3371
+ return;
3372
+ }
3373
+ const status = data.check_run_status ?? "unknown";
3374
+ const conclusion = data.check_run_conclusion ?? "\u2014";
3375
+ const updated = data.check_run_updated_at ? new Date(data.check_run_updated_at).toISOString() : "\u2014";
3376
+ console.log(`CI status: ${status} \xB7 conclusion: ${conclusion} \xB7 updated: ${updated}`);
3377
+ });
3276
3378
  var stories = program.command("stories").description("TDD story mapping and test generation");
3277
3379
  stories.command("map").description("Crawl a live app URL and automatically discover user stories (writes inventory proposal)").requiredOption("--url <url>", "Live app URL to crawl (e.g. https://your-app.vercel.app)").option("--max-pages <n>", "Max pages to crawl", "20").option("--provider <p>", "Crawl provider: firecrawl (default) or browserbase", "firecrawl").option("--cursor-refine", "Open a Cursor Cloud PR to refine the draft against repo code").option("--wait", "Wait for the crawl to complete and print results").action(async (opts) => {
3278
3380
  const config = loadConfig();
@@ -3760,11 +3862,22 @@ Examples:
3760
3862
  mushi audit --json
3761
3863
  mushi audit --project-id abc123`).action(async (opts) => {
3762
3864
  const config = requireConfig();
3763
- const projectId = opts.projectId ?? config.projectId;
3764
- if (!projectId) {
3865
+ const rawProjectId = opts.projectId ?? config.projectId;
3866
+ if (!rawProjectId) {
3765
3867
  process.stderr.write("error: project ID required. Run `mushi login` or pass --project-id\n");
3766
3868
  process.exit(1);
3767
3869
  }
3870
+ let endpoint;
3871
+ let projectId;
3872
+ try {
3873
+ endpoint = sanitizeEndpoint(config.endpoint);
3874
+ projectId = sanitizeProjectId(rawProjectId);
3875
+ } catch (err) {
3876
+ const msg = err instanceof Error ? err.message : String(err);
3877
+ process.stderr.write(`error: ${msg}
3878
+ `);
3879
+ process.exit(2);
3880
+ }
3768
3881
  const headers = {
3769
3882
  "Content-Type": "application/json",
3770
3883
  "X-Mushi-Project-Id": projectId
@@ -3772,9 +3885,9 @@ Examples:
3772
3885
  const jwt = config.jwt ?? null;
3773
3886
  const apiKey = config.apiKey ?? null;
3774
3887
  if (jwt) {
3775
- headers["Authorization"] = `Bearer ${jwt}`;
3888
+ headers["Authorization"] = `Bearer ${jwt.replace(/[\r\n\0]/g, "")}`;
3776
3889
  } else if (apiKey) {
3777
- headers["X-Mushi-Api-Key"] = apiKey;
3890
+ headers["X-Mushi-Api-Key"] = sanitizeApiKey(apiKey);
3778
3891
  } else {
3779
3892
  process.stderr.write("error: no credentials found. Run `mushi login` first.\n");
3780
3893
  process.exit(1);
@@ -3784,7 +3897,7 @@ Examples:
3784
3897
  const controller = new AbortController();
3785
3898
  const timer = setTimeout(() => controller.abort(), 3e4);
3786
3899
  const res = await fetch(
3787
- `${config.endpoint}/v1/admin/projects/${projectId}/audit`,
3900
+ `${endpoint}/v1/admin/projects/${projectId}/audit`,
3788
3901
  { method: "POST", headers, body: "{}", signal: controller.signal }
3789
3902
  );
3790
3903
  clearTimeout(timer);
@@ -3845,3 +3958,254 @@ program.parseAsync().catch((err) => {
3845
3958
  console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
3846
3959
  process.exit(1);
3847
3960
  });
3961
+ var skills = program.command("skills").description("Manage agent skill catalog");
3962
+ skills.command("list").description("List all skills in the catalog").option("--category <cat>", "Filter by category (workflow, debug, test, audit, \u2026)").option("--search <q>", "Search slug, title, or description").option("--page <n>", "Page number (default 1)", "1").option("--limit <n>", "Max results per page (1\u2013200, default 200)", "200").option("--json", "Machine-readable output").action(async (opts) => {
3963
+ const config = loadConfig();
3964
+ if (!config.apiKey) {
3965
+ console.error("Run `mushi login` first");
3966
+ process.exit(2);
3967
+ }
3968
+ const qs = new URLSearchParams();
3969
+ if (opts.category) qs.set("category", opts.category);
3970
+ if (opts.search) qs.set("q", opts.search);
3971
+ qs.set("page", String(Math.max(1, parseInt(opts.page) || 1)));
3972
+ qs.set("limit", String(Math.min(Math.max(1, parseInt(opts.limit) || 200), 200)));
3973
+ const result = await apiCall(
3974
+ `/v1/admin/skills?${qs}`,
3975
+ config
3976
+ );
3977
+ if (!result.ok) {
3978
+ console.error("Failed:", result.error);
3979
+ process.exit(1);
3980
+ }
3981
+ const rows = result.data ?? [];
3982
+ if (opts.json) {
3983
+ console.log(JSON.stringify({ skills: rows, count: rows.length }, null, 2));
3984
+ return;
3985
+ }
3986
+ if (rows.length === 0) {
3987
+ console.log("No skills in catalog. Add a source with `mushi skills sync`.");
3988
+ return;
3989
+ }
3990
+ console.log(`
3991
+ Skill catalog (${rows.length} skills):
3992
+ `);
3993
+ let lastCat = "";
3994
+ for (const s of rows) {
3995
+ if (s.category !== lastCat) {
3996
+ lastCat = s.category;
3997
+ console.log(`
3998
+ [${s.category}]`);
3999
+ }
4000
+ const chain = s.chain_slugs?.length ? ` \u2192 ${s.chain_slugs.length} steps` : "";
4001
+ console.log(` ${s.slug.padEnd(40)} ${s.title}${chain}`);
4002
+ }
4003
+ console.log();
4004
+ });
4005
+ skills.command("show <slug>").description("Show full details and chain for a skill").action(async (slug) => {
4006
+ const config = loadConfig();
4007
+ if (!config.apiKey) {
4008
+ console.error("Run `mushi login` first");
4009
+ process.exit(2);
4010
+ }
4011
+ const result = await apiCall(`/v1/admin/skills/${slug}`, config);
4012
+ if (!result.ok) {
4013
+ console.error("Skill not found:", slug);
4014
+ process.exit(1);
4015
+ }
4016
+ const s = result.data;
4017
+ console.log(`
4018
+ ${s.title} (${s.slug})
4019
+ ${"\u2500".repeat(50)}`);
4020
+ console.log(`Category: ${s.category}`);
4021
+ console.log(`Chain: ${s.chain_slugs?.length ? s.chain_slugs.join(" \u2192 ") : "none"}`);
4022
+ console.log(`
4023
+ Description:
4024
+ ${s.description}
4025
+ `);
4026
+ });
4027
+ skills.command("sync").description("Trigger skill sync for all configured skill sources").option("--source-id <id>", "Sync only a specific source ID").action(async (opts) => {
4028
+ const config = loadConfig();
4029
+ if (!config.apiKey) {
4030
+ console.error("Run `mushi login` first");
4031
+ process.exit(2);
4032
+ }
4033
+ if (!config.projectId) {
4034
+ console.error("No projectId. Run `mushi config projectId <uuid>`");
4035
+ process.exit(2);
4036
+ }
4037
+ let ids;
4038
+ if (opts.sourceId) {
4039
+ ids = [opts.sourceId];
4040
+ } else {
4041
+ const sourcesResult = await apiCall(
4042
+ `/v1/admin/skills/sources?project_id=${config.projectId}`,
4043
+ config
4044
+ );
4045
+ if (!sourcesResult.ok) {
4046
+ console.error("Failed to list sources:", sourcesResult.error);
4047
+ process.exit(1);
4048
+ }
4049
+ ids = (sourcesResult.data ?? []).map((s) => s.id);
4050
+ }
4051
+ if (ids.length === 0) {
4052
+ console.log("No skill sources configured. Add one in the Skill Pipelines console page.");
4053
+ return;
4054
+ }
4055
+ for (const id of ids) {
4056
+ console.log(`Syncing source ${id.slice(0, 8)}\u2026`);
4057
+ const result = await apiCall(
4058
+ `/v1/admin/skills/sources/${id}/sync`,
4059
+ config,
4060
+ { method: "POST" }
4061
+ );
4062
+ if (!result.ok) {
4063
+ console.error(" Sync failed:", result.error);
4064
+ } else console.log(` Done: ${result.data?.synced ?? 0} synced, ${result.data?.skipped ?? 0} skipped, ${result.data?.errors ?? 0} errors`);
4065
+ }
4066
+ console.log();
4067
+ });
4068
+ var pipeline = program.command("pipeline").description("Manage skill pipeline runs");
4069
+ pipeline.command("start <reportId>").description("Start a skill pipeline for a report").requiredOption("--skill <slug>", "Root skill slug (e.g. workflow-fix-and-ship)").option("--mode <mode>", "Execution mode: handoff (default) or cloud", "handoff").option("--json", "Machine-readable output").action(async (reportId, opts) => {
4070
+ const config = loadConfig();
4071
+ if (!config.apiKey) {
4072
+ console.error("Run `mushi login` first");
4073
+ process.exit(2);
4074
+ }
4075
+ if (!config.projectId) {
4076
+ console.error("No projectId. Run `mushi config projectId <uuid>`");
4077
+ process.exit(2);
4078
+ }
4079
+ const result = await apiCall(
4080
+ `/v1/admin/skills/pipelines`,
4081
+ config,
4082
+ {
4083
+ method: "POST",
4084
+ body: JSON.stringify({
4085
+ project_id: config.projectId,
4086
+ root_skill_slug: opts.skill,
4087
+ report_id: reportId,
4088
+ mode: opts.mode
4089
+ })
4090
+ }
4091
+ );
4092
+ if (!result.ok) {
4093
+ console.error("Failed:", result.error);
4094
+ process.exit(1);
4095
+ }
4096
+ if (opts.json) {
4097
+ console.log(JSON.stringify(result.data, null, 2));
4098
+ return;
4099
+ }
4100
+ const runId = result.data?.id ?? "";
4101
+ const chain = result.data?.chain_slugs ?? [];
4102
+ console.log(`
4103
+ Pipeline started!
4104
+ `);
4105
+ console.log(` Run ID: ${runId.slice(0, 8)}\u2026 (full: ${runId})`);
4106
+ console.log(` Skill: ${opts.skill}`);
4107
+ console.log(` Chain: ${chain.length > 0 ? chain.join(" \u2192 ") : "(root only)"}`);
4108
+ console.log(` Mode: ${opts.mode}`);
4109
+ if (opts.mode === "handoff") {
4110
+ console.log(`
4111
+ Get context packet: mushi pipeline watch ${runId.slice(0, 8)}`);
4112
+ console.log(` Check in step 0: mushi pipeline checkin ${runId.slice(0, 8)} --step 0 --status passed`);
4113
+ }
4114
+ console.log();
4115
+ });
4116
+ pipeline.command("watch <runIdOrPrefix>").description("Watch a pipeline run and print the context packet").option("--json", "Machine-readable output").action(async (runIdOrPrefix, opts) => {
4117
+ const config = loadConfig();
4118
+ if (!config.apiKey) {
4119
+ console.error("Run `mushi login` first");
4120
+ process.exit(2);
4121
+ }
4122
+ let runId = runIdOrPrefix;
4123
+ if (runIdOrPrefix.length < 36) {
4124
+ const list = await apiCall(
4125
+ `/v1/admin/skills/pipelines?project_id=${config.projectId}&limit=50`,
4126
+ config
4127
+ );
4128
+ if (!list.ok) {
4129
+ console.error("Failed:", list.error);
4130
+ process.exit(1);
4131
+ }
4132
+ const match = list.data.find((r) => r.id.startsWith(runIdOrPrefix));
4133
+ if (!match) {
4134
+ console.error("Run not found:", runIdOrPrefix);
4135
+ process.exit(1);
4136
+ }
4137
+ runId = match.id;
4138
+ }
4139
+ const result = await apiCall(
4140
+ `/v1/admin/skills/pipelines/${runId}`,
4141
+ config
4142
+ );
4143
+ if (!result.ok) {
4144
+ console.error("Failed:", result.error);
4145
+ process.exit(1);
4146
+ }
4147
+ if (opts.json) {
4148
+ console.log(JSON.stringify(result.data, null, 2));
4149
+ return;
4150
+ }
4151
+ const run = result.data;
4152
+ const statusIcon = run.status === "completed" ? "\u2705" : run.status === "failed" ? "\u274C" : run.status === "running" ? "\u23F3" : "\u26AA";
4153
+ console.log(`
4154
+ ${statusIcon} Pipeline ${runId.slice(0, 8)} \xB7 ${run.root_skill_slug} \xB7 ${run.mode}
4155
+ `);
4156
+ const steps = run.steps ?? [];
4157
+ for (const step of steps) {
4158
+ const icon = step.status === "passed" ? "\u2705" : step.status === "failed" ? "\u274C" : step.status === "running" ? "\u23F3" : "\u26AA";
4159
+ const pr = step.pr_url ? ` \u2192 ${step.pr_url}` : "";
4160
+ console.log(` ${icon} Step ${step.step_index + 1}: ${step.skill_slug}${pr}`);
4161
+ if (step.notes) console.log(` ${step.notes}`);
4162
+ }
4163
+ if (run.context_packet) {
4164
+ console.log(`
4165
+ ${"\u2500".repeat(60)}`);
4166
+ console.log(`Context Packet (paste into your Cursor agent):
4167
+ `);
4168
+ console.log(run.context_packet.slice(0, 6e3));
4169
+ if (run.context_packet.length > 6e3) console.log("\n\u2026 [truncated \u2014 full packet via --json]");
4170
+ }
4171
+ console.log();
4172
+ });
4173
+ pipeline.command("checkin <runIdOrPrefix>").description("Check in a pipeline step (CLI agent reports status after completing a step)").requiredOption("--step <n>", "Step index (0-based)", parseInt).requiredOption("--status <status>", "Step status: passed | failed | running | skipped").option("--notes <text>", "Optional notes / output summary").option("--pr-url <url>", "PR URL opened during this step").action(async (runIdOrPrefix, opts) => {
4174
+ const config = loadConfig();
4175
+ if (!config.apiKey) {
4176
+ console.error("Run `mushi login` first");
4177
+ process.exit(2);
4178
+ }
4179
+ let runId = runIdOrPrefix;
4180
+ if (runIdOrPrefix.length < 36) {
4181
+ const list = await apiCall(
4182
+ `/v1/admin/skills/pipelines?project_id=${config.projectId}&limit=50`,
4183
+ config
4184
+ );
4185
+ if (!list.ok) {
4186
+ console.error("Failed:", list.error);
4187
+ process.exit(1);
4188
+ }
4189
+ const match = list.data.find((r) => r.id.startsWith(runIdOrPrefix));
4190
+ if (!match) {
4191
+ console.error("Run not found:", runIdOrPrefix);
4192
+ process.exit(1);
4193
+ }
4194
+ runId = match.id;
4195
+ }
4196
+ const result = await apiCall(
4197
+ `/v1/admin/skills/pipelines/${runId}/steps/${opts.step}/checkin`,
4198
+ config,
4199
+ {
4200
+ method: "POST",
4201
+ body: JSON.stringify({ status: opts.status, notes: opts.notes, pr_url: opts.prUrl })
4202
+ }
4203
+ );
4204
+ if (!result.ok) {
4205
+ console.error("Failed:", result.error);
4206
+ process.exit(1);
4207
+ }
4208
+ console.log(` Step ${opts.step} \u2192 ${opts.status}. Console live flow updated.`);
4209
+ console.log(` Next: mushi pipeline watch ${runId.slice(0, 8)}`);
4210
+ console.log();
4211
+ });
package/dist/init.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-NYPX5KXR.js";
9
9
  import {
10
10
  MUSHI_CLI_VERSION
11
- } from "./chunk-4W6VOIQT.js";
11
+ } from "./chunk-BCTHNCIY.js";
12
12
 
13
13
  // src/init.ts
14
14
  import * as p from "@clack/prompts";
package/dist/version.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MUSHI_CLI_VERSION
3
- } from "./chunk-4W6VOIQT.js";
3
+ } from "./chunk-BCTHNCIY.js";
4
4
  export {
5
5
  MUSHI_CLI_VERSION
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mushi-mushi/cli",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "license": "MIT",
5
5
  "description": "CLI for Mushi Mushi — `mushi init` wizard installs the right SDK for your framework, plus report triage and pipeline health commands",
6
6
  "bin": {
@@ -1,6 +0,0 @@
1
- // src/version.ts
2
- var MUSHI_CLI_VERSION = true ? "0.14.0" : "0.0.0-dev";
3
-
4
- export {
5
- MUSHI_CLI_VERSION
6
- };