@happy-nut/monacori 0.1.5 → 0.1.7
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 +55 -23
- package/assets/monacori-demo.gif +0 -0
- package/dist/app-main.js +4 -31
- package/dist/viewer.client.js +25 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,30 @@
|
|
|
1
1
|
# monacori
|
|
2
2
|
|
|
3
|
-
**A local desktop
|
|
3
|
+
**A local desktop review workspace for AI-generated code changes.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Run `mo` after an AI edits your repository. monacori opens a side-by-side diff, lets you attach line-level questions or change requests, and bundles that feedback back into the AI CLI (command-line interface) session running in the built-in terminal.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Why monacori
|
|
10
|
+
|
|
11
|
+
AI coding tools are fast, but their "done" message is not a review. monacori gives the human reviewer a dedicated control surface for the gap between generated code and trusted code:
|
|
12
|
+
|
|
13
|
+
- See every changed, added, and untracked file in an IntelliJ-style review sidebar.
|
|
14
|
+
- Review side-by-side diffs with syntax highlighting, changed-line emphasis, and keyboard navigation.
|
|
15
|
+
- Leave questions or change requests directly on the relevant line.
|
|
16
|
+
- Send all reviewer comments, with file paths and code context, into `claude`, `codex`, or another terminal session without copy-paste.
|
|
17
|
+
- Keep all generated review state local, plain, and inspectable under `.monacori/`.
|
|
18
|
+
|
|
19
|
+
## Workflow
|
|
20
|
+
|
|
21
|
+
1. Let an AI coding tool make changes in your repository.
|
|
22
|
+
2. Run `mo` from that repository.
|
|
23
|
+
3. Inspect the diff, mark files as viewed, and attach line comments where needed.
|
|
24
|
+
4. Open the built-in terminal and keep your AI CLI session beside the review.
|
|
25
|
+
5. Send the merged questions or change requests back to the session as a focused follow-up prompt.
|
|
26
|
+
|
|
27
|
+
The result is a tighter review loop: the AI produces changes, the human reviews the actual diff, and the next prompt is grounded in exact file and line context.
|
|
8
28
|
|
|
9
29
|
## Install
|
|
10
30
|
|
|
@@ -12,43 +32,55 @@ A chat log or a "done" claim is a poor way to review what an AI changed. monacor
|
|
|
12
32
|
npm install -g @happy-nut/monacori
|
|
13
33
|
```
|
|
14
34
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
## What you get
|
|
35
|
+
The short command is `mo`.
|
|
18
36
|
|
|
19
|
-
|
|
20
|
-
- **Integrated terminal** — run AI CLIs like `claude` or `codex` right inside the app, split into panes.
|
|
21
|
-
- **Comments → session** — annotate any line, then send your comments (bundled with their code context) into a terminal pane as one merged prompt: pick the target pane visually and press Enter.
|
|
37
|
+
## Quick Start
|
|
22
38
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Inside the repository you want to review:
|
|
39
|
+
Inside any Git repository:
|
|
26
40
|
|
|
27
41
|
```bash
|
|
28
42
|
mo
|
|
29
43
|
```
|
|
30
44
|
|
|
31
|
-
On first run, `mo` creates `.monacori/`, adds it to `.gitignore`, and includes untracked files so new AI-created files
|
|
45
|
+
On first run, `mo` creates `.monacori/`, adds it to `.gitignore`, and includes untracked files so new AI-created files appear immediately.
|
|
46
|
+
|
|
47
|
+
## Highlights
|
|
48
|
+
|
|
49
|
+
- **Desktop diff review**: reads the repository directly, refreshes from local Git state, and does not require a web server.
|
|
50
|
+
- **AI handoff comments**: questions and change requests are stored with their file, line, and code context.
|
|
51
|
+
- **Integrated terminal**: keep `claude`, `codex`, or a shell open inside the same window, with split panes when needed.
|
|
52
|
+
- **Source navigation**: jump between changed files, search indexed files, preview source, and move through hunks from the keyboard.
|
|
53
|
+
- **Plain local artifacts**: generated review files and state are Markdown, JSON, and static HTML under `.monacori/`.
|
|
32
54
|
|
|
33
55
|
## Commands
|
|
34
56
|
|
|
35
57
|
| Command | What it does |
|
|
36
58
|
| --- | --- |
|
|
37
|
-
| `mo` | Open the desktop diff-review app
|
|
38
|
-
| `monacori
|
|
59
|
+
| `mo` | Open the desktop diff-review app for the current repository. Alias for `monacori open`. |
|
|
60
|
+
| `monacori open` | Launch the review app, auto-initialize `.monacori/`, and include untracked files by default. |
|
|
61
|
+
| `monacori app` | Launch the same desktop app explicitly. |
|
|
39
62
|
| `monacori init` | Initialize `.monacori/` in the current directory. |
|
|
40
|
-
| `monacori install` | Initialize and write agent instruction snippets. `--apply-agent-docs`
|
|
63
|
+
| `monacori install` | Initialize and write agent instruction snippets. Use `--apply-agent-docs` to patch `AGENTS.md` or `CLAUDE.md`. |
|
|
64
|
+
|
|
65
|
+
Useful review options:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
mo --staged # review only staged changes
|
|
69
|
+
mo --tracked-only # exclude untracked files
|
|
70
|
+
mo --base main # compare against a specific base
|
|
71
|
+
mo --context 20 # show more context around each hunk
|
|
72
|
+
```
|
|
41
73
|
|
|
42
|
-
##
|
|
74
|
+
## Local State
|
|
43
75
|
|
|
44
|
-
`monacori init`
|
|
76
|
+
`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.
|
|
45
77
|
|
|
46
|
-
## Design
|
|
78
|
+
## Design Principles
|
|
47
79
|
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
- No required
|
|
80
|
+
- Real diffs beat chat summaries.
|
|
81
|
+
- Human review should stay close to the code and the running AI session.
|
|
82
|
+
- The core should be local, inspectable, and agent-agnostic.
|
|
83
|
+
- No required terminal multiplexer, editor plugin, hosted service, or worktree strategy.
|
|
52
84
|
|
|
53
85
|
## License
|
|
54
86
|
|
|
Binary file
|
package/dist/app-main.js
CHANGED
|
@@ -71,37 +71,10 @@ ipcMain.handle("monacori:pty-spawn", (_event, size) => {
|
|
|
71
71
|
if (mainWindow && !mainWindow.isDestroyed())
|
|
72
72
|
mainWindow.webContents.send(channel, payload);
|
|
73
73
|
};
|
|
74
|
-
// pty output
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
let flushTimer;
|
|
79
|
-
const flushOut = () => {
|
|
80
|
-
flushTimer = undefined;
|
|
81
|
-
if (!outBuf)
|
|
82
|
-
return;
|
|
83
|
-
const data = outBuf;
|
|
84
|
-
outBuf = "";
|
|
85
|
-
deliver("monacori:pty-data", { id, data });
|
|
86
|
-
};
|
|
87
|
-
t.onData((data) => {
|
|
88
|
-
outBuf += data;
|
|
89
|
-
if (outBuf.length >= 64 * 1024) {
|
|
90
|
-
if (flushTimer)
|
|
91
|
-
clearTimeout(flushTimer);
|
|
92
|
-
flushOut();
|
|
93
|
-
}
|
|
94
|
-
else if (!flushTimer) {
|
|
95
|
-
flushTimer = setTimeout(flushOut, 12);
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
t.onExit(() => {
|
|
99
|
-
if (flushTimer)
|
|
100
|
-
clearTimeout(flushTimer);
|
|
101
|
-
flushOut(); // deliver any buffered tail before signaling exit
|
|
102
|
-
terms.delete(id);
|
|
103
|
-
deliver("monacori:pty-exit", { id });
|
|
104
|
-
});
|
|
74
|
+
// Relay pty output to the renderer immediately, one IPC per chunk. (A coalescing buffer was tried as an
|
|
75
|
+
// optimization but it broke terminal I/O — the shell prompt and echo stopped appearing — so it's removed.)
|
|
76
|
+
t.onData((data) => deliver("monacori:pty-data", { id, data }));
|
|
77
|
+
t.onExit(() => { terms.delete(id); deliver("monacori:pty-exit", { id }); });
|
|
105
78
|
return { ok: true, id };
|
|
106
79
|
});
|
|
107
80
|
ipcMain.on("monacori:pty-write", (_event, msg) => { terms.get(msg?.id)?.write(msg.data); });
|
package/dist/viewer.client.js
CHANGED
|
@@ -455,6 +455,20 @@ function scheduleDiffScroll(row) {
|
|
|
455
455
|
});
|
|
456
456
|
}
|
|
457
457
|
|
|
458
|
+
// Coalesced scrollIntoView for caret/focus moves. Holding an arrow key fires key-repeat faster than a
|
|
459
|
+
// reflow-forcing scrollIntoView can run, so collapse them to one (latest element) per animation frame and
|
|
460
|
+
// use block:nearest (no per-row center-jump). Shared by the tree, source caret, and diff caret.
|
|
461
|
+
var pendingScrollEl = null, scrollElRaf = 0;
|
|
462
|
+
function scheduleScrollIntoView(el) {
|
|
463
|
+
pendingScrollEl = el || null;
|
|
464
|
+
if (scrollElRaf) return;
|
|
465
|
+
scrollElRaf = requestAnimationFrame(function () {
|
|
466
|
+
scrollElRaf = 0;
|
|
467
|
+
var e = pendingScrollEl; pendingScrollEl = null;
|
|
468
|
+
if (e && e.scrollIntoView) { try { e.scrollIntoView({ block: 'nearest', inline: 'nearest' }); } catch (x) {} }
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
458
472
|
var setActiveRaf = 0, setActiveScrollPending = true;
|
|
459
473
|
function setActive(index, shouldScroll = true) {
|
|
460
474
|
if (hunkTotal() === 0) return;
|
|
@@ -814,10 +828,11 @@ function treeRows() {
|
|
|
814
828
|
function focusTree(index) {
|
|
815
829
|
const rows = treeRows();
|
|
816
830
|
if (rows.length === 0) return;
|
|
831
|
+
// Incremental: drop the old focus class and add the new one — no full forEach over every row per keystroke.
|
|
832
|
+
if (treeFocusIndex >= 0 && treeFocusIndex < rows.length) rows[treeFocusIndex]?.classList.remove('tree-focus');
|
|
817
833
|
treeFocusIndex = Math.max(0, Math.min(rows.length - 1, index));
|
|
818
|
-
rows.forEach((row, i) => row.classList.toggle('tree-focus', i === treeFocusIndex));
|
|
819
834
|
const el = rows[treeFocusIndex];
|
|
820
|
-
if (el) el.
|
|
835
|
+
if (el) { el.classList.add('tree-focus'); scheduleScrollIntoView(el); }
|
|
821
836
|
}
|
|
822
837
|
|
|
823
838
|
function clearTreeFocus() {
|
|
@@ -1454,10 +1469,7 @@ function setDiffCursor(path, side, rowIndex, column, reveal) {
|
|
|
1454
1469
|
diffSelectionAnchor = null; // any direct caret placement (click/F7/Cmd-arrow) drops the selection; Shift+Arrow re-sets it
|
|
1455
1470
|
renderDiffCaret();
|
|
1456
1471
|
applyDiffSelection();
|
|
1457
|
-
if (reveal)
|
|
1458
|
-
var r = diffRowAt(wrapper, side, ri);
|
|
1459
|
-
if (r && r.scrollIntoView) requestAnimationFrame(function () { try { r.scrollIntoView({ block: 'nearest' }); } catch (e) {} });
|
|
1460
|
-
}
|
|
1472
|
+
if (reveal) scheduleScrollIntoView(diffRowAt(wrapper, side, ri));
|
|
1461
1473
|
recordNav(navEntryOf('diff'));
|
|
1462
1474
|
}
|
|
1463
1475
|
function navEntryOf(kind) {
|
|
@@ -2138,7 +2150,12 @@ refreshComments();
|
|
|
2138
2150
|
// 2+ panes: dim every pane but the active one (no border, just a clean focus cue). A lone pane stays full.
|
|
2139
2151
|
q.el.classList.toggle('is-inactive', panes.length > 1 && q !== p);
|
|
2140
2152
|
});
|
|
2141
|
-
if (p) requestAnimationFrame(function () {
|
|
2153
|
+
if (p) requestAnimationFrame(function () {
|
|
2154
|
+
try {
|
|
2155
|
+
if (p.labelEl && p.labelEl.getAttribute('contenteditable') === 'true') return;
|
|
2156
|
+
p.term.focus();
|
|
2157
|
+
} catch (e) {}
|
|
2158
|
+
});
|
|
2142
2159
|
}
|
|
2143
2160
|
|
|
2144
2161
|
function makePane() {
|
|
@@ -2918,11 +2935,7 @@ function setSourceCursor(path, lineIndex, column, shouldReveal = false, targetLi
|
|
|
2918
2935
|
const shouldSwitch = !viewer || viewer.dataset.openPath !== path || viewer.classList.contains('hidden');
|
|
2919
2936
|
openSourceFile(path, shouldSwitch);
|
|
2920
2937
|
}
|
|
2921
|
-
if (shouldReveal)
|
|
2922
|
-
requestAnimationFrame(() => {
|
|
2923
|
-
document.querySelector('.source-row.cursor-line')?.scrollIntoView({ block: 'center' });
|
|
2924
|
-
});
|
|
2925
|
-
}
|
|
2938
|
+
if (shouldReveal) scheduleScrollIntoView(document.querySelector('.source-row.cursor-line'));
|
|
2926
2939
|
recordNav(navEntryOf('source'));
|
|
2927
2940
|
}
|
|
2928
2941
|
|