@happy-nut/monacori 0.1.9 → 0.1.11
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 +47 -0
- package/dist/app-main.js +17 -4
- package/dist/commands.js +6 -0
- package/dist/diff.js +11 -9
- package/dist/git.d.ts +1 -0
- package/dist/git.js +9 -0
- package/dist/util.d.ts +3 -0
- package/dist/util.js +18 -0
- package/dist/viewer.client.js +27 -3
- package/dist/viewer.css +7 -1
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -71,6 +71,53 @@ mo --base main # compare against a specific base
|
|
|
71
71
|
mo --context 20 # show more context around each hunk
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
## Development
|
|
75
|
+
|
|
76
|
+
Working on monacori itself? The globally-installed `mo` runs the **published** package, not your
|
|
77
|
+
checkout — local edits won't appear until you build and run locally.
|
|
78
|
+
|
|
79
|
+
Run your checkout directly (builds, then launches in the foreground with DevTools open):
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm run dev
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
This reviews the monacori repo itself. To point a local build at another project:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
MONACORI_DEV=1 node /path/to/monacori/bin/monacori.js --foreground
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Which build is running?** A dev build titles its window `monacori (dev)` and opens DevTools, and
|
|
92
|
+
every launch prints its app path — so a local checkout is distinguishable from the installed package
|
|
93
|
+
even when their version numbers match:
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
monacori: launching /…/repos/monacori/dist/app-main.js # local checkout
|
|
97
|
+
monacori: launching /…/lib/node_modules/@happy-nut/monacori/dist/app-main.js # installed package
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Prefer the `mo` command pointed at your checkout? `npm link` once, then rebuild after each change:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npm link # global `mo` now runs this checkout
|
|
104
|
+
npm run build # rebuild dist/ after editing src/
|
|
105
|
+
npm unlink -g @happy-nut/monacori # restore the published `mo`
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`src/viewer.client.js` and `src/viewer.css` are copied (not compiled) into `dist/` by the build, so
|
|
109
|
+
re-run `npm run build` (or `npm run dev`) after editing them.
|
|
110
|
+
|
|
111
|
+
### Tests
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm test
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`npm test` builds, then runs the jsdom regression suite (`test/*.test.mjs`) against the built `dist/`.
|
|
118
|
+
It guards the core user flows end to end — see [test/USER_FLOWS.md](test/USER_FLOWS.md) — and the same
|
|
119
|
+
suite gates every release.
|
|
120
|
+
|
|
74
121
|
## Local State
|
|
75
122
|
|
|
76
123
|
`monacori init` creates a git-ignored `.monacori/` directory for generated diff reviews, local config, comments, logs, and validation notes. Keep it ignored unless your team intentionally wants to version review artifacts.
|
package/dist/app-main.js
CHANGED
|
@@ -4,7 +4,13 @@ import { dirname, join, resolve } from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { app, BrowserWindow, ipcMain, Menu, nativeImage } from "electron";
|
|
6
6
|
import { buildDiffReview, performHttpRequest } from "./cli.js";
|
|
7
|
+
import { sanitizeTerminalEnv } from "./util.js";
|
|
7
8
|
import { spawn as spawnPty } from "node-pty";
|
|
9
|
+
// `npm run dev` sets MONACORI_DEV=1 so a locally-built app announces itself — a window-title suffix
|
|
10
|
+
// plus a boot log with its on-disk path — making it obvious whether `mo` launched THIS checkout or
|
|
11
|
+
// the globally-installed package (their version numbers can be identical; the path is the tell).
|
|
12
|
+
const DEV_BUILD = process.env.MONACORI_DEV === "1";
|
|
13
|
+
const APP_TITLE = DEV_BUILD ? "monacori (dev)" : "monacori";
|
|
8
14
|
const FLOW_DIR = ".monacori";
|
|
9
15
|
const REVIEW_FILE = "app-review.html";
|
|
10
16
|
const WATCH_INTERVAL_MS = 1000;
|
|
@@ -61,7 +67,7 @@ ipcMain.handle("monacori:pty-spawn", (_event, size) => {
|
|
|
61
67
|
cols: size?.cols ?? 80,
|
|
62
68
|
rows: size?.rows ?? 24,
|
|
63
69
|
cwd: options.root,
|
|
64
|
-
env: process.env,
|
|
70
|
+
env: sanitizeTerminalEnv(process.env),
|
|
65
71
|
});
|
|
66
72
|
terms.set(id, t);
|
|
67
73
|
// mainWindow?. only guards null, NOT a *destroyed* window — sending to a closed window's webContents
|
|
@@ -137,6 +143,9 @@ if (!existsSync(options.root)) {
|
|
|
137
143
|
throw new Error(`Repository path does not exist: ${options.root}`);
|
|
138
144
|
}
|
|
139
145
|
app.whenReady().then(async () => {
|
|
146
|
+
// Foreground (`npm run dev` / `mo --foreground`) surfaces this in the terminal; detached `mo` drops
|
|
147
|
+
// it. Either way the path disambiguates a local checkout from the installed package.
|
|
148
|
+
console.error(`[monacori] ${DEV_BUILD ? "DEV build" : "build"} — ${app.getAppPath()} (electron ${process.versions.electron})`);
|
|
140
149
|
process.chdir(options.root);
|
|
141
150
|
mkdirSync(FLOW_DIR, { recursive: true });
|
|
142
151
|
// Keep the standard Edit/Window roles so Cmd+C/V/X/A (copy comments into prompts) and Cmd+Q work.
|
|
@@ -208,7 +217,7 @@ app.whenReady().then(async () => {
|
|
|
208
217
|
minWidth: 960,
|
|
209
218
|
minHeight: 640,
|
|
210
219
|
show: false,
|
|
211
|
-
title:
|
|
220
|
+
title: APP_TITLE,
|
|
212
221
|
icon: iconPath,
|
|
213
222
|
backgroundColor: "#2b2b2b",
|
|
214
223
|
autoHideMenuBar: true,
|
|
@@ -221,7 +230,11 @@ app.whenReady().then(async () => {
|
|
|
221
230
|
},
|
|
222
231
|
});
|
|
223
232
|
mainWindow.webContents.setWindowOpenHandler(() => ({ action: "deny" }));
|
|
224
|
-
mainWindow.once("ready-to-show", () =>
|
|
233
|
+
mainWindow.once("ready-to-show", () => {
|
|
234
|
+
mainWindow?.show();
|
|
235
|
+
if (DEV_BUILD)
|
|
236
|
+
mainWindow?.webContents.openDevTools({ mode: "detach" });
|
|
237
|
+
});
|
|
225
238
|
// Paint the window with a spinner immediately, then build the (potentially heavy) review off the first
|
|
226
239
|
// paint and swap it in. The first build used to run synchronously *before* the window existed, so the
|
|
227
240
|
// screen stayed blank for the first few seconds of startup; now the user sees a loading screen instead.
|
|
@@ -289,7 +302,7 @@ function writeReviewFile(input) {
|
|
|
289
302
|
staged: input.staged,
|
|
290
303
|
includeUntracked: input.includeUntracked,
|
|
291
304
|
context: input.context,
|
|
292
|
-
title:
|
|
305
|
+
title: APP_TITLE,
|
|
293
306
|
ignoreWhitespace: input.ignoreWhitespace,
|
|
294
307
|
lazyLoad: true, // Electron streams per-file bodies/source over IPC (monacori:get-file / get-source)
|
|
295
308
|
app: true, // gate the integrated terminal (xterm) into the HTML — Electron only
|
package/dist/commands.js
CHANGED
|
@@ -124,6 +124,12 @@ function launchReviewApp(args) {
|
|
|
124
124
|
if (args.includes("--no-watch"))
|
|
125
125
|
appArgs.push("--no-watch");
|
|
126
126
|
const electronBinary = resolveElectronBinary();
|
|
127
|
+
// In dev only (`npm run dev` sets MONACORI_DEV=1) announce which build is launching, so a local
|
|
128
|
+
// checkout is distinguishable from the installed package. Normal `mo` runs stay silent — the shell
|
|
129
|
+
// should be clean, not littered with our internal path.
|
|
130
|
+
if (process.env.MONACORI_DEV === "1") {
|
|
131
|
+
console.error(`monacori: launching ${appMainPath()}`);
|
|
132
|
+
}
|
|
127
133
|
if (args.includes("--foreground")) {
|
|
128
134
|
const result = spawnSync(electronBinary, appArgs, { stdio: "inherit" });
|
|
129
135
|
process.exit(result.status ?? 0);
|
package/dist/diff.js
CHANGED
|
@@ -3,8 +3,9 @@ import { existsSync, readFileSync, statSync } from "node:fs";
|
|
|
3
3
|
import { basename, join } from "node:path";
|
|
4
4
|
import { FLOW_DIR, IMAGE_MAX_BYTES, SOURCE_MAX_FILE_BYTES, SOURCE_MAX_FILES, SOURCE_MAX_TOTAL_BYTES } from "./constants.js";
|
|
5
5
|
import { formatBytes, hashText, isLikelyBinary, languageForPath, stripDiffPath } from "./util.js";
|
|
6
|
-
import { git } from "./git.js";
|
|
6
|
+
import { git, repoRoot } from "./git.js";
|
|
7
7
|
export function readUnifiedDiff(options) {
|
|
8
|
+
const root = repoRoot();
|
|
8
9
|
const args = ["diff", "--no-ext-diff", "--find-renames", `--unified=${options.context}`];
|
|
9
10
|
if (options.ignoreWhitespace)
|
|
10
11
|
args.push("--ignore-all-space");
|
|
@@ -16,7 +17,7 @@ export function readUnifiedDiff(options) {
|
|
|
16
17
|
}
|
|
17
18
|
args.push("--");
|
|
18
19
|
const result = spawnSync("git", args, {
|
|
19
|
-
cwd:
|
|
20
|
+
cwd: root,
|
|
20
21
|
encoding: "utf8",
|
|
21
22
|
maxBuffer: 1024 * 1024 * 100,
|
|
22
23
|
});
|
|
@@ -25,18 +26,18 @@ export function readUnifiedDiff(options) {
|
|
|
25
26
|
}
|
|
26
27
|
const chunks = [result.stdout ?? ""];
|
|
27
28
|
if (options.includeUntracked && !options.staged) {
|
|
28
|
-
chunks.push(readUntrackedDiff(options.context));
|
|
29
|
+
chunks.push(readUntrackedDiff(options.context, root));
|
|
29
30
|
}
|
|
30
31
|
return chunks.filter(Boolean).join("\n");
|
|
31
32
|
}
|
|
32
|
-
function readUntrackedDiff(context) {
|
|
33
|
-
const files = git(
|
|
33
|
+
function readUntrackedDiff(context, root) {
|
|
34
|
+
const files = git(root, ["ls-files", "--others", "--exclude-standard"])
|
|
34
35
|
.split(/\r?\n/)
|
|
35
36
|
.map((line) => line.trim())
|
|
36
37
|
.filter((line) => line && !line.startsWith(`${FLOW_DIR}/`));
|
|
37
38
|
const chunks = [];
|
|
38
39
|
for (const file of files) {
|
|
39
|
-
const absolute = join(
|
|
40
|
+
const absolute = join(root, file);
|
|
40
41
|
if (!existsSync(absolute) || !statSync(absolute).isFile()) {
|
|
41
42
|
continue;
|
|
42
43
|
}
|
|
@@ -225,14 +226,15 @@ export function collectSourceFiles(diffFiles) {
|
|
|
225
226
|
}
|
|
226
227
|
changedLinesByPath.set(file.displayPath, nums);
|
|
227
228
|
}
|
|
228
|
-
const
|
|
229
|
+
const root = repoRoot();
|
|
230
|
+
const vcsByPath = gitStatusMap(root);
|
|
229
231
|
for (const file of diffFiles) {
|
|
230
232
|
const kind = vcsByPath.get(file.displayPath);
|
|
231
233
|
if (kind)
|
|
232
234
|
file.vcs = kind; // color the Changes list from the same status map
|
|
233
235
|
}
|
|
234
236
|
const paths = new Set();
|
|
235
|
-
const gitFiles = git(
|
|
237
|
+
const gitFiles = git(root, ["ls-files", "--cached", "--others", "--exclude-standard"]);
|
|
236
238
|
for (const file of gitFiles.split(/\r?\n/)) {
|
|
237
239
|
const path = file.trim();
|
|
238
240
|
if (path && isSourceCandidate(path)) {
|
|
@@ -248,7 +250,7 @@ export function collectSourceFiles(diffFiles) {
|
|
|
248
250
|
let embeddedFiles = 0;
|
|
249
251
|
let embeddedBytes = 0;
|
|
250
252
|
for (const path of Array.from(paths).sort((a, b) => a.localeCompare(b))) {
|
|
251
|
-
const absolute = join(
|
|
253
|
+
const absolute = join(root, path);
|
|
252
254
|
const base = {
|
|
253
255
|
path,
|
|
254
256
|
name: basename(path),
|
package/dist/git.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { GitSnapshot } from "./types.js";
|
|
2
2
|
export declare function isGitRepository(root: string): boolean;
|
|
3
3
|
export declare function git(root: string, args: string[]): string;
|
|
4
|
+
export declare function repoRoot(cwd?: string): string;
|
|
4
5
|
export declare function readGitSnapshot(root: string): GitSnapshot;
|
package/dist/git.js
CHANGED
|
@@ -13,6 +13,15 @@ export function git(root, args) {
|
|
|
13
13
|
}
|
|
14
14
|
return (result.stdout ?? "").trim();
|
|
15
15
|
}
|
|
16
|
+
// Resolve the repository root. `git diff` and `git ls-files` print paths relative to it, and the
|
|
17
|
+
// desktop tree shows them as-is — so every filesystem read of those paths must resolve against the
|
|
18
|
+
// SAME root, not process.cwd(). When `mo` runs from a monorepo subdirectory (cwd != root), joining a
|
|
19
|
+
// repo-root-relative path onto cwd points at a file that doesn't exist, which surfaced as a diff with
|
|
20
|
+
// no source preview ("file is not present in the working tree"). Falls back to cwd outside a repo.
|
|
21
|
+
export function repoRoot(cwd = process.cwd()) {
|
|
22
|
+
const top = git(cwd, ["rev-parse", "--show-toplevel"]);
|
|
23
|
+
return top || cwd;
|
|
24
|
+
}
|
|
16
25
|
export function readGitSnapshot(root) {
|
|
17
26
|
return {
|
|
18
27
|
branch: git(root, ["branch", "--show-current"]),
|
package/dist/util.d.ts
CHANGED
|
@@ -16,3 +16,6 @@ export declare function jsonForScript(value: unknown): string;
|
|
|
16
16
|
export declare function escapeAttr(value: string): string;
|
|
17
17
|
export declare function formatBytes(bytes: number): string;
|
|
18
18
|
export declare function listRecentFiles(dir: string, limit: number): string[];
|
|
19
|
+
export declare function sanitizeTerminalEnv(env: NodeJS.ProcessEnv): {
|
|
20
|
+
[key: string]: string;
|
|
21
|
+
};
|
package/dist/util.js
CHANGED
|
@@ -142,3 +142,21 @@ export function listRecentFiles(dir, limit) {
|
|
|
142
142
|
.sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs)
|
|
143
143
|
.slice(0, limit);
|
|
144
144
|
}
|
|
145
|
+
// The integrated terminal should behave like the user's own login shell — not like a child of however
|
|
146
|
+
// monacori was launched. When started through npm (`npm run dev`, or a global install run via an npm
|
|
147
|
+
// shim), npm injects npm_config_* / npm_lifecycle_* / npm_package_* vars into our process. Inheriting
|
|
148
|
+
// them into the pty leaks our run's npm config into the user's shell and, with nvm, triggers:
|
|
149
|
+
// "nvm is not compatible with the npm_config_prefix environment variable …"
|
|
150
|
+
// Strip every npm_*-injected var (npm_config_prefix is the one nvm rejects) and drop undefined holes,
|
|
151
|
+
// so the shell starts clean. Returns a fresh object; the input is not mutated.
|
|
152
|
+
export function sanitizeTerminalEnv(env) {
|
|
153
|
+
const out = {};
|
|
154
|
+
for (const [key, value] of Object.entries(env)) {
|
|
155
|
+
if (value === undefined)
|
|
156
|
+
continue;
|
|
157
|
+
if (key.startsWith("npm_"))
|
|
158
|
+
continue;
|
|
159
|
+
out[key] = value;
|
|
160
|
+
}
|
|
161
|
+
return out;
|
|
162
|
+
}
|
package/dist/viewer.client.js
CHANGED
|
@@ -124,7 +124,16 @@ var I18N = JSON.parse(document.getElementById('i18n-data')?.textContent || '{}')
|
|
|
124
124
|
// app restart; file:// localStorage doesn't); browser/serve falls back to localStorage. persistRead
|
|
125
125
|
// returns the bridge value (native) if present, else undefined so callers parse localStorage themselves.
|
|
126
126
|
function persistRead(key) {
|
|
127
|
-
|
|
127
|
+
// window.monacoriSettings.all crosses Electron's contextBridge, which DEEP-FREEZES every value it
|
|
128
|
+
// exposes. Returning that frozen object/array directly breaks callers that mutate the result —
|
|
129
|
+
// reviewComments.push(...) and mergePrompts[kind]=... both throw "object is not extensible". Hand
|
|
130
|
+
// back a mutable deep copy so the persisted snapshot is a starting point, not a locked one.
|
|
131
|
+
try {
|
|
132
|
+
if (window.monacoriSettings && window.monacoriSettings.all && key in window.monacoriSettings.all) {
|
|
133
|
+
var v = window.monacoriSettings.all[key];
|
|
134
|
+
return v && typeof v === 'object' ? JSON.parse(JSON.stringify(v)) : v;
|
|
135
|
+
}
|
|
136
|
+
} catch (e) {}
|
|
128
137
|
return undefined;
|
|
129
138
|
}
|
|
130
139
|
function persistSave(key, value) {
|
|
@@ -1847,7 +1856,7 @@ function refreshComments() {
|
|
|
1847
1856
|
if (composerState) {
|
|
1848
1857
|
var composerFocusTries = 0;
|
|
1849
1858
|
var tryFocusComposer = function () {
|
|
1850
|
-
var ta =
|
|
1859
|
+
var ta = activeComposerInput();
|
|
1851
1860
|
if (!ta) return true; // composer gone — stop retrying
|
|
1852
1861
|
if (document.activeElement === ta) return true; // already focused — done
|
|
1853
1862
|
try { ta.focus({ preventScroll: true }); } catch (e) { try { ta.focus(); } catch (e2) {} }
|
|
@@ -1879,9 +1888,24 @@ function closeComposer() {
|
|
|
1879
1888
|
composerState = null;
|
|
1880
1889
|
refreshComments();
|
|
1881
1890
|
}
|
|
1891
|
+
// The composer is injected into BOTH the diff and source views (refreshComments renders comments in
|
|
1892
|
+
// each), but only one view is on screen at a time — the other lives inside a `.hidden` container with
|
|
1893
|
+
// its own, empty textarea. Pick the textarea in the *visible* view so save/auto-focus never grab the
|
|
1894
|
+
// off-screen duplicate. This was the "Comment doesn't save" bug: clicking Save ran
|
|
1895
|
+
// document.querySelector('.mc-composer .mc-input'), which returns the hidden diff-view textarea first
|
|
1896
|
+
// (it precedes #source-viewer in the DOM), so addComment got its empty value and bailed.
|
|
1897
|
+
function activeComposerInput() {
|
|
1898
|
+
var inputs = document.querySelectorAll('.mc-composer .mc-input');
|
|
1899
|
+
for (var i = 0; i < inputs.length; i++) {
|
|
1900
|
+
if (inputs[i].closest('#diff-view') && !isDiffViewVisible()) continue;
|
|
1901
|
+
if (inputs[i].closest('#source-viewer') && !isSourceViewerVisible()) continue;
|
|
1902
|
+
return inputs[i];
|
|
1903
|
+
}
|
|
1904
|
+
return inputs[0] || null;
|
|
1905
|
+
}
|
|
1882
1906
|
function saveComposer(ta) {
|
|
1883
1907
|
if (!composerState) return;
|
|
1884
|
-
var box = ta ||
|
|
1908
|
+
var box = ta || activeComposerInput();
|
|
1885
1909
|
if (!box) return;
|
|
1886
1910
|
addComment(composerState.kind, composerState.path, composerState.line, composerState.code, box.value);
|
|
1887
1911
|
composerState = null;
|
package/dist/viewer.css
CHANGED
|
@@ -532,6 +532,9 @@ summary.tree-focus { background: var(--bg); }
|
|
|
532
532
|
file's diff body extends all the way down. */
|
|
533
533
|
#diff-view:not(.hidden) { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }
|
|
534
534
|
#diff-view .diff2html-container { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }
|
|
535
|
+
/* Source view mirrors the diff view: fill the content column so the source-body (its last child, after
|
|
536
|
+
the tabs + toolbar) reaches the bottom even for short files, instead of floating at the top. */
|
|
537
|
+
#source-viewer:not(.hidden) { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }
|
|
535
538
|
.diff2html-container .d2h-wrapper { flex: 1 1 auto; display: flex; flex-direction: column; }
|
|
536
539
|
.diff2html-container .d2h-file-wrapper:last-child { flex: 1 1 auto; }
|
|
537
540
|
.diff2html-container .d2h-file-wrapper:last-child .d2h-files-diff { height: 100%; }
|
|
@@ -639,10 +642,13 @@ h1 { margin: 0; font-size: 18px; }
|
|
|
639
642
|
.source-tab-close:hover { background: var(--line); color: var(--text); }
|
|
640
643
|
.source-body {
|
|
641
644
|
border: 1px solid var(--border);
|
|
642
|
-
border-radius: 8px;
|
|
643
645
|
overflow: auto;
|
|
644
646
|
background: var(--panel);
|
|
645
647
|
user-select: text;
|
|
648
|
+
/* Square edges flush to the chrome (no rounded corners), and fill the content column so a short
|
|
649
|
+
file still reaches the bottom instead of floating at the top — IntelliJ-style. */
|
|
650
|
+
flex: 1 1 auto;
|
|
651
|
+
min-height: 0;
|
|
646
652
|
}
|
|
647
653
|
/* Extend the line-number gutter (and its divider) to the bottom of the panel so it never stops at the
|
|
648
654
|
last line with an empty strip + cut-off border below it — one continuous gutter. The .num cell is 58px
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happy-nut/monacori",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Validation control plane for AI-generated code changes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -29,10 +29,13 @@
|
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
31
|
"build": "tsc -p tsconfig.json && node scripts/copy-viewer-assets.mjs",
|
|
32
|
+
"dev": "npm run build && MONACORI_DEV=1 node bin/monacori.js --foreground",
|
|
32
33
|
"icon": "node scripts/gen-icon.mjs",
|
|
33
34
|
"postinstall": "node scripts/patch-electron-name.mjs && node scripts/fix-pty-spawn-helper.mjs",
|
|
34
35
|
"prepare": "npm run build",
|
|
35
|
-
"smoke": "npm run build && node dist/cli.js --help"
|
|
36
|
+
"smoke": "npm run build && node dist/cli.js --help",
|
|
37
|
+
"pretest": "npm run build",
|
|
38
|
+
"test": "node --test test/*.test.mjs"
|
|
36
39
|
},
|
|
37
40
|
"keywords": [
|
|
38
41
|
"ai",
|
|
@@ -45,6 +48,7 @@
|
|
|
45
48
|
"license": "MIT",
|
|
46
49
|
"devDependencies": {
|
|
47
50
|
"@types/node": "^22.15.21",
|
|
51
|
+
"jsdom": "^29.1.1",
|
|
48
52
|
"typescript": "^5.8.3"
|
|
49
53
|
},
|
|
50
54
|
"engines": {
|