@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 +71 -0
- package/dist/chunk-BCTHNCIY.js +6 -0
- package/dist/index.js +403 -39
- package/dist/init.js +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-4W6VOIQT.js +0 -6
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.
|
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.
|
|
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
|
|
1434
|
-
const
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
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
|
|
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 ${
|
|
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
|
-
`${
|
|
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
|
-
`${
|
|
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
|
-
`${
|
|
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
|
-
`${
|
|
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
|
|
3764
|
-
if (!
|
|
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
|
-
`${
|
|
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
package/dist/version.js
CHANGED
package/package.json
CHANGED