mergestorm 0.3.2 → 0.3.3
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 +11 -4
- package/dist/api.d.ts +31 -0
- package/dist/api.js +57 -0
- package/dist/cli.js +8 -0
- package/dist/commands/stack.d.ts +8 -0
- package/dist/commands/stack.js +163 -0
- package/dist/shell.js +5 -0
- package/dist/ui/banner.js +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,14 +48,14 @@ mergestorm # uses your local build
|
|
|
48
48
|
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.
|
|
49
49
|
|
|
50
50
|
```
|
|
51
|
-
╭─ mergestorm v0.3.
|
|
51
|
+
╭─ mergestorm v0.3.3 ──────────────────────────────────────────────────────────╮
|
|
52
52
|
│ ▟██████████▛ Tips for getting started │
|
|
53
53
|
│ ▜████████▛ review review main...HEAD │
|
|
54
|
-
│ ▝▜████▛▘
|
|
54
|
+
│ ▝▜████▛▘ stack list / adopt / restack / land │
|
|
55
55
|
│ ▜██▛ /help list all commands │
|
|
56
56
|
│ ██ │
|
|
57
|
-
│ ▝▘ What's new in v0.3.
|
|
58
|
-
│ ● msk… · maelstrom •
|
|
57
|
+
│ ▝▘ What's new in v0.3.3 │
|
|
58
|
+
│ ● msk… · maelstrom • stack list / adopt / restack / land │
|
|
59
59
|
│ [████░░░░░░░░░░] 12% used │
|
|
60
60
|
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
61
61
|
mergestorm
|
|
@@ -92,8 +92,15 @@ mergestorm branches [n] Recently reviewed branches (arrow-pick on TTY)
|
|
|
92
92
|
mergestorm chain [slug] Branch review-chain timeline (default: current branch)
|
|
93
93
|
mergestorm whoami [--json] Key prefix, plan, API base, config path
|
|
94
94
|
mergestorm thread <slug> Jobs in a review thread
|
|
95
|
+
mergestorm stack list [--json] List adopted stacks
|
|
96
|
+
mergestorm stack adopt <owner/repo>#<pr> Adopt an open PR chain
|
|
97
|
+
mergestorm stack restack <stack-id> Restack descendants
|
|
98
|
+
mergestorm stack land <stack-id> Land the bottom open PR
|
|
99
|
+
mergestorm stack auto-land on|off <stack-id> Toggle auto-land when green
|
|
95
100
|
```
|
|
96
101
|
|
|
102
|
+
`mg` is a short alias for `mergestorm` (same binary), e.g. `mg stack list`. Stack ops use the same login key as review commands (`/api/v1/stacks`).
|
|
103
|
+
|
|
97
104
|
### `login`
|
|
98
105
|
|
|
99
106
|
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.
|
package/dist/api.d.ts
CHANGED
|
@@ -81,5 +81,36 @@ export type ThreadDetail = {
|
|
|
81
81
|
/** Fetch /api/v1/me; returns null on 404/network so callers can degrade. */
|
|
82
82
|
export declare function getMe(cfg?: Config): Promise<MeResponse | null>;
|
|
83
83
|
export declare function listJobs(limit?: number, cfg?: Config): Promise<JobListItem[]>;
|
|
84
|
+
export type StackLayerDto = {
|
|
85
|
+
branch: string;
|
|
86
|
+
parentBranch: string | null;
|
|
87
|
+
prNumber: number;
|
|
88
|
+
position: number;
|
|
89
|
+
state: string;
|
|
90
|
+
title: string | null;
|
|
91
|
+
htmlUrl: string | null;
|
|
92
|
+
ciStatus: string;
|
|
93
|
+
reviewStatus: string;
|
|
94
|
+
conflictDetail: string | null;
|
|
95
|
+
lastRestackedSha: string | null;
|
|
96
|
+
};
|
|
97
|
+
export type StackDto = {
|
|
98
|
+
id: string;
|
|
99
|
+
owner: string;
|
|
100
|
+
repo: string;
|
|
101
|
+
trunkBranch: string;
|
|
102
|
+
autoLandWhenGreen: boolean;
|
|
103
|
+
layers: StackLayerDto[];
|
|
104
|
+
};
|
|
105
|
+
/** List stacks (Bearer GET /api/v1/stacks). */
|
|
106
|
+
export declare function listStacks(cfg?: Config): Promise<StackDto[]>;
|
|
107
|
+
/** Adopt an open PR chain (Bearer POST /api/v1/stacks/adopt). */
|
|
108
|
+
export declare function adoptStack(owner: string, repo: string, prNumber: number, cfg?: Config): Promise<unknown>;
|
|
109
|
+
/** Restack descendants (Bearer POST /api/v1/stacks/:id/restack). */
|
|
110
|
+
export declare function restackStack(stackId: string, cfg?: Config): Promise<unknown>;
|
|
111
|
+
/** Land the bottom open layer (Bearer POST /api/v1/stacks/:id/land-next). */
|
|
112
|
+
export declare function landNextStack(stackId: string, cfg?: Config): Promise<unknown>;
|
|
113
|
+
/** Toggle auto-land when green (Bearer PATCH /api/v1/stacks/:id). */
|
|
114
|
+
export declare function setStackAutoLand(stackId: string, autoLandWhenGreen: boolean, cfg?: Config): Promise<unknown>;
|
|
84
115
|
/** Recently reviewed branches/threads (Bearer GET /api/v1/threads). */
|
|
85
116
|
export declare function listThreads(limit?: number, cfg?: Config): Promise<ThreadListItem[]>;
|
package/dist/api.js
CHANGED
|
@@ -69,6 +69,63 @@ export async function listJobs(limit = 10, cfg) {
|
|
|
69
69
|
const items = body.items;
|
|
70
70
|
return Array.isArray(items) ? items : [];
|
|
71
71
|
}
|
|
72
|
+
/** List stacks (Bearer GET /api/v1/stacks). */
|
|
73
|
+
export async function listStacks(cfg) {
|
|
74
|
+
const resolved = cfg ?? (await loadConfig());
|
|
75
|
+
const { status, body } = await apiFetch(resolved, "/api/v1/stacks");
|
|
76
|
+
if (status === 404) {
|
|
77
|
+
throw new CommandError("Stacks API is not available on this server yet. Deploy the API update or use the dashboard.");
|
|
78
|
+
}
|
|
79
|
+
if (status !== 200) {
|
|
80
|
+
throw new CommandError(`Failed to list stacks (HTTP ${status}): ${JSON.stringify(body)}`);
|
|
81
|
+
}
|
|
82
|
+
const stacks = body.stacks;
|
|
83
|
+
return Array.isArray(stacks) ? stacks : [];
|
|
84
|
+
}
|
|
85
|
+
/** Adopt an open PR chain (Bearer POST /api/v1/stacks/adopt). */
|
|
86
|
+
export async function adoptStack(owner, repo, prNumber, cfg) {
|
|
87
|
+
const resolved = cfg ?? (await loadConfig());
|
|
88
|
+
const { status, body } = await apiFetch(resolved, "/api/v1/stacks/adopt", {
|
|
89
|
+
method: "POST",
|
|
90
|
+
json: { owner, repo, prNumber },
|
|
91
|
+
});
|
|
92
|
+
if (status === 404) {
|
|
93
|
+
throw new CommandError("Stacks API is not available on this server yet. Deploy the API update or use the dashboard.");
|
|
94
|
+
}
|
|
95
|
+
if (status !== 200) {
|
|
96
|
+
throw new CommandError(`Failed to adopt stack (HTTP ${status}): ${JSON.stringify(body)}`);
|
|
97
|
+
}
|
|
98
|
+
return body;
|
|
99
|
+
}
|
|
100
|
+
async function stackMutation(stackId, pathSuffix, method, json, failLabel, cfg) {
|
|
101
|
+
const resolved = cfg ?? (await loadConfig());
|
|
102
|
+
const { status, body } = await apiFetch(resolved, `/api/v1/stacks/${encodeURIComponent(stackId)}${pathSuffix}`, method === "PATCH"
|
|
103
|
+
? { method, json }
|
|
104
|
+
: { method, json: json ?? {} });
|
|
105
|
+
if (status === 404) {
|
|
106
|
+
const err = body?.error;
|
|
107
|
+
if (err === "stack_not_found") {
|
|
108
|
+
throw new CommandError(`Stack not found: ${stackId}`);
|
|
109
|
+
}
|
|
110
|
+
throw new CommandError("Stacks API is not available on this server yet. Deploy the API update or use the dashboard.");
|
|
111
|
+
}
|
|
112
|
+
if (status !== 200) {
|
|
113
|
+
throw new CommandError(`${failLabel} (HTTP ${status}): ${JSON.stringify(body)}`);
|
|
114
|
+
}
|
|
115
|
+
return body;
|
|
116
|
+
}
|
|
117
|
+
/** Restack descendants (Bearer POST /api/v1/stacks/:id/restack). */
|
|
118
|
+
export async function restackStack(stackId, cfg) {
|
|
119
|
+
return stackMutation(stackId, "/restack", "POST", {}, "Failed to restack", cfg);
|
|
120
|
+
}
|
|
121
|
+
/** Land the bottom open layer (Bearer POST /api/v1/stacks/:id/land-next). */
|
|
122
|
+
export async function landNextStack(stackId, cfg) {
|
|
123
|
+
return stackMutation(stackId, "/land-next", "POST", {}, "Failed to land next", cfg);
|
|
124
|
+
}
|
|
125
|
+
/** Toggle auto-land when green (Bearer PATCH /api/v1/stacks/:id). */
|
|
126
|
+
export async function setStackAutoLand(stackId, autoLandWhenGreen, cfg) {
|
|
127
|
+
return stackMutation(stackId, "", "PATCH", { autoLandWhenGreen }, "Failed to update auto-land", cfg);
|
|
128
|
+
}
|
|
72
129
|
/** Recently reviewed branches/threads (Bearer GET /api/v1/threads). */
|
|
73
130
|
export async function listThreads(limit = 20, cfg) {
|
|
74
131
|
const resolved = cfg ?? (await loadConfig());
|
package/dist/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ import { cmdJobs, cmdThread } from "./commands/jobs.js";
|
|
|
8
8
|
import { cmdLogin } from "./commands/login.js";
|
|
9
9
|
import { cmdLogout } from "./commands/logout.js";
|
|
10
10
|
import { cmdReview } from "./commands/review.js";
|
|
11
|
+
import { cmdStack } from "./commands/stack.js";
|
|
11
12
|
import { cmdStatus } from "./commands/status.js";
|
|
12
13
|
import { cmdWhoami } from "./commands/whoami.js";
|
|
13
14
|
import { runShell } from "./shell.js";
|
|
@@ -29,6 +30,11 @@ function usage() {
|
|
|
29
30
|
mergestorm chain [slug] Branch review-chain timeline (default: current)
|
|
30
31
|
mergestorm whoami [--json] Key + plan + API base
|
|
31
32
|
mergestorm thread <slug> Jobs in a review thread
|
|
33
|
+
mergestorm stack list [--json] List adopted stacks
|
|
34
|
+
mergestorm stack adopt <owner/repo>#<pr> Adopt an open PR chain
|
|
35
|
+
mergestorm stack restack <stack-id> Restack stack descendants
|
|
36
|
+
mergestorm stack land <stack-id> Land the bottom open PR
|
|
37
|
+
mergestorm stack auto-land on|off <stack-id> Toggle auto-land when green
|
|
32
38
|
|
|
33
39
|
mg is a short alias for mergestorm (same binary).
|
|
34
40
|
|
|
@@ -64,6 +70,8 @@ async function runCommand(cmd, args) {
|
|
|
64
70
|
throw new CommandError("usage: mergestorm thread <slug>");
|
|
65
71
|
return cmdThread(args[0], args.slice(1));
|
|
66
72
|
}
|
|
73
|
+
if (cmd === "stack")
|
|
74
|
+
return cmdStack(args);
|
|
67
75
|
if (cmd === "shell")
|
|
68
76
|
return runShell();
|
|
69
77
|
usage();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function requireStackId(raw: string | undefined, usage: string): string;
|
|
2
|
+
/** Parse `owner/repo#12` or `owner/repo 12`. */
|
|
3
|
+
export declare function parseAdoptTarget(args: string[]): {
|
|
4
|
+
owner: string;
|
|
5
|
+
repo: string;
|
|
6
|
+
prNumber: number;
|
|
7
|
+
};
|
|
8
|
+
export declare function cmdStack(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { adoptStack, landNextStack, listStacks, restackStack, setStackAutoLand, } from "../api.js";
|
|
2
|
+
import { loadConfig } from "../config.js";
|
|
3
|
+
import { CommandError } from "../errors.js";
|
|
4
|
+
import { ansi } from "../ui/ansi.js";
|
|
5
|
+
const STACK_USAGE = `usage:
|
|
6
|
+
mergestorm stack list [--json]
|
|
7
|
+
mergestorm stack adopt <owner/repo>#<pr>
|
|
8
|
+
mergestorm stack restack <stack-id> [--json]
|
|
9
|
+
mergestorm stack land <stack-id> [--json]
|
|
10
|
+
mergestorm stack auto-land on|off <stack-id> [--json]`;
|
|
11
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
12
|
+
export function requireStackId(raw, usage) {
|
|
13
|
+
const id = raw?.trim() ?? "";
|
|
14
|
+
if (!id || !UUID_RE.test(id)) {
|
|
15
|
+
throw new CommandError(usage);
|
|
16
|
+
}
|
|
17
|
+
return id;
|
|
18
|
+
}
|
|
19
|
+
/** Parse `owner/repo#12` or `owner/repo 12`. */
|
|
20
|
+
export function parseAdoptTarget(args) {
|
|
21
|
+
const joined = args.join(" ").trim();
|
|
22
|
+
const hashMatch = /^([^/\s]+)\/([^#\s]+)#([1-9]\d*)$/.exec(joined);
|
|
23
|
+
if (hashMatch) {
|
|
24
|
+
return {
|
|
25
|
+
owner: hashMatch[1],
|
|
26
|
+
repo: hashMatch[2],
|
|
27
|
+
prNumber: Number(hashMatch[3]),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (args.length >= 2) {
|
|
31
|
+
const slash = /^([^/\s]+)\/([^/\s]+)$/.exec(args[0]);
|
|
32
|
+
const pr = Number(args[1]);
|
|
33
|
+
if (slash && Number.isInteger(pr) && pr > 0) {
|
|
34
|
+
return { owner: slash[1], repo: slash[2], prNumber: pr };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
throw new CommandError(`usage: mergestorm stack adopt <owner/repo>#<pr>`);
|
|
38
|
+
}
|
|
39
|
+
function formatStackHuman(s) {
|
|
40
|
+
const lines = [];
|
|
41
|
+
const auto = s.autoLandWhenGreen ? " · auto-land" : "";
|
|
42
|
+
lines.push(` ${ansi.bold(`${s.owner}/${s.repo}`)} trunk=${s.trunkBranch}${auto}`);
|
|
43
|
+
lines.push(` ${ansi.dim(s.id)}`);
|
|
44
|
+
if (s.layers.length === 0) {
|
|
45
|
+
lines.push(ansi.dim(" (no layers)"));
|
|
46
|
+
return lines;
|
|
47
|
+
}
|
|
48
|
+
for (const layer of s.layers) {
|
|
49
|
+
const title = layer.title ? ` ${layer.title}` : "";
|
|
50
|
+
const pr = layer.prNumber > 0 ? `#${layer.prNumber}` : "—";
|
|
51
|
+
lines.push(` ${String(layer.position).padStart(2)} ${pr.padEnd(5)} ${layer.state.padEnd(14)} ${layer.branch}${title}`);
|
|
52
|
+
}
|
|
53
|
+
return lines;
|
|
54
|
+
}
|
|
55
|
+
async function cmdStackList(args) {
|
|
56
|
+
const asJson = args.includes("--json");
|
|
57
|
+
const cfg = await loadConfig();
|
|
58
|
+
const stacks = await listStacks(cfg);
|
|
59
|
+
if (asJson) {
|
|
60
|
+
console.log(JSON.stringify({ stacks }, null, 2));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (stacks.length === 0) {
|
|
64
|
+
console.log(ansi.dim(" No stacks yet. Adopt one: `stack adopt owner/repo#pr`"));
|
|
65
|
+
console.log(ansi.dim(" Dashboard: https://mergestorm.ai/dashboard/stacks"));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
for (const s of stacks) {
|
|
69
|
+
for (const line of formatStackHuman(s))
|
|
70
|
+
console.log(line);
|
|
71
|
+
console.log("");
|
|
72
|
+
}
|
|
73
|
+
console.log(ansi.dim(" Dashboard: https://mergestorm.ai/dashboard/stacks"));
|
|
74
|
+
}
|
|
75
|
+
async function cmdStackAdopt(args) {
|
|
76
|
+
const asJson = args.includes("--json");
|
|
77
|
+
const targetArgs = args.filter((a) => a !== "--json");
|
|
78
|
+
if (targetArgs.length === 0) {
|
|
79
|
+
throw new CommandError(`usage: mergestorm stack adopt <owner/repo>#<pr>`);
|
|
80
|
+
}
|
|
81
|
+
const { owner, repo, prNumber } = parseAdoptTarget(targetArgs);
|
|
82
|
+
const cfg = await loadConfig();
|
|
83
|
+
const body = await adoptStack(owner, repo, prNumber, cfg);
|
|
84
|
+
if (asJson) {
|
|
85
|
+
console.log(JSON.stringify(body, null, 2));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (typeof body !== "object" || body === null) {
|
|
89
|
+
throw new CommandError("Unexpected response shape from adopt API");
|
|
90
|
+
}
|
|
91
|
+
const data = body;
|
|
92
|
+
const stack = data.stack;
|
|
93
|
+
const chain = data.chain;
|
|
94
|
+
console.log(ansi.green(` Adopted ${owner}/${repo}#${prNumber}` +
|
|
95
|
+
(stack?.id ? ` → ${stack.id}` : "") +
|
|
96
|
+
(Array.isArray(chain) ? ` (${chain.length} layer(s))` : "")));
|
|
97
|
+
if (stack?.trunkBranch) {
|
|
98
|
+
console.log(ansi.dim(` trunk: ${stack.trunkBranch}`));
|
|
99
|
+
}
|
|
100
|
+
console.log(ansi.dim(" Dashboard: https://mergestorm.ai/dashboard/stacks"));
|
|
101
|
+
}
|
|
102
|
+
async function cmdStackRestack(args) {
|
|
103
|
+
const asJson = args.includes("--json");
|
|
104
|
+
const id = requireStackId(args.find((a) => a !== "--json"), "usage: mergestorm stack restack <stack-id>");
|
|
105
|
+
const cfg = await loadConfig();
|
|
106
|
+
const body = await restackStack(id, cfg);
|
|
107
|
+
if (asJson) {
|
|
108
|
+
console.log(JSON.stringify(body, null, 2));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const fromBranch = body.fromBranch;
|
|
112
|
+
console.log(ansi.green(` Restack started` + (fromBranch ? ` from ${fromBranch}` : "") + ` (${id})`));
|
|
113
|
+
}
|
|
114
|
+
async function cmdStackLand(args) {
|
|
115
|
+
const asJson = args.includes("--json");
|
|
116
|
+
const id = requireStackId(args.find((a) => a !== "--json"), "usage: mergestorm stack land <stack-id>");
|
|
117
|
+
const cfg = await loadConfig();
|
|
118
|
+
const body = await landNextStack(id, cfg);
|
|
119
|
+
if (asJson) {
|
|
120
|
+
console.log(JSON.stringify(body, null, 2));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const pr = body.mergedPrNumber;
|
|
124
|
+
const branch = body.mergedBranch;
|
|
125
|
+
console.log(ansi.green(` Landed` +
|
|
126
|
+
(pr != null ? ` #${pr}` : "") +
|
|
127
|
+
(branch ? ` (${branch})` : "") +
|
|
128
|
+
` on ${id}`));
|
|
129
|
+
}
|
|
130
|
+
async function cmdStackAutoLand(args) {
|
|
131
|
+
const asJson = args.includes("--json");
|
|
132
|
+
const filtered = args.filter((a) => a !== "--json");
|
|
133
|
+
const mode = filtered[0]?.toLowerCase();
|
|
134
|
+
const id = requireStackId(filtered[1], "usage: mergestorm stack auto-land on|off <stack-id>");
|
|
135
|
+
if (mode !== "on" && mode !== "off") {
|
|
136
|
+
throw new CommandError("usage: mergestorm stack auto-land on|off <stack-id>");
|
|
137
|
+
}
|
|
138
|
+
const cfg = await loadConfig();
|
|
139
|
+
const body = await setStackAutoLand(id, mode === "on", cfg);
|
|
140
|
+
if (asJson) {
|
|
141
|
+
console.log(JSON.stringify(body, null, 2));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
console.log(ansi.green(` Auto-land ${mode} for ${id}`));
|
|
145
|
+
}
|
|
146
|
+
export async function cmdStack(args) {
|
|
147
|
+
const sub = args[0]?.toLowerCase();
|
|
148
|
+
const rest = args.slice(1);
|
|
149
|
+
if (!sub || sub === "-h" || sub === "--help") {
|
|
150
|
+
throw new CommandError(STACK_USAGE);
|
|
151
|
+
}
|
|
152
|
+
if (sub === "list" || sub === "ls")
|
|
153
|
+
return cmdStackList(rest);
|
|
154
|
+
if (sub === "adopt")
|
|
155
|
+
return cmdStackAdopt(rest);
|
|
156
|
+
if (sub === "restack")
|
|
157
|
+
return cmdStackRestack(rest);
|
|
158
|
+
if (sub === "land" || sub === "land-next")
|
|
159
|
+
return cmdStackLand(rest);
|
|
160
|
+
if (sub === "auto-land" || sub === "autoland")
|
|
161
|
+
return cmdStackAutoLand(rest);
|
|
162
|
+
throw new CommandError(`${STACK_USAGE}\nunknown stack subcommand: ${sub}`);
|
|
163
|
+
}
|
package/dist/shell.js
CHANGED
|
@@ -7,6 +7,7 @@ import { cmdJobs, cmdThread } from "./commands/jobs.js";
|
|
|
7
7
|
import { cmdLogin } from "./commands/login.js";
|
|
8
8
|
import { cmdLogout } from "./commands/logout.js";
|
|
9
9
|
import { cmdReview } from "./commands/review.js";
|
|
10
|
+
import { cmdStack } from "./commands/stack.js";
|
|
10
11
|
import { cmdStatus } from "./commands/status.js";
|
|
11
12
|
import { cmdWhoami } from "./commands/whoami.js";
|
|
12
13
|
import { ansi } from "./ui/ansi.js";
|
|
@@ -27,6 +28,7 @@ export const COMMANDS = [
|
|
|
27
28
|
{ name: "chain", summary: "Show a branch review-chain timeline" },
|
|
28
29
|
{ name: "whoami", summary: "Key + plan + API base" },
|
|
29
30
|
{ name: "thread", summary: "Jobs in a review thread" },
|
|
31
|
+
{ name: "stack", summary: "Stacks: list, adopt, restack, land, auto-land" },
|
|
30
32
|
{ name: "clear", summary: "Clear screen and reprint banner" },
|
|
31
33
|
{ name: "exit", summary: "Leave the shell" },
|
|
32
34
|
{ name: "quit", summary: "Leave the shell" },
|
|
@@ -161,6 +163,9 @@ export async function runShell() {
|
|
|
161
163
|
}
|
|
162
164
|
await cmdThread(args[0], args.slice(1));
|
|
163
165
|
}
|
|
166
|
+
else if (cmd === "stack") {
|
|
167
|
+
await cmdStack(args);
|
|
168
|
+
}
|
|
164
169
|
else {
|
|
165
170
|
console.error(ansi.dim(`unknown command "${cmd}" -- type help`));
|
|
166
171
|
}
|
package/dist/ui/banner.js
CHANGED