residoo 0.1.0 → 0.2.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 (50) hide show
  1. package/README.md +225 -46
  2. package/SECURITY.md +29 -22
  3. package/package.json +1 -1
  4. package/src/cli.js +82 -16
  5. package/src/integrity.js +669 -0
  6. package/src/patterns.js +78 -5
  7. package/src/report.js +74 -7
  8. package/src/sources/agent-configs.js +308 -0
  9. package/src/sources/aider.js +361 -0
  10. package/src/sources/amazon-q.js +199 -0
  11. package/src/sources/antigravity-cli.js +155 -0
  12. package/src/sources/cline.js +208 -0
  13. package/src/sources/codebuff.js +295 -0
  14. package/src/sources/codex-cli.js +258 -0
  15. package/src/sources/cody.js +325 -0
  16. package/src/sources/continue.js +408 -0
  17. package/src/sources/copilot-chat.js +272 -0
  18. package/src/sources/copilot-cli.js +300 -0
  19. package/src/sources/crush.js +364 -0
  20. package/src/sources/cursor.js +374 -0
  21. package/src/sources/devin-cli.js +241 -0
  22. package/src/sources/factory-droid.js +153 -0
  23. package/src/sources/fx.js +136 -0
  24. package/src/sources/gemini-cli.js +242 -0
  25. package/src/sources/goose.js +366 -0
  26. package/src/sources/grok-cli.js +267 -0
  27. package/src/sources/hermes.js +282 -0
  28. package/src/sources/index.js +172 -8
  29. package/src/sources/jetbrains-ai-assistant.js +343 -0
  30. package/src/sources/jetbrains-junie.js +292 -0
  31. package/src/sources/kilo-code.js +430 -0
  32. package/src/sources/kimi-code.js +147 -0
  33. package/src/sources/kiro-cli.js +393 -0
  34. package/src/sources/kiro-ide.js +230 -0
  35. package/src/sources/llm.js +328 -0
  36. package/src/sources/mentat.js +143 -0
  37. package/src/sources/open-interpreter.js +224 -0
  38. package/src/sources/openclaw.js +218 -0
  39. package/src/sources/opencode.js +379 -0
  40. package/src/sources/openhands.js +181 -0
  41. package/src/sources/pearai.js +151 -0
  42. package/src/sources/pi-agent.js +130 -0
  43. package/src/sources/qodo-gen.js +189 -0
  44. package/src/sources/qwen-code.js +244 -0
  45. package/src/sources/roo-code.js +239 -0
  46. package/src/sources/trae.js +294 -0
  47. package/src/sources/void.js +273 -0
  48. package/src/sources/warp.js +395 -0
  49. package/src/sources/windsurf.js +256 -0
  50. package/src/sources/zed.js +374 -0
@@ -0,0 +1,282 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const os = require("os");
6
+
7
+ /**
8
+ * Hermes Agent (NousResearch/hermes-agent, "the agent that grows with you")
9
+ * local state database.
10
+ *
11
+ * VERIFICATION STATUS: NOT checked against a real install — `hermes` is not
12
+ * on PATH and no `~/.hermes` directory exists on the machine this adapter
13
+ * was built on. Ships per CONTRIBUTING.md rule 3 on two independent
14
+ * corroborating sources, WITH an integrity caveat below that is unusually
15
+ * important to read before trusting this file:
16
+ *
17
+ * 1. Hermes' own docs (fetched from its docs site): primary directory is
18
+ * `~/.hermes` on Linux/macOS/WSL2, `%LOCALAPPDATA%\hermes` on native
19
+ * Windows, and explicitly reference `$HERMES_HOME` as the env var that
20
+ * relocates it (e.g. "Session checkout: $HERMES_HOME/hermes-agent").
21
+ * 2. ccusage (github.com/ccusage/ccusage — a real, independent, actively
22
+ * developed usage-tracking CLI; its own GitHub star count was sanity-
23
+ * checked against known repos before being trusted at all, see below)
24
+ * ships a tested Rust adapter for Hermes
25
+ * (rust/adapters/hermes/src/{paths,parser,loader}.rs), fetched and read
26
+ * directly. It confirms: `${HERMES_HOME:-~/.hermes}/state.db`, a real
27
+ * SQLite database, opened read-only, containing (at minimum) a table
28
+ * literally named `sessions` — its own unit test creates that table
29
+ * with `CREATE TABLE sessions (id TEXT PRIMARY KEY, source TEXT NOT
30
+ * NULL, model TEXT, started_at REAL NOT NULL, message_count INTEGER
31
+ * ..., input_tokens INTEGER ..., ... billing_provider TEXT,
32
+ * estimated_cost_usd REAL, actual_cost_usd REAL)` and queries it with
33
+ * `SELECT id, model, billing_provider, started_at, message_count,
34
+ * input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
35
+ * reasoning_tokens, estimated_cost_usd, actual_cost_usd FROM sessions`.
36
+ *
37
+ * THE INTEGRITY CAVEAT: while researching this source, `gh api` search
38
+ * turned up openclaw/openclaw and NousResearch/hermes-agent GitHub star
39
+ * counts (388,546 and 239,598 respectively, for repos 9-13 months old) that
40
+ * are implausible for organic growth — comparable to or exceeding
41
+ * decade-plus flagship repos like facebook/react and torvalds/linux, which
42
+ * were fetched in the same session as a sanity check and came back in the
43
+ * same 240-250k range. Independent search corroborates a real integrity
44
+ * problem, not just a suspicious number: an arXiv paper on large-scale
45
+ * GitHub fake-star campaigns turned up in the same research pass, alongside
46
+ * a Hacker News thread titled "Nous Research edits GitHub issue to remove
47
+ * plagiarism claims about Hermes Agent," and a GitHub topic description
48
+ * referencing "Two zero-human AI companies battle for GitHub stars using
49
+ * Hermes Agent + Paperclip." None of this proves the file format below is
50
+ * wrong — ccusage's corroborating source is independent of Hermes/OpenClaw
51
+ * and its parsing code is real and tested — but it does mean the two "docs"
52
+ * sources for Hermes and OpenClaw may not be independent of EACH OTHER (one
53
+ * plausibly forked/copied the other; Hermes' own docs describe importing
54
+ * OpenClaw's config directory directly), which weakens this source's
55
+ * corroboration below CONTRIBUTING.md's "2+ independent sources" bar in
56
+ * spirit even where it's technically met. Flagged here and in this
57
+ * adapter's PR description/report; a human should weigh this before
58
+ * treating either this file or openclaw.js as more than
59
+ * multi-source-corroborated-but-unverified.
60
+ *
61
+ * WHAT THIS SOURCE ACTUALLY READS: ccusage's own adapter only ever SELECTs
62
+ * the `sessions` table's usage/cost columns — it has no reason to touch
63
+ * anything else, and its code is silent on whether state.db holds the
64
+ * user's actual conversation text anywhere, and if so, in which table.
65
+ * Hermes' own marketing copy ("searches its own past conversations") implies
66
+ * real message content is persisted somewhere in this database, but no
67
+ * source here names the table. Rather than guess a table/column name likely
68
+ * to be wrong, this source applies the same tolerance cursor.js already
69
+ * established for exactly this situation: it enumerates EVERY table via
70
+ * `sqlite_master` at read time and turns every row of every table into one
71
+ * scanned line, keyed by column name, so it works regardless of which table
72
+ * (if any) turns out to hold conversation content, and keeps working if
73
+ * Hermes' schema drifts. See cursor.js's own docstring for the full
74
+ * reasoning against a hardcoded key/table allowlist.
75
+ */
76
+ const HERMES_HOME_ENV = "HERMES_HOME";
77
+
78
+ function hermesHomeDirs() {
79
+ const envVal = process.env[HERMES_HOME_ENV];
80
+ if (envVal && envVal.trim() !== "") {
81
+ return envVal.split(",").map((s) => s.trim()).filter((s) => s !== "").map((p) => path.resolve(p));
82
+ }
83
+ const home = os.homedir();
84
+ if (process.platform === "win32") {
85
+ const localAppData = process.env.LOCALAPPDATA || path.join(home, "AppData", "Local");
86
+ return [path.join(localAppData, "hermes")];
87
+ }
88
+ return [path.join(home, ".hermes")];
89
+ }
90
+
91
+ function stateDbPaths() {
92
+ return hermesHomeDirs().map((dir) => path.join(dir, "state.db"));
93
+ }
94
+
95
+ /**
96
+ * Lazily require node:sqlite — see cursor.js's getDatabaseSync() docstring
97
+ * for the full reasoning (avoid Node's one-time ExperimentalWarning on every
98
+ * `residoo scan` for users who have never touched a SQLite-backed source).
99
+ * Duplicated rather than shared per this project's one-small-self-contained-
100
+ * file convention.
101
+ */
102
+ let sqliteRequireAttempted = false;
103
+ let DatabaseSync = null;
104
+ function getDatabaseSync() {
105
+ if (!sqliteRequireAttempted) {
106
+ sqliteRequireAttempted = true;
107
+ try { ({ DatabaseSync } = require("node:sqlite")); }
108
+ catch { DatabaseSync = null; }
109
+ }
110
+ return DatabaseSync;
111
+ }
112
+
113
+ function id() { return "hermes"; }
114
+ function label() { return "Hermes"; }
115
+
116
+ function homeDirExists() {
117
+ return hermesHomeDirs().some((dir) => {
118
+ try { return fs.statSync(dir).isDirectory(); } catch { return false; }
119
+ });
120
+ }
121
+
122
+ function available() {
123
+ // Cheap fs check first — see cursor.js's available() for why short-
124
+ // circuiting matters: the common case is Hermes simply isn't installed,
125
+ // and that answer must not cost requiring node:sqlite.
126
+ return homeDirExists() && Boolean(getDatabaseSync());
127
+ }
128
+
129
+ function unavailableReason() {
130
+ if (!homeDirExists()) return null;
131
+ if (getDatabaseSync()) return null;
132
+ return "Hermes detected but not scanned — needs Node.js 22.5+ (node:sqlite not present in this runtime)";
133
+ }
134
+
135
+ /**
136
+ * Same lstat-first, follow-if-symlink shape as cursor.js's statIfPresent —
137
+ * duplicated for the same reason (no Dirent available for a constructed
138
+ * path). A HERMES_HOME that doesn't have a state.db yet (Hermes installed
139
+ * but never run) is normal and not broken.
140
+ */
141
+ function* statIfPresent(dbPath) {
142
+ let lst;
143
+ try { lst = fs.lstatSync(dbPath); }
144
+ catch { return; }
145
+
146
+ if (lst.isSymbolicLink()) {
147
+ try {
148
+ const st = fs.statSync(dbPath);
149
+ if (!st.isFile()) { yield { file: dbPath, broken: true }; return; }
150
+ yield { file: dbPath, mtimeMs: st.mtimeMs, sizeBytes: st.size, broken: false };
151
+ } catch {
152
+ yield { file: dbPath, broken: true };
153
+ }
154
+ return;
155
+ }
156
+
157
+ if (!lst.isFile()) return;
158
+ yield { file: dbPath, mtimeMs: lst.mtimeMs, sizeBytes: lst.size, broken: false };
159
+ }
160
+
161
+ function* files() {
162
+ for (const dbPath of stateDbPaths()) yield* statIfPresent(dbPath);
163
+ }
164
+
165
+ // No real state.db has been inspected to size this against — unlike
166
+ // claude-code.js's MAX_BYTES, this is a generous, untested backstop only,
167
+ // same honesty as cursor.js's own MAX_DB_BYTES about the same gap.
168
+ const MAX_DB_BYTES = 512 * 1024 * 1024;
169
+ const READ_TIMEOUT_MS = 60_000;
170
+ const BUSY_TIMEOUT_MS = 5_000;
171
+ const YIELD_EVERY_N_ROWS = 500;
172
+
173
+ /**
174
+ * A BLOB-affinity column comes back as a Uint8Array from node:sqlite, not a
175
+ * Buffer — see cursor.js's valueToText() docstring, verified there directly
176
+ * against this project's own node:sqlite, not assumed from Node's docs.
177
+ */
178
+ function valueToText(value) {
179
+ if (typeof value === "string") return value;
180
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
181
+ if (value instanceof Uint8Array) return Buffer.from(value).toString("utf-8");
182
+ return null;
183
+ }
184
+
185
+ /**
186
+ * Read one state.db as an array of raw text "lines" — one per row, across
187
+ * EVERY table found in sqlite_master (see the module docstring for why this
188
+ * is deliberately schema-agnostic rather than hardcoding just `sessions`).
189
+ * Each row becomes one JSON-stringified object of {column: text}, skipping
190
+ * columns whose value isn't text/number/blob (NULL, mainly).
191
+ *
192
+ * Same synchronous-native-call constraint cursor.js's readLines() docstring
193
+ * explains: node:sqlite's DatabaseSync/StatementSync have no event/AbortSignal
194
+ * to hook a real preemptive timeout onto, so this yields to the event loop
195
+ * and checks a wall-clock deadline every YIELD_EVERY_N_ROWS rows across all
196
+ * tables combined — bounding "too many total rows across too many tables
197
+ * taking too long", not a single pathological row's own decode time.
198
+ */
199
+ async function readLines(file) {
200
+ const DB = getDatabaseSync();
201
+ if (!DB) return { lines: [], status: "failed", bytesRead: 0 };
202
+
203
+ let stat;
204
+ try { stat = fs.statSync(file); }
205
+ catch { return { lines: [], status: "failed", bytesRead: 0 }; }
206
+ if (stat.size > MAX_DB_BYTES) return { lines: [], status: "too-large", bytesRead: 0 };
207
+
208
+ let db;
209
+ try {
210
+ db = new DB(file, { readOnly: true });
211
+ db.exec(`PRAGMA busy_timeout = ${BUSY_TIMEOUT_MS}`);
212
+ } catch {
213
+ return { lines: [], status: "failed", bytesRead: 0 };
214
+ }
215
+
216
+ let tableNames;
217
+ try {
218
+ tableNames = db
219
+ .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'")
220
+ .all()
221
+ .map((row) => row.name)
222
+ .filter((name) => typeof name === "string");
223
+ } catch {
224
+ try { db.close(); } catch { /* best-effort */ }
225
+ return { lines: [], status: "failed", bytesRead: 0 };
226
+ }
227
+
228
+ const lines = [];
229
+ let bytesRead = 0;
230
+ const deadline = Date.now() + READ_TIMEOUT_MS;
231
+ let timedOut = false;
232
+ let sawError = false;
233
+ let foundAnyTable = false;
234
+
235
+ for (const table of tableNames) {
236
+ let rows;
237
+ try {
238
+ // Table names come from sqlite_master itself, not user input, but are
239
+ // still interpolated into SQL text — quote as a SQLite identifier
240
+ // (doubled internal quotes) rather than trusting they're bare words.
241
+ const quoted = `"${table.replace(/"/g, '""')}"`;
242
+ rows = db.prepare(`SELECT * FROM ${quoted}`).iterate();
243
+ } catch {
244
+ continue; // this table genuinely can't be queried — move on, not fatal to the others
245
+ }
246
+ foundAnyTable = true;
247
+
248
+ let n = 0;
249
+ try {
250
+ for (const row of rows) {
251
+ const record = {};
252
+ let hasText = false;
253
+ for (const [column, value] of Object.entries(row)) {
254
+ const text = valueToText(value);
255
+ if (text !== null && text !== "") { record[column] = text; hasText = true; }
256
+ }
257
+ if (hasText) {
258
+ const line = JSON.stringify(record);
259
+ lines.push(line);
260
+ bytesRead += Buffer.byteLength(line, "utf-8");
261
+ }
262
+ n++;
263
+ if (n % YIELD_EVERY_N_ROWS === 0) {
264
+ await new Promise((resolve) => setImmediate(resolve));
265
+ if (Date.now() > deadline) { timedOut = true; break; }
266
+ }
267
+ }
268
+ } catch {
269
+ sawError = true;
270
+ }
271
+ if (timedOut) break;
272
+ }
273
+
274
+ try { db.close(); } catch { /* best-effort close */ }
275
+
276
+ if (!foundAnyTable) return { lines: [], status: "failed", bytesRead: 0 };
277
+ if (sawError && lines.length === 0) return { lines: [], status: "failed", bytesRead };
278
+ if (timedOut || sawError) return { lines, status: lines.length > 0 ? "partial" : "failed", bytesRead };
279
+ return { lines, status: "complete", bytesRead };
280
+ }
281
+
282
+ module.exports = { id, label, available, unavailableReason, files, readLines };
@@ -1,20 +1,184 @@
1
1
  "use strict";
2
2
 
3
3
  /**
4
- * Registry of transcript sources. Each source is a small adapter exposing
4
+ * Registry of scan sources. Each source is a small adapter exposing
5
5
  * { id, label, available, files, readLines } — see claude-code.js for the
6
6
  * reference implementation and CONTRIBUTING.md for how to add one.
7
7
  *
8
- * Deliberately NOT included here: guessed paths for Cursor, GitHub Copilot,
9
- * or Windsurf. Their local history formats are real but weren't verified
10
- * against an actual installation while building this — shipping a scanner
11
- * that silently checks the wrong path and reports "all clear" is worse than
12
- * not supporting the tool at all. PRs adding a verified adapter are the
13
- * fastest way to get a tool covered.
8
+ * All but one of these read TRANSCRIPT stores — the session histories agents
9
+ * write as a side effect of working. The exception is agent-configs.js, which
10
+ * reads agent CONFIG files (settings, MCP server configs, memory files)
11
+ * through the identical contract: configs are where users deliberately put
12
+ * env blocks and where approved-command caches accumulate tokens, so they
13
+ * leak by a different mechanism than transcripts but are scanned the same
14
+ * way. The distinction matters for scope reasoning — a transcript source
15
+ * covers what an agent SAW, the config source covers what an agent was
16
+ * CONFIGURED with — and each file's header states which it is.
17
+ *
18
+ * TRUST TIERS — read this before treating every row below the same way.
19
+ * Each source file states its own tier plainly in its header docstring;
20
+ * this is a summary, not a substitute for reading one before relying on it.
21
+ *
22
+ * - REAL-INSTALL-VERIFIED: the adapter was run against an actual,
23
+ * populated installation of the tool and confirmed to find real
24
+ * content. Currently Claude Code, plus agent-configs.js's Claude-family
25
+ * paths (its non-Claude paths sit in the tier below — that file's
26
+ * header tracks verification per path, not per file).
27
+ * - MULTI-SOURCE-CORROBORATED-BUT-UNVERIFIED: the path/schema is backed by
28
+ * 2+ independent, credible sources (official docs, the tool's own
29
+ * shipped source code, a real community tool that reads the same files
30
+ * for a living, or a real user's own reported install) but was NOT
31
+ * checked against a real install of the tool on any machine this project
32
+ * was built on. This is the tier every source below Cursor is in. A
33
+ * scanner that silently checks the wrong path and reports "all clear" is
34
+ * worse than not supporting the tool, so every adapter in this tier is
35
+ * built to fail loudly (`broken: true`, `status: "failed"`) rather than
36
+ * silently — but the PATH ITSELF could still be stale or wrong in a way
37
+ * only a real install can catch. If you have one of these tools
38
+ * installed, running `residoo scan` and reporting back whether
39
+ * `filesScanned` looks right for what you know is on disk is the single
40
+ * most useful thing you can do.
41
+ *
42
+ * Deliberately NOT included here, investigated and skipped rather than
43
+ * guessed at (see each PR/commit description for the full reasoning):
44
+ * - Plandex — confirmed, by reading its actual CLI source, to be
45
+ * client-server with no local transcript content on disk at all (only
46
+ * auth tokens and a project-id pointer live locally); nothing to scan.
47
+ * - CodeGPT — an account/cloud-based product; its own docs describe
48
+ * conversation retention in plan-tier/account terms, and no local
49
+ * chat-history file (official or third-party) was ever found.
50
+ * - Augment Code — only local config/rules files were confirmed; no
51
+ * evidence anywhere (official or third-party) of local chat-transcript
52
+ * storage, consistent with its server-side "Context Engine" design.
53
+ * - Tabby, Tabnine, Zencoder, Tongyi Lingma, Berd — researched, but no
54
+ * source reached this project's 2-independent-source bar for a
55
+ * transcript-content path in the time available. Worth a follow-up PR.
56
+ * - Replit Agent — confirmed cloud-only (server-side storage, nothing
57
+ * local to scan).
14
58
  */
15
59
  const claudeCode = require("./claude-code");
60
+ // Not a transcript store — agent config/state files (see module docstring
61
+ // above and that file's own header for the per-path verification trail).
62
+ const agentConfigs = require("./agent-configs");
63
+ const cursor = require("./cursor");
64
+
65
+ // The rest of these are grouped the way they were researched/built, purely
66
+ // to keep this list navigable — the grouping carries no behavioral meaning,
67
+ // every entry goes through the identical { id, label, available, files,
68
+ // readLines } contract. All are MULTI-SOURCE-CORROBORATED-BUT-UNVERIFIED —
69
+ // see each file's own header for exactly what was and wasn't checked, and
70
+ // note two partial exceptions worth naming here rather than only in the
71
+ // file: jetbrains-junie.js and jetbrains-ai-assistant.js had their directory
72
+ // *layout* (not their chat-content schema) confirmed against a real, if
73
+ // long-dormant, JetBrains install found on this project's own build machine
74
+ // — see those two files for what that does and doesn't cover. qodo-gen.js
75
+ // is flagged in its own header as the single weakest-verified source here
76
+ // (one vendor's own docs, restated twice, plus one third-party artifact that
77
+ // likely documents a different Qodo product).
78
+ const codexCli = require("./codex-cli");
79
+ const opencode = require("./opencode");
80
+
81
+ const aider = require("./aider");
82
+
83
+ const cline = require("./cline");
84
+ const rooCode = require("./roo-code");
85
+ const kiloCode = require("./kilo-code");
86
+
87
+ const windsurf = require("./windsurf");
88
+
89
+ const pearai = require("./pearai");
90
+ const trae = require("./trae");
91
+ const voidEditor = require("./void");
92
+
93
+ const geminiCli = require("./gemini-cli");
94
+ const qwenCode = require("./qwen-code");
95
+
96
+ const continueDev = require("./continue");
97
+
98
+ const openInterpreter = require("./open-interpreter");
99
+ const goose = require("./goose");
100
+
101
+ const copilotChat = require("./copilot-chat");
102
+ const copilotCli = require("./copilot-cli");
103
+
104
+ const llm = require("./llm");
105
+
106
+ const codebuff = require("./codebuff");
107
+ const mentat = require("./mentat");
108
+ const hermes = require("./hermes");
109
+ const openclaw = require("./openclaw");
110
+ // Plandex investigated and deliberately not included — see module docstring.
111
+
112
+ const warp = require("./warp");
113
+ const crush = require("./crush");
114
+ const grokCli = require("./grok-cli");
115
+ const kiroCli = require("./kiro-cli");
116
+ const kiroIde = require("./kiro-ide");
117
+
118
+ const zed = require("./zed");
119
+
120
+ const jetbrainsJunie = require("./jetbrains-junie");
121
+ const jetbrainsAiAssistant = require("./jetbrains-ai-assistant");
122
+
123
+ const cody = require("./cody");
124
+ const amazonQ = require("./amazon-q");
125
+ const qodoGen = require("./qodo-gen");
126
+ // Augment Code and CodeGPT investigated and deliberately not included —
127
+ // see module docstring.
128
+
129
+ const openhands = require("./openhands");
130
+ const factoryDroid = require("./factory-droid");
131
+ const devinCli = require("./devin-cli");
132
+ const piAgent = require("./pi-agent");
133
+ const antigravityCli = require("./antigravity-cli");
134
+ const kimiCode = require("./kimi-code");
135
+ const fx = require("./fx");
16
136
 
17
- const ALL_SOURCES = [claudeCode];
137
+ const ALL_SOURCES = [
138
+ claudeCode,
139
+ agentConfigs,
140
+ cursor,
141
+ codexCli,
142
+ opencode,
143
+ aider,
144
+ cline,
145
+ rooCode,
146
+ kiloCode,
147
+ windsurf,
148
+ pearai,
149
+ trae,
150
+ voidEditor,
151
+ geminiCli,
152
+ qwenCode,
153
+ continueDev,
154
+ openInterpreter,
155
+ goose,
156
+ copilotChat,
157
+ copilotCli,
158
+ llm,
159
+ codebuff,
160
+ mentat,
161
+ hermes,
162
+ openclaw,
163
+ warp,
164
+ crush,
165
+ grokCli,
166
+ kiroCli,
167
+ kiroIde,
168
+ zed,
169
+ jetbrainsJunie,
170
+ jetbrainsAiAssistant,
171
+ cody,
172
+ amazonQ,
173
+ qodoGen,
174
+ openhands,
175
+ factoryDroid,
176
+ devinCli,
177
+ piAgent,
178
+ antigravityCli,
179
+ kimiCode,
180
+ fx,
181
+ ];
18
182
 
19
183
  function availableSources() {
20
184
  return ALL_SOURCES.filter((s) => s.available());