@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
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small Chrome DevTools Protocol client — enough to drive an Electron agent
|
|
3
|
+
* app, and nothing more.
|
|
4
|
+
*
|
|
5
|
+
* Antigravity and Kiro are Electron apps. Launched with a debugging port they
|
|
6
|
+
* speak CDP, which is the only interface either of them offers: no API, no
|
|
7
|
+
* headless mode, no way in but the one the browser gives us. The approach is
|
|
8
|
+
* the same one krishnakanthb13/antigravity_phone_chat uses to put Antigravity
|
|
9
|
+
* on a phone — connect over CDP, read the DOM, drive the real widgets.
|
|
10
|
+
*
|
|
11
|
+
* Typing goes through Input.insertText rather than assigning to `.value`.
|
|
12
|
+
* These UIs are React and Monaco: an assignment updates the DOM and the
|
|
13
|
+
* framework never hears about it, so the app still believes its composer is
|
|
14
|
+
* empty and sends nothing. insertText enters through the same pipeline a
|
|
15
|
+
* keyboard does, so the app cannot tell the difference.
|
|
16
|
+
*/
|
|
17
|
+
import WebSocket from "ws";
|
|
18
|
+
import { fetchJson } from "../base.js";
|
|
19
|
+
/** Is anything listening for CDP at host:port? */
|
|
20
|
+
export async function cdpUp(endpoint, timeoutMs = 2000) {
|
|
21
|
+
try {
|
|
22
|
+
await fetchJson(`${endpoint}/json/version`, undefined, timeoutMs);
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export async function cdpTargets(endpoint, timeoutMs = 3000) {
|
|
30
|
+
return fetchJson(`${endpoint}/json`, undefined, timeoutMs).catch(() => []);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The window that holds the app, not its splash screen.
|
|
34
|
+
*
|
|
35
|
+
* Antigravity shows a `data:` URL loader for the first seconds after launch and
|
|
36
|
+
* the workbench arrives later on a different target; picking the first page
|
|
37
|
+
* gets you an animated ellipsis with no chat in it.
|
|
38
|
+
*/
|
|
39
|
+
export function workbenchTarget(targets) {
|
|
40
|
+
return chatTargets(targets)[0] ?? null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Every target that could hold a chat, best first.
|
|
44
|
+
*
|
|
45
|
+
* A page is not enough. Kiro keeps its chat in a `vscode-webview` target — a
|
|
46
|
+
* separate CDP target of type `iframe` — so a page-only filter finds the
|
|
47
|
+
* workbench, reports "no chat panel", and is wrong about the whole app.
|
|
48
|
+
* Antigravity keeps its chat in its page. Both have to be reachable, so this
|
|
49
|
+
* returns candidates and the driver asks each one whether a chat is in it.
|
|
50
|
+
*
|
|
51
|
+
* Ordering, not exclusion, is the tool here. AntiGravity-AutoAccept drops
|
|
52
|
+
* http(s) targets outright, because Antigravity's *Manager* is an https page
|
|
53
|
+
* behind a sign-in with no chat in it — a real trap, and it cost an afternoon.
|
|
54
|
+
* But "no chat" is a question the driver already asks, and answering it by URL
|
|
55
|
+
* scheme also throws away any app that legitimately serves its UI over http.
|
|
56
|
+
* So the Manager sorts last and eliminates itself by having no composer, which
|
|
57
|
+
* is both true and non-fatal.
|
|
58
|
+
*
|
|
59
|
+
* Workers are dropped for real: there is no DOM in one.
|
|
60
|
+
*/
|
|
61
|
+
export function chatTargets(targets) {
|
|
62
|
+
const usable = targets.filter((t) => {
|
|
63
|
+
if (!t.webSocketDebuggerUrl)
|
|
64
|
+
return false;
|
|
65
|
+
const url = String(t.url ?? "");
|
|
66
|
+
if (!url || url === "about:blank")
|
|
67
|
+
return false;
|
|
68
|
+
if (t.type === "service_worker" || t.type === "worker" || t.type === "shared_worker")
|
|
69
|
+
return false;
|
|
70
|
+
return t.type === "page" || t.type === "iframe" || url.includes("webview");
|
|
71
|
+
});
|
|
72
|
+
const rank = (t) => {
|
|
73
|
+
const url = String(t.url ?? "");
|
|
74
|
+
if (url.startsWith("data:"))
|
|
75
|
+
return 4; // a splash screen
|
|
76
|
+
if (url.startsWith("http://") || url.startsWith("https://"))
|
|
77
|
+
return 3; // probably a Manager
|
|
78
|
+
if (url.includes("webview"))
|
|
79
|
+
return 1; // where a panel's chat tends to live
|
|
80
|
+
return 0;
|
|
81
|
+
};
|
|
82
|
+
return [...usable].sort((a, b) => rank(a) - rank(b));
|
|
83
|
+
}
|
|
84
|
+
/** One CDP session against one target. */
|
|
85
|
+
export class CdpSession {
|
|
86
|
+
url;
|
|
87
|
+
ws = null;
|
|
88
|
+
nextId = 1;
|
|
89
|
+
pending = new Map();
|
|
90
|
+
constructor(url) {
|
|
91
|
+
this.url = url;
|
|
92
|
+
}
|
|
93
|
+
static async open(target, timeoutMs = 5000) {
|
|
94
|
+
if (!target.webSocketDebuggerUrl)
|
|
95
|
+
throw new Error("target has no debugger url");
|
|
96
|
+
const session = new CdpSession(target.webSocketDebuggerUrl);
|
|
97
|
+
await session.connect(timeoutMs);
|
|
98
|
+
return session;
|
|
99
|
+
}
|
|
100
|
+
connect(timeoutMs) {
|
|
101
|
+
return new Promise((resolve, reject) => {
|
|
102
|
+
const ws = new WebSocket(this.url, { maxPayload: 64 * 1024 * 1024 });
|
|
103
|
+
this.ws = ws;
|
|
104
|
+
const timer = setTimeout(() => {
|
|
105
|
+
ws.terminate();
|
|
106
|
+
reject(new Error("timed out connecting to the app's debugger"));
|
|
107
|
+
}, timeoutMs);
|
|
108
|
+
ws.on("open", () => {
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
resolve();
|
|
111
|
+
});
|
|
112
|
+
ws.on("error", (err) => {
|
|
113
|
+
clearTimeout(timer);
|
|
114
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
115
|
+
});
|
|
116
|
+
ws.on("message", (raw) => this.onMessage(String(raw)));
|
|
117
|
+
ws.on("close", () => {
|
|
118
|
+
// Nothing else is coming; don't leave callers hanging forever.
|
|
119
|
+
for (const [, p] of this.pending)
|
|
120
|
+
p.reject(new Error("debugger connection closed"));
|
|
121
|
+
this.pending.clear();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
onMessage(raw) {
|
|
126
|
+
let msg;
|
|
127
|
+
try {
|
|
128
|
+
msg = JSON.parse(raw);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (typeof msg.id !== "number")
|
|
134
|
+
return; // an event, not our reply
|
|
135
|
+
const p = this.pending.get(msg.id);
|
|
136
|
+
if (!p)
|
|
137
|
+
return;
|
|
138
|
+
this.pending.delete(msg.id);
|
|
139
|
+
if (msg.error)
|
|
140
|
+
p.reject(new Error(msg.error.message ?? "cdp error"));
|
|
141
|
+
else
|
|
142
|
+
p.resolve(msg.result);
|
|
143
|
+
}
|
|
144
|
+
send(method, params = {}, timeoutMs = 15_000) {
|
|
145
|
+
const ws = this.ws;
|
|
146
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
147
|
+
return Promise.reject(new Error("debugger not connected"));
|
|
148
|
+
}
|
|
149
|
+
const id = this.nextId++;
|
|
150
|
+
return new Promise((resolve, reject) => {
|
|
151
|
+
const timer = setTimeout(() => {
|
|
152
|
+
this.pending.delete(id);
|
|
153
|
+
reject(new Error(`${method} timed out`));
|
|
154
|
+
}, timeoutMs);
|
|
155
|
+
this.pending.set(id, {
|
|
156
|
+
resolve: (v) => {
|
|
157
|
+
clearTimeout(timer);
|
|
158
|
+
resolve(v);
|
|
159
|
+
},
|
|
160
|
+
reject: (e) => {
|
|
161
|
+
clearTimeout(timer);
|
|
162
|
+
reject(e);
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
ws.send(JSON.stringify({ id, method, params }));
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
/** Evaluate an expression in the page and return it by value. */
|
|
169
|
+
async evaluate(expression, timeoutMs = 15_000) {
|
|
170
|
+
const res = await this.send("Runtime.evaluate", { expression, returnByValue: true, awaitPromise: true }, timeoutMs);
|
|
171
|
+
if (res.exceptionDetails) {
|
|
172
|
+
const detail = res.exceptionDetails.exception?.description ?? res.exceptionDetails.text;
|
|
173
|
+
throw new Error(`page threw: ${String(detail).slice(0, 300)}`);
|
|
174
|
+
}
|
|
175
|
+
return res.result?.value;
|
|
176
|
+
}
|
|
177
|
+
/** Type text into whatever currently has focus, the way a keyboard would. */
|
|
178
|
+
async insertText(text) {
|
|
179
|
+
await this.send("Input.insertText", { text });
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Click where a person would.
|
|
183
|
+
*
|
|
184
|
+
* `el.focus()` is a request the page can decline — a re-render, an overlay, a
|
|
185
|
+
* framework that manages focus itself — and when it's declined, Input.insertText
|
|
186
|
+
* types into whatever else has focus, which is nowhere useful. A real mouse
|
|
187
|
+
* event at real coordinates is how a human gets the caret into a box, and it
|
|
188
|
+
* doesn't ask.
|
|
189
|
+
*/
|
|
190
|
+
async clickAt(x, y) {
|
|
191
|
+
const base = { x, y, button: "left", clickCount: 1 };
|
|
192
|
+
await this.send("Input.dispatchMouseEvent", { ...base, type: "mousePressed" });
|
|
193
|
+
await this.send("Input.dispatchMouseEvent", { ...base, type: "mouseReleased" });
|
|
194
|
+
}
|
|
195
|
+
async pressEnter() {
|
|
196
|
+
for (const type of ["keyDown", "keyUp"]) {
|
|
197
|
+
await this.send("Input.dispatchKeyEvent", {
|
|
198
|
+
type,
|
|
199
|
+
key: "Enter",
|
|
200
|
+
code: "Enter",
|
|
201
|
+
windowsVirtualKeyCode: 13,
|
|
202
|
+
nativeVirtualKeyCode: 13,
|
|
203
|
+
text: type === "keyDown" ? "\r" : undefined,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
close() {
|
|
208
|
+
const ws = this.ws;
|
|
209
|
+
this.ws = null;
|
|
210
|
+
if (!ws)
|
|
211
|
+
return;
|
|
212
|
+
try {
|
|
213
|
+
ws.removeAllListeners();
|
|
214
|
+
ws.on("error", () => { });
|
|
215
|
+
ws.terminate();
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
/* already gone */
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=cdp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdp.js","sourceRoot":"","sources":["../../../src/adapters/bridges/cdp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,SAAS,MAAM,IAAI,CAAC;AAC3B,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAUvC,kDAAkD;AAClD,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,QAAgB,EAAE,SAAS,GAAG,IAAI;IAC5D,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,GAAG,QAAQ,eAAe,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;QAClE,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB,EAAE,SAAS,GAAG,IAAI;IACjE,OAAO,SAAS,CAAc,GAAG,QAAQ,OAAO,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;AAC1F,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,OAAoB;IAClD,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AACzC,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,WAAW,CAAC,OAAoB;IAC9C,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QAClC,IAAI,CAAC,CAAC,CAAC,oBAAoB;YAAE,OAAO,KAAK,CAAC;QAC1C,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;QAChC,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK,aAAa;YAAE,OAAO,KAAK,CAAC;QAChD,IAAI,CAAC,CAAC,IAAI,KAAK,gBAAgB,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,KAAK,eAAe;YAAE,OAAO,KAAK,CAAC;QACnG,OAAO,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,CAAC,CAAY,EAAU,EAAE;QACpC,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;QAChC,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO,CAAC,CAAC,CAAC,kBAAkB;QACzD,IAAI,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,OAAO,CAAC,CAAC,CAAC,qBAAqB;QAC5F,IAAI,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC;YAAE,OAAO,CAAC,CAAC,CAAC,qCAAqC;QAC5E,OAAO,CAAC,CAAC;IACX,CAAC,CAAC;IACF,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,0CAA0C;AAC1C,MAAM,OAAO,UAAU;IAKgB;IAJ7B,EAAE,GAAqB,IAAI,CAAC;IAC5B,MAAM,GAAG,CAAC,CAAC;IACX,OAAO,GAAG,IAAI,GAAG,EAAyE,CAAC;IAEnG,YAAqC,GAAW;QAAX,QAAG,GAAH,GAAG,CAAQ;IAAG,CAAC;IAEpD,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAiB,EAAE,SAAS,GAAG,IAAI;QACnD,IAAI,CAAC,MAAM,CAAC,oBAAoB;YAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAChF,MAAM,OAAO,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;QAC5D,MAAM,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACjC,OAAO,OAAO,CAAC;IACjB,CAAC;IAEO,OAAO,CAAC,SAAiB;QAC/B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC;YACrE,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;YACb,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,EAAE,CAAC,SAAS,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC,CAAC;YAClE,CAAC,EAAE,SAAS,CAAC,CAAC;YACd,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;gBACjB,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC,CAAC;YACH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBACrB,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,MAAM,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC9D,CAAC,CAAC,CAAC;YACH,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACvD,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;gBAClB,+DAA+D;gBAC/D,KAAK,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO;oBAAE,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC,CAAC;gBACpF,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACvB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,SAAS,CAAC,GAAW;QAC3B,IAAI,GAAoE,CAAC;QACzE,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAe,CAAC;QACtC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QACD,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ;YAAE,OAAO,CAAC,0BAA0B;QAClE,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,CAAC,CAAC;YAAE,OAAO;QACf,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5B,IAAI,GAAG,CAAC,KAAK;YAAE,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,IAAI,WAAW,CAAC,CAAC,CAAC;;YAChE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC;IAED,IAAI,CAAc,MAAc,EAAE,SAAkC,EAAE,EAAE,SAAS,GAAG,MAAM;QACxF,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QACnB,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;YAC5C,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC,CAAC;QAC7D,CAAC;QACD,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACzB,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACxC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBACxB,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,MAAM,YAAY,CAAC,CAAC,CAAC;YAC3C,CAAC,EAAE,SAAS,CAAC,CAAC;YACd,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE;gBACnB,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;oBACb,YAAY,CAAC,KAAK,CAAC,CAAC;oBACpB,OAAO,CAAC,CAAM,CAAC,CAAC;gBAClB,CAAC;gBACD,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE;oBACZ,YAAY,CAAC,KAAK,CAAC,CAAC;oBACpB,MAAM,CAAC,CAAC,CAAC,CAAC;gBACZ,CAAC;aACF,CAAC,CAAC;YACH,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;IACL,CAAC;IAED,iEAAiE;IACjE,KAAK,CAAC,QAAQ,CAAI,UAAkB,EAAE,SAAS,GAAG,MAAM;QACtD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAGxB,kBAAkB,EAAE,EAAE,UAAU,EAAE,aAAa,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,SAAS,CAAC,CAAC;QAC3F,IAAI,GAAG,CAAC,gBAAgB,EAAE,CAAC;YACzB,MAAM,MAAM,GAAG,GAAG,CAAC,gBAAgB,CAAC,SAAS,EAAE,WAAW,IAAI,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC;YACxF,MAAM,IAAI,KAAK,CAAC,eAAe,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QACjE,CAAC;QACD,OAAO,GAAG,CAAC,MAAM,EAAE,KAAU,CAAC;IAChC,CAAC;IAED,6EAA6E;IAC7E,KAAK,CAAC,UAAU,CAAC,IAAY;QAC3B,MAAM,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,OAAO,CAAC,CAAS,EAAE,CAAS;QAChC,MAAM,IAAI,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,CAAC,IAAI,CAAC,0BAA0B,EAAE,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC;QAC/E,MAAM,IAAI,CAAC,IAAI,CAAC,0BAA0B,EAAE,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC;IAClF,CAAC;IAED,KAAK,CAAC,UAAU;QACd,KAAK,MAAM,IAAI,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC;YACxC,MAAM,IAAI,CAAC,IAAI,CAAC,wBAAwB,EAAE;gBACxC,IAAI;gBACJ,GAAG,EAAE,OAAO;gBACZ,IAAI,EAAE,OAAO;gBACb,qBAAqB,EAAE,EAAE;gBACzB,oBAAoB,EAAE,EAAE;gBACxB,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;aAC5C,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,KAAK;QACH,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QACnB,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;QACf,IAAI,CAAC,EAAE;YAAE,OAAO;QAChB,IAAI,CAAC;YACH,EAAE,CAAC,kBAAkB,EAAE,CAAC;YACxB,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YACzB,EAAE,CAAC,SAAS,EAAE,CAAC;QACjB,CAAC;QAAC,MAAM,CAAC;YACP,kBAAkB;QACpB,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Driving a GUI agent's chat box over CDP.
|
|
3
|
+
*
|
|
4
|
+
* Shared by the Antigravity and Kiro bridges, because the problem is identical:
|
|
5
|
+
* an Electron app with a chat panel, no API, and a DOM we can reach.
|
|
6
|
+
*
|
|
7
|
+
* The approach — and most of the hard-won detail — comes from two projects that
|
|
8
|
+
* do this for real every day: krishnakanthb13/antigravity_phone_chat for the
|
|
9
|
+
* shape of the chat, yazanbaker94/AntiGravity-AutoAccept for how to attach.
|
|
10
|
+
* Both MIT. Connect over CDP, find the composer inside the chat panel, type
|
|
11
|
+
* through the browser's own input pipeline, click the app's own submit button.
|
|
12
|
+
* Never touch the provider APIs, never lift a token: drive the app that's
|
|
13
|
+
* already signed in, the way a person would.
|
|
14
|
+
*
|
|
15
|
+
* ## Why Input.insertText and not `el.textContent = …`
|
|
16
|
+
*
|
|
17
|
+
* Antigravity's composer is a Lexical editor; Kiro's is React. Both keep their
|
|
18
|
+
* own model of the text and only update it when the browser tells them about an
|
|
19
|
+
* edit. Assigning to the DOM changes what you see and nothing else — the app
|
|
20
|
+
* still believes the box is empty and submits nothing.
|
|
21
|
+
*
|
|
22
|
+
* CDP's `Input.insertText` is what's used, and the choice was measured rather
|
|
23
|
+
* than assumed. In a real Chromium:
|
|
24
|
+
*
|
|
25
|
+
* document.execCommand("insertText") → fires `input` only
|
|
26
|
+
* Input.insertText (CDP) → fires `beforeinput` AND `input`
|
|
27
|
+
*
|
|
28
|
+
* `beforeinput` is the one an editor builds its model from, so execCommand is
|
|
29
|
+
* the worse tool despite being the obvious one — it fills the box while leaving
|
|
30
|
+
* the app convinced it's empty, which then looks like a send bug. It survives
|
|
31
|
+
* only as a fallback, after synthesising the events by hand.
|
|
32
|
+
*
|
|
33
|
+
* ## Why it refuses
|
|
34
|
+
*
|
|
35
|
+
* Both apps are VS Code family, and Monaco — the editor holding YOUR SOURCE
|
|
36
|
+
* FILE — is a `contenteditable`. A driver that hunts the document for something
|
|
37
|
+
* editable will eventually find that one, type a prompt into your code and press
|
|
38
|
+
* Enter. No error message repairs that afterwards.
|
|
39
|
+
*
|
|
40
|
+
* So the composer is only ever looked for *inside* the chat panel
|
|
41
|
+
* (profiles.ts), never in the document at large; anything under `.monaco-editor`
|
|
42
|
+
* is excluded even then; and no match, or an ambiguous one, is a refusal that
|
|
43
|
+
* names the fix. Not sending is a bad outcome. Sending into the wrong box is a
|
|
44
|
+
* worse one.
|
|
45
|
+
*/
|
|
46
|
+
export interface GuiChatSelectors {
|
|
47
|
+
/** CSS selector for the chat input. Set this when discovery can't. */
|
|
48
|
+
composer?: string;
|
|
49
|
+
/** CSS selector for the submit button. Falls back to pressing Enter. */
|
|
50
|
+
send?: string;
|
|
51
|
+
/** CSS selector for the transcript container, to read replies from. */
|
|
52
|
+
transcript?: string;
|
|
53
|
+
}
|
|
54
|
+
export interface GuiChatOptions {
|
|
55
|
+
host?: string;
|
|
56
|
+
debugPort?: number;
|
|
57
|
+
selectors?: GuiChatSelectors;
|
|
58
|
+
/** How long a reply may take before we stop waiting. */
|
|
59
|
+
replyTimeoutMs?: number;
|
|
60
|
+
/** Quiet time that means "it stopped typing". */
|
|
61
|
+
settleMs?: number;
|
|
62
|
+
}
|
|
63
|
+
export declare class GuiChatDriver {
|
|
64
|
+
private readonly options;
|
|
65
|
+
private profile;
|
|
66
|
+
private readonly appName;
|
|
67
|
+
/** The target that had the chat last time; tried first, never trusted. */
|
|
68
|
+
private lastTargetId;
|
|
69
|
+
constructor(appName: string, options: GuiChatOptions, kind?: string);
|
|
70
|
+
get endpoint(): string;
|
|
71
|
+
/** How to start this app with a debugger, on this OS. */
|
|
72
|
+
get launchHint(): string;
|
|
73
|
+
reachable(): Promise<boolean>;
|
|
74
|
+
/**
|
|
75
|
+
* A session on the target that actually has the chat in it.
|
|
76
|
+
*
|
|
77
|
+
* There is usually more than one candidate and the right one isn't
|
|
78
|
+
* predictable from the app: Antigravity's chat is in its page, Kiro's is in a
|
|
79
|
+
* webview target. So every candidate is asked "is there a composer here?" and
|
|
80
|
+
* the first that says yes wins. The answer is remembered — the targets don't
|
|
81
|
+
* move around between two calls a second apart — but it's re-derived whenever
|
|
82
|
+
* that target has gone.
|
|
83
|
+
*/
|
|
84
|
+
private session;
|
|
85
|
+
/**
|
|
86
|
+
* Can we actually drive it right now? Reachable is not enough: a signed-out
|
|
87
|
+
* Antigravity answers CDP happily and has no chat in it at all.
|
|
88
|
+
*/
|
|
89
|
+
driveable(): Promise<{
|
|
90
|
+
ok: boolean;
|
|
91
|
+
reason?: string;
|
|
92
|
+
}>;
|
|
93
|
+
/**
|
|
94
|
+
* Type a prompt into the app, submit it, and wait for the panel to stop
|
|
95
|
+
* growing.
|
|
96
|
+
*
|
|
97
|
+
* What comes back is the panel's text, not the model's response object —
|
|
98
|
+
* these apps render their conversation, they don't hand it out. Same trade
|
|
99
|
+
* the phone-chat project makes: mirror what's on screen rather than pretend
|
|
100
|
+
* to have something structured.
|
|
101
|
+
*/
|
|
102
|
+
ask(text: string, onProgress?: (partial: string) => void): Promise<string>;
|
|
103
|
+
/** Poll until the transcript stops changing, or we run out of patience. */
|
|
104
|
+
private waitForReply;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* What the panel gained.
|
|
108
|
+
*
|
|
109
|
+
* Not a real diff: the transcript is a rendered panel that scrolls, collapses
|
|
110
|
+
* and reflows. When `after` simply grew, the tail is the new part; when it
|
|
111
|
+
* changed shape, the common prefix is the best we can honestly do.
|
|
112
|
+
*/
|
|
113
|
+
export declare function delta(before: string, after: string): string;
|