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/README.md +37 -9
- package/dist/api.d.ts +48 -0
- package/dist/api.js +71 -0
- package/dist/cli.js +65 -353
- package/dist/commands/credits.d.ts +1 -0
- package/dist/commands/credits.js +22 -0
- package/dist/commands/jobs.d.ts +2 -0
- package/dist/commands/jobs.js +69 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +101 -0
- package/dist/commands/logout.d.ts +1 -0
- package/dist/commands/logout.js +11 -0
- package/dist/commands/review.d.ts +6 -0
- package/dist/commands/review.js +109 -0
- package/dist/commands/status.d.ts +5 -0
- package/dist/commands/status.js +39 -0
- package/dist/commands/whoami.d.ts +1 -0
- package/dist/commands/whoami.js +35 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.js +41 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.js +18 -0
- package/dist/git.d.ts +9 -0
- package/dist/git.js +66 -0
- package/dist/shell.d.ts +1 -0
- package/dist/shell.js +187 -0
- package/dist/ui/ansi.d.ts +9 -0
- package/dist/ui/ansi.js +20 -0
- package/dist/ui/banner.d.ts +3 -0
- package/dist/ui/banner.js +47 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +11 -0
- package/package.json +11 -2
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
|
+
}
|
package/dist/ui/ansi.js
ADDED
|
@@ -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,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;
|
package/dist/version.js
ADDED
package/package.json
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mergestorm",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MergeStorm CLI —
|
|
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
|
],
|