mergestorm 0.2.0 → 0.3.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 +43 -10
- package/dist/api.d.ts +41 -4
- package/dist/api.js +14 -0
- package/dist/cli.js +8 -1
- package/dist/commands/branches.d.ts +10 -0
- package/dist/commands/branches.js +85 -0
- package/dist/commands/credits.js +10 -4
- package/dist/commands/jobs.d.ts +4 -0
- package/dist/commands/jobs.js +43 -5
- package/dist/commands/login.js +2 -0
- package/dist/commands/review.d.ts +5 -0
- package/dist/commands/review.js +22 -2
- package/dist/shell.d.ts +7 -0
- package/dist/shell.js +65 -62
- package/dist/ui/ansi.d.ts +4 -1
- package/dist/ui/ansi.js +16 -9
- package/dist/ui/banner.d.ts +1 -0
- package/dist/ui/banner.js +84 -14
- package/dist/ui/box.d.ts +31 -0
- package/dist/ui/box.js +99 -0
- package/dist/ui/ctrl-c-exit.d.ts +13 -0
- package/dist/ui/ctrl-c-exit.js +22 -0
- package/dist/ui/logo.d.ts +5 -0
- package/dist/ui/logo.js +12 -0
- package/dist/ui/prompt.d.ts +18 -0
- package/dist/ui/prompt.js +237 -0
- package/dist/ui/select.d.ts +13 -0
- package/dist/ui/select.js +103 -0
- package/dist/ui/usage.d.ts +24 -0
- package/dist/ui/usage.js +67 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -20,29 +20,56 @@ npm install -g mergestorm
|
|
|
20
20
|
mergestorm login
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
### From this monorepo (development)
|
|
23
|
+
### From this monorepo (development / local UI testing)
|
|
24
|
+
|
|
25
|
+
You do **not** need to bump/publish npm to try shell layout changes. From the repo root:
|
|
24
26
|
|
|
25
27
|
```bash
|
|
28
|
+
# Fastest — runs TypeScript via tsx (no build, no version bump):
|
|
29
|
+
npm run dev --workspace=mergestorm
|
|
30
|
+
|
|
31
|
+
# Or build once, then run the compiled binary:
|
|
32
|
+
npm run build --workspace=mergestorm
|
|
33
|
+
node packages/mergestorm-cli/dist/cli.js
|
|
34
|
+
|
|
35
|
+
# Optional: expose a local binary as `mergestorm` without publishing
|
|
26
36
|
npm run build --workspace=mergestorm
|
|
27
|
-
|
|
28
|
-
#
|
|
29
|
-
npm
|
|
37
|
+
npm link --workspace=mergestorm
|
|
38
|
+
mergestorm # uses your local build
|
|
39
|
+
# later: npm unlink -g mergestorm && npm install -g mergestorm
|
|
30
40
|
```
|
|
31
41
|
|
|
32
42
|
## Interactive shell
|
|
33
43
|
|
|
34
|
-
On a TTY, bare `mergestorm` (or `mergestorm shell`) opens a branded REPL
|
|
44
|
+
On a TTY, bare `mergestorm` (or `mergestorm shell`) opens a branded REPL: a **full-width** welcome panel (logo + status on the left, tips / what's new on the right) and a **full-width** bordered input box with a live `/` slash-command dropdown. Non-TTY (CI/pipes) prints usage instead — no hanging prompt.
|
|
35
45
|
|
|
36
46
|
```
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
╭─ mergestorm v0.3.1 ──────────────────────────────────────────────────────────╮
|
|
48
|
+
│ ▟██████████▛ Tips for getting started │
|
|
49
|
+
│ ▜████████▛ review review main...HEAD │
|
|
50
|
+
│ ▝▜████▛▘ branches pick a reviewed branch │
|
|
51
|
+
│ ▜██▛ /help list all commands │
|
|
52
|
+
│ ██ │
|
|
53
|
+
│ ▝▘ What's new in v0.3.1 │
|
|
54
|
+
│ ● msk… · maelstrom • Full-width welcome + input │
|
|
55
|
+
│ [████░░░░░░░░░░] 12% used │
|
|
56
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
57
|
+
mergestorm
|
|
58
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
59
|
+
│ › /re │
|
|
60
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
61
|
+
/review Review git diff (default main...HEAD)
|
|
40
62
|
|
|
41
63
|
mergestorm> credits
|
|
42
|
-
mergestorm>
|
|
64
|
+
mergestorm> branches
|
|
65
|
+
mergestorm> chain
|
|
43
66
|
mergestorm> exit
|
|
44
67
|
```
|
|
45
68
|
|
|
69
|
+
Bare command names still work exactly as before (`review`, `help`, `exit`, …) — typing `/` at the start of a line just opens the live autocomplete dropdown (filtered as you type, navigate with Up/Down, accept with Tab/Enter/Right). Arrow-key history, Ctrl+A/E, Ctrl+U/K work as in a regular shell. At the idle prompt, **Ctrl+C twice** (within ~1.5s) exits the shell; a single Ctrl+C clears the line and shows a confirm hint. While `review` / `status` is waiting, Ctrl+C detaches or aborts that wait (job may keep running) — it does not exit the shell. Ctrl+D on an empty line, or `exit` / `quit`, also leave the shell. On a dumb terminal, without color, or when piped, the box falls back to plain ASCII borders (or no shell at all for non-TTY input).
|
|
70
|
+
|
|
71
|
+
`credits` / `usage` print a usage meter — long bar + `N% used`, then `Resets … (UTC)` — for standard and premium. `branches` (alias `chains`) lists recently reviewed branches; on a TTY you arrow-select one to open its review-chain timeline. `chain [slug]` shows that timeline directly (defaults to `local/<current-git-branch>`).
|
|
72
|
+
|
|
46
73
|
## Commands
|
|
47
74
|
|
|
48
75
|
One-shot subcommands work the same inside the shell and from argv (scripts/CI):
|
|
@@ -55,8 +82,10 @@ mergestorm login --key Paste an existing API key instead (headless/CI)
|
|
|
55
82
|
mergestorm logout Remove the stored API key
|
|
56
83
|
mergestorm review [base] [head] Review git diff base...head (default: main...HEAD)
|
|
57
84
|
mergestorm status <job_id> Poll a review job and print JSON
|
|
58
|
-
mergestorm credits [--json] Standard + premium credit balance
|
|
85
|
+
mergestorm credits [--json] Standard + premium credit balance (usage bars)
|
|
59
86
|
mergestorm jobs [n] [--json] Recent review jobs (default 10, max 50)
|
|
87
|
+
mergestorm branches [n] Recently reviewed branches (arrow-pick on TTY)
|
|
88
|
+
mergestorm chain [slug] Branch review-chain timeline (default: current branch)
|
|
60
89
|
mergestorm whoami [--json] Key prefix, plan, API base, config path
|
|
61
90
|
mergestorm thread <slug> Jobs in a review thread
|
|
62
91
|
```
|
|
@@ -91,6 +120,10 @@ Fetches a job by id and prints the raw JSON (status, verdict, summary, findings)
|
|
|
91
120
|
|
|
92
121
|
Require a logged-in key and API support for `GET /api/v1/me` and `GET /api/v1/reviews`. Older API deployments degrade gracefully (banner shows local key only).
|
|
93
122
|
|
|
123
|
+
### `branches` / `chain`
|
|
124
|
+
|
|
125
|
+
`branches` calls `GET /api/v1/threads` for recently active review threads (branches). On a TTY it opens an arrow-key selector; Enter loads that thread's job timeline. `chain [slug]` is the direct timeline view — omit the slug to use `local/<current-git-branch>` (same slug `review` stamps).
|
|
126
|
+
|
|
94
127
|
## Context pack (`.mergestorm/`)
|
|
95
128
|
|
|
96
129
|
Any `*.md` / `*.txt` files under `./.mergestorm/` (one level of nesting) are attached as context so the reviewer understands intent, conventions, or migration notes without you resending history.
|
package/dist/api.d.ts
CHANGED
|
@@ -18,16 +18,18 @@ export type MeResponse = {
|
|
|
18
18
|
};
|
|
19
19
|
plan_key: string;
|
|
20
20
|
plan_label_key?: string;
|
|
21
|
+
/** Start of next UTC month (usage meters reset). Older APIs omit this. */
|
|
22
|
+
resets_at?: string;
|
|
21
23
|
usage: {
|
|
22
24
|
standard: {
|
|
23
25
|
used: number;
|
|
24
|
-
limit: number;
|
|
25
|
-
remaining: number;
|
|
26
|
+
limit: number | null;
|
|
27
|
+
remaining: number | null;
|
|
26
28
|
};
|
|
27
29
|
premium: {
|
|
28
30
|
used: number;
|
|
29
|
-
limit: number;
|
|
30
|
-
remaining: number;
|
|
31
|
+
limit: number | null;
|
|
32
|
+
remaining: number | null;
|
|
31
33
|
};
|
|
32
34
|
};
|
|
33
35
|
};
|
|
@@ -43,6 +45,41 @@ export type JobListItem = {
|
|
|
43
45
|
finished_at: string | null;
|
|
44
46
|
credits: number;
|
|
45
47
|
};
|
|
48
|
+
export type ThreadListItem = {
|
|
49
|
+
slug: string;
|
|
50
|
+
branch: string | null;
|
|
51
|
+
owner: string | null;
|
|
52
|
+
repo: string | null;
|
|
53
|
+
pr_number: number | null;
|
|
54
|
+
status: string;
|
|
55
|
+
job_count: number;
|
|
56
|
+
last_verdict: string | null;
|
|
57
|
+
last_activity_at: string;
|
|
58
|
+
};
|
|
59
|
+
export type ThreadDetail = {
|
|
60
|
+
thread_id: string;
|
|
61
|
+
slug: string;
|
|
62
|
+
source?: string;
|
|
63
|
+
branch: string | null;
|
|
64
|
+
owner: string | null;
|
|
65
|
+
repo: string | null;
|
|
66
|
+
pr_number: number | null;
|
|
67
|
+
status: string;
|
|
68
|
+
pr_linked_at: string | null;
|
|
69
|
+
job_count: number;
|
|
70
|
+
created_at: string;
|
|
71
|
+
jobs: {
|
|
72
|
+
job_id: string;
|
|
73
|
+
status: string;
|
|
74
|
+
summary?: string | null;
|
|
75
|
+
verdict?: string | null;
|
|
76
|
+
thread_job_number?: number | null;
|
|
77
|
+
created_at: string;
|
|
78
|
+
finished_at?: string | null;
|
|
79
|
+
}[];
|
|
80
|
+
};
|
|
46
81
|
/** Fetch /api/v1/me; returns null on 404/network so callers can degrade. */
|
|
47
82
|
export declare function getMe(cfg?: Config): Promise<MeResponse | null>;
|
|
48
83
|
export declare function listJobs(limit?: number, cfg?: Config): Promise<JobListItem[]>;
|
|
84
|
+
/** Recently reviewed branches/threads (Bearer GET /api/v1/threads). */
|
|
85
|
+
export declare function listThreads(limit?: number, cfg?: Config): Promise<ThreadListItem[]>;
|
package/dist/api.js
CHANGED
|
@@ -69,3 +69,17 @@ export async function listJobs(limit = 10, cfg) {
|
|
|
69
69
|
const items = body.items;
|
|
70
70
|
return Array.isArray(items) ? items : [];
|
|
71
71
|
}
|
|
72
|
+
/** Recently reviewed branches/threads (Bearer GET /api/v1/threads). */
|
|
73
|
+
export async function listThreads(limit = 20, cfg) {
|
|
74
|
+
const resolved = cfg ?? (await loadConfig());
|
|
75
|
+
const capped = Math.min(Math.max(limit, 1), 50);
|
|
76
|
+
const { status, body } = await apiFetch(resolved, `/api/v1/threads?limit=${capped}`);
|
|
77
|
+
if (status === 404) {
|
|
78
|
+
throw new CommandError("Branch list is not available on this server yet. Deploy the API update or use the dashboard.");
|
|
79
|
+
}
|
|
80
|
+
if (status !== 200) {
|
|
81
|
+
throw new CommandError(`Failed to list branches (HTTP ${status}): ${JSON.stringify(body)}`);
|
|
82
|
+
}
|
|
83
|
+
const items = body.items;
|
|
84
|
+
return Array.isArray(items) ? items : [];
|
|
85
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { stdin as input, stdout as output } from "node:process";
|
|
3
3
|
import { DEFAULT_API } from "./config.js";
|
|
4
4
|
import { CommandError } from "./errors.js";
|
|
5
|
+
import { cmdBranches, cmdChain } from "./commands/branches.js";
|
|
5
6
|
import { cmdCredits } from "./commands/credits.js";
|
|
6
7
|
import { cmdJobs, cmdThread } from "./commands/jobs.js";
|
|
7
8
|
import { cmdLogin } from "./commands/login.js";
|
|
@@ -22,8 +23,10 @@ function usage() {
|
|
|
22
23
|
mergestorm logout Remove the stored API key
|
|
23
24
|
mergestorm review [base] [head] Review git diff base...head (default: main...HEAD)
|
|
24
25
|
mergestorm status <job_id> Poll a review job (JSON)
|
|
25
|
-
mergestorm credits [--json] Credit balance
|
|
26
|
+
mergestorm credits [--json] Credit balance (usage bars)
|
|
26
27
|
mergestorm jobs [n] [--json] Recent review jobs
|
|
28
|
+
mergestorm branches [n] Recently reviewed branches (pick on TTY)
|
|
29
|
+
mergestorm chain [slug] Branch review-chain timeline (default: current)
|
|
27
30
|
mergestorm whoami [--json] Key + plan + API base
|
|
28
31
|
mergestorm thread <slug> Jobs in a review thread
|
|
29
32
|
|
|
@@ -48,6 +51,10 @@ async function runCommand(cmd, args) {
|
|
|
48
51
|
return cmdCredits(args);
|
|
49
52
|
if (cmd === "jobs")
|
|
50
53
|
return cmdJobs(args);
|
|
54
|
+
if (cmd === "branches" || cmd === "chains")
|
|
55
|
+
return cmdBranches(args);
|
|
56
|
+
if (cmd === "chain")
|
|
57
|
+
return cmdChain(args);
|
|
51
58
|
if (cmd === "whoami")
|
|
52
59
|
return cmdWhoami(args);
|
|
53
60
|
if (cmd === "thread") {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ThreadListItem } from "../api.js";
|
|
2
|
+
/** One-line label for a branch/thread row (also used by tests). */
|
|
3
|
+
export declare function formatBranchRow(item: ThreadListItem): string;
|
|
4
|
+
/** Interactive list of recently reviewed branches → open chain on select. */
|
|
5
|
+
export declare function cmdBranches(args?: string[]): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Show the review-chain timeline for a branch/thread.
|
|
8
|
+
* Defaults to `local/<current-git-branch>` when no slug is given.
|
|
9
|
+
*/
|
|
10
|
+
export declare function cmdChain(args?: string[]): Promise<void>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
2
|
+
import { listThreads } from "../api.js";
|
|
3
|
+
import { loadConfig } from "../config.js";
|
|
4
|
+
import { CommandError } from "../errors.js";
|
|
5
|
+
import { git } from "../git.js";
|
|
6
|
+
import { ansi } from "../ui/ansi.js";
|
|
7
|
+
import { selectFromList } from "../ui/select.js";
|
|
8
|
+
import { cmdThread, relativeWhen } from "./jobs.js";
|
|
9
|
+
/** One-line label for a branch/thread row (also used by tests). */
|
|
10
|
+
export function formatBranchRow(item) {
|
|
11
|
+
const name = (item.branch?.trim() || item.slug).slice(0, 36);
|
|
12
|
+
const jobs = `${item.job_count} job${item.job_count === 1 ? "" : "s"}`;
|
|
13
|
+
const verdict = item.last_verdict ?? "-";
|
|
14
|
+
const when = relativeWhen(item.last_activity_at);
|
|
15
|
+
const pr = item.owner && item.repo && item.pr_number != null
|
|
16
|
+
? ` PR #${item.pr_number}`
|
|
17
|
+
: "";
|
|
18
|
+
const closed = item.status === "closed" ? " closed" : "";
|
|
19
|
+
return `${name.padEnd(36)} ${jobs.padEnd(8)} ${verdict.padEnd(12)} ${when}${pr}${closed}`;
|
|
20
|
+
}
|
|
21
|
+
function printBranchTable(items) {
|
|
22
|
+
const header = ["BRANCH / SLUG", "JOBS", "VERDICT", "WHEN", "PR"];
|
|
23
|
+
const rows = items.map((t) => [
|
|
24
|
+
(t.branch?.trim() || t.slug).slice(0, 36),
|
|
25
|
+
String(t.job_count),
|
|
26
|
+
t.last_verdict ?? "-",
|
|
27
|
+
relativeWhen(t.last_activity_at),
|
|
28
|
+
t.pr_number != null ? `#${t.pr_number}` : "-",
|
|
29
|
+
]);
|
|
30
|
+
const widths = header.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i]?.length ?? 0)));
|
|
31
|
+
const fmt = (cols) => " " + cols.map((c, i) => c.padEnd(widths[i])).join(" ");
|
|
32
|
+
console.log(ansi.dim(fmt(header)));
|
|
33
|
+
for (const r of rows)
|
|
34
|
+
console.log(fmt(r));
|
|
35
|
+
console.log(ansi.dim(" open a chain: chain <slug> · or re-run on a TTY to pick"));
|
|
36
|
+
}
|
|
37
|
+
/** Interactive list of recently reviewed branches → open chain on select. */
|
|
38
|
+
export async function cmdBranches(args = []) {
|
|
39
|
+
const asJson = args.includes("--json");
|
|
40
|
+
const nArg = args.find((a) => /^\d+$/.test(a));
|
|
41
|
+
const limit = nArg ? Number.parseInt(nArg, 10) : 20;
|
|
42
|
+
const cfg = await loadConfig();
|
|
43
|
+
const items = await listThreads(limit, cfg);
|
|
44
|
+
if (asJson) {
|
|
45
|
+
console.log(JSON.stringify({ items }, null, 2));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (items.length === 0) {
|
|
49
|
+
console.log(ansi.dim(" No reviewed branches yet. Run `review` on a branch first."));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (!input.isTTY || !output.isTTY) {
|
|
53
|
+
printBranchTable(items);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const picked = await selectFromList({
|
|
57
|
+
title: "Recently reviewed branches",
|
|
58
|
+
items,
|
|
59
|
+
render: (item) => formatBranchRow(item),
|
|
60
|
+
});
|
|
61
|
+
if (!picked) {
|
|
62
|
+
console.log(ansi.dim(" cancelled"));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
await cmdThread(picked.slug, []);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Show the review-chain timeline for a branch/thread.
|
|
69
|
+
* Defaults to `local/<current-git-branch>` when no slug is given.
|
|
70
|
+
*/
|
|
71
|
+
export async function cmdChain(args = []) {
|
|
72
|
+
const asJson = args.includes("--json");
|
|
73
|
+
const positional = args.filter((a) => a !== "--json");
|
|
74
|
+
let slug = positional[0]?.trim() ?? "";
|
|
75
|
+
if (!slug) {
|
|
76
|
+
try {
|
|
77
|
+
const branch = git(["rev-parse", "--abbrev-ref", "HEAD"]).trim() || "local";
|
|
78
|
+
slug = `local/${branch}`.replace(/[^a-zA-Z0-9._/-]/g, "-").slice(0, 120);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
throw new CommandError("usage: chain <slug> (or run inside a git repo to use the current branch)");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
await cmdThread(slug, asJson ? ["--json"] : []);
|
|
85
|
+
}
|
package/dist/commands/credits.js
CHANGED
|
@@ -2,6 +2,7 @@ import { getMe } from "../api.js";
|
|
|
2
2
|
import { loadConfig } from "../config.js";
|
|
3
3
|
import { CommandError } from "../errors.js";
|
|
4
4
|
import { ansi } from "../ui/ansi.js";
|
|
5
|
+
import { formatResetLabel, usageBar } from "../ui/usage.js";
|
|
5
6
|
export async function cmdCredits(args) {
|
|
6
7
|
const asJson = args.includes("--json");
|
|
7
8
|
const cfg = await loadConfig();
|
|
@@ -11,12 +12,17 @@ export async function cmdCredits(args) {
|
|
|
11
12
|
"Try `mergestorm login` or open https://mergestorm.ai/billing");
|
|
12
13
|
}
|
|
13
14
|
if (asJson) {
|
|
14
|
-
console.log(JSON.stringify(me.usage, null, 2));
|
|
15
|
+
console.log(JSON.stringify({ usage: me.usage, resets_at: me.resets_at ?? null }, null, 2));
|
|
15
16
|
return;
|
|
16
17
|
}
|
|
17
18
|
const s = me.usage.standard;
|
|
18
19
|
const p = me.usage.premium;
|
|
19
|
-
|
|
20
|
-
console.log(`
|
|
21
|
-
console.log(
|
|
20
|
+
// Claude Usage tab layout: label → long bar + "N% used" → Resets line.
|
|
21
|
+
console.log(` ${ansi.bold("Standard")}`);
|
|
22
|
+
console.log(` ${usageBar(s.used, s.limit)}`);
|
|
23
|
+
console.log("");
|
|
24
|
+
console.log(` ${ansi.bold("Premium")}`);
|
|
25
|
+
console.log(` ${usageBar(p.used, p.limit)}`);
|
|
26
|
+
console.log("");
|
|
27
|
+
console.log(` ${ansi.dim(formatResetLabel(me.resets_at))}`);
|
|
22
28
|
}
|
package/dist/commands/jobs.d.ts
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
|
+
import { type ThreadDetail } from "../api.js";
|
|
2
|
+
export declare function relativeWhen(iso: string): string;
|
|
1
3
|
export declare function cmdJobs(args: string[]): Promise<void>;
|
|
4
|
+
/** Numbered chain timeline for a thread (branch/PR meta when present). */
|
|
5
|
+
export declare function formatThreadTimeline(data: ThreadDetail): string[];
|
|
2
6
|
export declare function cmdThread(slug: string, args?: string[]): Promise<void>;
|
package/dist/commands/jobs.js
CHANGED
|
@@ -2,7 +2,7 @@ import { apiFetch, listJobs } from "../api.js";
|
|
|
2
2
|
import { loadConfig } from "../config.js";
|
|
3
3
|
import { CommandError } from "../errors.js";
|
|
4
4
|
import { ansi } from "../ui/ansi.js";
|
|
5
|
-
function relativeWhen(iso) {
|
|
5
|
+
export function relativeWhen(iso) {
|
|
6
6
|
const t = new Date(iso).getTime();
|
|
7
7
|
if (Number.isNaN(t))
|
|
8
8
|
return "—";
|
|
@@ -45,7 +45,37 @@ export async function cmdJobs(args) {
|
|
|
45
45
|
console.log(ansi.dim(fmt(header)));
|
|
46
46
|
for (const r of rows)
|
|
47
47
|
console.log(fmt(r));
|
|
48
|
-
console.log(ansi.dim(" full history: https://mergestorm.ai/dashboard/api"));
|
|
48
|
+
console.log(ansi.dim(" branches: `branches` · full history: https://mergestorm.ai/dashboard/api"));
|
|
49
|
+
}
|
|
50
|
+
/** Numbered chain timeline for a thread (branch/PR meta when present). */
|
|
51
|
+
export function formatThreadTimeline(data) {
|
|
52
|
+
const lines = [];
|
|
53
|
+
const title = data.branch?.trim()
|
|
54
|
+
? `${data.branch} (${data.slug})`
|
|
55
|
+
: data.slug;
|
|
56
|
+
lines.push(` ${ansi.bold(title)} · ${data.job_count} job(s) · ${data.status}`);
|
|
57
|
+
if (data.owner && data.repo) {
|
|
58
|
+
const pr = data.pr_number != null
|
|
59
|
+
? `https://github.com/${data.owner}/${data.repo}/pull/${data.pr_number}`
|
|
60
|
+
: `${data.owner}/${data.repo}`;
|
|
61
|
+
lines.push(` ${ansi.dim(pr)}`);
|
|
62
|
+
}
|
|
63
|
+
const jobs = [...(data.jobs ?? [])].sort((a, b) => {
|
|
64
|
+
const an = a.thread_job_number ?? Infinity;
|
|
65
|
+
const bn = b.thread_job_number ?? Infinity;
|
|
66
|
+
if (an !== bn)
|
|
67
|
+
return an - bn;
|
|
68
|
+
return new Date(a.created_at).getTime() - new Date(b.created_at).getTime();
|
|
69
|
+
});
|
|
70
|
+
if (jobs.length === 0) {
|
|
71
|
+
lines.push(ansi.dim(" (no jobs in this chain yet)"));
|
|
72
|
+
return lines;
|
|
73
|
+
}
|
|
74
|
+
for (const j of jobs) {
|
|
75
|
+
const n = j.thread_job_number != null ? `#${j.thread_job_number}` : "#?";
|
|
76
|
+
lines.push(` ${n.padEnd(4)} ${j.job_id.slice(0, 8)} ${(j.status ?? "-").padEnd(12)} ${(j.verdict ?? "-").padEnd(16)} ${relativeWhen(j.created_at)}`);
|
|
77
|
+
}
|
|
78
|
+
return lines;
|
|
49
79
|
}
|
|
50
80
|
export async function cmdThread(slug, args = []) {
|
|
51
81
|
const asJson = args.includes("--json");
|
|
@@ -62,8 +92,16 @@ export async function cmdThread(slug, args = []) {
|
|
|
62
92
|
return;
|
|
63
93
|
}
|
|
64
94
|
const data = body;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
95
|
+
for (const line of formatThreadTimeline({
|
|
96
|
+
...data,
|
|
97
|
+
branch: data.branch ?? null,
|
|
98
|
+
owner: data.owner ?? null,
|
|
99
|
+
repo: data.repo ?? null,
|
|
100
|
+
pr_number: data.pr_number ?? null,
|
|
101
|
+
status: data.status ?? "active",
|
|
102
|
+
pr_linked_at: data.pr_linked_at ?? null,
|
|
103
|
+
jobs: data.jobs ?? [],
|
|
104
|
+
})) {
|
|
105
|
+
console.log(line);
|
|
68
106
|
}
|
|
69
107
|
}
|
package/dist/commands/login.js
CHANGED
|
@@ -3,4 +3,9 @@ export type ReviewOptions = {
|
|
|
3
3
|
/** When true, show Ctrl+C detach hint during poll. */
|
|
4
4
|
interactive?: boolean;
|
|
5
5
|
};
|
|
6
|
+
/** Parse `origin` remote into GitHub owner/repo when possible. */
|
|
7
|
+
export declare function parseGithubOriginRepo(): {
|
|
8
|
+
owner: string;
|
|
9
|
+
repo: string;
|
|
10
|
+
} | null;
|
|
6
11
|
export declare function cmdReview(args: string[], opts?: ReviewOptions): Promise<void>;
|
package/dist/commands/review.js
CHANGED
|
@@ -3,6 +3,21 @@ import { loadConfig } from "../config.js";
|
|
|
3
3
|
import { CommandError, DetachedError } from "../errors.js";
|
|
4
4
|
import { collectChangedFiles, collectContextFiles, git } from "../git.js";
|
|
5
5
|
import { ansi } from "../ui/ansi.js";
|
|
6
|
+
/** Parse `origin` remote into GitHub owner/repo when possible. */
|
|
7
|
+
export function parseGithubOriginRepo() {
|
|
8
|
+
try {
|
|
9
|
+
const url = git(["remote", "get-url", "origin"]).trim();
|
|
10
|
+
if (!url)
|
|
11
|
+
return null;
|
|
12
|
+
const m = url.match(/^(?:https?:\/\/|git@)?github\.com[:/]([A-Za-z0-9_-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?\/?$/i);
|
|
13
|
+
if (!m?.[1] || !m[2])
|
|
14
|
+
return null;
|
|
15
|
+
return { owner: m[1], repo: m[2] };
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
6
21
|
function sleep(ms, signal) {
|
|
7
22
|
return new Promise((resolve, reject) => {
|
|
8
23
|
if (signal?.aborted) {
|
|
@@ -32,11 +47,14 @@ export async function cmdReview(args, opts = {}) {
|
|
|
32
47
|
const context_files = await collectContextFiles(process.cwd());
|
|
33
48
|
const branch = git(["rev-parse", "--abbrev-ref", "HEAD"]).trim() || "local";
|
|
34
49
|
const thread = `local/${branch}`.replace(/[^a-zA-Z0-9._/-]/g, "-").slice(0, 120);
|
|
50
|
+
const originRepo = parseGithubOriginRepo();
|
|
35
51
|
console.log(`Submitting review (${Buffer.byteLength(diff, "utf8")} byte diff, ${files.length} file(s), ${context_files.length} context file(s))…`);
|
|
36
52
|
const { status, body } = await apiFetch(cfg, "/api/v1/reviews", {
|
|
37
53
|
method: "POST",
|
|
38
54
|
json: {
|
|
39
55
|
thread,
|
|
56
|
+
branch,
|
|
57
|
+
...(originRepo ? { repo: `${originRepo.owner}/${originRepo.repo}` } : {}),
|
|
40
58
|
base_label: base,
|
|
41
59
|
head_label: head,
|
|
42
60
|
diff,
|
|
@@ -90,9 +108,11 @@ export async function cmdReview(args, opts = {}) {
|
|
|
90
108
|
if (opts.interactive && meBefore) {
|
|
91
109
|
const meAfter = await getMe(cfg);
|
|
92
110
|
if (meAfter) {
|
|
93
|
-
const
|
|
111
|
+
const beforeLeft = meBefore.usage.standard.remaining ?? 0;
|
|
112
|
+
const afterLeft = meAfter.usage.standard.remaining ?? 0;
|
|
113
|
+
const used = Math.max(0, beforeLeft - afterLeft);
|
|
94
114
|
if (used > 0) {
|
|
95
|
-
console.log(ansi.dim(`\n${used} credit${used === 1 ? "" : "s"} used · ${
|
|
115
|
+
console.log(ansi.dim(`\n${used} credit${used === 1 ? "" : "s"} used · ${afterLeft} remaining`));
|
|
96
116
|
}
|
|
97
117
|
}
|
|
98
118
|
}
|
package/dist/shell.d.ts
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
|
+
import { type CommandSpec } from "./ui/prompt.js";
|
|
2
|
+
export declare const COMMANDS: CommandSpec[];
|
|
3
|
+
/** Strips an optional leading "/" so both "review" and "/review" work. */
|
|
4
|
+
export declare function parseLine(line: string): {
|
|
5
|
+
cmd: string;
|
|
6
|
+
args: string[];
|
|
7
|
+
} | null;
|
|
1
8
|
export declare function runShell(): Promise<void>;
|