residoo 0.1.0 → 0.3.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/README.md +334 -46
- package/SECURITY.md +29 -22
- package/package.json +1 -1
- package/src/cli.js +249 -17
- package/src/integrity.js +689 -0
- package/src/patterns.js +78 -5
- package/src/report.js +188 -8
- package/src/rotation.js +834 -0
- package/src/sources/agent-configs.js +308 -0
- package/src/sources/aider.js +361 -0
- package/src/sources/amazon-q.js +199 -0
- package/src/sources/antigravity-cli.js +155 -0
- package/src/sources/cline.js +208 -0
- package/src/sources/codebuff.js +295 -0
- package/src/sources/codex-cli.js +258 -0
- package/src/sources/cody.js +325 -0
- package/src/sources/continue.js +408 -0
- package/src/sources/copilot-chat.js +272 -0
- package/src/sources/copilot-cli.js +300 -0
- package/src/sources/crush.js +364 -0
- package/src/sources/cursor.js +374 -0
- package/src/sources/devin-cli.js +241 -0
- package/src/sources/factory-droid.js +153 -0
- package/src/sources/fx.js +136 -0
- package/src/sources/gemini-cli.js +242 -0
- package/src/sources/goose.js +366 -0
- package/src/sources/grok-cli.js +267 -0
- package/src/sources/hermes.js +282 -0
- package/src/sources/index.js +172 -8
- package/src/sources/jetbrains-ai-assistant.js +343 -0
- package/src/sources/jetbrains-junie.js +292 -0
- package/src/sources/kilo-code.js +430 -0
- package/src/sources/kimi-code.js +147 -0
- package/src/sources/kiro-cli.js +393 -0
- package/src/sources/kiro-ide.js +230 -0
- package/src/sources/llm.js +328 -0
- package/src/sources/mentat.js +143 -0
- package/src/sources/open-interpreter.js +224 -0
- package/src/sources/openclaw.js +218 -0
- package/src/sources/opencode.js +379 -0
- package/src/sources/openhands.js +181 -0
- package/src/sources/pearai.js +151 -0
- package/src/sources/pi-agent.js +130 -0
- package/src/sources/project-artifacts.js +355 -0
- package/src/sources/qodo-gen.js +189 -0
- package/src/sources/qwen-code.js +244 -0
- package/src/sources/roo-code.js +239 -0
- package/src/sources/trae.js +294 -0
- package/src/sources/void.js +273 -0
- package/src/sources/warp.js +395 -0
- package/src/sources/windsurf.js +256 -0
- package/src/sources/zed.js +374 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Sourcegraph Cody — the VS Code extension (publisher.name `sourcegraph.cody-ai`).
|
|
9
|
+
*
|
|
10
|
+
* VERIFICATION STATUS (read this before trusting anything below):
|
|
11
|
+
* multi-source-corroborated-but-UNVERIFIED against a real install. Neither VS
|
|
12
|
+
* Code nor the Cody extension is installed on the machine this adapter was
|
|
13
|
+
* built on (checked: no /Applications/*Code*.app, no `code`/`code-insiders`
|
|
14
|
+
* on PATH, no ~/Library/Application Support/Code, no
|
|
15
|
+
* ~/.vscode/extensions/sourcegraph.cody-ai-*, no
|
|
16
|
+
* ~/Library/Application Support/JetBrains/<product>/options/cody_history.xml).
|
|
17
|
+
* What
|
|
18
|
+
* IS unusually strong here, short of a real install: the storage mechanism
|
|
19
|
+
* was read directly out of two projects' own current shipped source — not
|
|
20
|
+
* inferred from a blog post — cross-checked against Sourcegraph's own docs:
|
|
21
|
+
*
|
|
22
|
+
* 1. CODY'S OWN SOURCE, read directly from
|
|
23
|
+
* github.com/sourcegraph/cody-public-snapshot (`main`,
|
|
24
|
+
* vscode/src/services/LocalStorageProvider.ts, fetched verbatim via
|
|
25
|
+
* `gh api repos/sourcegraph/cody-public-snapshot/contents/...` on
|
|
26
|
+
* 2026-09-02): the class keeps
|
|
27
|
+
* `protected readonly KEY_LOCAL_HISTORY = 'cody-local-chatHistory-v2'`
|
|
28
|
+
* and every read/write of chat history
|
|
29
|
+
* (getChatHistory/setChatHistory/deleteChatHistory) goes through
|
|
30
|
+
* `this.storage.get/update(this.KEY_LOCAL_HISTORY, ...)`, where
|
|
31
|
+
* `this.storage` is set, once, at extension activation via
|
|
32
|
+
* `localStorage.setStorage(context.globalState)` (see the same file,
|
|
33
|
+
* `activate()`/`initVSCodeStorage()` in `LocalStorageProvider.ts` and
|
|
34
|
+
* `extension.node.ts`) — i.e. the standard VS Code extension `Memento`
|
|
35
|
+
* API (`context.globalState`), not a bespoke file Cody writes itself.
|
|
36
|
+
* `vscode/package.json` (same repo, same fetch) confirms the extension's
|
|
37
|
+
* identity: `"name": "cody-ai"`, `"publisher": "sourcegraph"` — VS
|
|
38
|
+
* Code's own `getExtensionId(publisher, name)` (see next point) makes
|
|
39
|
+
* that `sourcegraph.cody-ai`, matching the `globalStorage/
|
|
40
|
+
* sourcegraph.cody-ai/` path fragment independently named in
|
|
41
|
+
* Sourcegraph's own troubleshooting docs
|
|
42
|
+
* (sourcegraph.com/docs/cody/troubleshooting, a real user's globalStorage
|
|
43
|
+
* resource path is quoted there verbatim).
|
|
44
|
+
*
|
|
45
|
+
* 2. VS CODE'S OWN SOURCE for what `context.globalState` actually resolves
|
|
46
|
+
* to on disk, read directly from github.com/microsoft/vscode (`main`,
|
|
47
|
+
* fetched the same way, 2026-09-02):
|
|
48
|
+
* - `src/vs/workbench/api/browser/mainThreadStorage.ts` —
|
|
49
|
+
* `$setValue(shared, key, value)` calls
|
|
50
|
+
* `extensionStorageService.setExtensionState(key, value, shared)`,
|
|
51
|
+
* where `key` here is the calling extension's id, not a
|
|
52
|
+
* caller-chosen storage key.
|
|
53
|
+
* - `src/vs/platform/extensionManagement/common/extensionStorage.ts`
|
|
54
|
+
* — `setExtensionState(extension, state, global)` resolves
|
|
55
|
+
* `extensionId = getExtensionId(extension)` and then does
|
|
56
|
+
* `storageService.store(extensionId, JSON.stringify(state),
|
|
57
|
+
* global ? StorageScope.PROFILE : StorageScope.WORKSPACE, ...)`.
|
|
58
|
+
* `getExtensionId(publisher, name)` (`extensionManagementUtil.ts`)
|
|
59
|
+
* is exactly `` `${publisher}.${name}` ``.
|
|
60
|
+
* - Net effect: EVERY globalState key an extension sets (Cody's
|
|
61
|
+
* `cody-local-chatHistory-v2` included) is merged into ONE JSON
|
|
62
|
+
* object, and that whole object is stored as a SINGLE row, keyed by
|
|
63
|
+
* the extension id itself, in the PROFILE-scope storage — which is
|
|
64
|
+
* the same shared, per-profile `state.vscdb` / `ItemTable` that
|
|
65
|
+
* cursor.js already reads for Cursor's own tables in this project
|
|
66
|
+
* (Cursor is a VS Code fork; this is the un-forked, upstream version
|
|
67
|
+
* of that exact mechanism). This is CORE VS Code behavior, not
|
|
68
|
+
* Cody-specific or version-fragile the way Cursor's own bespoke
|
|
69
|
+
* `cursorDiskKV` table naming has reportedly been (see cursor.js).
|
|
70
|
+
*
|
|
71
|
+
* Net path: `<VS Code User dir>/globalStorage/state.vscdb`, table
|
|
72
|
+
* `ItemTable`, one row with `key = 'sourcegraph.cody-ai'` whose `value` is a
|
|
73
|
+
* JSON object containing (among any other keys the extension has ever set)
|
|
74
|
+
* `cody-local-chatHistory-v2` — every chat transcript title, message, and any
|
|
75
|
+
* pasted code/output the user has had Cody see.
|
|
76
|
+
*
|
|
77
|
+
* Deliberately scoped to VS Code only. Cody also ships a JetBrains plugin,
|
|
78
|
+
* but its chat history lives in a fundamentally different place: read
|
|
79
|
+
* directly from the same cody-public-snapshot repo,
|
|
80
|
+
* `jetbrains/src/main/kotlin/com/sourcegraph/cody/history/HistoryService.kt`
|
|
81
|
+
* declares `@State(name = "ChatHistory", storages =
|
|
82
|
+
* [Storage("cody_history.xml")])` at `@Service(Service.Level.PROJECT)` —
|
|
83
|
+
* PROJECT level, meaning one `cody_history.xml` per JetBrains project
|
|
84
|
+
* (typically under that project's own `.idea/` directory), not one file
|
|
85
|
+
* under a single, enumerable user-profile directory the way every other
|
|
86
|
+
* source in this project works. Finding those would mean either scanning the
|
|
87
|
+
* whole filesystem for `.idea/cody_history.xml` or trusting a JetBrains
|
|
88
|
+
* "recent projects" list — neither verified here and both a meaningfully
|
|
89
|
+
* different shape of problem — so it is left out rather than guessed at. See
|
|
90
|
+
* CONTRIBUTING.md.
|
|
91
|
+
*
|
|
92
|
+
* Only the DEFAULT VS Code profile is covered, for the same reason
|
|
93
|
+
* copilot-chat.js in this project already gives for itself: a non-default
|
|
94
|
+
* profile's globalStorage lives under `User/profiles/<profileId>/...`
|
|
95
|
+
* instead of `User/globalStorage` directly, per
|
|
96
|
+
* `IUserDataProfilesService.defaultProfile` in the same VS Code source read
|
|
97
|
+
* above.
|
|
98
|
+
*/
|
|
99
|
+
function vscodeUserDirs() {
|
|
100
|
+
const home = os.homedir();
|
|
101
|
+
const variants = ["Code", "Code - Insiders"];
|
|
102
|
+
if (process.platform === "darwin") {
|
|
103
|
+
return variants.map((v) => path.join(home, "Library", "Application Support", v, "User"));
|
|
104
|
+
}
|
|
105
|
+
if (process.platform === "win32") {
|
|
106
|
+
const appData = process.env.APPDATA || path.join(home, "AppData", "Roaming");
|
|
107
|
+
return variants.map((v) => path.join(appData, v, "User"));
|
|
108
|
+
}
|
|
109
|
+
// Linux and other XDG-following unix platforms.
|
|
110
|
+
const configHome = process.env.XDG_CONFIG_HOME || path.join(home, ".config");
|
|
111
|
+
return variants.map((v) => path.join(configHome, v, "User"));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function id() { return "cody"; }
|
|
115
|
+
function label() { return "Sourcegraph Cody"; }
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Same lazy-require pattern as cursor.js, for the same reason: index.js
|
|
119
|
+
* requires every source unconditionally, so an eager top-level
|
|
120
|
+
* `require("node:sqlite")` would print Node's one-per-process
|
|
121
|
+
* ExperimentalWarning for every user on Node 22.5+, even the large majority
|
|
122
|
+
* who have never installed VS Code at all. See cursor.js's own docstring on
|
|
123
|
+
* getDatabaseSync() for the full reasoning — duplicated here rather than
|
|
124
|
+
* imported, matching this project's "small, self-contained file" convention.
|
|
125
|
+
*/
|
|
126
|
+
const NODE_SQLITE_REQUIREMENT = "needs Node.js 22.5+ (node:sqlite not present in this runtime)";
|
|
127
|
+
let sqliteRequireAttempted = false;
|
|
128
|
+
let DatabaseSync = null;
|
|
129
|
+
|
|
130
|
+
function getDatabaseSync() {
|
|
131
|
+
if (!sqliteRequireAttempted) {
|
|
132
|
+
sqliteRequireAttempted = true;
|
|
133
|
+
try { ({ DatabaseSync } = require("node:sqlite")); }
|
|
134
|
+
catch { DatabaseSync = null; }
|
|
135
|
+
}
|
|
136
|
+
return DatabaseSync;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function anyVscodeUserDirExists() {
|
|
140
|
+
return vscodeUserDirs().some((dir) => {
|
|
141
|
+
try { return fs.statSync(dir).isDirectory(); } catch { return false; }
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function available() {
|
|
146
|
+
// Cheap fs check first, on purpose — see cursor.js's available() for why
|
|
147
|
+
// short-circuiting here matters (skip requiring node:sqlite, and its
|
|
148
|
+
// possible warning, when there is plainly nothing to read).
|
|
149
|
+
return anyVscodeUserDirExists() && Boolean(getDatabaseSync());
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Same optional, additive contract as cursor.js's unavailableReason() — see
|
|
154
|
+
* that file's docstring. Only fires for the one case worth calling out: a VS
|
|
155
|
+
* Code User dir genuinely exists but this Node runtime is too old for
|
|
156
|
+
* node:sqlite, so silently vanishing from "Sources checked" would misread as
|
|
157
|
+
* "VS Code isn't installed," which would be false.
|
|
158
|
+
*/
|
|
159
|
+
function unavailableReason() {
|
|
160
|
+
if (!anyVscodeUserDirExists()) return null;
|
|
161
|
+
if (getDatabaseSync()) return null;
|
|
162
|
+
return `Sourcegraph Cody detected but not scanned — ${NODE_SQLITE_REQUIREMENT}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Same defensive symlink-following stat, duplicated from cursor.js's
|
|
167
|
+
* statIfPresent — see that file's docstring for the full reasoning. A path
|
|
168
|
+
* that doesn't exist yields nothing (normal: e.g. no "Code - Insiders" User
|
|
169
|
+
* dir because Insiders was never installed); a dangling symlink is reported
|
|
170
|
+
* `broken: true` rather than silently skipped.
|
|
171
|
+
*/
|
|
172
|
+
function* statIfPresent(dbPath) {
|
|
173
|
+
let lst;
|
|
174
|
+
try { lst = fs.lstatSync(dbPath); }
|
|
175
|
+
catch { return; }
|
|
176
|
+
|
|
177
|
+
if (lst.isSymbolicLink()) {
|
|
178
|
+
try {
|
|
179
|
+
const st = fs.statSync(dbPath); // follow the link
|
|
180
|
+
if (!st.isFile()) { yield { file: dbPath, broken: true }; return; }
|
|
181
|
+
yield { file: dbPath, mtimeMs: st.mtimeMs, sizeBytes: st.size, broken: false };
|
|
182
|
+
} catch {
|
|
183
|
+
yield { file: dbPath, broken: true }; // dangling symlink
|
|
184
|
+
}
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!lst.isFile()) return; // something unexpected sits at this path — out of scope, not broken
|
|
189
|
+
yield { file: dbPath, mtimeMs: lst.mtimeMs, sizeBytes: lst.size, broken: false };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Yield { file, mtimeMs, sizeBytes, broken } for the default profile's
|
|
194
|
+
* state.vscdb under every VS Code variant this adapter checks (standard +
|
|
195
|
+
* Insiders). Purely a filesystem walk + stat — never opens the database, so
|
|
196
|
+
* it works even in a Node runtime where node:sqlite isn't available (only
|
|
197
|
+
* readLines() actually needs it, same division of labour as cursor.js).
|
|
198
|
+
*/
|
|
199
|
+
function* files() {
|
|
200
|
+
for (const userDir of vscodeUserDirs()) {
|
|
201
|
+
yield* statIfPresent(path.join(userDir, "globalStorage", "state.vscdb"));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Not backed by a real Cody state.vscdb row this tool was tested against (no
|
|
206
|
+
// VS Code install to test with) — see the verification-status note above.
|
|
207
|
+
// Generous backstop against a corrupted/pathological file, same rationale as
|
|
208
|
+
// cursor.js's own MAX_DB_BYTES.
|
|
209
|
+
const MAX_DB_BYTES = 512 * 1024 * 1024;
|
|
210
|
+
const READ_TIMEOUT_MS = 60_000;
|
|
211
|
+
const BUSY_TIMEOUT_MS = 5_000;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Cody's extension id is `sourcegraph.cody-ai` (verified directly against
|
|
215
|
+
* vscode/package.json — see the module docstring). The LIKE clause is a
|
|
216
|
+
* deliberately narrow safety margin, not a guess-widening: it catches a
|
|
217
|
+
* differently-suffixed Sourcegraph Cody extension id (e.g. a possible future
|
|
218
|
+
* `sourcegraph.cody-ai-nightly`-style variant) without sweeping in any other
|
|
219
|
+
* publisher's or extension's state the way reading the whole ItemTable
|
|
220
|
+
* (cursor.js's approach, appropriate there because Cursor owns that entire
|
|
221
|
+
* file) would for a shared, multi-extension file like this one.
|
|
222
|
+
*/
|
|
223
|
+
const CODY_KEY_PATTERN = "sourcegraph.cody%";
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Same storage-class handling as cursor.js's valueToText — see that file's
|
|
227
|
+
* docstring. A PROFILE-scope row here is written via
|
|
228
|
+
* `JSON.stringify(state)` (see module docstring), i.e. always a JS string
|
|
229
|
+
* (TEXT storage class) in every real case this adapter's research found, but
|
|
230
|
+
* the BLOB fallback is kept for parity with cursor.js and cheap insurance
|
|
231
|
+
* against a SQLite storage-class surprise.
|
|
232
|
+
*/
|
|
233
|
+
function valueToText(value) {
|
|
234
|
+
if (typeof value === "string") return value;
|
|
235
|
+
if (value instanceof Uint8Array) return Buffer.from(value).toString("utf-8");
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Read one state.vscdb's Cody-owned row(s) as an array of raw text "lines" —
|
|
241
|
+
* one per matching ItemTable row's decoded value (in practice, at most one:
|
|
242
|
+
* a single row keyed `sourcegraph.cody-ai` holding a JSON object with every
|
|
243
|
+
* globalState key the extension has ever set, `cody-local-chatHistory-v2`
|
|
244
|
+
* included). Returns { lines, status, bytesRead } with the same status
|
|
245
|
+
* vocabulary as every other source in this project.
|
|
246
|
+
*
|
|
247
|
+
* Synchronous node:sqlite, same "no real preemptive timeout possible, so
|
|
248
|
+
* check a wall-clock deadline between statements" approach as cursor.js —
|
|
249
|
+
* see that file's docstring. With at most a handful of matching rows here
|
|
250
|
+
* (unlike cursor.js's tens-of-thousands-of-rows cursorDiskKV table), the
|
|
251
|
+
* per-row yield-to-event-loop machinery is far less likely to matter in
|
|
252
|
+
* practice, but the deadline check is kept for consistency and as insurance
|
|
253
|
+
* against a single pathologically large row's own decode time — the same
|
|
254
|
+
* named, un-bounded asymmetry cursor.js's own docstring admits.
|
|
255
|
+
*/
|
|
256
|
+
async function readLines(file) {
|
|
257
|
+
const DB = getDatabaseSync();
|
|
258
|
+
if (!DB) return { lines: [], status: "failed", bytesRead: 0 };
|
|
259
|
+
|
|
260
|
+
let stat;
|
|
261
|
+
try { stat = fs.statSync(file); }
|
|
262
|
+
catch { return { lines: [], status: "failed", bytesRead: 0 }; }
|
|
263
|
+
if (stat.size > MAX_DB_BYTES) return { lines: [], status: "too-large", bytesRead: 0 };
|
|
264
|
+
|
|
265
|
+
let db;
|
|
266
|
+
try {
|
|
267
|
+
db = new DB(file, { readOnly: true });
|
|
268
|
+
db.exec(`PRAGMA busy_timeout = ${BUSY_TIMEOUT_MS}`);
|
|
269
|
+
} catch {
|
|
270
|
+
// Deleted between files() and this call, a corrupt/non-SQLite file at
|
|
271
|
+
// this path, or VS Code holding a lock this readonly open can't get past
|
|
272
|
+
// within BUSY_TIMEOUT_MS — genuinely "could not read this," not "read it,
|
|
273
|
+
// found nothing." Status "failed" keeps the scan report honest about
|
|
274
|
+
// that difference (CONTRIBUTING.md rule 5).
|
|
275
|
+
return { lines: [], status: "failed", bytesRead: 0 };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
let rows;
|
|
279
|
+
try {
|
|
280
|
+
rows = db.prepare("SELECT value FROM ItemTable WHERE key LIKE ?").iterate(CODY_KEY_PATTERN);
|
|
281
|
+
} catch {
|
|
282
|
+
// ItemTable itself doesn't exist — this file opened fine as SQLite but
|
|
283
|
+
// didn't match the schema this adapter understands, a real "could not
|
|
284
|
+
// extract anything," not "extracted zero real rows."
|
|
285
|
+
try { db.close(); } catch { /* best-effort */ }
|
|
286
|
+
return { lines: [], status: "failed", bytesRead: 0 };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const lines = [];
|
|
290
|
+
let bytesRead = 0;
|
|
291
|
+
const deadline = Date.now() + READ_TIMEOUT_MS;
|
|
292
|
+
let timedOut = false;
|
|
293
|
+
let sawError = false;
|
|
294
|
+
let n = 0;
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
for (const row of rows) {
|
|
298
|
+
const text = valueToText(row.value);
|
|
299
|
+
// One matching row becomes one scanned "line," using the value exactly
|
|
300
|
+
// as VS Code wrote it — not re-parsed/re-stringified — same reasoning
|
|
301
|
+
// as cursor.js: keeps every byte the regexes depend on intact, and
|
|
302
|
+
// sidesteps needing to track Cody's own nested key name
|
|
303
|
+
// (`cody-local-chatHistory-v2`) as it evolves across versions.
|
|
304
|
+
if (text) { lines.push(text); bytesRead += Buffer.byteLength(text, "utf-8"); }
|
|
305
|
+
n++;
|
|
306
|
+
if (n % 500 === 0) {
|
|
307
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
308
|
+
if (Date.now() > deadline) { timedOut = true; break; }
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
} catch {
|
|
312
|
+
// Whatever WAS read before a mid-iteration failure (e.g. a corrupted
|
|
313
|
+
// page) is real content and may contain a real secret — kept, not
|
|
314
|
+
// discarded, same as claude-code.js/cursor.js.
|
|
315
|
+
sawError = true;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
try { db.close(); } catch { /* best-effort close */ }
|
|
319
|
+
|
|
320
|
+
if (sawError && lines.length === 0) return { lines: [], status: "failed", bytesRead };
|
|
321
|
+
if (timedOut || sawError) return { lines, status: lines.length > 0 ? "partial" : "failed", bytesRead };
|
|
322
|
+
return { lines, status: "complete", bytesRead };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
module.exports = { id, label, available, unavailableReason, files, readLines };
|