@loompad/cli 0.1.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/ARCHITECTURE.md +211 -0
- package/CHANGELOG.md +470 -0
- package/LICENSE +21 -0
- package/README.md +630 -0
- package/dist/adapters/base.d.ts +71 -0
- package/dist/adapters/base.js +187 -0
- package/dist/adapters/base.js.map +1 -0
- package/dist/adapters/bridges/antigravity.d.ts +53 -0
- package/dist/adapters/bridges/antigravity.js +106 -0
- package/dist/adapters/bridges/antigravity.js.map +1 -0
- package/dist/adapters/bridges/cdp.d.ts +82 -0
- package/dist/adapters/bridges/cdp.js +222 -0
- package/dist/adapters/bridges/cdp.js.map +1 -0
- package/dist/adapters/bridges/gui-chat.d.ts +113 -0
- package/dist/adapters/bridges/gui-chat.js +498 -0
- package/dist/adapters/bridges/gui-chat.js.map +1 -0
- package/dist/adapters/bridges/kiro.d.ts +19 -0
- package/dist/adapters/bridges/kiro.js +22 -0
- package/dist/adapters/bridges/kiro.js.map +1 -0
- package/dist/adapters/bridges/profiles.d.ts +82 -0
- package/dist/adapters/bridges/profiles.js +125 -0
- package/dist/adapters/bridges/profiles.js.map +1 -0
- package/dist/adapters/claude-code.d.ts +26 -0
- package/dist/adapters/claude-code.js +209 -0
- package/dist/adapters/claude-code.js.map +1 -0
- package/dist/adapters/codex.d.ts +51 -0
- package/dist/adapters/codex.js +291 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/echo.d.ts +20 -0
- package/dist/adapters/echo.js +79 -0
- package/dist/adapters/echo.js.map +1 -0
- package/dist/adapters/grok.d.ts +61 -0
- package/dist/adapters/grok.js +217 -0
- package/dist/adapters/grok.js.map +1 -0
- package/dist/adapters/index.d.ts +9 -0
- package/dist/adapters/index.js +33 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/opencode.d.ts +64 -0
- package/dist/adapters/opencode.js +426 -0
- package/dist/adapters/opencode.js.map +1 -0
- package/dist/cli/common.d.ts +11 -0
- package/dist/cli/common.js +40 -0
- package/dist/cli/common.js.map +1 -0
- package/dist/cli/doctor.d.ts +12 -0
- package/dist/cli/doctor.js +181 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.js +641 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/tui-model.d.ts +73 -0
- package/dist/cli/tui-model.js +312 -0
- package/dist/cli/tui-model.js.map +1 -0
- package/dist/cli/tui.d.ts +15 -0
- package/dist/cli/tui.js +451 -0
- package/dist/cli/tui.js.map +1 -0
- package/dist/cli/ui.d.ts +10 -0
- package/dist/cli/ui.js +135 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/core/ades.d.ts +79 -0
- package/dist/core/ades.js +130 -0
- package/dist/core/ades.js.map +1 -0
- package/dist/core/baton.d.ts +37 -0
- package/dist/core/baton.js +92 -0
- package/dist/core/baton.js.map +1 -0
- package/dist/core/brain-extract.d.ts +111 -0
- package/dist/core/brain-extract.js +248 -0
- package/dist/core/brain-extract.js.map +1 -0
- package/dist/core/brain-index.d.ts +128 -0
- package/dist/core/brain-index.js +338 -0
- package/dist/core/brain-index.js.map +1 -0
- package/dist/core/brain.d.ts +214 -0
- package/dist/core/brain.js +425 -0
- package/dist/core/brain.js.map +1 -0
- package/dist/core/claude-cli.d.ts +12 -0
- package/dist/core/claude-cli.js +47 -0
- package/dist/core/claude-cli.js.map +1 -0
- package/dist/core/distill.d.ts +19 -0
- package/dist/core/distill.js +88 -0
- package/dist/core/distill.js.map +1 -0
- package/dist/core/eventlog.d.ts +43 -0
- package/dist/core/eventlog.js +211 -0
- package/dist/core/eventlog.js.map +1 -0
- package/dist/core/git.d.ts +203 -0
- package/dist/core/git.js +460 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/logbook.d.ts +59 -0
- package/dist/core/logbook.js +118 -0
- package/dist/core/logbook.js.map +1 -0
- package/dist/core/memory.d.ts +34 -0
- package/dist/core/memory.js +124 -0
- package/dist/core/memory.js.map +1 -0
- package/dist/core/notify.d.ts +11 -0
- package/dist/core/notify.js +30 -0
- package/dist/core/notify.js.map +1 -0
- package/dist/core/projection.d.ts +20 -0
- package/dist/core/projection.js +96 -0
- package/dist/core/projection.js.map +1 -0
- package/dist/core/registry.d.ts +76 -0
- package/dist/core/registry.js +147 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/router.d.ts +42 -0
- package/dist/core/router.js +99 -0
- package/dist/core/router.js.map +1 -0
- package/dist/core/routes.d.ts +93 -0
- package/dist/core/routes.js +399 -0
- package/dist/core/routes.js.map +1 -0
- package/dist/core/search.d.ts +63 -0
- package/dist/core/search.js +0 -0
- package/dist/core/search.js.map +1 -0
- package/dist/core/setup.d.ts +70 -0
- package/dist/core/setup.js +250 -0
- package/dist/core/setup.js.map +1 -0
- package/dist/core/suggestions.d.ts +15 -0
- package/dist/core/suggestions.js +38 -0
- package/dist/core/suggestions.js.map +1 -0
- package/dist/core/worktree.d.ts +32 -0
- package/dist/core/worktree.js +83 -0
- package/dist/core/worktree.js.map +1 -0
- package/dist/daemon/app-page.d.ts +32 -0
- package/dist/daemon/app-page.js +6363 -0
- package/dist/daemon/app-page.js.map +1 -0
- package/dist/daemon/auth.d.ts +51 -0
- package/dist/daemon/auth.js +135 -0
- package/dist/daemon/auth.js.map +1 -0
- package/dist/daemon/board.d.ts +117 -0
- package/dist/daemon/board.js +202 -0
- package/dist/daemon/board.js.map +1 -0
- package/dist/daemon/brand-icons.d.ts +29 -0
- package/dist/daemon/brand-icons.js +44 -0
- package/dist/daemon/brand-icons.js.map +1 -0
- package/dist/daemon/client.d.ts +127 -0
- package/dist/daemon/client.js +237 -0
- package/dist/daemon/client.js.map +1 -0
- package/dist/daemon/geist-font.d.ts +10 -0
- package/dist/daemon/geist-font.js +783 -0
- package/dist/daemon/geist-font.js.map +1 -0
- package/dist/daemon/guards.d.ts +18 -0
- package/dist/daemon/guards.js +40 -0
- package/dist/daemon/guards.js.map +1 -0
- package/dist/daemon/linear.d.ts +59 -0
- package/dist/daemon/linear.js +117 -0
- package/dist/daemon/linear.js.map +1 -0
- package/dist/daemon/push.d.ts +22 -0
- package/dist/daemon/push.js +58 -0
- package/dist/daemon/push.js.map +1 -0
- package/dist/daemon/runtime.d.ts +299 -0
- package/dist/daemon/runtime.js +1016 -0
- package/dist/daemon/runtime.js.map +1 -0
- package/dist/daemon/server.d.ts +125 -0
- package/dist/daemon/server.js +1728 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/daemon/tasks.d.ts +153 -0
- package/dist/daemon/tasks.js +264 -0
- package/dist/daemon/tasks.js.map +1 -0
- package/dist/daemon/terminals.d.ts +82 -0
- package/dist/daemon/terminals.js +372 -0
- package/dist/daemon/terminals.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/sdk.d.ts +17 -0
- package/dist/sdk.js +17 -0
- package/dist/sdk.js.map +1 -0
- package/dist/types.d.ts +236 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/docs/SETUP.md +265 -0
- package/docs/adapters.md +132 -0
- package/docs/design-system.md +245 -0
- package/docs/img/board-linear.png +0 -0
- package/docs/img/board-projects.png +0 -0
- package/docs/img/brain.png +0 -0
- package/docs/img/settings.png +0 -0
- package/docs/img/workspace.png +0 -0
- package/docs/integration-notes.md +76 -0
- package/docs/proposals/brain.md +262 -0
- package/package.json +84 -0
package/dist/core/git.js
ADDED
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source control that does something.
|
|
3
|
+
*
|
|
4
|
+
* worktree.ts reads: what changed, what the patch is. This writes: stage,
|
|
5
|
+
* unstage, discard, commit. The split is deliberate — reading is safe and runs
|
|
6
|
+
* on a timer, writing touches your repository and only ever happens because you
|
|
7
|
+
* clicked something.
|
|
8
|
+
*
|
|
9
|
+
* ## Why every path is checked instead of trusted
|
|
10
|
+
*
|
|
11
|
+
* These take file paths from an HTTP body, and `git checkout -- <path>` deletes
|
|
12
|
+
* your work. A path that escapes the project ("../../etc/…") would let a paired
|
|
13
|
+
* device discard files outside the repo entirely. Every path is resolved and
|
|
14
|
+
* confirmed to sit inside the project before git sees it, and `--` separates
|
|
15
|
+
* paths from flags so a file named `-f` can't become one.
|
|
16
|
+
*
|
|
17
|
+
* ## Why errors come back instead of being swallowed
|
|
18
|
+
*
|
|
19
|
+
* worktree.ts's git() resolves "" on failure, which is right for a read — a
|
|
20
|
+
* missing branch name isn't worth an exception. It is wrong for a write: a
|
|
21
|
+
* commit that silently does nothing is the worst outcome available. These throw
|
|
22
|
+
* with git's own stderr, which is nearly always the actual answer ("nothing to
|
|
23
|
+
* commit", "please tell me who you are").
|
|
24
|
+
*/
|
|
25
|
+
import { execFile } from "node:child_process";
|
|
26
|
+
import path from "node:path";
|
|
27
|
+
import { logbook } from "./logbook.js";
|
|
28
|
+
export class GitError extends Error {
|
|
29
|
+
stderr;
|
|
30
|
+
constructor(message, stderr) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.stderr = stderr;
|
|
33
|
+
this.name = "GitError";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Run git, and say what went wrong when it does. */
|
|
37
|
+
function git(args, cwd) {
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
execFile("git", args, { cwd, maxBuffer: 8 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
40
|
+
if (err) {
|
|
41
|
+
const detail = String(stderr || err.message).trim();
|
|
42
|
+
// git's own words beat ours: "nothing to commit, working tree clean" is
|
|
43
|
+
// a better message than anything we'd write about exit code 1.
|
|
44
|
+
reject(new GitError(firstLine(detail) || `git ${args[0]} failed`, detail));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
resolve(String(stdout));
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function firstLine(s) {
|
|
52
|
+
return s.split("\n").find((l) => l.trim())?.trim() ?? "";
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validate a ref/branch name before it becomes a positional git argument.
|
|
56
|
+
*
|
|
57
|
+
* These names arrive over HTTP. Everything runs via `execFile` (no shell), so
|
|
58
|
+
* there's no command injection — but a name beginning with `-` is read by git
|
|
59
|
+
* as a *flag*, and `git checkout -f` force-discards the working tree. Reject the
|
|
60
|
+
* leading dash (and keep the char set tight); callers pass the label for the
|
|
61
|
+
* error. Returns the trimmed name.
|
|
62
|
+
*/
|
|
63
|
+
function assertRef(name, label = "ref") {
|
|
64
|
+
const clean = name.trim();
|
|
65
|
+
if (!clean)
|
|
66
|
+
throw new GitError(`no ${label} given`, "");
|
|
67
|
+
if (!/^[A-Za-z0-9_.+/][\w./+-]*$/.test(clean)) {
|
|
68
|
+
throw new GitError(`"${clean}" is not a valid ${label} name`, "");
|
|
69
|
+
}
|
|
70
|
+
return clean;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Keep a path inside the project.
|
|
74
|
+
*
|
|
75
|
+
* `git checkout -- <path>` is destructive and these paths arrive over HTTP from
|
|
76
|
+
* a device that could be anywhere on your tailnet. Resolve, then prove it's
|
|
77
|
+
* within the root — a prefix check on the un-normalised string would wave
|
|
78
|
+
* "../.." straight through.
|
|
79
|
+
*/
|
|
80
|
+
export function safeRelPath(dir, rel) {
|
|
81
|
+
const root = path.resolve(dir);
|
|
82
|
+
const full = path.resolve(root, rel);
|
|
83
|
+
const inside = full === root || full.startsWith(root + path.sep);
|
|
84
|
+
if (!inside)
|
|
85
|
+
throw new GitError(`"${rel}" is outside the project`, "");
|
|
86
|
+
const out = path.relative(root, full);
|
|
87
|
+
if (!out)
|
|
88
|
+
throw new GitError("that's the project itself, not a file in it", "");
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
function checkAll(dir, paths) {
|
|
92
|
+
if (!paths.length)
|
|
93
|
+
throw new GitError("no files given", "");
|
|
94
|
+
return paths.map((p) => safeRelPath(dir, p));
|
|
95
|
+
}
|
|
96
|
+
export async function isRepo(dir) {
|
|
97
|
+
try {
|
|
98
|
+
return (await git(["rev-parse", "--is-inside-work-tree"], dir)).trim() === "true";
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export async function stage(dir, paths) {
|
|
105
|
+
const rels = checkAll(dir, paths);
|
|
106
|
+
// `--` ends the flags: a file called "-f" is a file, not an option.
|
|
107
|
+
await git(["add", "--", ...rels], dir);
|
|
108
|
+
logbook.info("git", `staged ${rels.length} file${rels.length === 1 ? "" : "s"}`, rels.join("\n"));
|
|
109
|
+
return { staged: rels };
|
|
110
|
+
}
|
|
111
|
+
export async function unstage(dir, paths) {
|
|
112
|
+
const rels = checkAll(dir, paths);
|
|
113
|
+
// Before the first commit there is no HEAD to restore *from*, and both of the
|
|
114
|
+
// obvious commands — `reset HEAD --` and `restore --staged` — fail with
|
|
115
|
+
// "fatal: could not resolve HEAD". (I wrote a comment here claiming restore
|
|
116
|
+
// was immune. It isn't; a test said so.) `rm --cached` is the one that works
|
|
117
|
+
// there, because it only ever removes the index entry.
|
|
118
|
+
if (await hasCommits(dir))
|
|
119
|
+
await git(["restore", "--staged", "--", ...rels], dir);
|
|
120
|
+
else
|
|
121
|
+
await git(["rm", "--cached", "-q", "--", ...rels], dir);
|
|
122
|
+
logbook.info("git", `unstaged ${rels.length} file${rels.length === 1 ? "" : "s"}`, rels.join("\n"));
|
|
123
|
+
return { unstaged: rels };
|
|
124
|
+
}
|
|
125
|
+
/** Does HEAD resolve? False in a repo whose first commit hasn't happened. */
|
|
126
|
+
async function hasCommits(dir) {
|
|
127
|
+
try {
|
|
128
|
+
await git(["rev-parse", "--verify", "HEAD"], dir);
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Throw work away.
|
|
137
|
+
*
|
|
138
|
+
* The one operation here that can lose something you can't get back, so it is
|
|
139
|
+
* the one that is loudest in the log. Tracked files are restored from the
|
|
140
|
+
* index; untracked ones have nothing to restore to and must be deleted, which
|
|
141
|
+
* `git checkout` won't do — hence `clean -f` for those, and hence the split.
|
|
142
|
+
*/
|
|
143
|
+
export async function discard(dir, paths, untracked = []) {
|
|
144
|
+
const tracked = paths.length ? checkAll(dir, paths) : [];
|
|
145
|
+
const toClean = untracked.length ? checkAll(dir, untracked) : [];
|
|
146
|
+
if (!tracked.length && !toClean.length)
|
|
147
|
+
throw new GitError("no files given", "");
|
|
148
|
+
if (tracked.length)
|
|
149
|
+
await git(["checkout", "--", ...tracked], dir);
|
|
150
|
+
if (toClean.length)
|
|
151
|
+
await git(["clean", "-fd", "--", ...toClean], dir);
|
|
152
|
+
const all = [...tracked, ...toClean];
|
|
153
|
+
logbook.warn("git", `discarded ${all.length} file${all.length === 1 ? "" : "s"} — this is not undoable`, all.join("\n"));
|
|
154
|
+
return { discarded: all };
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Commit what's staged.
|
|
158
|
+
*
|
|
159
|
+
* Nothing is staged for you. "Commit all" is a convenience that has ended more
|
|
160
|
+
* afternoons than it saved — an agent's stray file swept into your commit
|
|
161
|
+
* because the button was easier than looking. If you want it in, stage it.
|
|
162
|
+
*/
|
|
163
|
+
export async function commit(dir, message) {
|
|
164
|
+
const text = message.trim();
|
|
165
|
+
if (!text)
|
|
166
|
+
throw new GitError("a commit needs a message", "");
|
|
167
|
+
const staged = (await git(["diff", "--cached", "--name-only"], dir)).trim();
|
|
168
|
+
if (!staged)
|
|
169
|
+
throw new GitError("nothing staged — tick the files you want in this commit", "");
|
|
170
|
+
// -m twice would make a body; one message, passed as one arg, can contain
|
|
171
|
+
// anything including newlines and quotes because it never touches a shell.
|
|
172
|
+
await git(["commit", "-m", text], dir);
|
|
173
|
+
const sha = (await git(["rev-parse", "--short", "HEAD"], dir)).trim();
|
|
174
|
+
const files = staged.split("\n").filter(Boolean).length;
|
|
175
|
+
logbook.info("git", `committed ${sha}: ${firstLine(text)}`, `${files} file(s)\n${staged}`);
|
|
176
|
+
return { sha, subject: firstLine(text), files };
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* The status the UI needs: staged and unstaged as separate lists.
|
|
180
|
+
*
|
|
181
|
+
* Porcelain's XY is two columns for a reason — X is the index, Y is the working
|
|
182
|
+
* tree — and a file can be in both at once (staged, then edited again). Flatten
|
|
183
|
+
* that into one list and you get a checkbox that lies about what a commit will
|
|
184
|
+
* contain.
|
|
185
|
+
*/
|
|
186
|
+
export async function status(dir) {
|
|
187
|
+
if (!(await isRepo(dir))) {
|
|
188
|
+
return { branch: "", ahead: 0, behind: 0, upstream: null, staged: [], unstaged: [], untracked: [] };
|
|
189
|
+
}
|
|
190
|
+
const porcelain = await git(["status", "--porcelain=v1", "-uall", "--branch"], dir).catch(() => "");
|
|
191
|
+
const lines = porcelain.split("\n").filter(Boolean);
|
|
192
|
+
let branch = "";
|
|
193
|
+
let upstream = null;
|
|
194
|
+
let ahead = 0;
|
|
195
|
+
let behind = 0;
|
|
196
|
+
const staged = [];
|
|
197
|
+
const unstaged = [];
|
|
198
|
+
const untracked = [];
|
|
199
|
+
for (const line of lines) {
|
|
200
|
+
if (line.startsWith("## ")) {
|
|
201
|
+
const head = line.slice(3);
|
|
202
|
+
const [names, counts] = head.split(" [");
|
|
203
|
+
const [local, up] = (names ?? "").split("...");
|
|
204
|
+
branch = (local ?? "").trim();
|
|
205
|
+
upstream = up ? up.trim() : null;
|
|
206
|
+
if (counts) {
|
|
207
|
+
ahead = Number(/ahead (\d+)/.exec(counts)?.[1] ?? 0);
|
|
208
|
+
behind = Number(/behind (\d+)/.exec(counts)?.[1] ?? 0);
|
|
209
|
+
}
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const x = line[0] ?? " ";
|
|
213
|
+
const y = line[1] ?? " ";
|
|
214
|
+
const file = line.slice(3).trim();
|
|
215
|
+
if (x === "?" && y === "?") {
|
|
216
|
+
untracked.push(file);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
// A file can be in both lists at once — staged, then edited again. That's
|
|
220
|
+
// the truth, and hiding it is how you commit a version you never saw.
|
|
221
|
+
if (x !== " ")
|
|
222
|
+
staged.push({ status: x, path: file });
|
|
223
|
+
if (y !== " ")
|
|
224
|
+
unstaged.push({ status: y, path: file });
|
|
225
|
+
}
|
|
226
|
+
return { branch, ahead, behind, upstream, staged, unstaged, untracked };
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* git that might touch the network (push/fetch), bounded by a timeout.
|
|
230
|
+
*
|
|
231
|
+
* The plain git() above has none, which is fine for local commands — they
|
|
232
|
+
* finish or fail fast. A push against an unreachable or auth-prompting remote
|
|
233
|
+
* hangs forever, and a hung request is a worse failure than a slow one. Auth is
|
|
234
|
+
* delegated entirely to the user's own git credential helper; Loom stores no
|
|
235
|
+
* tokens, the same posture as its gh usage.
|
|
236
|
+
*/
|
|
237
|
+
function gitNet(args, cwd, timeoutMs = 45_000) {
|
|
238
|
+
return new Promise((resolve, reject) => {
|
|
239
|
+
execFile("git", args, { cwd, maxBuffer: 8 * 1024 * 1024, timeout: timeoutMs }, (err, stdout, stderr) => {
|
|
240
|
+
if (err) {
|
|
241
|
+
const detail = String(stderr || err.message).trim();
|
|
242
|
+
const killed = err.killed;
|
|
243
|
+
reject(new GitError(killed ? "push timed out — is the remote reachable?" : firstLine(detail) || `git ${args[0]} failed`, detail));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
// push writes its progress to stderr even on success, so include it.
|
|
247
|
+
resolve(String(stdout) + String(stderr));
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Start version control here.
|
|
253
|
+
*
|
|
254
|
+
* Refuses if the directory is already inside a repo, so an accidental click
|
|
255
|
+
* can't nest one repo inside another. Renames the initial branch to `main` when
|
|
256
|
+
* git left it on `master` — safe because a just-init'd repo has no commits, and
|
|
257
|
+
* it matches what remotes expect.
|
|
258
|
+
*/
|
|
259
|
+
export async function init(dir) {
|
|
260
|
+
if (await isRepo(dir))
|
|
261
|
+
throw new GitError("this is already a git repository", "");
|
|
262
|
+
await git(["init"], dir);
|
|
263
|
+
const branch = (await git(["rev-parse", "--abbrev-ref", "HEAD"], dir).catch(() => "")).trim();
|
|
264
|
+
if (branch === "master")
|
|
265
|
+
await git(["branch", "-m", "main"], dir).catch(() => { });
|
|
266
|
+
logbook.info("git", "initialised a git repository", dir);
|
|
267
|
+
return { branch: branch === "master" || !branch ? "main" : branch };
|
|
268
|
+
}
|
|
269
|
+
/** Recent commits, newest first. Empty in a repo whose first commit hasn't landed. */
|
|
270
|
+
export async function log(dir, limit = 30) {
|
|
271
|
+
if (!(await hasCommits(dir)))
|
|
272
|
+
return [];
|
|
273
|
+
// Unit and record separators keep subjects with commas/newlines intact.
|
|
274
|
+
const US = "\x1f";
|
|
275
|
+
const RS = "\x1e";
|
|
276
|
+
const fmt = ["%H", "%h", "%s", "%an", "%ar", "%at"].join(US) + RS;
|
|
277
|
+
const n = String(Math.min(200, Math.max(1, Math.floor(limit) || 30)));
|
|
278
|
+
const out = await git(["log", `--pretty=format:${fmt}`, "-n", n], dir).catch(() => "");
|
|
279
|
+
return out
|
|
280
|
+
.split(RS)
|
|
281
|
+
.map((r) => r.replace(/^\n/, "").trim())
|
|
282
|
+
.filter(Boolean)
|
|
283
|
+
.map((r) => {
|
|
284
|
+
const [sha, short, subject, author, relative, at] = r.split(US);
|
|
285
|
+
return {
|
|
286
|
+
sha: sha ?? "",
|
|
287
|
+
short: short ?? "",
|
|
288
|
+
subject: subject ?? "",
|
|
289
|
+
author: author ?? "",
|
|
290
|
+
relative: relative ?? "",
|
|
291
|
+
ts: Number(at ?? 0) * 1000,
|
|
292
|
+
};
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* The unified diff for one file, HEAD → working tree (both staged and unstaged
|
|
297
|
+
* changes). Before the first commit there's no HEAD, so it diffs against the
|
|
298
|
+
* index instead. Empty string when nothing differs.
|
|
299
|
+
*/
|
|
300
|
+
export async function fileDiff(dir, rel) {
|
|
301
|
+
const safe = safeRelPath(dir, rel);
|
|
302
|
+
if (await hasCommits(dir))
|
|
303
|
+
return git(["diff", "HEAD", "--", safe], dir).catch(() => "");
|
|
304
|
+
return git(["diff", "--", safe], dir).catch(() => "");
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* The diff a commit would capture — staged changes, or the whole working tree
|
|
308
|
+
* when nothing is staged yet. Used to write a commit message from. Bounded so a
|
|
309
|
+
* giant refactor doesn't blow the LLM's context (the summary only needs the
|
|
310
|
+
* shape of the change, not every line).
|
|
311
|
+
*/
|
|
312
|
+
export async function stagedDiff(dir, maxChars = 12_000) {
|
|
313
|
+
let out = await git(["diff", "--cached"], dir).catch(() => "");
|
|
314
|
+
if (!out.trim())
|
|
315
|
+
out = await git(["diff"], dir).catch(() => "");
|
|
316
|
+
return out.length > maxChars ? out.slice(0, maxChars) + "\n… (truncated)" : out;
|
|
317
|
+
}
|
|
318
|
+
/** Local branches and which one is checked out — for the checkout picker. */
|
|
319
|
+
export async function branches(dir) {
|
|
320
|
+
if (!(await hasCommits(dir)))
|
|
321
|
+
return { current: "", all: [] };
|
|
322
|
+
const out = await git(["branch", "--format=%(refname:short)"], dir).catch(() => "");
|
|
323
|
+
const all = out.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
324
|
+
const current = (await git(["rev-parse", "--abbrev-ref", "HEAD"], dir).catch(() => "")).trim();
|
|
325
|
+
return { current, all };
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Switch to a branch or commit.
|
|
329
|
+
*
|
|
330
|
+
* git refuses on its own when the switch would overwrite uncommitted changes,
|
|
331
|
+
* and that refusal (with its list of the files at risk) is exactly the message
|
|
332
|
+
* to surface — so we don't second-guess it, we just pass its stderr back. The
|
|
333
|
+
* ref is character-validated because it reaches a git command, though `--`
|
|
334
|
+
* already stops it being read as a flag.
|
|
335
|
+
*/
|
|
336
|
+
export async function checkout(dir, ref) {
|
|
337
|
+
const clean = assertRef(ref);
|
|
338
|
+
await git(["checkout", clean], dir);
|
|
339
|
+
const branch = (await git(["rev-parse", "--abbrev-ref", "HEAD"], dir).catch(() => "")).trim();
|
|
340
|
+
logbook.info("git", `checked out ${clean}`, dir);
|
|
341
|
+
return { ref: clean, branch };
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* How far behind (and ahead of) the upstream this checkout is — for "check for
|
|
345
|
+
* updates". Fetches first (bounded), so it reflects the remote, not a stale
|
|
346
|
+
* local ref. null when the dir isn't a git checkout; behind 0 with no upstream
|
|
347
|
+
* means "nothing to compare against", not "up to date".
|
|
348
|
+
*/
|
|
349
|
+
export async function remoteBehind(dir) {
|
|
350
|
+
if (!(await isRepo(dir)))
|
|
351
|
+
return null;
|
|
352
|
+
const branch = (await git(["rev-parse", "--abbrev-ref", "HEAD"], dir).catch(() => "")).trim();
|
|
353
|
+
const up = (await git(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], dir).catch(() => "")).trim();
|
|
354
|
+
if (!up)
|
|
355
|
+
return { behind: 0, ahead: 0, branch, hasUpstream: false };
|
|
356
|
+
await gitNet(["fetch", "--quiet"], dir).catch(() => { }); // best-effort; offline is fine
|
|
357
|
+
const count = async (range) => Number((await git(["rev-list", "--count", range], dir).catch(() => "0")).trim()) || 0;
|
|
358
|
+
return {
|
|
359
|
+
behind: await count("HEAD..@{u}"),
|
|
360
|
+
ahead: await count("@{u}..HEAD"),
|
|
361
|
+
branch,
|
|
362
|
+
hasUpstream: true,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Push to the upstream, setting it on the first push.
|
|
367
|
+
*
|
|
368
|
+
* A branch with no upstream needs `-u origin <branch>` once; after that a bare
|
|
369
|
+
* `git push` is enough. Either way the network is bounded (gitNet) and auth is
|
|
370
|
+
* the user's own credential helper. A repo with no remote at all fails with
|
|
371
|
+
* git's "No configured push destination", which is the honest answer.
|
|
372
|
+
*/
|
|
373
|
+
export async function push(dir) {
|
|
374
|
+
if (!(await isRepo(dir)))
|
|
375
|
+
throw new GitError("not a git repository", "");
|
|
376
|
+
if (!(await hasCommits(dir)))
|
|
377
|
+
throw new GitError("nothing to push — make a commit first", "");
|
|
378
|
+
const st = await status(dir);
|
|
379
|
+
const branch = st.branch || "HEAD";
|
|
380
|
+
const detail = st.upstream
|
|
381
|
+
? await gitNet(["push"], dir)
|
|
382
|
+
: await gitNet(["push", "-u", "origin", branch], dir);
|
|
383
|
+
logbook.info("git", `pushed ${branch}`, detail.trim());
|
|
384
|
+
return { branch, detail: detail.trim() };
|
|
385
|
+
}
|
|
386
|
+
/** A directory-safe slug for a branch or task name. */
|
|
387
|
+
function wtSlug(s) {
|
|
388
|
+
return (s.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "wt");
|
|
389
|
+
}
|
|
390
|
+
/** Where a new worktree for this repo goes: `<repo>--<slug>`, a sibling. */
|
|
391
|
+
export function worktreePath(dir, slug) {
|
|
392
|
+
const root = path.resolve(dir);
|
|
393
|
+
return path.join(path.dirname(root), path.basename(root) + "--" + wtSlug(slug));
|
|
394
|
+
}
|
|
395
|
+
export async function listWorktrees(dir) {
|
|
396
|
+
if (!(await isRepo(dir)))
|
|
397
|
+
return [];
|
|
398
|
+
const out = await git(["worktree", "list", "--porcelain"], dir).catch(() => "");
|
|
399
|
+
const trees = [];
|
|
400
|
+
let cur = null;
|
|
401
|
+
const flush = () => {
|
|
402
|
+
if (cur?.path)
|
|
403
|
+
trees.push({ path: cur.path, branch: cur.branch ?? null, head: cur.head ?? "" });
|
|
404
|
+
};
|
|
405
|
+
for (const line of out.split("\n")) {
|
|
406
|
+
if (line.startsWith("worktree ")) {
|
|
407
|
+
flush();
|
|
408
|
+
cur = { path: line.slice("worktree ".length).trim() };
|
|
409
|
+
}
|
|
410
|
+
else if (line.startsWith("HEAD ")) {
|
|
411
|
+
if (cur)
|
|
412
|
+
cur.head = line.slice("HEAD ".length).trim();
|
|
413
|
+
}
|
|
414
|
+
else if (line.startsWith("branch ")) {
|
|
415
|
+
if (cur)
|
|
416
|
+
cur.branch = line.slice("branch ".length).trim().replace(/^refs\/heads\//, "");
|
|
417
|
+
}
|
|
418
|
+
else if (line.trim() === "detached") {
|
|
419
|
+
if (cur)
|
|
420
|
+
cur.branch = null;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
flush();
|
|
424
|
+
const root = path.resolve(dir);
|
|
425
|
+
return trees.map((t, i) => ({ ...t, main: path.resolve(t.path) === root || i === 0 }));
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Open a worktree. Three shapes:
|
|
429
|
+
* - `newBranch`: cut a fresh branch off `base` (default HEAD) — used for an
|
|
430
|
+
* issue or a card you want to start.
|
|
431
|
+
* - `branch`: check out an existing (or DWIM-remote) branch.
|
|
432
|
+
* - `detached`: park at HEAD, for the caller to `gh pr checkout` into (handles
|
|
433
|
+
* fork PRs, which have no local branch).
|
|
434
|
+
* Returns the created path; `git worktree add` refuses to clobber, so an
|
|
435
|
+
* existing path surfaces git's own error rather than overwriting work.
|
|
436
|
+
*/
|
|
437
|
+
export async function addWorktree(dir, opts) {
|
|
438
|
+
if (!(await isRepo(dir)))
|
|
439
|
+
throw new GitError("not a git repository", "");
|
|
440
|
+
if (!(await hasCommits(dir)))
|
|
441
|
+
throw new GitError("make a commit before opening a worktree", "");
|
|
442
|
+
const wt = worktreePath(dir, opts.slug);
|
|
443
|
+
if (opts.newBranch) {
|
|
444
|
+
const nb = assertRef(opts.newBranch, "branch");
|
|
445
|
+
const base = assertRef(opts.base || "HEAD", "base");
|
|
446
|
+
await git(["worktree", "add", "-b", nb, wt, base], dir);
|
|
447
|
+
return { path: wt, branch: nb };
|
|
448
|
+
}
|
|
449
|
+
if (opts.branch && !opts.detached) {
|
|
450
|
+
const b = assertRef(opts.branch, "branch");
|
|
451
|
+
await git(["worktree", "add", wt, b], dir);
|
|
452
|
+
return { path: wt, branch: b };
|
|
453
|
+
}
|
|
454
|
+
await git(["worktree", "add", "--detach", wt], dir);
|
|
455
|
+
return { path: wt, branch: null };
|
|
456
|
+
}
|
|
457
|
+
export async function removeWorktree(dir, wtPath, force = false) {
|
|
458
|
+
await git(["worktree", "remove", ...(force ? ["--force"] : []), wtPath], dir);
|
|
459
|
+
}
|
|
460
|
+
//# sourceMappingURL=git.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/core/git.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything that went wrong, kept where you can see it.
|
|
3
|
+
*
|
|
4
|
+
* Before this, an error had two possible fates: `console.error` into
|
|
5
|
+
* ~/.loom/daemon.log — a file you have to know about, find, and tail — or one
|
|
6
|
+
* of the 48 empty `catch {}` blocks in this codebase, where it stopped existing
|
|
7
|
+
* entirely. Neither reaches the person looking at the window wondering why
|
|
8
|
+
* nothing happened.
|
|
9
|
+
*
|
|
10
|
+
* So: a ring buffer in the daemon, streamed to the app's Console tab. Bounded
|
|
11
|
+
* on purpose — a long-lived daemon that keeps every log line it ever wrote is a
|
|
12
|
+
* memory leak with good intentions. The tail is what you want anyway; the whole
|
|
13
|
+
* history is in daemon.log.
|
|
14
|
+
*
|
|
15
|
+
* This does NOT replace the log file, and the duplication is deliberate. The
|
|
16
|
+
* Console is for the error you're looking at right now; the file is for the one
|
|
17
|
+
* that killed the daemon at 3am, when there was no window to look at.
|
|
18
|
+
*/
|
|
19
|
+
export type LogLevel = "error" | "warn" | "info";
|
|
20
|
+
export interface LogRecord {
|
|
21
|
+
id: number;
|
|
22
|
+
at: number;
|
|
23
|
+
level: LogLevel;
|
|
24
|
+
/** Which part of Loom is talking: "daemon", "adapter:codex", "board", … */
|
|
25
|
+
scope: string;
|
|
26
|
+
message: string;
|
|
27
|
+
/** A stack, a stderr tail, an HTTP body — whatever helps. Trimmed. */
|
|
28
|
+
detail?: string;
|
|
29
|
+
/** The project it belongs to, when it belongs to one. */
|
|
30
|
+
project?: string;
|
|
31
|
+
}
|
|
32
|
+
type Listener = (r: LogRecord) => void;
|
|
33
|
+
declare class Logbook {
|
|
34
|
+
private records;
|
|
35
|
+
private listeners;
|
|
36
|
+
private nextId;
|
|
37
|
+
/**
|
|
38
|
+
* Record something, and say it out loud.
|
|
39
|
+
*
|
|
40
|
+
* Both, always: the buffer is for the window and the console is for the file.
|
|
41
|
+
* An error that only reaches one of them is an error someone won't see.
|
|
42
|
+
*/
|
|
43
|
+
add(level: LogLevel, scope: string, message: string, detail?: unknown, project?: string): LogRecord;
|
|
44
|
+
error(scope: string, message: string, detail?: unknown, project?: string): LogRecord;
|
|
45
|
+
warn(scope: string, message: string, detail?: unknown, project?: string): LogRecord;
|
|
46
|
+
info(scope: string, message: string, detail?: unknown, project?: string): LogRecord;
|
|
47
|
+
/** The tail, newest last. `since` is an id, for polling without duplicates. */
|
|
48
|
+
list(opts?: {
|
|
49
|
+
since?: number;
|
|
50
|
+
level?: LogLevel;
|
|
51
|
+
project?: string;
|
|
52
|
+
limit?: number;
|
|
53
|
+
}): LogRecord[];
|
|
54
|
+
clear(): void;
|
|
55
|
+
subscribe(l: Listener): () => void;
|
|
56
|
+
}
|
|
57
|
+
/** One book per process. The daemon is the process that has things go wrong in it. */
|
|
58
|
+
export declare const logbook: Logbook;
|
|
59
|
+
export {};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything that went wrong, kept where you can see it.
|
|
3
|
+
*
|
|
4
|
+
* Before this, an error had two possible fates: `console.error` into
|
|
5
|
+
* ~/.loom/daemon.log — a file you have to know about, find, and tail — or one
|
|
6
|
+
* of the 48 empty `catch {}` blocks in this codebase, where it stopped existing
|
|
7
|
+
* entirely. Neither reaches the person looking at the window wondering why
|
|
8
|
+
* nothing happened.
|
|
9
|
+
*
|
|
10
|
+
* So: a ring buffer in the daemon, streamed to the app's Console tab. Bounded
|
|
11
|
+
* on purpose — a long-lived daemon that keeps every log line it ever wrote is a
|
|
12
|
+
* memory leak with good intentions. The tail is what you want anyway; the whole
|
|
13
|
+
* history is in daemon.log.
|
|
14
|
+
*
|
|
15
|
+
* This does NOT replace the log file, and the duplication is deliberate. The
|
|
16
|
+
* Console is for the error you're looking at right now; the file is for the one
|
|
17
|
+
* that killed the daemon at 3am, when there was no window to look at.
|
|
18
|
+
*/
|
|
19
|
+
const MAX_RECORDS = 500;
|
|
20
|
+
const MAX_DETAIL = 4000;
|
|
21
|
+
class Logbook {
|
|
22
|
+
records = [];
|
|
23
|
+
listeners = new Set();
|
|
24
|
+
nextId = 1;
|
|
25
|
+
/**
|
|
26
|
+
* Record something, and say it out loud.
|
|
27
|
+
*
|
|
28
|
+
* Both, always: the buffer is for the window and the console is for the file.
|
|
29
|
+
* An error that only reaches one of them is an error someone won't see.
|
|
30
|
+
*/
|
|
31
|
+
add(level, scope, message, detail, project) {
|
|
32
|
+
const record = {
|
|
33
|
+
id: this.nextId++,
|
|
34
|
+
at: Date.now(),
|
|
35
|
+
level,
|
|
36
|
+
scope,
|
|
37
|
+
message: String(message).slice(0, 500),
|
|
38
|
+
...(detail === undefined ? {} : { detail: stringify(detail) }),
|
|
39
|
+
...(project ? { project } : {}),
|
|
40
|
+
};
|
|
41
|
+
this.records.push(record);
|
|
42
|
+
if (this.records.length > MAX_RECORDS)
|
|
43
|
+
this.records.splice(0, this.records.length - MAX_RECORDS);
|
|
44
|
+
const line = `[${new Date(record.at).toISOString()}] ${level} ${scope}: ${record.message}`;
|
|
45
|
+
if (level === "error")
|
|
46
|
+
console.error(line + (record.detail ? `\n${record.detail}` : ""));
|
|
47
|
+
else if (level === "warn")
|
|
48
|
+
console.warn(line);
|
|
49
|
+
else
|
|
50
|
+
console.log(line);
|
|
51
|
+
for (const l of this.listeners) {
|
|
52
|
+
try {
|
|
53
|
+
l(record);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// A broken subscriber must not break logging — that would be a fine
|
|
57
|
+
// irony to debug with no logs.
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return record;
|
|
61
|
+
}
|
|
62
|
+
error(scope, message, detail, project) {
|
|
63
|
+
return this.add("error", scope, message, detail, project);
|
|
64
|
+
}
|
|
65
|
+
warn(scope, message, detail, project) {
|
|
66
|
+
return this.add("warn", scope, message, detail, project);
|
|
67
|
+
}
|
|
68
|
+
info(scope, message, detail, project) {
|
|
69
|
+
return this.add("info", scope, message, detail, project);
|
|
70
|
+
}
|
|
71
|
+
/** The tail, newest last. `since` is an id, for polling without duplicates. */
|
|
72
|
+
list(opts = {}) {
|
|
73
|
+
let out = this.records;
|
|
74
|
+
if (opts.since !== undefined)
|
|
75
|
+
out = out.filter((r) => r.id > opts.since);
|
|
76
|
+
if (opts.level)
|
|
77
|
+
out = out.filter((r) => r.level === opts.level);
|
|
78
|
+
if (opts.project)
|
|
79
|
+
out = out.filter((r) => r.project === opts.project);
|
|
80
|
+
const limit = opts.limit ?? 200;
|
|
81
|
+
return out.slice(-limit);
|
|
82
|
+
}
|
|
83
|
+
clear() {
|
|
84
|
+
this.records = [];
|
|
85
|
+
}
|
|
86
|
+
subscribe(l) {
|
|
87
|
+
this.listeners.add(l);
|
|
88
|
+
return () => this.listeners.delete(l);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** One book per process. The daemon is the process that has things go wrong in it. */
|
|
92
|
+
export const logbook = new Logbook();
|
|
93
|
+
/**
|
|
94
|
+
* Turn whatever was thrown into something readable.
|
|
95
|
+
*
|
|
96
|
+
* Errors carry a stack; the rest carry whatever they carry. `String(err)` on a
|
|
97
|
+
* plain object gives "[object Object]", which is the least useful sentence in
|
|
98
|
+
* computing.
|
|
99
|
+
*/
|
|
100
|
+
function stringify(detail) {
|
|
101
|
+
if (detail === undefined || detail === null)
|
|
102
|
+
return "";
|
|
103
|
+
let text;
|
|
104
|
+
if (detail instanceof Error)
|
|
105
|
+
text = detail.stack ?? `${detail.name}: ${detail.message}`;
|
|
106
|
+
else if (typeof detail === "string")
|
|
107
|
+
text = detail;
|
|
108
|
+
else {
|
|
109
|
+
try {
|
|
110
|
+
text = JSON.stringify(detail, null, 2);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
text = String(detail);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return text.length > MAX_DETAIL ? `${text.slice(0, MAX_DETAIL)}\n… (${text.length - MAX_DETAIL} more)` : text;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=logbook.js.map
|