@mushi-mushi/cli 0.11.1 → 0.12.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/dist/chunk-F43X732E.js +6 -0
- package/dist/index.js +203 -3
- package/dist/init.js +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-EXLNHPSC.js +0 -6
package/dist/index.js
CHANGED
|
@@ -595,7 +595,7 @@ function getFrameworkFromPkg(pkg) {
|
|
|
595
595
|
}
|
|
596
596
|
|
|
597
597
|
// src/version.ts
|
|
598
|
-
var MUSHI_CLI_VERSION = true ? "0.
|
|
598
|
+
var MUSHI_CLI_VERSION = true ? "0.12.0" : "0.0.0-dev";
|
|
599
599
|
|
|
600
600
|
// src/init.ts
|
|
601
601
|
var ENV_FILES = [".env.local", ".env"];
|
|
@@ -1453,11 +1453,10 @@ async function checkEndpointReachability(endpoint, doFetch = globalThis.fetch) {
|
|
|
1453
1453
|
}
|
|
1454
1454
|
async function checkSdkInstall(cwd) {
|
|
1455
1455
|
try {
|
|
1456
|
-
const { readFile: readFile2
|
|
1456
|
+
const { readFile: readFile2 } = await import("fs/promises");
|
|
1457
1457
|
const { join: join6, resolve: resolve2 } = await import("path");
|
|
1458
1458
|
const root = resolve2(cwd);
|
|
1459
1459
|
const pkgPath = join6(root, "package.json");
|
|
1460
|
-
await access(pkgPath);
|
|
1461
1460
|
const pkg = JSON.parse(await readFile2(pkgPath, "utf8"));
|
|
1462
1461
|
const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
1463
1462
|
const sdks = [
|
|
@@ -2757,4 +2756,205 @@ fixes.command("tail").description(
|
|
|
2757
2756
|
}
|
|
2758
2757
|
}
|
|
2759
2758
|
});
|
|
2759
|
+
var stories = program.command("stories").description("TDD story mapping and test generation");
|
|
2760
|
+
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) => {
|
|
2761
|
+
const config = loadConfig();
|
|
2762
|
+
if (!config.apiKey || !config.projectId) {
|
|
2763
|
+
console.error("Run `mushi login` and `mushi init` first");
|
|
2764
|
+
process.exit(1);
|
|
2765
|
+
}
|
|
2766
|
+
const res = await apiCall(
|
|
2767
|
+
`/v1/admin/inventory/${config.projectId}/map-from-live`,
|
|
2768
|
+
config,
|
|
2769
|
+
{
|
|
2770
|
+
method: "POST",
|
|
2771
|
+
body: JSON.stringify({
|
|
2772
|
+
base_url: opts.url,
|
|
2773
|
+
max_pages: parseInt(opts.maxPages, 10),
|
|
2774
|
+
provider: opts.provider,
|
|
2775
|
+
cursor_cloud_refine: opts.cursorRefine ?? false
|
|
2776
|
+
})
|
|
2777
|
+
}
|
|
2778
|
+
);
|
|
2779
|
+
if (!res.ok) {
|
|
2780
|
+
console.error(`Error: ${res.error.message}`);
|
|
2781
|
+
process.exit(1);
|
|
2782
|
+
}
|
|
2783
|
+
console.log(`\u2713 Crawl started \u2014 run id: ${res.data.runId}`);
|
|
2784
|
+
console.log(` Crawling ${opts.url} with ${opts.provider}\u2026`);
|
|
2785
|
+
if (opts.wait) {
|
|
2786
|
+
console.log(" Polling for results\u2026");
|
|
2787
|
+
for (let i = 0; i < 40; i++) {
|
|
2788
|
+
await new Promise((r) => setTimeout(r, 5e3));
|
|
2789
|
+
const runsRes = await apiCall(
|
|
2790
|
+
`/v1/admin/inventory/${config.projectId}/map-runs`,
|
|
2791
|
+
config
|
|
2792
|
+
);
|
|
2793
|
+
if (!runsRes.ok) break;
|
|
2794
|
+
const run = runsRes.data.runs.find((r) => r.id === res.data.runId);
|
|
2795
|
+
if (!run) break;
|
|
2796
|
+
if (run.status === "completed") {
|
|
2797
|
+
console.log(`
|
|
2798
|
+
\u2713 Done! ${run.pages_crawled ?? 0} pages crawled.`);
|
|
2799
|
+
if (run.proposal_id) console.log(` Proposal id: ${run.proposal_id}`);
|
|
2800
|
+
console.log(` Review in the console: Inventory \u2192 Discovery \u2192 Past proposals`);
|
|
2801
|
+
break;
|
|
2802
|
+
}
|
|
2803
|
+
if (run.status === "failed") {
|
|
2804
|
+
console.error(`
|
|
2805
|
+
\u2717 Crawl failed: ${run.error_message ?? "unknown"}`);
|
|
2806
|
+
process.exit(1);
|
|
2807
|
+
}
|
|
2808
|
+
process.stdout.write(".");
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
});
|
|
2812
|
+
var tdd = program.command("tdd").description("TDD test generation and management");
|
|
2813
|
+
tdd.command("gen <storyId>").description("Generate a Playwright TDD test from an inventory user story id").option("--mode <m>", "Gate mode: auto (run immediately) | review (needs approval) | approve (manual)", "review").option("--no-pr", "Skip opening a GitHub PR").action(async (storyId, opts) => {
|
|
2814
|
+
const config = loadConfig();
|
|
2815
|
+
if (!config.apiKey || !config.projectId) {
|
|
2816
|
+
console.error("Run `mushi login` first");
|
|
2817
|
+
process.exit(1);
|
|
2818
|
+
}
|
|
2819
|
+
console.log(`Generating TDD test for story: ${storyId}\u2026`);
|
|
2820
|
+
const res = await apiCall(
|
|
2821
|
+
`/v1/admin/inventory/${config.projectId}/stories/${storyId}/generate-test`,
|
|
2822
|
+
config,
|
|
2823
|
+
{ method: "POST", body: JSON.stringify({ automation_mode: opts.mode, open_pr: opts.pr }) }
|
|
2824
|
+
);
|
|
2825
|
+
if (!res.ok) {
|
|
2826
|
+
console.error(`Error: ${res.error.message}`);
|
|
2827
|
+
process.exit(1);
|
|
2828
|
+
}
|
|
2829
|
+
console.log(`\u2713 Test generated \u2014 qa_story id: ${res.data.qaStoryId}`);
|
|
2830
|
+
console.log(` Approval status: ${res.data.approvalStatus}`);
|
|
2831
|
+
if (res.data.prUrl) console.log(` PR: ${res.data.prUrl}`);
|
|
2832
|
+
if (res.data.needsHumanReview) console.log(` \u26A0 Human review recommended \u2014 some selectors or flows are uncertain.`);
|
|
2833
|
+
});
|
|
2834
|
+
tdd.command("improve").description("Run PDCA auto-improve on recently failed QA tests").action(async () => {
|
|
2835
|
+
const config = loadConfig();
|
|
2836
|
+
if (!config.apiKey || !config.projectId) {
|
|
2837
|
+
console.error("Run `mushi login` first");
|
|
2838
|
+
process.exit(1);
|
|
2839
|
+
}
|
|
2840
|
+
console.log("Running PDCA QA story improver\u2026");
|
|
2841
|
+
const res = await apiCall(
|
|
2842
|
+
"/v1/admin/pdca/improve-qa-stories",
|
|
2843
|
+
config,
|
|
2844
|
+
{ method: "POST", body: JSON.stringify({ project_id: config.projectId }) }
|
|
2845
|
+
);
|
|
2846
|
+
if (!res.ok) {
|
|
2847
|
+
console.error(`Error: ${res.error.message}`);
|
|
2848
|
+
process.exit(1);
|
|
2849
|
+
}
|
|
2850
|
+
console.log(`\u2713 Improved ${res.data.improved} QA stories.`);
|
|
2851
|
+
});
|
|
2852
|
+
tdd.command("run <qaStoryId>").description("Trigger a manual run for a QA story").action(async (qaStoryId) => {
|
|
2853
|
+
const config = loadConfig();
|
|
2854
|
+
if (!config.apiKey || !config.projectId) {
|
|
2855
|
+
console.error("Run `mushi login` first");
|
|
2856
|
+
process.exit(1);
|
|
2857
|
+
}
|
|
2858
|
+
const res = await apiCall(
|
|
2859
|
+
`/v1/admin/projects/${config.projectId}/qa-stories/${qaStoryId}/run`,
|
|
2860
|
+
config,
|
|
2861
|
+
{ method: "POST" }
|
|
2862
|
+
);
|
|
2863
|
+
if (!res.ok) {
|
|
2864
|
+
console.error(`Error: ${res.error.message}`);
|
|
2865
|
+
process.exit(1);
|
|
2866
|
+
}
|
|
2867
|
+
console.log(`\u2713 Run queued \u2014 id: ${res.data.runId}`);
|
|
2868
|
+
});
|
|
2869
|
+
tdd.command("pending").description("List QA tests pending review").action(async () => {
|
|
2870
|
+
const config = loadConfig();
|
|
2871
|
+
if (!config.apiKey || !config.projectId) {
|
|
2872
|
+
console.error("Run `mushi login` first");
|
|
2873
|
+
process.exit(1);
|
|
2874
|
+
}
|
|
2875
|
+
const res = await apiCall(
|
|
2876
|
+
`/v1/admin/inventory/${config.projectId}/stories/pending-review`,
|
|
2877
|
+
config
|
|
2878
|
+
);
|
|
2879
|
+
if (!res.ok) {
|
|
2880
|
+
console.error(`Error: ${res.error.message}`);
|
|
2881
|
+
process.exit(1);
|
|
2882
|
+
}
|
|
2883
|
+
if (res.data.stories.length === 0) {
|
|
2884
|
+
console.log("No stories pending review.");
|
|
2885
|
+
return;
|
|
2886
|
+
}
|
|
2887
|
+
console.log(`${res.data.stories.length} stories pending review:
|
|
2888
|
+
`);
|
|
2889
|
+
for (const s of res.data.stories) {
|
|
2890
|
+
console.log(` ${s.id} ${s.name}${s.origin_story_node_id ? ` (story: ${s.origin_story_node_id})` : ""}`);
|
|
2891
|
+
if (s.generated_pr_url) console.log(` PR: ${s.generated_pr_url}`);
|
|
2892
|
+
}
|
|
2893
|
+
console.log(`
|
|
2894
|
+
Approve: mushi tdd approve <id>`);
|
|
2895
|
+
});
|
|
2896
|
+
tdd.command("approve <qaStoryId>").description("Approve a pending QA story (enables it in the schedule)").option("--reject", "Reject instead of approve").action(async (qaStoryId, opts) => {
|
|
2897
|
+
const config = loadConfig();
|
|
2898
|
+
if (!config.apiKey || !config.projectId) {
|
|
2899
|
+
console.error("Run `mushi login` first");
|
|
2900
|
+
process.exit(1);
|
|
2901
|
+
}
|
|
2902
|
+
const status = opts.reject ? "rejected" : "approved";
|
|
2903
|
+
const res = await apiCall(
|
|
2904
|
+
`/v1/admin/inventory/${config.projectId}/stories/${qaStoryId}/approval`,
|
|
2905
|
+
config,
|
|
2906
|
+
{ method: "PATCH", body: JSON.stringify({ status }) }
|
|
2907
|
+
);
|
|
2908
|
+
if (!res.ok) {
|
|
2909
|
+
console.error(`Error: ${res.error.message}`);
|
|
2910
|
+
process.exit(1);
|
|
2911
|
+
}
|
|
2912
|
+
console.log(`\u2713 Story ${status}.`);
|
|
2913
|
+
});
|
|
2914
|
+
var keys = program.command("keys").description("Manage API key pool (BYOK)");
|
|
2915
|
+
keys.command("list").description("List all API keys in the pool with their status").action(async () => {
|
|
2916
|
+
const config = loadConfig();
|
|
2917
|
+
if (!config.apiKey || !config.projectId) {
|
|
2918
|
+
console.error("Run `mushi login` first");
|
|
2919
|
+
process.exit(1);
|
|
2920
|
+
}
|
|
2921
|
+
const res = await apiCall(
|
|
2922
|
+
`/v1/admin/byok/keys?project_id=${encodeURIComponent(config.projectId)}`,
|
|
2923
|
+
config
|
|
2924
|
+
);
|
|
2925
|
+
if (!res.ok) {
|
|
2926
|
+
console.error(`Error: ${res.error.message}`);
|
|
2927
|
+
process.exit(1);
|
|
2928
|
+
}
|
|
2929
|
+
if (res.data.keys.length === 0) {
|
|
2930
|
+
console.log("No keys configured.");
|
|
2931
|
+
return;
|
|
2932
|
+
}
|
|
2933
|
+
for (const k of res.data.keys) {
|
|
2934
|
+
const cooldown = k.cooldown_until && new Date(k.cooldown_until) > /* @__PURE__ */ new Date() ? ` [cooldown until ${new Date(k.cooldown_until).toLocaleTimeString()}]` : "";
|
|
2935
|
+
console.log(`${k.provider_slug.padEnd(14)} [${k.status}] p=${k.priority} ${k.label ?? "(no label)"}${cooldown} \u2014 ${k.id}`);
|
|
2936
|
+
}
|
|
2937
|
+
});
|
|
2938
|
+
keys.command("add").description("Add a new API key to the pool").requiredOption("--provider <p>", "Provider: anthropic, openai, firecrawl, browserbase, cursor").option("--key <k>", "The API key value (prefer the MUSHI_BYOK_KEY env var to keep it out of shell history)").option("--label <l>", "Human-readable label").option("--priority <n>", "Priority (lower = higher priority)", "100").action(async (opts) => {
|
|
2939
|
+
const config = loadConfig();
|
|
2940
|
+
if (!config.apiKey || !config.projectId) {
|
|
2941
|
+
console.error("Run `mushi login` first");
|
|
2942
|
+
process.exit(1);
|
|
2943
|
+
}
|
|
2944
|
+
const key = process.env.MUSHI_BYOK_KEY ?? opts.key;
|
|
2945
|
+
if (!key) {
|
|
2946
|
+
console.error("Provide the key via the MUSHI_BYOK_KEY env var (recommended) or --key <value>.");
|
|
2947
|
+
process.exit(1);
|
|
2948
|
+
}
|
|
2949
|
+
const res = await apiCall(
|
|
2950
|
+
"/v1/admin/byok/keys",
|
|
2951
|
+
config,
|
|
2952
|
+
{ method: "POST", body: JSON.stringify({ project_id: config.projectId, provider_slug: opts.provider, key, label: opts.label, priority: parseInt(opts.priority, 10) }) }
|
|
2953
|
+
);
|
|
2954
|
+
if (!res.ok) {
|
|
2955
|
+
console.error(`Error: ${res.error.message}`);
|
|
2956
|
+
process.exit(1);
|
|
2957
|
+
}
|
|
2958
|
+
console.log(`\u2713 Key added \u2014 id: ${res.data.id}`);
|
|
2959
|
+
});
|
|
2760
2960
|
program.parse();
|
package/dist/init.js
CHANGED
package/dist/version.js
CHANGED
package/package.json
CHANGED