mergestorm 0.1.0 → 0.2.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/shell.js ADDED
@@ -0,0 +1,187 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { stdin as input, stdout as output } from "node:process";
3
+ import { loadConfig, resolveApiKey } from "./config.js";
4
+ import { CommandError, DetachedError } from "./errors.js";
5
+ import { cmdCredits } from "./commands/credits.js";
6
+ import { cmdJobs, cmdThread } from "./commands/jobs.js";
7
+ import { cmdLogin } from "./commands/login.js";
8
+ import { cmdLogout } from "./commands/logout.js";
9
+ import { cmdReview } from "./commands/review.js";
10
+ import { cmdStatus } from "./commands/status.js";
11
+ import { cmdWhoami } from "./commands/whoami.js";
12
+ import { ansi } from "./ui/ansi.js";
13
+ import { printBannerHeader, shellPrompt } from "./ui/banner.js";
14
+ const COMMANDS = [
15
+ "help",
16
+ "login",
17
+ "logout",
18
+ "review",
19
+ "status",
20
+ "credits",
21
+ "usage",
22
+ "jobs",
23
+ "whoami",
24
+ "thread",
25
+ "clear",
26
+ "exit",
27
+ "quit",
28
+ ];
29
+ function completer(line) {
30
+ const hits = COMMANDS.filter((c) => c.startsWith(line));
31
+ return [hits.length ? [...hits] : [...COMMANDS], line];
32
+ }
33
+ function printHelp() {
34
+ console.log(`
35
+ ${ansi.bold("Commands")}
36
+ login [--key] Sign in via browser (or paste a key)
37
+ logout Remove the stored API key
38
+ review [base] [head] Review git diff (default main...HEAD)
39
+ status <job_id> Show a review job
40
+ credits / usage Credit balance
41
+ jobs [n] Recent review jobs (default 10)
42
+ whoami Key + plan + API base
43
+ thread <slug> Jobs in a review thread
44
+ clear Clear screen and reprint banner
45
+ help This help
46
+ exit / quit Leave the shell
47
+
48
+ Dashboard: https://mergestorm.ai/dashboard/api
49
+ `);
50
+ }
51
+ function parseLine(line) {
52
+ const trimmed = line.trim();
53
+ if (!trimmed)
54
+ return null;
55
+ const parts = trimmed.split(/\s+/);
56
+ const cmd = parts[0].toLowerCase();
57
+ return { cmd, args: parts.slice(1) };
58
+ }
59
+ export async function runShell() {
60
+ try {
61
+ await printBannerHeader();
62
+ }
63
+ catch { /* banner is best-effort */ }
64
+ const rl = createInterface({
65
+ input,
66
+ output,
67
+ completer,
68
+ historySize: 200,
69
+ terminal: Boolean(output.isTTY),
70
+ });
71
+ let busyAbort = null;
72
+ let sigintHint = false;
73
+ let sigintDuringPrompt = false;
74
+ const onSigint = () => {
75
+ sigintDuringPrompt = true;
76
+ if (busyAbort) {
77
+ busyAbort.abort();
78
+ return;
79
+ }
80
+ if (!sigintHint) {
81
+ output.write(`\n${ansi.dim("(type exit or press ctrl+d to quit)")}\n`);
82
+ sigintHint = true;
83
+ }
84
+ else {
85
+ output.write("\n");
86
+ }
87
+ };
88
+ process.on("SIGINT", onSigint);
89
+ try {
90
+ while (true) {
91
+ sigintDuringPrompt = false;
92
+ const cfg = await loadConfig();
93
+ const loggedIn = Boolean(resolveApiKey(cfg));
94
+ let line;
95
+ try {
96
+ line = await rl.question(shellPrompt(loggedIn));
97
+ }
98
+ catch {
99
+ if (sigintDuringPrompt) {
100
+ sigintDuringPrompt = false;
101
+ continue;
102
+ }
103
+ // Ctrl+D / closed
104
+ console.log("");
105
+ break;
106
+ }
107
+ sigintHint = false;
108
+ const parsed = parseLine(line);
109
+ if (!parsed)
110
+ continue;
111
+ const { cmd, args } = parsed;
112
+ if (cmd === "exit" || cmd === "quit")
113
+ break;
114
+ if (cmd === "help" || cmd === "?") {
115
+ printHelp();
116
+ continue;
117
+ }
118
+ if (cmd === "clear") {
119
+ output.write("\u001b[2J\u001b[H");
120
+ try {
121
+ await printBannerHeader();
122
+ }
123
+ catch { /* banner is best-effort */ }
124
+ continue;
125
+ }
126
+ busyAbort = new AbortController();
127
+ try {
128
+ if (cmd === "login") {
129
+ await cmdLogin(args, rl);
130
+ await printBannerHeader();
131
+ }
132
+ else if (cmd === "logout") {
133
+ await cmdLogout();
134
+ await printBannerHeader();
135
+ }
136
+ else if (cmd === "review") {
137
+ await cmdReview(args, { signal: busyAbort.signal, interactive: true });
138
+ }
139
+ else if (cmd === "status") {
140
+ if (!args[0]) {
141
+ throw new CommandError('usage: status <job_id>');
142
+ }
143
+ await cmdStatus(args[0], { format: "pretty", signal: busyAbort.signal });
144
+ }
145
+ else if (cmd === "credits" || cmd === "usage") {
146
+ await cmdCredits(args);
147
+ }
148
+ else if (cmd === "jobs") {
149
+ await cmdJobs(args);
150
+ }
151
+ else if (cmd === "whoami") {
152
+ await cmdWhoami(args);
153
+ }
154
+ else if (cmd === "thread") {
155
+ if (!args[0]) {
156
+ throw new CommandError("usage: thread <slug>");
157
+ }
158
+ await cmdThread(args[0], args.slice(1));
159
+ }
160
+ else {
161
+ console.error(ansi.dim(`unknown command "${cmd}" — type help`));
162
+ }
163
+ }
164
+ catch (err) {
165
+ if (err instanceof DetachedError) {
166
+ console.log(`\n${ansi.yellow(err.message)}`);
167
+ }
168
+ else if (err instanceof CommandError) {
169
+ console.error(ansi.red(err.message));
170
+ }
171
+ else if (err instanceof Error && err.name === "AbortError") {
172
+ console.log(ansi.dim("\naborted"));
173
+ }
174
+ else {
175
+ console.error(ansi.red(err instanceof Error ? err.message : String(err)));
176
+ }
177
+ }
178
+ finally {
179
+ busyAbort = null;
180
+ }
181
+ }
182
+ }
183
+ finally {
184
+ process.off("SIGINT", onSigint);
185
+ rl.close();
186
+ }
187
+ }
@@ -0,0 +1,9 @@
1
+ export declare const ansi: {
2
+ enabled: boolean;
3
+ green: (t: string) => string;
4
+ bold: (t: string) => string;
5
+ dim: (t: string) => string;
6
+ red: (t: string) => string;
7
+ yellow: (t: string) => string;
8
+ boldGreen: (t: string) => string;
9
+ };
@@ -0,0 +1,20 @@
1
+ import { stdout } from "node:process";
2
+ const enabled = process.env.FORCE_COLOR !== undefined && process.env.FORCE_COLOR !== "0"
3
+ ? true
4
+ : Boolean(stdout.isTTY) &&
5
+ !process.env.NO_COLOR &&
6
+ process.env.TERM !== "dumb";
7
+ function wrap(code, text) {
8
+ if (!enabled)
9
+ return text;
10
+ return `\u001b[${code}m${text}\u001b[0m`;
11
+ }
12
+ export const ansi = {
13
+ enabled,
14
+ green: (t) => wrap("32", t),
15
+ bold: (t) => wrap("1", t),
16
+ dim: (t) => wrap("2", t),
17
+ red: (t) => wrap("31", t),
18
+ yellow: (t) => wrap("33", t),
19
+ boldGreen: (t) => (enabled ? `\u001b[1;32m${t}\u001b[0m` : t),
20
+ };
@@ -0,0 +1,3 @@
1
+ export declare function printWordmark(): void;
2
+ export declare function printBannerHeader(): Promise<void>;
3
+ export declare function shellPrompt(loggedIn: boolean): string;
@@ -0,0 +1,47 @@
1
+ import { getMe } from "../api.js";
2
+ import { keyDisplay, loadConfig, resolveApiKey } from "../config.js";
3
+ import { cliVersion } from "../version.js";
4
+ import { ansi } from "./ansi.js";
5
+ const WORDMARK = `
6
+ __ __ ___ ___ ___ ___ ___ _____ ___ ___ __ __
7
+ | \\/ | __| _ \\/ __| __/ __|_ _/ _ \\| _ \\ \\/ |
8
+ | |\\/| | _|| / (_ | _|\\__ \\ | || (_) | / |\\/| |
9
+ |_| |_|___|_|_\\\\___|___|___/ |_| \\___/|_|_\\_| |_|
10
+ `.trimEnd();
11
+ export function printWordmark() {
12
+ console.log(ansi.boldGreen(WORDMARK));
13
+ console.log(ansi.dim(` review local diffs fast · mergestorm.ai · v${cliVersion()}`));
14
+ console.log("");
15
+ }
16
+ function formatStatusLine(me, key) {
17
+ if (!key) {
18
+ return (` ${ansi.dim("○")} not logged in — type ${ansi.green("login")} to sign in via browser,\n` +
19
+ ` or ${ansi.green("login --key")} to paste an API key`);
20
+ }
21
+ const display = me?.key.prefix ?? keyDisplay(key) ?? "msk_live_…";
22
+ if (!me) {
23
+ return ` ${ansi.green("●")} logged in (${ansi.bold(display)})`;
24
+ }
25
+ const plan = me.plan_label_key ?? me.plan_key;
26
+ const left = me.usage.standard.remaining;
27
+ const limit = me.usage.standard.limit;
28
+ return (` ${ansi.green("●")} ${ansi.bold(display)} · ${plan} · ` +
29
+ `standard credits: ${left} of ${limit} left`);
30
+ }
31
+ export async function printBannerHeader() {
32
+ printWordmark();
33
+ const cfg = await loadConfig();
34
+ const key = resolveApiKey(cfg);
35
+ let me = null;
36
+ if (key) {
37
+ me = await getMe(cfg);
38
+ }
39
+ console.log(formatStatusLine(me, key));
40
+ console.log("");
41
+ console.log(ansi.dim(" type `help` for commands · `review` to review main...HEAD"));
42
+ console.log("");
43
+ }
44
+ export function shellPrompt(loggedIn) {
45
+ const name = loggedIn ? ansi.boldGreen("mergestorm") : ansi.dim("mergestorm");
46
+ return `${name}> `;
47
+ }
@@ -0,0 +1 @@
1
+ export declare function cliVersion(): string;
@@ -0,0 +1,11 @@
1
+ import { createRequire } from "node:module";
2
+ const require = createRequire(import.meta.url);
3
+ export function cliVersion() {
4
+ try {
5
+ const pkg = require("../package.json");
6
+ return pkg.version ?? "0.0.0";
7
+ }
8
+ catch {
9
+ return "0.0.0";
10
+ }
11
+ }
package/package.json CHANGED
@@ -1,11 +1,20 @@
1
1
  {
2
2
  "name": "mergestorm",
3
- "version": "0.1.0",
4
- "description": "MergeStorm CLI — review local diffs via the Review Jobs API",
3
+ "version": "0.2.0",
4
+ "description": "MergeStorm CLI — interactive shell and one-shot review commands",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "mergestorm": "./dist/cli.js"
8
8
  },
9
+ "homepage": "https://mergestorm.ai/docs#review-jobs-api",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/ohnodev/code-review-agent.git",
13
+ "directory": "packages/mergestorm-cli"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/ohnodev/code-review-agent/issues"
17
+ },
9
18
  "files": [
10
19
  "dist"
11
20
  ],