@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.
Files changed (177) hide show
  1. package/ARCHITECTURE.md +211 -0
  2. package/CHANGELOG.md +470 -0
  3. package/LICENSE +21 -0
  4. package/README.md +630 -0
  5. package/dist/adapters/base.d.ts +71 -0
  6. package/dist/adapters/base.js +187 -0
  7. package/dist/adapters/base.js.map +1 -0
  8. package/dist/adapters/bridges/antigravity.d.ts +53 -0
  9. package/dist/adapters/bridges/antigravity.js +106 -0
  10. package/dist/adapters/bridges/antigravity.js.map +1 -0
  11. package/dist/adapters/bridges/cdp.d.ts +82 -0
  12. package/dist/adapters/bridges/cdp.js +222 -0
  13. package/dist/adapters/bridges/cdp.js.map +1 -0
  14. package/dist/adapters/bridges/gui-chat.d.ts +113 -0
  15. package/dist/adapters/bridges/gui-chat.js +498 -0
  16. package/dist/adapters/bridges/gui-chat.js.map +1 -0
  17. package/dist/adapters/bridges/kiro.d.ts +19 -0
  18. package/dist/adapters/bridges/kiro.js +22 -0
  19. package/dist/adapters/bridges/kiro.js.map +1 -0
  20. package/dist/adapters/bridges/profiles.d.ts +82 -0
  21. package/dist/adapters/bridges/profiles.js +125 -0
  22. package/dist/adapters/bridges/profiles.js.map +1 -0
  23. package/dist/adapters/claude-code.d.ts +26 -0
  24. package/dist/adapters/claude-code.js +209 -0
  25. package/dist/adapters/claude-code.js.map +1 -0
  26. package/dist/adapters/codex.d.ts +51 -0
  27. package/dist/adapters/codex.js +291 -0
  28. package/dist/adapters/codex.js.map +1 -0
  29. package/dist/adapters/echo.d.ts +20 -0
  30. package/dist/adapters/echo.js +79 -0
  31. package/dist/adapters/echo.js.map +1 -0
  32. package/dist/adapters/grok.d.ts +61 -0
  33. package/dist/adapters/grok.js +217 -0
  34. package/dist/adapters/grok.js.map +1 -0
  35. package/dist/adapters/index.d.ts +9 -0
  36. package/dist/adapters/index.js +33 -0
  37. package/dist/adapters/index.js.map +1 -0
  38. package/dist/adapters/opencode.d.ts +64 -0
  39. package/dist/adapters/opencode.js +426 -0
  40. package/dist/adapters/opencode.js.map +1 -0
  41. package/dist/cli/common.d.ts +11 -0
  42. package/dist/cli/common.js +40 -0
  43. package/dist/cli/common.js.map +1 -0
  44. package/dist/cli/doctor.d.ts +12 -0
  45. package/dist/cli/doctor.js +181 -0
  46. package/dist/cli/doctor.js.map +1 -0
  47. package/dist/cli/index.d.ts +8 -0
  48. package/dist/cli/index.js +641 -0
  49. package/dist/cli/index.js.map +1 -0
  50. package/dist/cli/tui-model.d.ts +73 -0
  51. package/dist/cli/tui-model.js +312 -0
  52. package/dist/cli/tui-model.js.map +1 -0
  53. package/dist/cli/tui.d.ts +15 -0
  54. package/dist/cli/tui.js +451 -0
  55. package/dist/cli/tui.js.map +1 -0
  56. package/dist/cli/ui.d.ts +10 -0
  57. package/dist/cli/ui.js +135 -0
  58. package/dist/cli/ui.js.map +1 -0
  59. package/dist/core/ades.d.ts +79 -0
  60. package/dist/core/ades.js +130 -0
  61. package/dist/core/ades.js.map +1 -0
  62. package/dist/core/baton.d.ts +37 -0
  63. package/dist/core/baton.js +92 -0
  64. package/dist/core/baton.js.map +1 -0
  65. package/dist/core/brain-extract.d.ts +111 -0
  66. package/dist/core/brain-extract.js +248 -0
  67. package/dist/core/brain-extract.js.map +1 -0
  68. package/dist/core/brain-index.d.ts +128 -0
  69. package/dist/core/brain-index.js +338 -0
  70. package/dist/core/brain-index.js.map +1 -0
  71. package/dist/core/brain.d.ts +214 -0
  72. package/dist/core/brain.js +425 -0
  73. package/dist/core/brain.js.map +1 -0
  74. package/dist/core/claude-cli.d.ts +12 -0
  75. package/dist/core/claude-cli.js +47 -0
  76. package/dist/core/claude-cli.js.map +1 -0
  77. package/dist/core/distill.d.ts +19 -0
  78. package/dist/core/distill.js +88 -0
  79. package/dist/core/distill.js.map +1 -0
  80. package/dist/core/eventlog.d.ts +43 -0
  81. package/dist/core/eventlog.js +211 -0
  82. package/dist/core/eventlog.js.map +1 -0
  83. package/dist/core/git.d.ts +203 -0
  84. package/dist/core/git.js +460 -0
  85. package/dist/core/git.js.map +1 -0
  86. package/dist/core/logbook.d.ts +59 -0
  87. package/dist/core/logbook.js +118 -0
  88. package/dist/core/logbook.js.map +1 -0
  89. package/dist/core/memory.d.ts +34 -0
  90. package/dist/core/memory.js +124 -0
  91. package/dist/core/memory.js.map +1 -0
  92. package/dist/core/notify.d.ts +11 -0
  93. package/dist/core/notify.js +30 -0
  94. package/dist/core/notify.js.map +1 -0
  95. package/dist/core/projection.d.ts +20 -0
  96. package/dist/core/projection.js +96 -0
  97. package/dist/core/projection.js.map +1 -0
  98. package/dist/core/registry.d.ts +76 -0
  99. package/dist/core/registry.js +147 -0
  100. package/dist/core/registry.js.map +1 -0
  101. package/dist/core/router.d.ts +42 -0
  102. package/dist/core/router.js +99 -0
  103. package/dist/core/router.js.map +1 -0
  104. package/dist/core/routes.d.ts +93 -0
  105. package/dist/core/routes.js +399 -0
  106. package/dist/core/routes.js.map +1 -0
  107. package/dist/core/search.d.ts +63 -0
  108. package/dist/core/search.js +0 -0
  109. package/dist/core/search.js.map +1 -0
  110. package/dist/core/setup.d.ts +70 -0
  111. package/dist/core/setup.js +250 -0
  112. package/dist/core/setup.js.map +1 -0
  113. package/dist/core/suggestions.d.ts +15 -0
  114. package/dist/core/suggestions.js +38 -0
  115. package/dist/core/suggestions.js.map +1 -0
  116. package/dist/core/worktree.d.ts +32 -0
  117. package/dist/core/worktree.js +83 -0
  118. package/dist/core/worktree.js.map +1 -0
  119. package/dist/daemon/app-page.d.ts +32 -0
  120. package/dist/daemon/app-page.js +6363 -0
  121. package/dist/daemon/app-page.js.map +1 -0
  122. package/dist/daemon/auth.d.ts +51 -0
  123. package/dist/daemon/auth.js +135 -0
  124. package/dist/daemon/auth.js.map +1 -0
  125. package/dist/daemon/board.d.ts +117 -0
  126. package/dist/daemon/board.js +202 -0
  127. package/dist/daemon/board.js.map +1 -0
  128. package/dist/daemon/brand-icons.d.ts +29 -0
  129. package/dist/daemon/brand-icons.js +44 -0
  130. package/dist/daemon/brand-icons.js.map +1 -0
  131. package/dist/daemon/client.d.ts +127 -0
  132. package/dist/daemon/client.js +237 -0
  133. package/dist/daemon/client.js.map +1 -0
  134. package/dist/daemon/geist-font.d.ts +10 -0
  135. package/dist/daemon/geist-font.js +783 -0
  136. package/dist/daemon/geist-font.js.map +1 -0
  137. package/dist/daemon/guards.d.ts +18 -0
  138. package/dist/daemon/guards.js +40 -0
  139. package/dist/daemon/guards.js.map +1 -0
  140. package/dist/daemon/linear.d.ts +59 -0
  141. package/dist/daemon/linear.js +117 -0
  142. package/dist/daemon/linear.js.map +1 -0
  143. package/dist/daemon/push.d.ts +22 -0
  144. package/dist/daemon/push.js +58 -0
  145. package/dist/daemon/push.js.map +1 -0
  146. package/dist/daemon/runtime.d.ts +299 -0
  147. package/dist/daemon/runtime.js +1016 -0
  148. package/dist/daemon/runtime.js.map +1 -0
  149. package/dist/daemon/server.d.ts +125 -0
  150. package/dist/daemon/server.js +1728 -0
  151. package/dist/daemon/server.js.map +1 -0
  152. package/dist/daemon/tasks.d.ts +153 -0
  153. package/dist/daemon/tasks.js +264 -0
  154. package/dist/daemon/tasks.js.map +1 -0
  155. package/dist/daemon/terminals.d.ts +82 -0
  156. package/dist/daemon/terminals.js +372 -0
  157. package/dist/daemon/terminals.js.map +1 -0
  158. package/dist/index.d.ts +20 -0
  159. package/dist/index.js +20 -0
  160. package/dist/index.js.map +1 -0
  161. package/dist/sdk.d.ts +17 -0
  162. package/dist/sdk.js +17 -0
  163. package/dist/sdk.js.map +1 -0
  164. package/dist/types.d.ts +236 -0
  165. package/dist/types.js +18 -0
  166. package/dist/types.js.map +1 -0
  167. package/docs/SETUP.md +265 -0
  168. package/docs/adapters.md +132 -0
  169. package/docs/design-system.md +245 -0
  170. package/docs/img/board-linear.png +0 -0
  171. package/docs/img/board-projects.png +0 -0
  172. package/docs/img/brain.png +0 -0
  173. package/docs/img/settings.png +0 -0
  174. package/docs/img/workspace.png +0 -0
  175. package/docs/integration-notes.md +76 -0
  176. package/docs/proposals/brain.md +262 -0
  177. package/package.json +84 -0
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Append-only per-project event log — Loom's source of truth.
3
+ *
4
+ * Primary store: node:sqlite (built into Node >= 22.5, zero native deps).
5
+ * Fallback store: JSONL (if node:sqlite is unavailable, or LOOM_STORE=jsonl).
6
+ */
7
+ import type { EventKind, LoomEvent, NewEvent } from "../types.js";
8
+ export interface ListOpts {
9
+ since?: number;
10
+ limit?: number;
11
+ kinds?: EventKind[];
12
+ /**
13
+ * Only this conversation. Asking for the main chat also returns events
14
+ * written before chats existed — they have no id and belong to it.
15
+ * Omit to read the whole project, which is what the brain wants.
16
+ */
17
+ chat?: string;
18
+ }
19
+ /** The store behind a project's log. Exported so search can take a narrow
20
+ * slice of it (list) rather than the whole EventLog — a searcher has no
21
+ * business being able to append. */
22
+ export interface EventStore {
23
+ append(e: Required<Omit<NewEvent, "agentId" | "chat">> & {
24
+ agentId?: string;
25
+ chat?: string;
26
+ }): LoomEvent;
27
+ list(opts?: ListOpts): LoomEvent[];
28
+ lastId(): number;
29
+ close(): void;
30
+ }
31
+ export declare class EventLog {
32
+ private store;
33
+ private emitter;
34
+ private constructor();
35
+ /** Open (or create) the log inside a project's .loom directory. */
36
+ static open(loomDir: string): Promise<EventLog>;
37
+ append(e: NewEvent): LoomEvent;
38
+ list(opts?: ListOpts): LoomEvent[];
39
+ lastId(): number;
40
+ /** Live subscription to appended events; returns unsubscribe. */
41
+ onEvent(cb: (e: LoomEvent) => void): () => void;
42
+ close(): void;
43
+ }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Append-only per-project event log — Loom's source of truth.
3
+ *
4
+ * Primary store: node:sqlite (built into Node >= 22.5, zero native deps).
5
+ * Fallback store: JSONL (if node:sqlite is unavailable, or LOOM_STORE=jsonl).
6
+ */
7
+ import { EventEmitter } from "node:events";
8
+ import fs from "node:fs";
9
+ import path from "node:path";
10
+ import { MAIN_CHAT } from "../types.js";
11
+ class SqliteStore {
12
+ db;
13
+ constructor(sqlite, file) {
14
+ this.db = new sqlite.DatabaseSync(file);
15
+ this.db.exec(`
16
+ CREATE TABLE IF NOT EXISTS events (
17
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
18
+ ts INTEGER NOT NULL,
19
+ kind TEXT NOT NULL,
20
+ agent_id TEXT,
21
+ chat TEXT,
22
+ payload TEXT NOT NULL
23
+ );
24
+ CREATE INDEX IF NOT EXISTS idx_events_kind ON events(kind, id);
25
+ `);
26
+ // Migrate BEFORE indexing chat. A log written before chats existed already
27
+ // has an `events` table, so CREATE TABLE IF NOT EXISTS is a no-op and the
28
+ // column is still missing — indexing it here would throw "no such column"
29
+ // and take the whole log down with it. Add the column, then index.
30
+ // The log is append-only and those events are history: a NULL chat reads
31
+ // as the main conversation rather than being rewritten.
32
+ const cols = this.db.prepare("PRAGMA table_info(events)").all();
33
+ if (!cols.some((c) => c.name === "chat")) {
34
+ this.db.exec("ALTER TABLE events ADD COLUMN chat TEXT");
35
+ }
36
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_events_chat ON events(chat, id)");
37
+ }
38
+ append(e) {
39
+ const stmt = this.db.prepare("INSERT INTO events (ts, kind, agent_id, chat, payload) VALUES (?, ?, ?, ?, ?)");
40
+ const res = stmt.run(e.ts, e.kind, e.agentId ?? null, e.chat ?? null, JSON.stringify(e.payload));
41
+ return {
42
+ id: Number(res.lastInsertRowid),
43
+ ts: e.ts,
44
+ kind: e.kind,
45
+ ...(e.agentId ? { agentId: e.agentId } : {}),
46
+ ...(e.chat ? { chat: e.chat } : {}),
47
+ payload: e.payload,
48
+ };
49
+ }
50
+ list(opts = {}) {
51
+ const clauses = [];
52
+ const params = [];
53
+ if (opts.since !== undefined) {
54
+ clauses.push("id > ?");
55
+ params.push(opts.since);
56
+ }
57
+ if (opts.kinds?.length) {
58
+ clauses.push(`kind IN (${opts.kinds.map(() => "?").join(",")})`);
59
+ params.push(...opts.kinds);
60
+ }
61
+ if (opts.chat !== undefined) {
62
+ if (opts.chat === MAIN_CHAT) {
63
+ // pre-chat history has no id and belongs to the main conversation
64
+ clauses.push("(chat = ? OR chat IS NULL)");
65
+ }
66
+ else {
67
+ clauses.push("chat = ?");
68
+ }
69
+ params.push(opts.chat);
70
+ }
71
+ const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
72
+ // When limiting, we want the *most recent* N in ascending order.
73
+ const sql = opts.limit
74
+ ? `SELECT * FROM (SELECT * FROM events ${where} ORDER BY id DESC LIMIT ?) ORDER BY id ASC`
75
+ : `SELECT * FROM events ${where} ORDER BY id ASC`;
76
+ if (opts.limit)
77
+ params.push(opts.limit);
78
+ const rows = this.db.prepare(sql).all(...params);
79
+ return rows.map((r) => ({
80
+ id: Number(r.id),
81
+ ts: r.ts,
82
+ kind: r.kind,
83
+ ...(r.agent_id ? { agentId: r.agent_id } : {}),
84
+ ...(r.chat ? { chat: r.chat } : {}),
85
+ payload: JSON.parse(r.payload),
86
+ }));
87
+ }
88
+ lastId() {
89
+ const row = this.db.prepare("SELECT MAX(id) AS m FROM events").get();
90
+ return row?.m ? Number(row.m) : 0;
91
+ }
92
+ close() {
93
+ this.db.close();
94
+ }
95
+ }
96
+ // ---------------------------------------------------------------------------
97
+ // JSONL fallback store
98
+ // ---------------------------------------------------------------------------
99
+ class JsonlStore {
100
+ file;
101
+ nextId;
102
+ cache;
103
+ constructor(file) {
104
+ this.file = file;
105
+ this.cache = [];
106
+ if (fs.existsSync(file)) {
107
+ for (const line of fs.readFileSync(file, "utf8").split("\n")) {
108
+ if (!line.trim())
109
+ continue;
110
+ try {
111
+ this.cache.push(JSON.parse(line));
112
+ }
113
+ catch {
114
+ // Skip torn trailing writes; the log stays usable.
115
+ }
116
+ }
117
+ }
118
+ this.nextId = (this.cache[this.cache.length - 1]?.id ?? 0) + 1;
119
+ }
120
+ append(e) {
121
+ const ev = {
122
+ id: this.nextId++,
123
+ ts: e.ts,
124
+ kind: e.kind,
125
+ ...(e.agentId ? { agentId: e.agentId } : {}),
126
+ ...(e.chat ? { chat: e.chat } : {}),
127
+ payload: e.payload,
128
+ };
129
+ fs.appendFileSync(this.file, JSON.stringify(ev) + "\n");
130
+ this.cache.push(ev);
131
+ return ev;
132
+ }
133
+ list(opts = {}) {
134
+ let out = this.cache;
135
+ if (opts.since !== undefined)
136
+ out = out.filter((e) => e.id > opts.since);
137
+ if (opts.kinds?.length)
138
+ out = out.filter((e) => opts.kinds.includes(e.kind));
139
+ // must match SqliteStore exactly: an event with no chat is main's
140
+ if (opts.chat !== undefined) {
141
+ out = out.filter((e) => opts.chat === MAIN_CHAT ? (e.chat ?? MAIN_CHAT) === MAIN_CHAT : e.chat === opts.chat);
142
+ }
143
+ if (opts.limit && out.length > opts.limit)
144
+ out = out.slice(-opts.limit);
145
+ return [...out];
146
+ }
147
+ lastId() {
148
+ return this.cache[this.cache.length - 1]?.id ?? 0;
149
+ }
150
+ close() { }
151
+ }
152
+ // ---------------------------------------------------------------------------
153
+ // EventLog facade
154
+ // ---------------------------------------------------------------------------
155
+ export class EventLog {
156
+ store;
157
+ emitter = new EventEmitter();
158
+ constructor(store) {
159
+ this.store = store;
160
+ this.emitter.setMaxListeners(100);
161
+ }
162
+ /** Open (or create) the log inside a project's .loom directory. */
163
+ static async open(loomDir) {
164
+ fs.mkdirSync(loomDir, { recursive: true });
165
+ if (process.env.LOOM_STORE !== "jsonl") {
166
+ let sqlite;
167
+ try {
168
+ sqlite = await import("node:sqlite");
169
+ }
170
+ catch {
171
+ // No node:sqlite in this runtime — the JSONL store is the whole point
172
+ // of the fallback. This is the ONLY thing it catches.
173
+ return new EventLog(new JsonlStore(path.join(loomDir, "log.jsonl")));
174
+ }
175
+ // Deliberately outside the catch. If node:sqlite exists but the log won't
176
+ // open — corrupt file, failed migration, bad permissions — falling back
177
+ // would silently start an EMPTY jsonl log beside a database full of your
178
+ // history, and write new events there. Losing the thread is worse than
179
+ // failing loudly, so this throws.
180
+ return new EventLog(new SqliteStore(sqlite, path.join(loomDir, "log.db")));
181
+ }
182
+ return new EventLog(new JsonlStore(path.join(loomDir, "log.jsonl")));
183
+ }
184
+ append(e) {
185
+ const ev = this.store.append({
186
+ ts: e.ts ?? Date.now(),
187
+ kind: e.kind,
188
+ payload: e.payload,
189
+ ...(e.agentId ? { agentId: e.agentId } : {}),
190
+ ...(e.chat ? { chat: e.chat } : {}),
191
+ });
192
+ this.emitter.emit("event", ev);
193
+ return ev;
194
+ }
195
+ list(opts) {
196
+ return this.store.list(opts);
197
+ }
198
+ lastId() {
199
+ return this.store.lastId();
200
+ }
201
+ /** Live subscription to appended events; returns unsubscribe. */
202
+ onEvent(cb) {
203
+ this.emitter.on("event", cb);
204
+ return () => this.emitter.off("event", cb);
205
+ }
206
+ close() {
207
+ this.emitter.removeAllListeners();
208
+ this.store.close();
209
+ }
210
+ }
211
+ //# sourceMappingURL=eventlog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eventlog.js","sourceRoot":"","sources":["../../src/core/eventlog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,203 @@
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
+ export declare class GitError extends Error {
26
+ readonly stderr: string;
27
+ constructor(message: string, stderr: string);
28
+ }
29
+ /**
30
+ * Keep a path inside the project.
31
+ *
32
+ * `git checkout -- <path>` is destructive and these paths arrive over HTTP from
33
+ * a device that could be anywhere on your tailnet. Resolve, then prove it's
34
+ * within the root — a prefix check on the un-normalised string would wave
35
+ * "../.." straight through.
36
+ */
37
+ export declare function safeRelPath(dir: string, rel: string): string;
38
+ export declare function isRepo(dir: string): Promise<boolean>;
39
+ export declare function stage(dir: string, paths: string[]): Promise<{
40
+ staged: string[];
41
+ }>;
42
+ export declare function unstage(dir: string, paths: string[]): Promise<{
43
+ unstaged: string[];
44
+ }>;
45
+ /**
46
+ * Throw work away.
47
+ *
48
+ * The one operation here that can lose something you can't get back, so it is
49
+ * the one that is loudest in the log. Tracked files are restored from the
50
+ * index; untracked ones have nothing to restore to and must be deleted, which
51
+ * `git checkout` won't do — hence `clean -f` for those, and hence the split.
52
+ */
53
+ export declare function discard(dir: string, paths: string[], untracked?: string[]): Promise<{
54
+ discarded: string[];
55
+ }>;
56
+ export interface CommitResult {
57
+ sha: string;
58
+ subject: string;
59
+ files: number;
60
+ }
61
+ /**
62
+ * Commit what's staged.
63
+ *
64
+ * Nothing is staged for you. "Commit all" is a convenience that has ended more
65
+ * afternoons than it saved — an agent's stray file swept into your commit
66
+ * because the button was easier than looking. If you want it in, stage it.
67
+ */
68
+ export declare function commit(dir: string, message: string): Promise<CommitResult>;
69
+ export interface GitStatus {
70
+ branch: string;
71
+ /** Commits ahead / behind the upstream, when there is one. */
72
+ ahead: number;
73
+ behind: number;
74
+ upstream: string | null;
75
+ staged: {
76
+ status: string;
77
+ path: string;
78
+ }[];
79
+ unstaged: {
80
+ status: string;
81
+ path: string;
82
+ }[];
83
+ untracked: string[];
84
+ }
85
+ /**
86
+ * The status the UI needs: staged and unstaged as separate lists.
87
+ *
88
+ * Porcelain's XY is two columns for a reason — X is the index, Y is the working
89
+ * tree — and a file can be in both at once (staged, then edited again). Flatten
90
+ * that into one list and you get a checkbox that lies about what a commit will
91
+ * contain.
92
+ */
93
+ export declare function status(dir: string): Promise<GitStatus>;
94
+ /**
95
+ * Start version control here.
96
+ *
97
+ * Refuses if the directory is already inside a repo, so an accidental click
98
+ * can't nest one repo inside another. Renames the initial branch to `main` when
99
+ * git left it on `master` — safe because a just-init'd repo has no commits, and
100
+ * it matches what remotes expect.
101
+ */
102
+ export declare function init(dir: string): Promise<{
103
+ branch: string;
104
+ }>;
105
+ export interface Commit {
106
+ sha: string;
107
+ short: string;
108
+ subject: string;
109
+ author: string;
110
+ /** "2 hours ago" — git's own relative time. */
111
+ relative: string;
112
+ ts: number;
113
+ }
114
+ /** Recent commits, newest first. Empty in a repo whose first commit hasn't landed. */
115
+ export declare function log(dir: string, limit?: number): Promise<Commit[]>;
116
+ /**
117
+ * The unified diff for one file, HEAD → working tree (both staged and unstaged
118
+ * changes). Before the first commit there's no HEAD, so it diffs against the
119
+ * index instead. Empty string when nothing differs.
120
+ */
121
+ export declare function fileDiff(dir: string, rel: string): Promise<string>;
122
+ /**
123
+ * The diff a commit would capture — staged changes, or the whole working tree
124
+ * when nothing is staged yet. Used to write a commit message from. Bounded so a
125
+ * giant refactor doesn't blow the LLM's context (the summary only needs the
126
+ * shape of the change, not every line).
127
+ */
128
+ export declare function stagedDiff(dir: string, maxChars?: number): Promise<string>;
129
+ export interface Branches {
130
+ current: string;
131
+ all: string[];
132
+ }
133
+ /** Local branches and which one is checked out — for the checkout picker. */
134
+ export declare function branches(dir: string): Promise<Branches>;
135
+ /**
136
+ * Switch to a branch or commit.
137
+ *
138
+ * git refuses on its own when the switch would overwrite uncommitted changes,
139
+ * and that refusal (with its list of the files at risk) is exactly the message
140
+ * to surface — so we don't second-guess it, we just pass its stderr back. The
141
+ * ref is character-validated because it reaches a git command, though `--`
142
+ * already stops it being read as a flag.
143
+ */
144
+ export declare function checkout(dir: string, ref: string): Promise<{
145
+ ref: string;
146
+ branch: string;
147
+ }>;
148
+ /**
149
+ * How far behind (and ahead of) the upstream this checkout is — for "check for
150
+ * updates". Fetches first (bounded), so it reflects the remote, not a stale
151
+ * local ref. null when the dir isn't a git checkout; behind 0 with no upstream
152
+ * means "nothing to compare against", not "up to date".
153
+ */
154
+ export declare function remoteBehind(dir: string): Promise<{
155
+ behind: number;
156
+ ahead: number;
157
+ branch: string;
158
+ hasUpstream: boolean;
159
+ } | null>;
160
+ /**
161
+ * Push to the upstream, setting it on the first push.
162
+ *
163
+ * A branch with no upstream needs `-u origin <branch>` once; after that a bare
164
+ * `git push` is enough. Either way the network is bounded (gitNet) and auth is
165
+ * the user's own credential helper. A repo with no remote at all fails with
166
+ * git's "No configured push destination", which is the honest answer.
167
+ */
168
+ export declare function push(dir: string): Promise<{
169
+ branch: string;
170
+ detail: string;
171
+ }>;
172
+ export interface Worktree {
173
+ path: string;
174
+ /** null when the worktree is detached (e.g. mid-review of a fork PR). */
175
+ branch: string | null;
176
+ head: string;
177
+ /** The repo's own main working tree — you can't remove this one. */
178
+ main?: boolean;
179
+ }
180
+ /** Where a new worktree for this repo goes: `<repo>--<slug>`, a sibling. */
181
+ export declare function worktreePath(dir: string, slug: string): string;
182
+ export declare function listWorktrees(dir: string): Promise<Worktree[]>;
183
+ /**
184
+ * Open a worktree. Three shapes:
185
+ * - `newBranch`: cut a fresh branch off `base` (default HEAD) — used for an
186
+ * issue or a card you want to start.
187
+ * - `branch`: check out an existing (or DWIM-remote) branch.
188
+ * - `detached`: park at HEAD, for the caller to `gh pr checkout` into (handles
189
+ * fork PRs, which have no local branch).
190
+ * Returns the created path; `git worktree add` refuses to clobber, so an
191
+ * existing path surfaces git's own error rather than overwriting work.
192
+ */
193
+ export declare function addWorktree(dir: string, opts: {
194
+ slug: string;
195
+ branch?: string;
196
+ newBranch?: string;
197
+ base?: string;
198
+ detached?: boolean;
199
+ }): Promise<{
200
+ path: string;
201
+ branch: string | null;
202
+ }>;
203
+ export declare function removeWorktree(dir: string, wtPath: string, force?: boolean): Promise<void>;