mergestorm 0.1.1 → 0.3.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.
Files changed (45) hide show
  1. package/README.md +64 -8
  2. package/dist/api.d.ts +85 -0
  3. package/dist/api.js +85 -0
  4. package/dist/cli.js +72 -353
  5. package/dist/commands/branches.d.ts +10 -0
  6. package/dist/commands/branches.js +85 -0
  7. package/dist/commands/credits.d.ts +1 -0
  8. package/dist/commands/credits.js +28 -0
  9. package/dist/commands/jobs.d.ts +6 -0
  10. package/dist/commands/jobs.js +107 -0
  11. package/dist/commands/login.d.ts +2 -0
  12. package/dist/commands/login.js +103 -0
  13. package/dist/commands/logout.d.ts +1 -0
  14. package/dist/commands/logout.js +11 -0
  15. package/dist/commands/review.d.ts +11 -0
  16. package/dist/commands/review.js +129 -0
  17. package/dist/commands/status.d.ts +5 -0
  18. package/dist/commands/status.js +39 -0
  19. package/dist/commands/whoami.d.ts +1 -0
  20. package/dist/commands/whoami.js +35 -0
  21. package/dist/config.d.ts +13 -0
  22. package/dist/config.js +41 -0
  23. package/dist/errors.d.ts +10 -0
  24. package/dist/errors.js +18 -0
  25. package/dist/git.d.ts +9 -0
  26. package/dist/git.js +66 -0
  27. package/dist/shell.d.ts +8 -0
  28. package/dist/shell.js +183 -0
  29. package/dist/ui/ansi.d.ts +12 -0
  30. package/dist/ui/ansi.js +27 -0
  31. package/dist/ui/banner.d.ts +4 -0
  32. package/dist/ui/banner.js +98 -0
  33. package/dist/ui/box.d.ts +11 -0
  34. package/dist/ui/box.js +36 -0
  35. package/dist/ui/logo.d.ts +5 -0
  36. package/dist/ui/logo.js +12 -0
  37. package/dist/ui/prompt.d.ts +18 -0
  38. package/dist/ui/prompt.js +220 -0
  39. package/dist/ui/select.d.ts +13 -0
  40. package/dist/ui/select.js +103 -0
  41. package/dist/ui/usage.d.ts +24 -0
  42. package/dist/ui/usage.js +67 -0
  43. package/dist/version.d.ts +1 -0
  44. package/dist/version.js +11 -0
  45. package/package.json +5 -4
package/README.md CHANGED
@@ -29,21 +29,68 @@ node packages/mergestorm-cli/dist/cli.js <command>
29
29
  npm run dev --workspace=mergestorm -- <command>
30
30
  ```
31
31
 
32
+ ## Interactive shell
33
+
34
+ On a TTY, bare `mergestorm` (or `mergestorm shell`) opens a branded REPL: a boxed welcome panel (logo, auth/credits status, tips, what's new) and a bordered input box with a live `/` slash-command dropdown. Non-TTY (CI/pipes) prints usage instead — no hanging prompt.
35
+
36
+ ```
37
+ ╭──────────────────── mergestorm v0.3.0 ────────────────────╮
38
+ │ ▟██████████▛ │
39
+ │ ▜████████▛ │
40
+ │ ▝▜████▛▘ │
41
+ │ ▜██▛ │
42
+ │ ██ │
43
+ │ ▝▘ │
44
+ │ │
45
+ │ ● msk_live_a1b2… · maelstrom │
46
+ │ [████████████████████░░░░░░░░░░░░] 63% used │
47
+ │ │
48
+ │ Tips for getting started: │
49
+ │ review review the diff for main...HEAD │
50
+ │ branches pick a recently reviewed branch │
51
+ │ /help list all commands │
52
+ ╰─────────────────────────────────────────────────────────────╯
53
+ mergestorm
54
+ ╭─────────────────────────╮
55
+ │ › /re │
56
+ ╰─────────────────────────╯
57
+ ⁄review Review git diff (default main...HEAD)
58
+
59
+ mergestorm> credits
60
+ mergestorm> branches
61
+ mergestorm> chain
62
+ mergestorm> exit
63
+ ```
64
+
65
+ 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, and Ctrl+C-to-clear all work as in a regular 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).
66
+
67
+ `credits` / `usage` print a Claude-style 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>`).
68
+
32
69
  ## Commands
33
70
 
71
+ One-shot subcommands work the same inside the shell and from argv (scripts/CI):
72
+
34
73
  ```
35
- mergestorm login Sign in via browser; stores an API key in ~/.mergestorm/config.json
36
- mergestorm login --key Paste an existing API key instead (headless/CI)
37
- mergestorm logout Remove the stored API key
38
- mergestorm review [base] [head] Review git diff base...head (default: main...HEAD)
39
- mergestorm status <job_id> Poll a review job and print its result
74
+ mergestorm Interactive shell (TTY only)
75
+ mergestorm shell Explicit shell entry
76
+ mergestorm login Sign in via browser; stores an API key in ~/.mergestorm/config.json
77
+ mergestorm login --key Paste an existing API key instead (headless/CI)
78
+ mergestorm logout Remove the stored API key
79
+ mergestorm review [base] [head] Review git diff base...head (default: main...HEAD)
80
+ mergestorm status <job_id> Poll a review job and print JSON
81
+ mergestorm credits [--json] Standard + premium credit balance (usage bars)
82
+ mergestorm jobs [n] [--json] Recent review jobs (default 10, max 50)
83
+ mergestorm branches [n] Recently reviewed branches (arrow-pick on TTY)
84
+ mergestorm chain [slug] Branch review-chain timeline (default: current branch)
85
+ mergestorm whoami [--json] Key prefix, plan, API base, config path
86
+ mergestorm thread <slug> Jobs in a review thread
40
87
  ```
41
88
 
42
89
  ### `login`
43
90
 
44
91
  Runs a browser device-authorization flow: prints a short code, opens `https://mergestorm.ai/cli/auth`, and — once you approve — mints and stores an API key in `~/.mergestorm/config.json` (mode `600`). No key copy/paste needed.
45
92
 
46
- For headless or CI environments, use `mergestorm login --key` to paste an existing `msk_live_…` key (create one on the dashboard [Account page](https://mergestorm.ai/dashboard/account) → **API keys**).
93
+ For headless or CI environments, use `mergestorm login --key` to paste an existing `msk_live_…` key (create one on the dashboard [API tab](https://mergestorm.ai/dashboard/api)).
47
94
 
48
95
  ### `logout`
49
96
 
@@ -59,11 +106,19 @@ mergestorm review develop # develop...HEAD
59
106
  mergestorm review main feat/x # main...feat/x
60
107
  ```
61
108
 
62
- Exit code is non-zero on quota limit (402), submission failure, a `failed`/`quota_exceeded` job, or a poll timeout.
109
+ Exit code is non-zero on quota limit (402), submission failure, a `failed`/`quota_exceeded` job, or a poll timeout. In the interactive shell, Ctrl+C during the wait detaches (job keeps running) — use `status <job_id>` later.
63
110
 
64
111
  ### `status <job_id>`
65
112
 
66
- Fetches a job by id and prints the raw JSON (status, verdict, summary, findings).
113
+ Fetches a job by id and prints the raw JSON (status, verdict, summary, findings). In the shell, output is formatted for reading.
114
+
115
+ ### `credits` / `jobs` / `whoami`
116
+
117
+ 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).
118
+
119
+ ### `branches` / `chain`
120
+
121
+ `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).
67
122
 
68
123
  ## Context pack (`.mergestorm/`)
69
124
 
@@ -75,6 +130,7 @@ Any `*.md` / `*.txt` files under `./.mergestorm/` (one level of nesting) are att
75
130
  |----------|---------|---------|
76
131
  | `MERGESTORM_API_KEY` | API key (overrides the stored config) | — |
77
132
  | `MERGESTORM_API_URL` | API base URL | `https://api.mergestorm.ai` |
133
+ | `NO_COLOR` | Disable ANSI colors | — |
78
134
 
79
135
  ## See also
80
136
 
package/dist/api.d.ts ADDED
@@ -0,0 +1,85 @@
1
+ import { type Config } from "./config.js";
2
+ export declare function apiFetch(cfg: Config, route: string, init?: RequestInit & {
3
+ json?: unknown;
4
+ }): Promise<{
5
+ status: number;
6
+ body: unknown;
7
+ }>;
8
+ export declare function devicePost(base: string, route: string, json: unknown): Promise<{
9
+ status: number;
10
+ body: any;
11
+ }>;
12
+ export type MeResponse = {
13
+ key: {
14
+ prefix: string;
15
+ name: string;
16
+ created_at: string;
17
+ last_used_at: string | null;
18
+ };
19
+ plan_key: string;
20
+ plan_label_key?: string;
21
+ /** Start of next UTC month (usage meters reset). Older APIs omit this. */
22
+ resets_at?: string;
23
+ usage: {
24
+ standard: {
25
+ used: number;
26
+ limit: number | null;
27
+ remaining: number | null;
28
+ };
29
+ premium: {
30
+ used: number;
31
+ limit: number | null;
32
+ remaining: number | null;
33
+ };
34
+ };
35
+ };
36
+ export type JobListItem = {
37
+ job_id: string;
38
+ thread_slug: string | null;
39
+ status: string;
40
+ verdict: string | null;
41
+ summary: string | null;
42
+ base_label: string | null;
43
+ head_label: string | null;
44
+ created_at: string;
45
+ finished_at: string | null;
46
+ credits: number;
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
+ };
81
+ /** Fetch /api/v1/me; returns null on 404/network so callers can degrade. */
82
+ export declare function getMe(cfg?: Config): Promise<MeResponse | null>;
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 ADDED
@@ -0,0 +1,85 @@
1
+ import { apiBase, loadConfig, resolveApiKey } from "./config.js";
2
+ import { CommandError } from "./errors.js";
3
+ export async function apiFetch(cfg, route, init = {}) {
4
+ const key = resolveApiKey(cfg);
5
+ if (!key) {
6
+ throw new CommandError("No API key. Run `mergestorm login` or set MERGESTORM_API_KEY.");
7
+ }
8
+ const headers = {
9
+ Authorization: `Bearer ${key}`,
10
+ ...init.headers,
11
+ };
12
+ let body = init.body;
13
+ if (init.json !== undefined) {
14
+ headers["Content-Type"] = "application/json";
15
+ body = JSON.stringify(init.json);
16
+ }
17
+ const res = await fetch(`${apiBase(cfg)}${route}`, { ...init, headers, body });
18
+ const text = await res.text();
19
+ let parsed = text;
20
+ try {
21
+ parsed = JSON.parse(text);
22
+ }
23
+ catch {
24
+ // keep text
25
+ }
26
+ return { status: res.status, body: parsed };
27
+ }
28
+ export async function devicePost(base, route, json) {
29
+ const res = await fetch(`${base}${route}`, {
30
+ method: "POST",
31
+ headers: { "Content-Type": "application/json" },
32
+ body: JSON.stringify(json),
33
+ });
34
+ const text = await res.text();
35
+ let parsed = text;
36
+ try {
37
+ parsed = JSON.parse(text);
38
+ }
39
+ catch {
40
+ // keep text
41
+ }
42
+ return { status: res.status, body: parsed };
43
+ }
44
+ /** Fetch /api/v1/me; returns null on 404/network so callers can degrade. */
45
+ export async function getMe(cfg) {
46
+ const resolved = cfg ?? (await loadConfig());
47
+ try {
48
+ const { status, body } = await apiFetch(resolved, "/api/v1/me");
49
+ if (status === 404)
50
+ return null;
51
+ if (status !== 200)
52
+ return null;
53
+ return body;
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ }
59
+ export async function listJobs(limit = 10, cfg) {
60
+ const resolved = cfg ?? (await loadConfig());
61
+ const capped = Math.min(Math.max(limit, 1), 50);
62
+ const { status, body } = await apiFetch(resolved, `/api/v1/reviews?limit=${capped}`);
63
+ if (status === 404) {
64
+ throw new CommandError("Job list is not available on this server yet. Update the API or use the dashboard.");
65
+ }
66
+ if (status !== 200) {
67
+ throw new CommandError(`Failed to list jobs (HTTP ${status}): ${JSON.stringify(body)}`);
68
+ }
69
+ const items = body.items;
70
+ return Array.isArray(items) ? items : [];
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
+ }