devrage 0.0.3 → 0.0.4
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/dist/cli.js +96 -16
- package/dist/cli.js.map +4 -4
- package/dist/lib/adapters/index.d.ts.map +1 -1
- package/dist/lib/adapters/index.js +2 -0
- package/dist/lib/adapters/index.js.map +1 -1
- package/dist/lib/adapters/pi.d.ts +3 -0
- package/dist/lib/adapters/pi.d.ts.map +1 -0
- package/dist/lib/adapters/pi.js +107 -0
- package/dist/lib/adapters/pi.js.map +1 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -415,26 +415,105 @@ function* queryUserMessages(db, options) {
|
|
|
415
415
|
}
|
|
416
416
|
}
|
|
417
417
|
|
|
418
|
-
// src/adapters/
|
|
419
|
-
import {
|
|
420
|
-
import {
|
|
418
|
+
// src/adapters/pi.ts
|
|
419
|
+
import { createReadStream as createReadStream3 } from "node:fs";
|
|
420
|
+
import { readdir as readdir5, stat as stat4 } from "node:fs/promises";
|
|
421
|
+
import { createInterface as createInterface3 } from "node:readline";
|
|
421
422
|
import { homedir as homedir6 } from "node:os";
|
|
422
423
|
import { join as join6 } from "node:path";
|
|
424
|
+
var PI_SESSIONS_DIR = join6(homedir6(), ".pi", "agent", "sessions");
|
|
425
|
+
function piAdapter() {
|
|
426
|
+
return {
|
|
427
|
+
name: "pi",
|
|
428
|
+
async *messages(options) {
|
|
429
|
+
yield* walkPiSessions(PI_SESSIONS_DIR, options);
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
async function* walkPiSessions(dir, options, project) {
|
|
434
|
+
let entries;
|
|
435
|
+
try {
|
|
436
|
+
entries = await readdir5(dir);
|
|
437
|
+
} catch {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
for (const entry of entries) {
|
|
441
|
+
const fullPath = join6(dir, entry);
|
|
442
|
+
const entryStat = await stat4(fullPath).catch(() => null);
|
|
443
|
+
if (!entryStat) continue;
|
|
444
|
+
if (entryStat.isDirectory()) {
|
|
445
|
+
yield* walkPiSessions(fullPath, options, project ?? entry);
|
|
446
|
+
} else if (entry.endsWith(".jsonl")) {
|
|
447
|
+
const session = entry.replace(".jsonl", "");
|
|
448
|
+
yield* parsePiJsonl(fullPath, { session, project, since: options?.since });
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
async function* parsePiJsonl(filePath, context) {
|
|
453
|
+
const rl = createInterface3({
|
|
454
|
+
input: createReadStream3(filePath, { encoding: "utf-8" }),
|
|
455
|
+
crlfDelay: Infinity
|
|
456
|
+
});
|
|
457
|
+
let project = context.project;
|
|
458
|
+
for await (const line of rl) {
|
|
459
|
+
if (!line.trim()) continue;
|
|
460
|
+
try {
|
|
461
|
+
const entry = JSON.parse(line);
|
|
462
|
+
if (entry.type === "session") {
|
|
463
|
+
project = entry.cwd ?? project;
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
if (entry.type !== "message") continue;
|
|
467
|
+
const message = entry.message;
|
|
468
|
+
if (!message || message.role !== "user") continue;
|
|
469
|
+
const text = contentToString2(message.content);
|
|
470
|
+
if (!text) continue;
|
|
471
|
+
const timestamp = typeof entry.timestamp === "string" ? entry.timestamp : typeof message.timestamp === "number" ? new Date(message.timestamp).toISOString() : void 0;
|
|
472
|
+
if (context.since && timestamp) {
|
|
473
|
+
const ts = new Date(timestamp);
|
|
474
|
+
if (ts < context.since) continue;
|
|
475
|
+
}
|
|
476
|
+
yield {
|
|
477
|
+
text,
|
|
478
|
+
timestamp,
|
|
479
|
+
session: context.session,
|
|
480
|
+
project
|
|
481
|
+
};
|
|
482
|
+
} catch {
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function contentToString2(content) {
|
|
487
|
+
if (typeof content === "string") return content;
|
|
488
|
+
if (Array.isArray(content)) {
|
|
489
|
+
const parts = content.filter(
|
|
490
|
+
(p) => typeof p === "object" && p !== null && p.type === "text" && typeof p.text === "string"
|
|
491
|
+
).map((p) => p.text);
|
|
492
|
+
return parts.length > 0 ? parts.join(" ") : null;
|
|
493
|
+
}
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// src/adapters/zed.ts
|
|
498
|
+
import { readdir as readdir6, readFile as readFile3 } from "node:fs/promises";
|
|
499
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
500
|
+
import { homedir as homedir7 } from "node:os";
|
|
501
|
+
import { join as join7 } from "node:path";
|
|
423
502
|
function getZedPaths() {
|
|
424
503
|
if (process.platform === "darwin") {
|
|
425
|
-
const base2 =
|
|
504
|
+
const base2 = join7(homedir7(), "Library", "Application Support", "Zed");
|
|
426
505
|
return {
|
|
427
|
-
conversations:
|
|
428
|
-
db:
|
|
506
|
+
conversations: join7(base2, "conversations"),
|
|
507
|
+
db: join7(base2, "db")
|
|
429
508
|
};
|
|
430
509
|
}
|
|
431
|
-
const base =
|
|
432
|
-
process.env["XDG_DATA_HOME"] ??
|
|
510
|
+
const base = join7(
|
|
511
|
+
process.env["XDG_DATA_HOME"] ?? join7(homedir7(), ".local", "share"),
|
|
433
512
|
"zed"
|
|
434
513
|
);
|
|
435
514
|
return {
|
|
436
|
-
conversations:
|
|
437
|
-
db:
|
|
515
|
+
conversations: join7(base, "conversations"),
|
|
516
|
+
db: join7(base, "db")
|
|
438
517
|
};
|
|
439
518
|
}
|
|
440
519
|
function zedAdapter() {
|
|
@@ -451,13 +530,13 @@ async function* parseTextThreads(dir, _options) {
|
|
|
451
530
|
if (!existsSync3(dir)) return;
|
|
452
531
|
let files;
|
|
453
532
|
try {
|
|
454
|
-
files = await
|
|
533
|
+
files = await readdir6(dir);
|
|
455
534
|
} catch {
|
|
456
535
|
return;
|
|
457
536
|
}
|
|
458
537
|
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
459
538
|
for (const file of jsonFiles) {
|
|
460
|
-
const filePath =
|
|
539
|
+
const filePath = join7(dir, file);
|
|
461
540
|
const session = file.replace(".json", "");
|
|
462
541
|
try {
|
|
463
542
|
const raw = await readFile3(filePath, "utf-8");
|
|
@@ -480,7 +559,7 @@ async function* parseAgentThreads(dbDir, _options) {
|
|
|
480
559
|
if (!existsSync3(dbDir)) return;
|
|
481
560
|
let dbFiles;
|
|
482
561
|
try {
|
|
483
|
-
const entries = await
|
|
562
|
+
const entries = await readdir6(dbDir);
|
|
484
563
|
dbFiles = entries.filter((f) => f.endsWith(".db"));
|
|
485
564
|
} catch {
|
|
486
565
|
return;
|
|
@@ -494,7 +573,7 @@ async function* parseAgentThreads(dbDir, _options) {
|
|
|
494
573
|
return;
|
|
495
574
|
}
|
|
496
575
|
for (const dbFile of dbFiles) {
|
|
497
|
-
const dbPath =
|
|
576
|
+
const dbPath = join7(dbDir, dbFile);
|
|
498
577
|
let db;
|
|
499
578
|
try {
|
|
500
579
|
db = new Database(
|
|
@@ -542,6 +621,7 @@ var ADAPTERS = {
|
|
|
542
621
|
opencode: opencodeAdapter,
|
|
543
622
|
amp: ampAdapter,
|
|
544
623
|
cline: clineAdapter,
|
|
624
|
+
pi: piAdapter,
|
|
545
625
|
zed: zedAdapter
|
|
546
626
|
};
|
|
547
627
|
function createAdapter(name) {
|
|
@@ -780,7 +860,7 @@ function parseArgs(args) {
|
|
|
780
860
|
console.log(`devrage scan \u2014 scan sessions for profanity
|
|
781
861
|
|
|
782
862
|
Options:
|
|
783
|
-
--agent, -a <name> Scan only a specific agent (claude, codex, opencode, amp, cline, zed)
|
|
863
|
+
--agent, -a <name> Scan only a specific agent (claude, codex, opencode, amp, cline, pi, zed)
|
|
784
864
|
--since, -s <date> Only scan messages after this date (ISO 8601)
|
|
785
865
|
--help, -h Show this help`);
|
|
786
866
|
process.exit(0);
|
|
@@ -888,7 +968,7 @@ async function main() {
|
|
|
888
968
|
process.exit(0);
|
|
889
969
|
}
|
|
890
970
|
if (command === "--version") {
|
|
891
|
-
console.log("0.0.
|
|
971
|
+
console.log("0.0.4");
|
|
892
972
|
process.exit(0);
|
|
893
973
|
}
|
|
894
974
|
const handler = command ? COMMANDS[command] : void 0;
|
package/dist/cli.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/adapters/amp.ts", "../src/adapters/claude.ts", "../src/adapters/cline.ts", "../src/adapters/codex.ts", "../src/adapters/opencode.ts", "../src/adapters/zed.ts", "../src/adapters/index.ts", "../src/detector/index.ts", "../src/commands/scan.ts", "../src/cli.ts"],
|
|
4
|
-
"sourcesContent": ["import { readdir, readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Adapter, AdapterOptions, Message } from \"./index\";\n\n/**\n * Amp (Sourcegraph) stores threads as JSON files at:\n * ~/.local/share/amp/threads/<thread-id>.json\n *\n * Each file is a JSON object with a `messages` array:\n * { \"messages\": [{ \"role\": \"user\"|\"assistant\", \"content\": \"...\", ... }], \"usageLedger\": {...}, ... }\n *\n * Messages have `role`, `content` (string or array), and optionally a timestamp.\n */\n\nfunction getAmpThreadsDir(): string {\n return join(\n process.env[\"XDG_DATA_HOME\"] ?? join(homedir(), \".local\", \"share\"),\n \"amp\",\n \"threads\",\n );\n}\n\nexport function ampAdapter(): Adapter {\n return {\n name: \"amp\",\n async *messages(options?: AdapterOptions): AsyncGenerator<Message> {\n const threadsDir = getAmpThreadsDir();\n\n let files: string[];\n try {\n files = await readdir(threadsDir);\n } catch {\n return; // Amp not installed or no threads\n }\n\n const jsonFiles = files.filter((f) => f.endsWith(\".json\"));\n\n for (const file of jsonFiles) {\n const filePath = join(threadsDir, file);\n const threadId = file.replace(\".json\", \"\");\n\n try {\n const raw = await readFile(filePath, \"utf-8\");\n const thread = JSON.parse(raw) as AmpThread;\n\n if (!thread.messages || !Array.isArray(thread.messages)) continue;\n\n for (const msg of thread.messages) {\n if (msg.role !== \"user\") continue;\n\n const text = extractText(msg.content);\n if (!text) continue;\n\n const timestamp = msg.timestamp ?? msg.createdAt ?? undefined;\n if (options?.since && timestamp) {\n const ts = new Date(timestamp);\n if (ts < options.since) continue;\n }\n\n yield {\n text,\n timestamp,\n session: threadId,\n };\n }\n } catch {\n // Skip malformed files\n }\n }\n },\n };\n}\n\nfunction extractText(content: unknown): string | null {\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n const parts = content\n .filter(\n (p): p is { type: string; text: string } =>\n typeof p === \"object\" && p !== null && typeof p.text === \"string\",\n )\n .map((p) => p.text);\n return parts.length > 0 ? parts.join(\" \") : null;\n }\n return null;\n}\n\ninterface AmpMessage {\n role?: string;\n content?: unknown;\n timestamp?: string;\n createdAt?: string;\n}\n\ninterface AmpThread {\n messages?: AmpMessage[];\n}\n", "import { createReadStream } from \"node:fs\";\nimport { readdir, stat } from \"node:fs/promises\";\nimport { createInterface } from \"node:readline\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Adapter, AdapterOptions, Message } from \"./index\";\n\n/**\n * Claude Code stores sessions as JSONL files at:\n * ~/.claude/projects/<project-path>/<session-uuid>.jsonl\n *\n * Each line is a JSON object. User messages have:\n * { \"type\": \"human\", \"message\": { \"content\": [...] } }\n * or sometimes:\n * { \"role\": \"user\", \"content\": \"...\" }\n */\n\nconst CLAUDE_DIR = join(homedir(), \".claude\", \"projects\");\n\nexport function claudeAdapter(): Adapter {\n return {\n name: \"claude\",\n async *messages(options?: AdapterOptions): AsyncGenerator<Message> {\n const projectsDir = CLAUDE_DIR;\n\n let projectDirs: string[];\n try {\n projectDirs = await readdir(projectsDir);\n } catch {\n return; // Claude Code not installed or no sessions\n }\n\n for (const projectDir of projectDirs) {\n const projectPath = join(projectsDir, projectDir);\n const projectStat = await stat(projectPath);\n if (!projectStat.isDirectory()) continue;\n\n const entries = await readdir(projectPath);\n const jsonlFiles = entries.filter((f) => f.endsWith(\".jsonl\"));\n\n for (const file of jsonlFiles) {\n const filePath = join(projectPath, file);\n const session = file.replace(\".jsonl\", \"\");\n\n yield* parseClaudeJsonl(filePath, {\n session,\n project: projectDir,\n since: options?.since,\n });\n }\n\n // Also check for subagent JSONL files in session subdirectories\n const subdirs = entries.filter((f) => !f.includes(\".\"));\n for (const subdir of subdirs) {\n const subagentsDir = join(projectPath, subdir, \"subagents\");\n try {\n const subFiles = await readdir(subagentsDir);\n const subJsonl = subFiles.filter((f) => f.endsWith(\".jsonl\"));\n for (const file of subJsonl) {\n yield* parseClaudeJsonl(join(subagentsDir, file), {\n session: `${subdir}/${file.replace(\".jsonl\", \"\")}`,\n project: projectDir,\n since: options?.since,\n });\n }\n } catch {\n // No subagents directory, skip\n }\n }\n }\n },\n };\n}\n\nasync function* parseClaudeJsonl(\n filePath: string,\n context: { session: string; project: string; since?: Date },\n): AsyncGenerator<Message> {\n const rl = createInterface({\n input: createReadStream(filePath, { encoding: \"utf-8\" }),\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n if (!line.trim()) continue;\n\n try {\n const entry = JSON.parse(line) as Record<string, unknown>;\n const text = extractUserText(entry);\n if (!text) continue;\n\n const timestamp = extractTimestamp(entry);\n if (context.since && timestamp) {\n const ts = new Date(timestamp);\n if (ts < context.since) continue;\n }\n\n yield {\n text,\n timestamp: timestamp ?? undefined,\n session: context.session,\n project: context.project,\n };\n } catch {\n // Skip malformed lines\n }\n }\n}\n\nfunction extractUserText(entry: Record<string, unknown>): string | null {\n // Format: { \"type\": \"user\", \"message\": { \"role\": \"user\", \"content\": \"...\" } }\n if (entry[\"type\"] === \"user\") {\n const message = entry[\"message\"] as Record<string, unknown> | undefined;\n if (!message) return null;\n return contentToString(message[\"content\"]);\n }\n\n // Legacy format: { \"type\": \"human\", \"message\": { \"content\": [...] } }\n if (entry[\"type\"] === \"human\") {\n const message = entry[\"message\"] as Record<string, unknown> | undefined;\n if (!message) return null;\n return contentToString(message[\"content\"]);\n }\n\n // Flat format: { \"role\": \"user\", \"content\": \"...\" }\n if (entry[\"role\"] === \"user\") {\n return contentToString(entry[\"content\"]);\n }\n\n return null;\n}\n\nfunction contentToString(content: unknown): string | null {\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n const parts = content\n .filter(\n (p): p is { type: string; text: string } =>\n typeof p === \"object\" && p !== null && p.type === \"text\",\n )\n .map((p) => p.text);\n return parts.length > 0 ? parts.join(\" \") : null;\n }\n return null;\n}\n\nfunction extractTimestamp(entry: Record<string, unknown>): string | null {\n if (typeof entry[\"timestamp\"] === \"string\") return entry[\"timestamp\"];\n if (typeof entry[\"createdAt\"] === \"string\") return entry[\"createdAt\"];\n return null;\n}\n", "import { readdir, readFile, stat } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Adapter, AdapterOptions, Message } from \"./index\";\n\n/**\n * Cline (formerly Claude Dev) stores task history at:\n *\n * VS Code extension:\n * macOS: ~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/tasks/<task-id>/api_conversation_history.json\n * Linux: ~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/tasks/<task-id>/api_conversation_history.json\n * Windows: %APPDATA%/Code/User/globalStorage/saoudrizwan.claude-dev/tasks/<task-id>/api_conversation_history.json\n *\n * Standalone CLI / JetBrains (newer):\n * ~/.cline/data/tasks/<task-id>/api_conversation_history.json\n *\n * Roo Code (fork) uses the same format at:\n * globalStorage/rooveterinaryinc.roo-cline/tasks/<task-id>/api_conversation_history.json\n *\n * Each api_conversation_history.json is a JSON array of messages:\n * [{ \"role\": \"user\"|\"assistant\", \"content\": \"...\" | [{type: \"text\", text: \"...\"}] }]\n */\n\nfunction getClineTaskDirs(): string[] {\n const dirs: string[] = [];\n\n // VS Code extension paths\n const vscodePaths = getVSCodeGlobalStoragePaths();\n const extensionIds = [\"saoudrizwan.claude-dev\", \"rooveterinaryinc.roo-cline\"];\n\n for (const basePath of vscodePaths) {\n for (const extId of extensionIds) {\n const tasksDir = join(basePath, extId, \"tasks\");\n if (existsSync(tasksDir)) dirs.push(tasksDir);\n }\n }\n\n // Standalone CLI path\n const clineStandalone = join(homedir(), \".cline\", \"data\", \"tasks\");\n if (existsSync(clineStandalone)) dirs.push(clineStandalone);\n\n return dirs;\n}\n\nfunction getVSCodeGlobalStoragePaths(): string[] {\n const paths: string[] = [];\n\n if (process.platform === \"darwin\") {\n paths.push(\n join(homedir(), \"Library\", \"Application Support\", \"Code\", \"User\", \"globalStorage\"),\n join(homedir(), \"Library\", \"Application Support\", \"Code - Insiders\", \"User\", \"globalStorage\"),\n join(homedir(), \"Library\", \"Application Support\", \"Cursor\", \"User\", \"globalStorage\"),\n );\n } else if (process.platform === \"linux\") {\n const configBase = process.env[\"XDG_CONFIG_HOME\"] ?? join(homedir(), \".config\");\n paths.push(\n join(configBase, \"Code\", \"User\", \"globalStorage\"),\n join(configBase, \"Code - Insiders\", \"User\", \"globalStorage\"),\n join(configBase, \"Cursor\", \"User\", \"globalStorage\"),\n );\n } else {\n // Windows\n const appData = process.env[\"APPDATA\"] ?? join(homedir(), \"AppData\", \"Roaming\");\n paths.push(\n join(appData, \"Code\", \"User\", \"globalStorage\"),\n join(appData, \"Code - Insiders\", \"User\", \"globalStorage\"),\n join(appData, \"Cursor\", \"User\", \"globalStorage\"),\n );\n }\n\n return paths;\n}\n\nexport function clineAdapter(): Adapter {\n return {\n name: \"cline\",\n async *messages(options?: AdapterOptions): AsyncGenerator<Message> {\n const taskDirs = getClineTaskDirs();\n\n for (const tasksDir of taskDirs) {\n let taskIds: string[];\n try {\n taskIds = await readdir(tasksDir);\n } catch {\n continue;\n }\n\n for (const taskId of taskIds) {\n const taskDir = join(tasksDir, taskId);\n const taskStat = await stat(taskDir).catch(() => null);\n if (!taskStat?.isDirectory()) continue;\n\n const historyFile = join(taskDir, \"api_conversation_history.json\");\n\n try {\n const raw = await readFile(historyFile, \"utf-8\");\n const messages = JSON.parse(raw) as ClineMessage[];\n\n if (!Array.isArray(messages)) continue;\n\n for (const msg of messages) {\n if (msg.role !== \"user\") continue;\n\n const text = extractText(msg.content);\n if (!text) continue;\n\n // Cline doesn't store per-message timestamps in the conversation file\n // Use the task metadata file for approximate timing\n const timestamp = msg.ts ?? undefined;\n if (options?.since && timestamp) {\n const ts = new Date(timestamp);\n if (ts < options.since) continue;\n }\n\n yield {\n text,\n session: taskId,\n };\n }\n } catch {\n // Skip tasks without history or malformed files\n }\n }\n }\n },\n };\n}\n\nfunction extractText(content: unknown): string | null {\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n const parts = content\n .filter(\n (p): p is { type: string; text: string } =>\n typeof p === \"object\" &&\n p !== null &&\n p.type === \"text\" &&\n typeof p.text === \"string\",\n )\n .map((p) => p.text);\n return parts.length > 0 ? parts.join(\" \") : null;\n }\n return null;\n}\n\ninterface ClineMessage {\n role?: string;\n content?: unknown;\n ts?: string;\n}\n", "import { createReadStream } from \"node:fs\";\nimport { readdir, stat } from \"node:fs/promises\";\nimport { createInterface } from \"node:readline\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Adapter, AdapterOptions, Message } from \"./index\";\n\n/**\n * Codex stores sessions as JSONL files at:\n * ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl\n *\n * Each line is JSON with structure:\n * { \"timestamp\": \"...\", \"type\": \"response_item\", \"payload\": { \"type\": \"message\", \"role\": \"user\", \"content\": [...] } }\n *\n * User messages have payload.role === \"user\" and content is an array of\n * { \"type\": \"input_text\", \"text\": \"...\" }\n *\n * We skip messages that are just environment context injections.\n */\n\nconst CODEX_SESSIONS_DIR = join(homedir(), \".codex\", \"sessions\");\n\nexport function codexAdapter(): Adapter {\n return {\n name: \"codex\",\n async *messages(options?: AdapterOptions): AsyncGenerator<Message> {\n yield* walkCodexSessions(CODEX_SESSIONS_DIR, options);\n },\n };\n}\n\nasync function* walkCodexSessions(\n dir: string,\n options?: AdapterOptions,\n): AsyncGenerator<Message> {\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch {\n return; // Codex not installed or no sessions\n }\n\n for (const entry of entries) {\n const fullPath = join(dir, entry);\n const entryStat = await stat(fullPath);\n\n if (entryStat.isDirectory()) {\n yield* walkCodexSessions(fullPath, options);\n } else if (entry.endsWith(\".jsonl\")) {\n const session = entry.replace(\".jsonl\", \"\");\n yield* parseCodexJsonl(fullPath, { session, since: options?.since });\n }\n }\n}\n\nasync function* parseCodexJsonl(\n filePath: string,\n context: { session: string; since?: Date },\n): AsyncGenerator<Message> {\n const rl = createInterface({\n input: createReadStream(filePath, { encoding: \"utf-8\" }),\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n if (!line.trim()) continue;\n\n try {\n const entry = JSON.parse(line) as CodexEntry;\n\n // Only care about response_item entries with user messages\n if (entry.type !== \"response_item\") continue;\n\n const payload = entry.payload;\n if (!payload || payload.role !== \"user\") continue;\n\n const text = extractText(payload.content);\n if (!text) continue;\n\n // Skip environment context injections (they start with <environment_context>)\n if (text.startsWith(\"<environment_context>\")) continue;\n // Skip permission/sandbox instructions\n if (text.startsWith(\"<permissions instructions>\")) continue;\n\n if (context.since && entry.timestamp) {\n const ts = new Date(entry.timestamp);\n if (ts < context.since) continue;\n }\n\n yield {\n text,\n timestamp: entry.timestamp,\n session: context.session,\n };\n } catch {\n // Skip malformed lines\n }\n }\n}\n\nfunction extractText(content: unknown): string | null {\n if (!Array.isArray(content)) return null;\n\n const parts = content\n .filter(\n (p): p is { type: string; text: string } =>\n typeof p === \"object\" &&\n p !== null &&\n p.type === \"input_text\" &&\n typeof p.text === \"string\",\n )\n .map((p) => p.text);\n\n return parts.length > 0 ? parts.join(\" \") : null;\n}\n\ninterface CodexEntry {\n timestamp?: string;\n type: string;\n payload?: {\n type?: string;\n role?: string;\n content?: unknown;\n };\n}\n", "import { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Adapter, AdapterOptions, Message } from \"./index\";\n\n/**\n * OpenCode stores sessions in a SQLite database at:\n * ~/.local/share/opencode/opencode.db\n *\n * Schema:\n * message: { id, session_id, time_created (epoch ms), time_updated, data (JSON) }\n * part: { id, message_id, session_id, time_created, time_updated, data (JSON) }\n *\n * message.data: { \"role\": \"user\"|\"assistant\", \"time\": {...}, \"agent\": \"...\", ... }\n * part.data: { \"type\": \"text\", \"text\": \"the user's message\" }\n *\n * User messages have role=\"user\" in message.data. The actual text content is in\n * the associated part rows where part.data.type === \"text\".\n */\n\nfunction getOpencodeDatabasePath(): string | null {\n // macOS: ~/.local/share/opencode/opencode.db (despite XDG, this is where it actually lives)\n const xdgPath = join(\n process.env[\"XDG_DATA_HOME\"] ?? join(homedir(), \".local\", \"share\"),\n \"opencode\",\n \"opencode.db\",\n );\n if (existsSync(xdgPath)) return xdgPath;\n\n // macOS Application Support fallback\n if (process.platform === \"darwin\") {\n const macPath = join(\n homedir(),\n \"Library\",\n \"Application Support\",\n \"opencode\",\n \"opencode.db\",\n );\n if (existsSync(macPath)) return macPath;\n }\n\n return null;\n}\n\nexport function opencodeAdapter(): Adapter {\n return {\n name: \"opencode\",\n async *messages(options?: AdapterOptions): AsyncGenerator<Message> {\n const dbPath = getOpencodeDatabasePath();\n if (!dbPath) return;\n\n // Dynamic import so the CLI doesn't crash if better-sqlite3 isn't available\n let db: import(\"better-sqlite3\").Database;\n try {\n const BetterSqlite3 = await import(\"better-sqlite3\");\n const Ctor = BetterSqlite3.default ?? BetterSqlite3;\n db = new (Ctor as unknown as new (...args: unknown[]) => import(\"better-sqlite3\").Database)(dbPath, { readonly: true });\n } catch {\n console.warn(\n \"devrage: better-sqlite3 not available, skipping OpenCode sessions\",\n );\n return;\n }\n\n try {\n yield* queryUserMessages(db, options);\n } finally {\n db.close();\n }\n },\n };\n}\n\nfunction* queryUserMessages(\n db: import(\"better-sqlite3\").Database,\n options?: AdapterOptions,\n): Generator<Message> {\n // Query: join message + part, filter to user role and text parts\n let query = `\n SELECT\n m.session_id,\n m.time_created,\n json_extract(p.data, '$.text') as text\n FROM message m\n JOIN part p ON p.message_id = m.id\n WHERE json_extract(m.data, '$.role') = 'user'\n AND json_extract(p.data, '$.type') = 'text'\n `;\n\n if (options?.since) {\n const sinceMs = options.since.getTime();\n query += ` AND m.time_created >= ${sinceMs}`;\n }\n\n query += ` ORDER BY m.time_created ASC`;\n\n const rows = db.prepare(query).all() as {\n session_id: string;\n time_created: number;\n text: string;\n }[];\n\n for (const row of rows) {\n if (!row.text || !row.text.trim()) continue;\n\n yield {\n text: row.text,\n timestamp: new Date(row.time_created).toISOString(),\n session: row.session_id,\n };\n }\n}\n", "import { readdir, readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Adapter, AdapterOptions, Message } from \"./index\";\n\n/**\n * Zed stores AI conversations in two places:\n *\n * 1. Text threads (older assistant panel):\n * ~/.local/share/zed/conversations/*.json (Linux)\n * ~/Library/Application Support/Zed/conversations/*.json (macOS)\n * These are markdown-like JSON with messages.\n *\n * 2. Agent threads (newer):\n * Stored in SQLite at ~/.local/share/zed/db (Linux)\n * or ~/Library/Application Support/Zed/db (macOS)\n * We'd need to query this, but the schema isn't well documented.\n *\n * We support the text thread JSON files for now, and the SQLite agent\n * threads when better-sqlite3 is available.\n */\n\nfunction getZedPaths(): { conversations: string; db: string } {\n if (process.platform === \"darwin\") {\n const base = join(homedir(), \"Library\", \"Application Support\", \"Zed\");\n return {\n conversations: join(base, \"conversations\"),\n db: join(base, \"db\"),\n };\n }\n // Linux / others\n const base = join(\n process.env[\"XDG_DATA_HOME\"] ?? join(homedir(), \".local\", \"share\"),\n \"zed\",\n );\n return {\n conversations: join(base, \"conversations\"),\n db: join(base, \"db\"),\n };\n}\n\nexport function zedAdapter(): Adapter {\n return {\n name: \"zed\",\n async *messages(options?: AdapterOptions): AsyncGenerator<Message> {\n const paths = getZedPaths();\n\n // 1. Parse text thread JSON conversations\n yield* parseTextThreads(paths.conversations, options);\n\n // 2. Try SQLite agent threads\n yield* parseAgentThreads(paths.db, options);\n },\n };\n}\n\nasync function* parseTextThreads(\n dir: string,\n _options?: AdapterOptions,\n): AsyncGenerator<Message> {\n if (!existsSync(dir)) return;\n\n let files: string[];\n try {\n files = await readdir(dir);\n } catch {\n return;\n }\n\n const jsonFiles = files.filter((f) => f.endsWith(\".json\"));\n\n for (const file of jsonFiles) {\n const filePath = join(dir, file);\n const session = file.replace(\".json\", \"\");\n\n try {\n const raw = await readFile(filePath, \"utf-8\");\n const conversation = JSON.parse(raw) as ZedConversation;\n\n if (!conversation.messages || !Array.isArray(conversation.messages)) continue;\n\n for (const msg of conversation.messages) {\n if (msg.role !== \"user\") continue;\n\n const text = typeof msg.content === \"string\" ? msg.content : null;\n if (!text) continue;\n\n yield {\n text,\n session,\n };\n }\n } catch {\n // Skip malformed files\n }\n }\n}\n\nasync function* parseAgentThreads(\n dbDir: string,\n _options?: AdapterOptions,\n): AsyncGenerator<Message> {\n // Zed uses a directory of SQLite databases. The main one is typically\n // a file like 0-dev.db or similar inside the db/ directory.\n if (!existsSync(dbDir)) return;\n\n let dbFiles: string[];\n try {\n const entries = await readdir(dbDir);\n dbFiles = entries.filter((f) => f.endsWith(\".db\"));\n } catch {\n return;\n }\n\n if (dbFiles.length === 0) return;\n\n let Database: unknown;\n try {\n const mod = await import(\"better-sqlite3\");\n Database = mod.default ?? mod;\n } catch {\n return; // better-sqlite3 not available\n }\n\n for (const dbFile of dbFiles) {\n const dbPath = join(dbDir, dbFile);\n let db: import(\"better-sqlite3\").Database;\n\n try {\n db = new (Database as new (...args: unknown[]) => import(\"better-sqlite3\").Database)(\n dbPath,\n { readonly: true },\n );\n } catch {\n continue; // Can't open this DB\n }\n\n try {\n // Check if this DB has a threads/messages table\n const tables = db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table'\")\n .all() as { name: string }[];\n const tableNames = tables.map((t) => t.name);\n\n // Look for message-like tables (Zed's schema may vary by version)\n const msgTable = tableNames.find(\n (t) => t === \"messages\" || t === \"thread_messages\" || t.includes(\"message\"),\n );\n\n if (!msgTable) {\n db.close();\n continue;\n }\n\n const columns = db.prepare(`PRAGMA table_info(\"${msgTable}\")`).all() as {\n name: string;\n }[];\n const colNames = columns.map((c) => c.name);\n\n const hasRole = colNames.includes(\"role\");\n\n if (!hasRole) {\n db.close();\n continue;\n }\n\n const contentCol = colNames.includes(\"content\")\n ? \"content\"\n : colNames.includes(\"body\")\n ? \"body\"\n : \"text\";\n\n let query = `SELECT \"${contentCol}\" as text FROM \"${msgTable}\" WHERE role = 'user'`;\n\n const rows = db.prepare(query).all() as { text: string }[];\n for (const row of rows) {\n if (!row.text?.trim()) continue;\n yield { text: row.text };\n }\n } catch {\n // Schema mismatch or other error\n } finally {\n db.close();\n }\n }\n}\n\ninterface ZedConversation {\n messages?: { role?: string; content?: unknown }[];\n}\n", "import { ampAdapter } from \"./amp\";\nimport { claudeAdapter } from \"./claude\";\nimport { clineAdapter } from \"./cline\";\nimport { codexAdapter } from \"./codex\";\nimport { opencodeAdapter } from \"./opencode\";\nimport { zedAdapter } from \"./zed\";\n\nexport interface Message {\n text: string;\n timestamp?: string;\n session?: string;\n project?: string;\n}\n\nexport interface Adapter {\n name: string;\n /** Discover and yield all user messages from local session storage */\n messages(options?: AdapterOptions): AsyncGenerator<Message>;\n}\n\nexport interface AdapterOptions {\n since?: Date;\n}\n\nconst ADAPTERS: Record<string, () => Adapter> = {\n claude: claudeAdapter,\n codex: codexAdapter,\n opencode: opencodeAdapter,\n amp: ampAdapter,\n cline: clineAdapter,\n zed: zedAdapter,\n};\n\nexport function createAdapter(name: string): Adapter {\n const factory = ADAPTERS[name];\n if (!factory) {\n throw new Error(\n `unknown adapter: ${name} (available: ${Object.keys(ADAPTERS).join(\", \")})`,\n );\n }\n return factory();\n}\n\nexport function allAdapters(): Adapter[] {\n return Object.values(ADAPTERS).map((f) => f());\n}\n", "export interface DetectionResult {\n /** Total swear words found in the text */\n count: number;\n /** Individual matches */\n matches: Match[];\n}\n\nexport interface Match {\n word: string;\n index: number;\n severity: Severity;\n group: string;\n}\n\nexport type Severity = \"mild\" | \"moderate\" | \"strong\";\n\ninterface WordDef {\n word: string;\n severity: Severity;\n group: string;\n}\n\n/**\n * Core wordlist: canonical forms, conjugations, compound words, and common typos.\n * Grouped by root word for reporting rollup.\n *\n * Sources:\n * - swearjar npm (en_US.json) for compound words\n * - Manual typo variants based on common keyboard transpositions\n */\nconst WORDLIST: WordDef[] = [\n // === FUCK family (strong) ===\n // Canonical forms\n { word: \"fuck\", severity: \"strong\", group: \"fuck\" },\n { word: \"fucking\", severity: \"strong\", group: \"fuck\" },\n { word: \"fucked\", severity: \"strong\", group: \"fuck\" },\n { word: \"fucker\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuckin\", severity: \"strong\", group: \"fuck\" },\n { word: \"fucks\", severity: \"strong\", group: \"fuck\" },\n // Compound words\n { word: \"motherfucker\", severity: \"strong\", group: \"fuck\" },\n { word: \"motherfucking\", severity: \"strong\", group: \"fuck\" },\n { word: \"mothafucka\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuckup\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuckoff\", severity: \"strong\", group: \"fuck\" },\n { word: \"clusterfuck\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuckwit\", severity: \"strong\", group: \"fuck\" },\n { word: \"fucktard\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuckface\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuckhead\", severity: \"strong\", group: \"fuck\" },\n // Typos \u2014 transpositions\n { word: \"fukc\", severity: \"strong\", group: \"fuck\" },\n { word: \"fukcing\", severity: \"strong\", group: \"fuck\" },\n { word: \"fukced\", severity: \"strong\", group: \"fuck\" },\n { word: \"fukcer\", severity: \"strong\", group: \"fuck\" },\n { word: \"fcuk\", severity: \"strong\", group: \"fuck\" },\n { word: \"fcuking\", severity: \"strong\", group: \"fuck\" },\n { word: \"fcuked\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuk\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuking\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuked\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuker\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuxk\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuxking\", severity: \"strong\", group: \"fuck\" },\n\n // === SHIT family (strong) ===\n { word: \"shit\", severity: \"strong\", group: \"shit\" },\n { word: \"shitty\", severity: \"strong\", group: \"shit\" },\n { word: \"shitting\", severity: \"strong\", group: \"shit\" },\n { word: \"shits\", severity: \"strong\", group: \"shit\" },\n { word: \"shitted\", severity: \"strong\", group: \"shit\" },\n // Compound words\n { word: \"bullshit\", severity: \"strong\", group: \"shit\" },\n { word: \"horseshit\", severity: \"strong\", group: \"shit\" },\n { word: \"dipshit\", severity: \"strong\", group: \"shit\" },\n { word: \"shitshow\", severity: \"strong\", group: \"shit\" },\n { word: \"shithead\", severity: \"strong\", group: \"shit\" },\n { word: \"shithole\", severity: \"strong\", group: \"shit\" },\n { word: \"shitface\", severity: \"strong\", group: \"shit\" },\n { word: \"shitfaced\", severity: \"strong\", group: \"shit\" },\n { word: \"shitstain\", severity: \"strong\", group: \"shit\" },\n { word: \"shitbag\", severity: \"strong\", group: \"shit\" },\n // Typos\n { word: \"hsit\", severity: \"strong\", group: \"shit\" },\n { word: \"siht\", severity: \"strong\", group: \"shit\" },\n { word: \"shti\", severity: \"strong\", group: \"shit\" },\n { word: \"sjit\", severity: \"strong\", group: \"shit\" },\n { word: \"shjt\", severity: \"strong\", group: \"shit\" },\n { word: \"bulshit\", severity: \"strong\", group: \"shit\" },\n { word: \"bullsht\", severity: \"strong\", group: \"shit\" },\n\n // === ASS family (moderate) ===\n { word: \"ass\", severity: \"moderate\", group: \"ass\" },\n { word: \"asses\", severity: \"moderate\", group: \"ass\" },\n // Compound words (these are strong)\n { word: \"asshole\", severity: \"strong\", group: \"ass\" },\n { word: \"assholes\", severity: \"strong\", group: \"ass\" },\n { word: \"jackass\", severity: \"strong\", group: \"ass\" },\n { word: \"dumbass\", severity: \"strong\", group: \"ass\" },\n { word: \"fatass\", severity: \"moderate\", group: \"ass\" },\n { word: \"asshat\", severity: \"strong\", group: \"ass\" },\n { word: \"asswipe\", severity: \"strong\", group: \"ass\" },\n { word: \"badass\", severity: \"mild\", group: \"ass\" },\n\n // === DAMN family (moderate) ===\n { word: \"damn\", severity: \"moderate\", group: \"damn\" },\n { word: \"damned\", severity: \"moderate\", group: \"damn\" },\n { word: \"damnit\", severity: \"moderate\", group: \"damn\" },\n { word: \"dammit\", severity: \"moderate\", group: \"damn\" },\n { word: \"goddamn\", severity: \"moderate\", group: \"damn\" },\n { word: \"goddamnit\", severity: \"moderate\", group: \"damn\" },\n { word: \"goddammit\", severity: \"moderate\", group: \"damn\" },\n\n // === BITCH family (strong) ===\n { word: \"bitch\", severity: \"strong\", group: \"bitch\" },\n { word: \"bitches\", severity: \"strong\", group: \"bitch\" },\n { word: \"bitching\", severity: \"strong\", group: \"bitch\" },\n { word: \"bitchy\", severity: \"strong\", group: \"bitch\" },\n { word: \"bitchass\", severity: \"strong\", group: \"bitch\" },\n\n // === BASTARD (strong) ===\n { word: \"bastard\", severity: \"strong\", group: \"bastard\" },\n { word: \"bastards\", severity: \"strong\", group: \"bastard\" },\n\n // === PISS family (moderate) ===\n { word: \"piss\", severity: \"moderate\", group: \"piss\" },\n { word: \"pissed\", severity: \"moderate\", group: \"piss\" },\n { word: \"pissing\", severity: \"moderate\", group: \"piss\" },\n { word: \"pissoff\", severity: \"moderate\", group: \"piss\" },\n\n // === DICK (moderate) ===\n { word: \"dick\", severity: \"moderate\", group: \"dick\" },\n { word: \"dickhead\", severity: \"strong\", group: \"dick\" },\n\n // === CRAP (moderate) ===\n { word: \"crap\", severity: \"moderate\", group: \"crap\" },\n { word: \"crappy\", severity: \"moderate\", group: \"crap\" },\n { word: \"crapping\", severity: \"moderate\", group: \"crap\" },\n\n // === HELL (mild) ===\n { word: \"hell\", severity: \"mild\", group: \"hell\" },\n\n // === Abbreviations (mild) ===\n { word: \"wtf\", severity: \"mild\", group: \"wtf\" },\n { word: \"stfu\", severity: \"mild\", group: \"stfu\" },\n { word: \"lmfao\", severity: \"mild\", group: \"lmfao\" },\n { word: \"lmao\", severity: \"mild\", group: \"lmao\" },\n\n // === CUNT (strong) ===\n { word: \"cunt\", severity: \"strong\", group: \"cunt\" },\n { word: \"cunts\", severity: \"strong\", group: \"cunt\" },\n];\n\n/**\n * Normalize text before matching:\n * 1. Collapse repeated characters (3+ of the same char \u2192 2)\n * e.g. \"fuuuuck\" \u2192 \"fuuck\", \"shiiiiit\" \u2192 \"shiit\"\n * This lets \"fuuuuck\" match against \"fuck\" after the regex runs,\n * because the pattern also includes \"fuuck\" style intermediates.\n *\n * Actually \u2014 better approach: collapse ALL runs of 2+ to 1 for matching\n * purposes, while keeping the original text for position tracking.\n * e.g. \"fuuuuck\" \u2192 \"fuck\", \"shiiiit\" \u2192 \"shit\"\n * This directly normalizes to the root word.\n */\nfunction collapseRepeats(text: string): string {\n return text.replace(/(.)\\1+/g, \"$1\");\n}\n\n/**\n * Build the detection regex from the wordlist.\n * Sort longer words first so \"motherfucker\" matches before \"fuck\".\n */\nfunction buildPattern(words: WordDef[]): RegExp {\n const sorted = [...words].sort((a, b) => b.word.length - a.word.length);\n const pattern = sorted.map((w) => w.word).join(\"|\");\n return new RegExp(`\\\\b(${pattern})\\\\b`, \"gi\");\n}\n\nconst DEFAULT_PATTERN = buildPattern(WORDLIST);\nconst WORD_MAP = new Map(WORDLIST.map((w) => [w.word.toLowerCase(), w]));\n\n/**\n * Detect profanity in a string.\n *\n * Runs detection in two passes:\n * 1. Direct match on original text (preserves positions)\n * 2. Match on repeat-collapsed text (catches fuuuuck, shiiiiit, etc.)\n */\nexport function detect(text: string): DetectionResult {\n const matches: Match[] = [];\n const seen = new Set<number>(); // track original-text positions we've already matched\n\n // Pass 1: direct match on original (lowercase) text\n runPattern(text, text.toLowerCase(), matches, seen);\n\n // Pass 2: match on collapsed text to catch repeated chars\n const collapsed = collapseRepeats(text.toLowerCase());\n if (collapsed !== text.toLowerCase()) {\n runPattern(text, collapsed, matches, seen);\n }\n\n return { count: matches.length, matches };\n}\n\nfunction runPattern(\n _originalText: string,\n searchText: string,\n matches: Match[],\n seen: Set<number>,\n): void {\n DEFAULT_PATTERN.lastIndex = 0;\n\n let match: RegExpExecArray | null;\n while ((match = DEFAULT_PATTERN.exec(searchText)) !== null) {\n if (seen.has(match.index)) continue;\n\n const word = match[0].toLowerCase();\n const entry = WORD_MAP.get(word);\n if (!entry) continue;\n\n seen.add(match.index);\n matches.push({\n word,\n index: match.index,\n severity: entry.severity,\n group: entry.group,\n });\n }\n}\n\n/**\n * Create a custom detector with additional words.\n */\nexport function createDetector(\n extraWords?: WordDef[],\n): (text: string) => DetectionResult {\n const allWords = extraWords ? [...WORDLIST, ...extraWords] : WORDLIST;\n const pattern = buildPattern(allWords);\n const wordMap = new Map(allWords.map((w) => [w.word.toLowerCase(), w]));\n\n return (text: string): DetectionResult => {\n const matches: Match[] = [];\n const seen = new Set<number>();\n\n const lower = text.toLowerCase();\n pattern.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(lower)) !== null) {\n if (seen.has(match.index)) continue;\n const word = match[0].toLowerCase();\n const entry = wordMap.get(word);\n if (!entry) continue;\n seen.add(match.index);\n matches.push({ word, index: match.index, severity: entry.severity, group: entry.group });\n }\n\n const collapsed = collapseRepeats(lower);\n if (collapsed !== lower) {\n pattern.lastIndex = 0;\n while ((match = pattern.exec(collapsed)) !== null) {\n if (seen.has(match.index)) continue;\n const word = match[0].toLowerCase();\n const entry = wordMap.get(word);\n if (!entry) continue;\n seen.add(match.index);\n matches.push({ word, index: match.index, severity: entry.severity, group: entry.group });\n }\n }\n\n return { count: matches.length, matches };\n };\n}\n\nexport type { WordDef as WordEntry };\n", "import { allAdapters, createAdapter } from \"../adapters/index\";\nimport { detect } from \"../detector/index\";\n\n// ANSI color helpers \u2014 no dependencies needed\nconst c = {\n reset: \"\\x1b[0m\",\n bold: \"\\x1b[1m\",\n dim: \"\\x1b[2m\",\n red: \"\\x1b[31m\",\n green: \"\\x1b[32m\",\n yellow: \"\\x1b[33m\",\n blue: \"\\x1b[34m\",\n magenta: \"\\x1b[35m\",\n cyan: \"\\x1b[36m\",\n white: \"\\x1b[37m\",\n gray: \"\\x1b[90m\",\n};\n\nconst SPINNER_MESSAGES = [\n \"Tallying the damage\",\n \"Reviewing your outbursts\",\n \"Judging your vocabulary\",\n \"Computing your shame\",\n \"Cataloging the profanity\",\n \"Measuring your frustration\",\n \"Assessing the verbal carnage\",\n \"Quantifying your displeasure\",\n \"Auditing your language\",\n \"Tabulating regrets\",\n];\n\nfunction createSpinner() {\n let messageIdx = 0;\n let dotCount = 0;\n let timer: ReturnType<typeof setInterval> | null = null;\n\n return {\n start() {\n messageIdx = Math.floor(Math.random() * SPINNER_MESSAGES.length);\n timer = setInterval(() => {\n dotCount = (dotCount + 1) % 4;\n const msg = SPINNER_MESSAGES[messageIdx % SPINNER_MESSAGES.length];\n const dots = \".\".repeat(dotCount || 1);\n process.stdout.write(\n `\\r ${c.dim}${msg}${dots}${c.reset} `,\n );\n }, 300);\n },\n update() {\n messageIdx++;\n },\n stop() {\n if (timer) {\n clearInterval(timer);\n timer = null;\n }\n process.stdout.write(\"\\r\" + \" \".repeat(60) + \"\\r\");\n },\n };\n}\n\ninterface ScanOptions {\n agent?: string;\n since?: Date;\n}\n\nfunction parseArgs(args: string[]): ScanOptions {\n const options: ScanOptions = {};\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg === \"--agent\" || arg === \"-a\") {\n options.agent = args[++i];\n } else if (arg === \"--since\" || arg === \"-s\") {\n const val = args[++i];\n if (val) {\n options.since = new Date(val);\n if (isNaN(options.since.getTime())) {\n console.error(`invalid date: ${val}`);\n process.exit(1);\n }\n }\n } else if (arg === \"--help\" || arg === \"-h\") {\n console.log(`devrage scan \u2014 scan sessions for profanity\n\nOptions:\n --agent, -a <name> Scan only a specific agent (claude, codex, opencode, amp, cline, zed)\n --since, -s <date> Only scan messages after this date (ISO 8601)\n --help, -h Show this help`);\n process.exit(0);\n }\n }\n\n return options;\n}\n\nexport async function scan(args: string[]): Promise<void> {\n const options = parseArgs(args);\n\n const adapters = options.agent\n ? [createAdapter(options.agent)]\n : allAdapters();\n\n const spinner = createSpinner();\n spinner.start();\n\n const groupTally: Record<string, number> = {};\n const variantTally: Record<string, Record<string, number>> = {};\n\n let totalMessages = 0;\n let totalSwears = 0;\n const perAgent: Record<string, { messages: number; swears: number }> = {};\n\n for (const adapter of adapters) {\n let agentMessages = 0;\n let agentSwears = 0;\n spinner.update();\n\n for await (const message of adapter.messages({ since: options.since })) {\n totalMessages++;\n agentMessages++;\n\n const result = detect(message.text);\n if (result.count > 0) {\n totalSwears += result.count;\n agentSwears += result.count;\n\n for (const match of result.matches) {\n groupTally[match.group] = (groupTally[match.group] ?? 0) + 1;\n\n const variants = (variantTally[match.group] ??= {});\n variants[match.word] = (variants[match.word] ?? 0) + 1;\n }\n }\n }\n\n if (agentMessages > 0) {\n perAgent[adapter.name] = { messages: agentMessages, swears: agentSwears };\n }\n }\n\n spinner.stop();\n\n // Report\n console.log(\"\");\n console.log(` ${c.bold}${c.red}devrage${c.reset} ${c.dim}report${c.reset}`);\n console.log(` ${c.dim}${\"\u2500\".repeat(30)}${c.reset}`);\n console.log(\"\");\n console.log(` ${c.dim}messages scanned${c.reset} ${c.bold}${totalMessages}${c.reset}`);\n console.log(` ${c.dim}total swears${c.reset} ${c.bold}${c.red}${totalSwears}${c.reset}`);\n\n const activeAgents = Object.entries(perAgent);\n if (activeAgents.length > 1) {\n console.log(\"\");\n console.log(` ${c.bold}by agent${c.reset}`);\n for (const [name, stats] of activeAgents) {\n const rate = ((stats.swears / stats.messages) * 100).toFixed(1);\n console.log(\n ` ${c.cyan}${name.padEnd(10)}${c.reset} ${c.bold}${String(stats.swears).padStart(4)}${c.reset} ${c.dim}in ${stats.messages} messages (${rate}%)${c.reset}`,\n );\n }\n }\n\n if (totalSwears > 0) {\n const sorted = Object.entries(groupTally).sort(([, a], [, b]) => b - a);\n console.log(\"\");\n console.log(` ${c.bold}top words${c.reset}`);\n for (const [group, count] of sorted.slice(0, 10)) {\n const variants = variantTally[group] ?? {};\n const variantList = Object.entries(variants)\n .sort(([, a], [, b]) => b - a)\n .filter(([v]) => v !== group)\n .slice(0, 15)\n .map(([v, cnt]) => `${c.dim}${v}${c.reset} ${cnt}`)\n .join(`${c.dim},${c.reset} `);\n const suffix = variantList ? ` ${c.dim}(${c.reset}${variantList}${c.dim})${c.reset}` : \"\";\n console.log(\n ` ${c.yellow}${group.padEnd(12)}${c.reset} ${c.bold}${String(count).padStart(4)}${c.reset}${suffix}`,\n );\n }\n }\n\n console.log(\"\");\n if (totalSwears === 0) {\n console.log(` ${c.green}squeaky clean! not a single swear found.${c.reset}`);\n console.log(\"\");\n }\n}\n\n\n", "import { scan } from \"./commands/scan\";\n\nconst COMMANDS: Record<string, (args: string[]) => Promise<void>> = {\n scan,\n};\n\nfunction usage(): void {\n console.log(`devrage \u2014 count how many times you swear at your coding agents\n\nUsage:\n devrage <command> [options]\n\nCommands:\n scan Scan sessions for profanity\n\nOptions:\n --help, -h Show this help message\n --version Show version\n\nExamples:\n devrage scan\n devrage scan --agent claude\n devrage scan --since 2025-01-01`);\n}\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2);\n const command = args[0];\n\n if (command === \"--help\" || command === \"-h\") {\n usage();\n process.exit(0);\n }\n\n if (command === \"--version\") {\n console.log(\"0.0.3\");\n process.exit(0);\n }\n\n // If no command or not a known command, default to scan\n const handler = command ? COMMANDS[command] : undefined;\n if (handler) {\n await handler(args.slice(1));\n } else {\n // Pass all args through to scan (covers both no-arg and unknown-arg cases)\n await scan(args);\n }\n}\n\nmain().catch((err: unknown) => {\n console.error(err);\n process.exit(1);\n});\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["readdir", "homedir", "join", "readdir", "readFile", "stat", "homedir", "join", "extractText", "createReadStream", "readdir", "stat", "createInterface", "homedir", "join", "extractText", "existsSync", "homedir", "join", "readdir", "readFile", "existsSync", "homedir", "join", "base", "c"]
|
|
3
|
+
"sources": ["../src/adapters/amp.ts", "../src/adapters/claude.ts", "../src/adapters/cline.ts", "../src/adapters/codex.ts", "../src/adapters/opencode.ts", "../src/adapters/pi.ts", "../src/adapters/zed.ts", "../src/adapters/index.ts", "../src/detector/index.ts", "../src/commands/scan.ts", "../src/cli.ts"],
|
|
4
|
+
"sourcesContent": ["import { readdir, readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Adapter, AdapterOptions, Message } from \"./index\";\n\n/**\n * Amp (Sourcegraph) stores threads as JSON files at:\n * ~/.local/share/amp/threads/<thread-id>.json\n *\n * Each file is a JSON object with a `messages` array:\n * { \"messages\": [{ \"role\": \"user\"|\"assistant\", \"content\": \"...\", ... }], \"usageLedger\": {...}, ... }\n *\n * Messages have `role`, `content` (string or array), and optionally a timestamp.\n */\n\nfunction getAmpThreadsDir(): string {\n return join(\n process.env[\"XDG_DATA_HOME\"] ?? join(homedir(), \".local\", \"share\"),\n \"amp\",\n \"threads\",\n );\n}\n\nexport function ampAdapter(): Adapter {\n return {\n name: \"amp\",\n async *messages(options?: AdapterOptions): AsyncGenerator<Message> {\n const threadsDir = getAmpThreadsDir();\n\n let files: string[];\n try {\n files = await readdir(threadsDir);\n } catch {\n return; // Amp not installed or no threads\n }\n\n const jsonFiles = files.filter((f) => f.endsWith(\".json\"));\n\n for (const file of jsonFiles) {\n const filePath = join(threadsDir, file);\n const threadId = file.replace(\".json\", \"\");\n\n try {\n const raw = await readFile(filePath, \"utf-8\");\n const thread = JSON.parse(raw) as AmpThread;\n\n if (!thread.messages || !Array.isArray(thread.messages)) continue;\n\n for (const msg of thread.messages) {\n if (msg.role !== \"user\") continue;\n\n const text = extractText(msg.content);\n if (!text) continue;\n\n const timestamp = msg.timestamp ?? msg.createdAt ?? undefined;\n if (options?.since && timestamp) {\n const ts = new Date(timestamp);\n if (ts < options.since) continue;\n }\n\n yield {\n text,\n timestamp,\n session: threadId,\n };\n }\n } catch {\n // Skip malformed files\n }\n }\n },\n };\n}\n\nfunction extractText(content: unknown): string | null {\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n const parts = content\n .filter(\n (p): p is { type: string; text: string } =>\n typeof p === \"object\" && p !== null && typeof p.text === \"string\",\n )\n .map((p) => p.text);\n return parts.length > 0 ? parts.join(\" \") : null;\n }\n return null;\n}\n\ninterface AmpMessage {\n role?: string;\n content?: unknown;\n timestamp?: string;\n createdAt?: string;\n}\n\ninterface AmpThread {\n messages?: AmpMessage[];\n}\n", "import { createReadStream } from \"node:fs\";\nimport { readdir, stat } from \"node:fs/promises\";\nimport { createInterface } from \"node:readline\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Adapter, AdapterOptions, Message } from \"./index\";\n\n/**\n * Claude Code stores sessions as JSONL files at:\n * ~/.claude/projects/<project-path>/<session-uuid>.jsonl\n *\n * Each line is a JSON object. User messages have:\n * { \"type\": \"human\", \"message\": { \"content\": [...] } }\n * or sometimes:\n * { \"role\": \"user\", \"content\": \"...\" }\n */\n\nconst CLAUDE_DIR = join(homedir(), \".claude\", \"projects\");\n\nexport function claudeAdapter(): Adapter {\n return {\n name: \"claude\",\n async *messages(options?: AdapterOptions): AsyncGenerator<Message> {\n const projectsDir = CLAUDE_DIR;\n\n let projectDirs: string[];\n try {\n projectDirs = await readdir(projectsDir);\n } catch {\n return; // Claude Code not installed or no sessions\n }\n\n for (const projectDir of projectDirs) {\n const projectPath = join(projectsDir, projectDir);\n const projectStat = await stat(projectPath);\n if (!projectStat.isDirectory()) continue;\n\n const entries = await readdir(projectPath);\n const jsonlFiles = entries.filter((f) => f.endsWith(\".jsonl\"));\n\n for (const file of jsonlFiles) {\n const filePath = join(projectPath, file);\n const session = file.replace(\".jsonl\", \"\");\n\n yield* parseClaudeJsonl(filePath, {\n session,\n project: projectDir,\n since: options?.since,\n });\n }\n\n // Also check for subagent JSONL files in session subdirectories\n const subdirs = entries.filter((f) => !f.includes(\".\"));\n for (const subdir of subdirs) {\n const subagentsDir = join(projectPath, subdir, \"subagents\");\n try {\n const subFiles = await readdir(subagentsDir);\n const subJsonl = subFiles.filter((f) => f.endsWith(\".jsonl\"));\n for (const file of subJsonl) {\n yield* parseClaudeJsonl(join(subagentsDir, file), {\n session: `${subdir}/${file.replace(\".jsonl\", \"\")}`,\n project: projectDir,\n since: options?.since,\n });\n }\n } catch {\n // No subagents directory, skip\n }\n }\n }\n },\n };\n}\n\nasync function* parseClaudeJsonl(\n filePath: string,\n context: { session: string; project: string; since?: Date },\n): AsyncGenerator<Message> {\n const rl = createInterface({\n input: createReadStream(filePath, { encoding: \"utf-8\" }),\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n if (!line.trim()) continue;\n\n try {\n const entry = JSON.parse(line) as Record<string, unknown>;\n const text = extractUserText(entry);\n if (!text) continue;\n\n const timestamp = extractTimestamp(entry);\n if (context.since && timestamp) {\n const ts = new Date(timestamp);\n if (ts < context.since) continue;\n }\n\n yield {\n text,\n timestamp: timestamp ?? undefined,\n session: context.session,\n project: context.project,\n };\n } catch {\n // Skip malformed lines\n }\n }\n}\n\nfunction extractUserText(entry: Record<string, unknown>): string | null {\n // Format: { \"type\": \"user\", \"message\": { \"role\": \"user\", \"content\": \"...\" } }\n if (entry[\"type\"] === \"user\") {\n const message = entry[\"message\"] as Record<string, unknown> | undefined;\n if (!message) return null;\n return contentToString(message[\"content\"]);\n }\n\n // Legacy format: { \"type\": \"human\", \"message\": { \"content\": [...] } }\n if (entry[\"type\"] === \"human\") {\n const message = entry[\"message\"] as Record<string, unknown> | undefined;\n if (!message) return null;\n return contentToString(message[\"content\"]);\n }\n\n // Flat format: { \"role\": \"user\", \"content\": \"...\" }\n if (entry[\"role\"] === \"user\") {\n return contentToString(entry[\"content\"]);\n }\n\n return null;\n}\n\nfunction contentToString(content: unknown): string | null {\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n const parts = content\n .filter(\n (p): p is { type: string; text: string } =>\n typeof p === \"object\" && p !== null && p.type === \"text\",\n )\n .map((p) => p.text);\n return parts.length > 0 ? parts.join(\" \") : null;\n }\n return null;\n}\n\nfunction extractTimestamp(entry: Record<string, unknown>): string | null {\n if (typeof entry[\"timestamp\"] === \"string\") return entry[\"timestamp\"];\n if (typeof entry[\"createdAt\"] === \"string\") return entry[\"createdAt\"];\n return null;\n}\n", "import { readdir, readFile, stat } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Adapter, AdapterOptions, Message } from \"./index\";\n\n/**\n * Cline (formerly Claude Dev) stores task history at:\n *\n * VS Code extension:\n * macOS: ~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/tasks/<task-id>/api_conversation_history.json\n * Linux: ~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/tasks/<task-id>/api_conversation_history.json\n * Windows: %APPDATA%/Code/User/globalStorage/saoudrizwan.claude-dev/tasks/<task-id>/api_conversation_history.json\n *\n * Standalone CLI / JetBrains (newer):\n * ~/.cline/data/tasks/<task-id>/api_conversation_history.json\n *\n * Roo Code (fork) uses the same format at:\n * globalStorage/rooveterinaryinc.roo-cline/tasks/<task-id>/api_conversation_history.json\n *\n * Each api_conversation_history.json is a JSON array of messages:\n * [{ \"role\": \"user\"|\"assistant\", \"content\": \"...\" | [{type: \"text\", text: \"...\"}] }]\n */\n\nfunction getClineTaskDirs(): string[] {\n const dirs: string[] = [];\n\n // VS Code extension paths\n const vscodePaths = getVSCodeGlobalStoragePaths();\n const extensionIds = [\"saoudrizwan.claude-dev\", \"rooveterinaryinc.roo-cline\"];\n\n for (const basePath of vscodePaths) {\n for (const extId of extensionIds) {\n const tasksDir = join(basePath, extId, \"tasks\");\n if (existsSync(tasksDir)) dirs.push(tasksDir);\n }\n }\n\n // Standalone CLI path\n const clineStandalone = join(homedir(), \".cline\", \"data\", \"tasks\");\n if (existsSync(clineStandalone)) dirs.push(clineStandalone);\n\n return dirs;\n}\n\nfunction getVSCodeGlobalStoragePaths(): string[] {\n const paths: string[] = [];\n\n if (process.platform === \"darwin\") {\n paths.push(\n join(homedir(), \"Library\", \"Application Support\", \"Code\", \"User\", \"globalStorage\"),\n join(homedir(), \"Library\", \"Application Support\", \"Code - Insiders\", \"User\", \"globalStorage\"),\n join(homedir(), \"Library\", \"Application Support\", \"Cursor\", \"User\", \"globalStorage\"),\n );\n } else if (process.platform === \"linux\") {\n const configBase = process.env[\"XDG_CONFIG_HOME\"] ?? join(homedir(), \".config\");\n paths.push(\n join(configBase, \"Code\", \"User\", \"globalStorage\"),\n join(configBase, \"Code - Insiders\", \"User\", \"globalStorage\"),\n join(configBase, \"Cursor\", \"User\", \"globalStorage\"),\n );\n } else {\n // Windows\n const appData = process.env[\"APPDATA\"] ?? join(homedir(), \"AppData\", \"Roaming\");\n paths.push(\n join(appData, \"Code\", \"User\", \"globalStorage\"),\n join(appData, \"Code - Insiders\", \"User\", \"globalStorage\"),\n join(appData, \"Cursor\", \"User\", \"globalStorage\"),\n );\n }\n\n return paths;\n}\n\nexport function clineAdapter(): Adapter {\n return {\n name: \"cline\",\n async *messages(options?: AdapterOptions): AsyncGenerator<Message> {\n const taskDirs = getClineTaskDirs();\n\n for (const tasksDir of taskDirs) {\n let taskIds: string[];\n try {\n taskIds = await readdir(tasksDir);\n } catch {\n continue;\n }\n\n for (const taskId of taskIds) {\n const taskDir = join(tasksDir, taskId);\n const taskStat = await stat(taskDir).catch(() => null);\n if (!taskStat?.isDirectory()) continue;\n\n const historyFile = join(taskDir, \"api_conversation_history.json\");\n\n try {\n const raw = await readFile(historyFile, \"utf-8\");\n const messages = JSON.parse(raw) as ClineMessage[];\n\n if (!Array.isArray(messages)) continue;\n\n for (const msg of messages) {\n if (msg.role !== \"user\") continue;\n\n const text = extractText(msg.content);\n if (!text) continue;\n\n // Cline doesn't store per-message timestamps in the conversation file\n // Use the task metadata file for approximate timing\n const timestamp = msg.ts ?? undefined;\n if (options?.since && timestamp) {\n const ts = new Date(timestamp);\n if (ts < options.since) continue;\n }\n\n yield {\n text,\n session: taskId,\n };\n }\n } catch {\n // Skip tasks without history or malformed files\n }\n }\n }\n },\n };\n}\n\nfunction extractText(content: unknown): string | null {\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n const parts = content\n .filter(\n (p): p is { type: string; text: string } =>\n typeof p === \"object\" &&\n p !== null &&\n p.type === \"text\" &&\n typeof p.text === \"string\",\n )\n .map((p) => p.text);\n return parts.length > 0 ? parts.join(\" \") : null;\n }\n return null;\n}\n\ninterface ClineMessage {\n role?: string;\n content?: unknown;\n ts?: string;\n}\n", "import { createReadStream } from \"node:fs\";\nimport { readdir, stat } from \"node:fs/promises\";\nimport { createInterface } from \"node:readline\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Adapter, AdapterOptions, Message } from \"./index\";\n\n/**\n * Codex stores sessions as JSONL files at:\n * ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl\n *\n * Each line is JSON with structure:\n * { \"timestamp\": \"...\", \"type\": \"response_item\", \"payload\": { \"type\": \"message\", \"role\": \"user\", \"content\": [...] } }\n *\n * User messages have payload.role === \"user\" and content is an array of\n * { \"type\": \"input_text\", \"text\": \"...\" }\n *\n * We skip messages that are just environment context injections.\n */\n\nconst CODEX_SESSIONS_DIR = join(homedir(), \".codex\", \"sessions\");\n\nexport function codexAdapter(): Adapter {\n return {\n name: \"codex\",\n async *messages(options?: AdapterOptions): AsyncGenerator<Message> {\n yield* walkCodexSessions(CODEX_SESSIONS_DIR, options);\n },\n };\n}\n\nasync function* walkCodexSessions(\n dir: string,\n options?: AdapterOptions,\n): AsyncGenerator<Message> {\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch {\n return; // Codex not installed or no sessions\n }\n\n for (const entry of entries) {\n const fullPath = join(dir, entry);\n const entryStat = await stat(fullPath);\n\n if (entryStat.isDirectory()) {\n yield* walkCodexSessions(fullPath, options);\n } else if (entry.endsWith(\".jsonl\")) {\n const session = entry.replace(\".jsonl\", \"\");\n yield* parseCodexJsonl(fullPath, { session, since: options?.since });\n }\n }\n}\n\nasync function* parseCodexJsonl(\n filePath: string,\n context: { session: string; since?: Date },\n): AsyncGenerator<Message> {\n const rl = createInterface({\n input: createReadStream(filePath, { encoding: \"utf-8\" }),\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n if (!line.trim()) continue;\n\n try {\n const entry = JSON.parse(line) as CodexEntry;\n\n // Only care about response_item entries with user messages\n if (entry.type !== \"response_item\") continue;\n\n const payload = entry.payload;\n if (!payload || payload.role !== \"user\") continue;\n\n const text = extractText(payload.content);\n if (!text) continue;\n\n // Skip environment context injections (they start with <environment_context>)\n if (text.startsWith(\"<environment_context>\")) continue;\n // Skip permission/sandbox instructions\n if (text.startsWith(\"<permissions instructions>\")) continue;\n\n if (context.since && entry.timestamp) {\n const ts = new Date(entry.timestamp);\n if (ts < context.since) continue;\n }\n\n yield {\n text,\n timestamp: entry.timestamp,\n session: context.session,\n };\n } catch {\n // Skip malformed lines\n }\n }\n}\n\nfunction extractText(content: unknown): string | null {\n if (!Array.isArray(content)) return null;\n\n const parts = content\n .filter(\n (p): p is { type: string; text: string } =>\n typeof p === \"object\" &&\n p !== null &&\n p.type === \"input_text\" &&\n typeof p.text === \"string\",\n )\n .map((p) => p.text);\n\n return parts.length > 0 ? parts.join(\" \") : null;\n}\n\ninterface CodexEntry {\n timestamp?: string;\n type: string;\n payload?: {\n type?: string;\n role?: string;\n content?: unknown;\n };\n}\n", "import { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Adapter, AdapterOptions, Message } from \"./index\";\n\n/**\n * OpenCode stores sessions in a SQLite database at:\n * ~/.local/share/opencode/opencode.db\n *\n * Schema:\n * message: { id, session_id, time_created (epoch ms), time_updated, data (JSON) }\n * part: { id, message_id, session_id, time_created, time_updated, data (JSON) }\n *\n * message.data: { \"role\": \"user\"|\"assistant\", \"time\": {...}, \"agent\": \"...\", ... }\n * part.data: { \"type\": \"text\", \"text\": \"the user's message\" }\n *\n * User messages have role=\"user\" in message.data. The actual text content is in\n * the associated part rows where part.data.type === \"text\".\n */\n\nfunction getOpencodeDatabasePath(): string | null {\n // macOS: ~/.local/share/opencode/opencode.db (despite XDG, this is where it actually lives)\n const xdgPath = join(\n process.env[\"XDG_DATA_HOME\"] ?? join(homedir(), \".local\", \"share\"),\n \"opencode\",\n \"opencode.db\",\n );\n if (existsSync(xdgPath)) return xdgPath;\n\n // macOS Application Support fallback\n if (process.platform === \"darwin\") {\n const macPath = join(\n homedir(),\n \"Library\",\n \"Application Support\",\n \"opencode\",\n \"opencode.db\",\n );\n if (existsSync(macPath)) return macPath;\n }\n\n return null;\n}\n\nexport function opencodeAdapter(): Adapter {\n return {\n name: \"opencode\",\n async *messages(options?: AdapterOptions): AsyncGenerator<Message> {\n const dbPath = getOpencodeDatabasePath();\n if (!dbPath) return;\n\n // Dynamic import so the CLI doesn't crash if better-sqlite3 isn't available\n let db: import(\"better-sqlite3\").Database;\n try {\n const BetterSqlite3 = await import(\"better-sqlite3\");\n const Ctor = BetterSqlite3.default ?? BetterSqlite3;\n db = new (Ctor as unknown as new (...args: unknown[]) => import(\"better-sqlite3\").Database)(dbPath, { readonly: true });\n } catch {\n console.warn(\n \"devrage: better-sqlite3 not available, skipping OpenCode sessions\",\n );\n return;\n }\n\n try {\n yield* queryUserMessages(db, options);\n } finally {\n db.close();\n }\n },\n };\n}\n\nfunction* queryUserMessages(\n db: import(\"better-sqlite3\").Database,\n options?: AdapterOptions,\n): Generator<Message> {\n // Query: join message + part, filter to user role and text parts\n let query = `\n SELECT\n m.session_id,\n m.time_created,\n json_extract(p.data, '$.text') as text\n FROM message m\n JOIN part p ON p.message_id = m.id\n WHERE json_extract(m.data, '$.role') = 'user'\n AND json_extract(p.data, '$.type') = 'text'\n `;\n\n if (options?.since) {\n const sinceMs = options.since.getTime();\n query += ` AND m.time_created >= ${sinceMs}`;\n }\n\n query += ` ORDER BY m.time_created ASC`;\n\n const rows = db.prepare(query).all() as {\n session_id: string;\n time_created: number;\n text: string;\n }[];\n\n for (const row of rows) {\n if (!row.text || !row.text.trim()) continue;\n\n yield {\n text: row.text,\n timestamp: new Date(row.time_created).toISOString(),\n session: row.session_id,\n };\n }\n}\n", "import { createReadStream } from \"node:fs\";\nimport { readdir, stat } from \"node:fs/promises\";\nimport { createInterface } from \"node:readline\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Adapter, AdapterOptions, Message } from \"./index\";\n\n/**\n * Pi Agent stores sessions as JSONL files at:\n * ~/.pi/agent/sessions/<project>/<session-id>.jsonl\n *\n * Each line is a JSON object:\n * { \"type\": \"session\", \"cwd\": \"/path/to/project\" } \u2014 session metadata\n * { \"type\": \"message\", \"timestamp\": \"...\", \"message\": { \"role\": \"user\", \"content\": \"...\" } }\n *\n * Content can be a string or array of { type: \"text\", text: \"...\" } parts.\n */\n\nconst PI_SESSIONS_DIR = join(homedir(), \".pi\", \"agent\", \"sessions\");\n\nexport function piAdapter(): Adapter {\n return {\n name: \"pi\",\n async *messages(options?: AdapterOptions): AsyncGenerator<Message> {\n yield* walkPiSessions(PI_SESSIONS_DIR, options);\n },\n };\n}\n\nasync function* walkPiSessions(\n dir: string,\n options?: AdapterOptions,\n project?: string,\n): AsyncGenerator<Message> {\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch {\n return;\n }\n\n for (const entry of entries) {\n const fullPath = join(dir, entry);\n const entryStat = await stat(fullPath).catch(() => null);\n if (!entryStat) continue;\n\n if (entryStat.isDirectory()) {\n yield* walkPiSessions(fullPath, options, project ?? entry);\n } else if (entry.endsWith(\".jsonl\")) {\n const session = entry.replace(\".jsonl\", \"\");\n yield* parsePiJsonl(fullPath, { session, project, since: options?.since });\n }\n }\n}\n\nasync function* parsePiJsonl(\n filePath: string,\n context: { session: string; project?: string; since?: Date },\n): AsyncGenerator<Message> {\n const rl = createInterface({\n input: createReadStream(filePath, { encoding: \"utf-8\" }),\n crlfDelay: Infinity,\n });\n\n let project = context.project;\n\n for await (const line of rl) {\n if (!line.trim()) continue;\n\n try {\n const entry = JSON.parse(line) as PiEntry;\n\n // Session metadata line carries cwd\n if (entry.type === \"session\") {\n project = entry.cwd ?? project;\n continue;\n }\n\n if (entry.type !== \"message\") continue;\n\n const message = entry.message;\n if (!message || message.role !== \"user\") continue;\n\n const text = contentToString(message.content);\n if (!text) continue;\n\n const timestamp =\n typeof entry.timestamp === \"string\"\n ? entry.timestamp\n : typeof message.timestamp === \"number\"\n ? new Date(message.timestamp).toISOString()\n : undefined;\n\n if (context.since && timestamp) {\n const ts = new Date(timestamp);\n if (ts < context.since) continue;\n }\n\n yield {\n text,\n timestamp,\n session: context.session,\n project,\n };\n } catch {\n // Skip malformed lines\n }\n }\n}\n\nfunction contentToString(content: unknown): string | null {\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n const parts = content\n .filter(\n (p): p is { type: string; text: string } =>\n typeof p === \"object\" &&\n p !== null &&\n p.type === \"text\" &&\n typeof p.text === \"string\",\n )\n .map((p) => p.text);\n return parts.length > 0 ? parts.join(\" \") : null;\n }\n return null;\n}\n\ninterface PiEntry {\n type?: string;\n timestamp?: string;\n cwd?: string;\n message?: {\n role?: string;\n content?: unknown;\n timestamp?: number;\n };\n}\n", "import { readdir, readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Adapter, AdapterOptions, Message } from \"./index\";\n\n/**\n * Zed stores AI conversations in two places:\n *\n * 1. Text threads (older assistant panel):\n * ~/.local/share/zed/conversations/*.json (Linux)\n * ~/Library/Application Support/Zed/conversations/*.json (macOS)\n * These are markdown-like JSON with messages.\n *\n * 2. Agent threads (newer):\n * Stored in SQLite at ~/.local/share/zed/db (Linux)\n * or ~/Library/Application Support/Zed/db (macOS)\n * We'd need to query this, but the schema isn't well documented.\n *\n * We support the text thread JSON files for now, and the SQLite agent\n * threads when better-sqlite3 is available.\n */\n\nfunction getZedPaths(): { conversations: string; db: string } {\n if (process.platform === \"darwin\") {\n const base = join(homedir(), \"Library\", \"Application Support\", \"Zed\");\n return {\n conversations: join(base, \"conversations\"),\n db: join(base, \"db\"),\n };\n }\n // Linux / others\n const base = join(\n process.env[\"XDG_DATA_HOME\"] ?? join(homedir(), \".local\", \"share\"),\n \"zed\",\n );\n return {\n conversations: join(base, \"conversations\"),\n db: join(base, \"db\"),\n };\n}\n\nexport function zedAdapter(): Adapter {\n return {\n name: \"zed\",\n async *messages(options?: AdapterOptions): AsyncGenerator<Message> {\n const paths = getZedPaths();\n\n // 1. Parse text thread JSON conversations\n yield* parseTextThreads(paths.conversations, options);\n\n // 2. Try SQLite agent threads\n yield* parseAgentThreads(paths.db, options);\n },\n };\n}\n\nasync function* parseTextThreads(\n dir: string,\n _options?: AdapterOptions,\n): AsyncGenerator<Message> {\n if (!existsSync(dir)) return;\n\n let files: string[];\n try {\n files = await readdir(dir);\n } catch {\n return;\n }\n\n const jsonFiles = files.filter((f) => f.endsWith(\".json\"));\n\n for (const file of jsonFiles) {\n const filePath = join(dir, file);\n const session = file.replace(\".json\", \"\");\n\n try {\n const raw = await readFile(filePath, \"utf-8\");\n const conversation = JSON.parse(raw) as ZedConversation;\n\n if (!conversation.messages || !Array.isArray(conversation.messages)) continue;\n\n for (const msg of conversation.messages) {\n if (msg.role !== \"user\") continue;\n\n const text = typeof msg.content === \"string\" ? msg.content : null;\n if (!text) continue;\n\n yield {\n text,\n session,\n };\n }\n } catch {\n // Skip malformed files\n }\n }\n}\n\nasync function* parseAgentThreads(\n dbDir: string,\n _options?: AdapterOptions,\n): AsyncGenerator<Message> {\n // Zed uses a directory of SQLite databases. The main one is typically\n // a file like 0-dev.db or similar inside the db/ directory.\n if (!existsSync(dbDir)) return;\n\n let dbFiles: string[];\n try {\n const entries = await readdir(dbDir);\n dbFiles = entries.filter((f) => f.endsWith(\".db\"));\n } catch {\n return;\n }\n\n if (dbFiles.length === 0) return;\n\n let Database: unknown;\n try {\n const mod = await import(\"better-sqlite3\");\n Database = mod.default ?? mod;\n } catch {\n return; // better-sqlite3 not available\n }\n\n for (const dbFile of dbFiles) {\n const dbPath = join(dbDir, dbFile);\n let db: import(\"better-sqlite3\").Database;\n\n try {\n db = new (Database as new (...args: unknown[]) => import(\"better-sqlite3\").Database)(\n dbPath,\n { readonly: true },\n );\n } catch {\n continue; // Can't open this DB\n }\n\n try {\n // Check if this DB has a threads/messages table\n const tables = db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table'\")\n .all() as { name: string }[];\n const tableNames = tables.map((t) => t.name);\n\n // Look for message-like tables (Zed's schema may vary by version)\n const msgTable = tableNames.find(\n (t) => t === \"messages\" || t === \"thread_messages\" || t.includes(\"message\"),\n );\n\n if (!msgTable) {\n db.close();\n continue;\n }\n\n const columns = db.prepare(`PRAGMA table_info(\"${msgTable}\")`).all() as {\n name: string;\n }[];\n const colNames = columns.map((c) => c.name);\n\n const hasRole = colNames.includes(\"role\");\n\n if (!hasRole) {\n db.close();\n continue;\n }\n\n const contentCol = colNames.includes(\"content\")\n ? \"content\"\n : colNames.includes(\"body\")\n ? \"body\"\n : \"text\";\n\n let query = `SELECT \"${contentCol}\" as text FROM \"${msgTable}\" WHERE role = 'user'`;\n\n const rows = db.prepare(query).all() as { text: string }[];\n for (const row of rows) {\n if (!row.text?.trim()) continue;\n yield { text: row.text };\n }\n } catch {\n // Schema mismatch or other error\n } finally {\n db.close();\n }\n }\n}\n\ninterface ZedConversation {\n messages?: { role?: string; content?: unknown }[];\n}\n", "import { ampAdapter } from \"./amp\";\nimport { claudeAdapter } from \"./claude\";\nimport { clineAdapter } from \"./cline\";\nimport { codexAdapter } from \"./codex\";\nimport { opencodeAdapter } from \"./opencode\";\nimport { piAdapter } from \"./pi\";\nimport { zedAdapter } from \"./zed\";\n\nexport interface Message {\n text: string;\n timestamp?: string;\n session?: string;\n project?: string;\n}\n\nexport interface Adapter {\n name: string;\n /** Discover and yield all user messages from local session storage */\n messages(options?: AdapterOptions): AsyncGenerator<Message>;\n}\n\nexport interface AdapterOptions {\n since?: Date;\n}\n\nconst ADAPTERS: Record<string, () => Adapter> = {\n claude: claudeAdapter,\n codex: codexAdapter,\n opencode: opencodeAdapter,\n amp: ampAdapter,\n cline: clineAdapter,\n pi: piAdapter,\n zed: zedAdapter,\n};\n\nexport function createAdapter(name: string): Adapter {\n const factory = ADAPTERS[name];\n if (!factory) {\n throw new Error(\n `unknown adapter: ${name} (available: ${Object.keys(ADAPTERS).join(\", \")})`,\n );\n }\n return factory();\n}\n\nexport function allAdapters(): Adapter[] {\n return Object.values(ADAPTERS).map((f) => f());\n}\n", "export interface DetectionResult {\n /** Total swear words found in the text */\n count: number;\n /** Individual matches */\n matches: Match[];\n}\n\nexport interface Match {\n word: string;\n index: number;\n severity: Severity;\n group: string;\n}\n\nexport type Severity = \"mild\" | \"moderate\" | \"strong\";\n\ninterface WordDef {\n word: string;\n severity: Severity;\n group: string;\n}\n\n/**\n * Core wordlist: canonical forms, conjugations, compound words, and common typos.\n * Grouped by root word for reporting rollup.\n *\n * Sources:\n * - swearjar npm (en_US.json) for compound words\n * - Manual typo variants based on common keyboard transpositions\n */\nconst WORDLIST: WordDef[] = [\n // === FUCK family (strong) ===\n // Canonical forms\n { word: \"fuck\", severity: \"strong\", group: \"fuck\" },\n { word: \"fucking\", severity: \"strong\", group: \"fuck\" },\n { word: \"fucked\", severity: \"strong\", group: \"fuck\" },\n { word: \"fucker\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuckin\", severity: \"strong\", group: \"fuck\" },\n { word: \"fucks\", severity: \"strong\", group: \"fuck\" },\n // Compound words\n { word: \"motherfucker\", severity: \"strong\", group: \"fuck\" },\n { word: \"motherfucking\", severity: \"strong\", group: \"fuck\" },\n { word: \"mothafucka\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuckup\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuckoff\", severity: \"strong\", group: \"fuck\" },\n { word: \"clusterfuck\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuckwit\", severity: \"strong\", group: \"fuck\" },\n { word: \"fucktard\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuckface\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuckhead\", severity: \"strong\", group: \"fuck\" },\n // Typos \u2014 transpositions\n { word: \"fukc\", severity: \"strong\", group: \"fuck\" },\n { word: \"fukcing\", severity: \"strong\", group: \"fuck\" },\n { word: \"fukced\", severity: \"strong\", group: \"fuck\" },\n { word: \"fukcer\", severity: \"strong\", group: \"fuck\" },\n { word: \"fcuk\", severity: \"strong\", group: \"fuck\" },\n { word: \"fcuking\", severity: \"strong\", group: \"fuck\" },\n { word: \"fcuked\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuk\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuking\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuked\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuker\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuxk\", severity: \"strong\", group: \"fuck\" },\n { word: \"fuxking\", severity: \"strong\", group: \"fuck\" },\n\n // === SHIT family (strong) ===\n { word: \"shit\", severity: \"strong\", group: \"shit\" },\n { word: \"shitty\", severity: \"strong\", group: \"shit\" },\n { word: \"shitting\", severity: \"strong\", group: \"shit\" },\n { word: \"shits\", severity: \"strong\", group: \"shit\" },\n { word: \"shitted\", severity: \"strong\", group: \"shit\" },\n // Compound words\n { word: \"bullshit\", severity: \"strong\", group: \"shit\" },\n { word: \"horseshit\", severity: \"strong\", group: \"shit\" },\n { word: \"dipshit\", severity: \"strong\", group: \"shit\" },\n { word: \"shitshow\", severity: \"strong\", group: \"shit\" },\n { word: \"shithead\", severity: \"strong\", group: \"shit\" },\n { word: \"shithole\", severity: \"strong\", group: \"shit\" },\n { word: \"shitface\", severity: \"strong\", group: \"shit\" },\n { word: \"shitfaced\", severity: \"strong\", group: \"shit\" },\n { word: \"shitstain\", severity: \"strong\", group: \"shit\" },\n { word: \"shitbag\", severity: \"strong\", group: \"shit\" },\n // Typos\n { word: \"hsit\", severity: \"strong\", group: \"shit\" },\n { word: \"siht\", severity: \"strong\", group: \"shit\" },\n { word: \"shti\", severity: \"strong\", group: \"shit\" },\n { word: \"sjit\", severity: \"strong\", group: \"shit\" },\n { word: \"shjt\", severity: \"strong\", group: \"shit\" },\n { word: \"bulshit\", severity: \"strong\", group: \"shit\" },\n { word: \"bullsht\", severity: \"strong\", group: \"shit\" },\n\n // === ASS family (moderate) ===\n { word: \"ass\", severity: \"moderate\", group: \"ass\" },\n { word: \"asses\", severity: \"moderate\", group: \"ass\" },\n // Compound words (these are strong)\n { word: \"asshole\", severity: \"strong\", group: \"ass\" },\n { word: \"assholes\", severity: \"strong\", group: \"ass\" },\n { word: \"jackass\", severity: \"strong\", group: \"ass\" },\n { word: \"dumbass\", severity: \"strong\", group: \"ass\" },\n { word: \"fatass\", severity: \"moderate\", group: \"ass\" },\n { word: \"asshat\", severity: \"strong\", group: \"ass\" },\n { word: \"asswipe\", severity: \"strong\", group: \"ass\" },\n { word: \"badass\", severity: \"mild\", group: \"ass\" },\n\n // === DAMN family (moderate) ===\n { word: \"damn\", severity: \"moderate\", group: \"damn\" },\n { word: \"damned\", severity: \"moderate\", group: \"damn\" },\n { word: \"damnit\", severity: \"moderate\", group: \"damn\" },\n { word: \"dammit\", severity: \"moderate\", group: \"damn\" },\n { word: \"goddamn\", severity: \"moderate\", group: \"damn\" },\n { word: \"goddamnit\", severity: \"moderate\", group: \"damn\" },\n { word: \"goddammit\", severity: \"moderate\", group: \"damn\" },\n\n // === BITCH family (strong) ===\n { word: \"bitch\", severity: \"strong\", group: \"bitch\" },\n { word: \"bitches\", severity: \"strong\", group: \"bitch\" },\n { word: \"bitching\", severity: \"strong\", group: \"bitch\" },\n { word: \"bitchy\", severity: \"strong\", group: \"bitch\" },\n { word: \"bitchass\", severity: \"strong\", group: \"bitch\" },\n\n // === BASTARD (strong) ===\n { word: \"bastard\", severity: \"strong\", group: \"bastard\" },\n { word: \"bastards\", severity: \"strong\", group: \"bastard\" },\n\n // === PISS family (moderate) ===\n { word: \"piss\", severity: \"moderate\", group: \"piss\" },\n { word: \"pissed\", severity: \"moderate\", group: \"piss\" },\n { word: \"pissing\", severity: \"moderate\", group: \"piss\" },\n { word: \"pissoff\", severity: \"moderate\", group: \"piss\" },\n\n // === DICK (moderate) ===\n { word: \"dick\", severity: \"moderate\", group: \"dick\" },\n { word: \"dickhead\", severity: \"strong\", group: \"dick\" },\n\n // === CRAP (moderate) ===\n { word: \"crap\", severity: \"moderate\", group: \"crap\" },\n { word: \"crappy\", severity: \"moderate\", group: \"crap\" },\n { word: \"crapping\", severity: \"moderate\", group: \"crap\" },\n\n // === HELL (mild) ===\n { word: \"hell\", severity: \"mild\", group: \"hell\" },\n\n // === Abbreviations (mild) ===\n { word: \"wtf\", severity: \"mild\", group: \"wtf\" },\n { word: \"stfu\", severity: \"mild\", group: \"stfu\" },\n { word: \"lmfao\", severity: \"mild\", group: \"lmfao\" },\n { word: \"lmao\", severity: \"mild\", group: \"lmao\" },\n\n // === CUNT (strong) ===\n { word: \"cunt\", severity: \"strong\", group: \"cunt\" },\n { word: \"cunts\", severity: \"strong\", group: \"cunt\" },\n];\n\n/**\n * Normalize text before matching:\n * 1. Collapse repeated characters (3+ of the same char \u2192 2)\n * e.g. \"fuuuuck\" \u2192 \"fuuck\", \"shiiiiit\" \u2192 \"shiit\"\n * This lets \"fuuuuck\" match against \"fuck\" after the regex runs,\n * because the pattern also includes \"fuuck\" style intermediates.\n *\n * Actually \u2014 better approach: collapse ALL runs of 2+ to 1 for matching\n * purposes, while keeping the original text for position tracking.\n * e.g. \"fuuuuck\" \u2192 \"fuck\", \"shiiiit\" \u2192 \"shit\"\n * This directly normalizes to the root word.\n */\nfunction collapseRepeats(text: string): string {\n return text.replace(/(.)\\1+/g, \"$1\");\n}\n\n/**\n * Build the detection regex from the wordlist.\n * Sort longer words first so \"motherfucker\" matches before \"fuck\".\n */\nfunction buildPattern(words: WordDef[]): RegExp {\n const sorted = [...words].sort((a, b) => b.word.length - a.word.length);\n const pattern = sorted.map((w) => w.word).join(\"|\");\n return new RegExp(`\\\\b(${pattern})\\\\b`, \"gi\");\n}\n\nconst DEFAULT_PATTERN = buildPattern(WORDLIST);\nconst WORD_MAP = new Map(WORDLIST.map((w) => [w.word.toLowerCase(), w]));\n\n/**\n * Detect profanity in a string.\n *\n * Runs detection in two passes:\n * 1. Direct match on original text (preserves positions)\n * 2. Match on repeat-collapsed text (catches fuuuuck, shiiiiit, etc.)\n */\nexport function detect(text: string): DetectionResult {\n const matches: Match[] = [];\n const seen = new Set<number>(); // track original-text positions we've already matched\n\n // Pass 1: direct match on original (lowercase) text\n runPattern(text, text.toLowerCase(), matches, seen);\n\n // Pass 2: match on collapsed text to catch repeated chars\n const collapsed = collapseRepeats(text.toLowerCase());\n if (collapsed !== text.toLowerCase()) {\n runPattern(text, collapsed, matches, seen);\n }\n\n return { count: matches.length, matches };\n}\n\nfunction runPattern(\n _originalText: string,\n searchText: string,\n matches: Match[],\n seen: Set<number>,\n): void {\n DEFAULT_PATTERN.lastIndex = 0;\n\n let match: RegExpExecArray | null;\n while ((match = DEFAULT_PATTERN.exec(searchText)) !== null) {\n if (seen.has(match.index)) continue;\n\n const word = match[0].toLowerCase();\n const entry = WORD_MAP.get(word);\n if (!entry) continue;\n\n seen.add(match.index);\n matches.push({\n word,\n index: match.index,\n severity: entry.severity,\n group: entry.group,\n });\n }\n}\n\n/**\n * Create a custom detector with additional words.\n */\nexport function createDetector(\n extraWords?: WordDef[],\n): (text: string) => DetectionResult {\n const allWords = extraWords ? [...WORDLIST, ...extraWords] : WORDLIST;\n const pattern = buildPattern(allWords);\n const wordMap = new Map(allWords.map((w) => [w.word.toLowerCase(), w]));\n\n return (text: string): DetectionResult => {\n const matches: Match[] = [];\n const seen = new Set<number>();\n\n const lower = text.toLowerCase();\n pattern.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(lower)) !== null) {\n if (seen.has(match.index)) continue;\n const word = match[0].toLowerCase();\n const entry = wordMap.get(word);\n if (!entry) continue;\n seen.add(match.index);\n matches.push({ word, index: match.index, severity: entry.severity, group: entry.group });\n }\n\n const collapsed = collapseRepeats(lower);\n if (collapsed !== lower) {\n pattern.lastIndex = 0;\n while ((match = pattern.exec(collapsed)) !== null) {\n if (seen.has(match.index)) continue;\n const word = match[0].toLowerCase();\n const entry = wordMap.get(word);\n if (!entry) continue;\n seen.add(match.index);\n matches.push({ word, index: match.index, severity: entry.severity, group: entry.group });\n }\n }\n\n return { count: matches.length, matches };\n };\n}\n\nexport type { WordDef as WordEntry };\n", "import { allAdapters, createAdapter } from \"../adapters/index\";\nimport { detect } from \"../detector/index\";\n\n// ANSI color helpers \u2014 no dependencies needed\nconst c = {\n reset: \"\\x1b[0m\",\n bold: \"\\x1b[1m\",\n dim: \"\\x1b[2m\",\n red: \"\\x1b[31m\",\n green: \"\\x1b[32m\",\n yellow: \"\\x1b[33m\",\n blue: \"\\x1b[34m\",\n magenta: \"\\x1b[35m\",\n cyan: \"\\x1b[36m\",\n white: \"\\x1b[37m\",\n gray: \"\\x1b[90m\",\n};\n\nconst SPINNER_MESSAGES = [\n \"Tallying the damage\",\n \"Reviewing your outbursts\",\n \"Judging your vocabulary\",\n \"Computing your shame\",\n \"Cataloging the profanity\",\n \"Measuring your frustration\",\n \"Assessing the verbal carnage\",\n \"Quantifying your displeasure\",\n \"Auditing your language\",\n \"Tabulating regrets\",\n];\n\nfunction createSpinner() {\n let messageIdx = 0;\n let dotCount = 0;\n let timer: ReturnType<typeof setInterval> | null = null;\n\n return {\n start() {\n messageIdx = Math.floor(Math.random() * SPINNER_MESSAGES.length);\n timer = setInterval(() => {\n dotCount = (dotCount + 1) % 4;\n const msg = SPINNER_MESSAGES[messageIdx % SPINNER_MESSAGES.length];\n const dots = \".\".repeat(dotCount || 1);\n process.stdout.write(\n `\\r ${c.dim}${msg}${dots}${c.reset} `,\n );\n }, 300);\n },\n update() {\n messageIdx++;\n },\n stop() {\n if (timer) {\n clearInterval(timer);\n timer = null;\n }\n process.stdout.write(\"\\r\" + \" \".repeat(60) + \"\\r\");\n },\n };\n}\n\ninterface ScanOptions {\n agent?: string;\n since?: Date;\n}\n\nfunction parseArgs(args: string[]): ScanOptions {\n const options: ScanOptions = {};\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg === \"--agent\" || arg === \"-a\") {\n options.agent = args[++i];\n } else if (arg === \"--since\" || arg === \"-s\") {\n const val = args[++i];\n if (val) {\n options.since = new Date(val);\n if (isNaN(options.since.getTime())) {\n console.error(`invalid date: ${val}`);\n process.exit(1);\n }\n }\n } else if (arg === \"--help\" || arg === \"-h\") {\n console.log(`devrage scan \u2014 scan sessions for profanity\n\nOptions:\n --agent, -a <name> Scan only a specific agent (claude, codex, opencode, amp, cline, pi, zed)\n --since, -s <date> Only scan messages after this date (ISO 8601)\n --help, -h Show this help`);\n process.exit(0);\n }\n }\n\n return options;\n}\n\nexport async function scan(args: string[]): Promise<void> {\n const options = parseArgs(args);\n\n const adapters = options.agent\n ? [createAdapter(options.agent)]\n : allAdapters();\n\n const spinner = createSpinner();\n spinner.start();\n\n const groupTally: Record<string, number> = {};\n const variantTally: Record<string, Record<string, number>> = {};\n\n let totalMessages = 0;\n let totalSwears = 0;\n const perAgent: Record<string, { messages: number; swears: number }> = {};\n\n for (const adapter of adapters) {\n let agentMessages = 0;\n let agentSwears = 0;\n spinner.update();\n\n for await (const message of adapter.messages({ since: options.since })) {\n totalMessages++;\n agentMessages++;\n\n const result = detect(message.text);\n if (result.count > 0) {\n totalSwears += result.count;\n agentSwears += result.count;\n\n for (const match of result.matches) {\n groupTally[match.group] = (groupTally[match.group] ?? 0) + 1;\n\n const variants = (variantTally[match.group] ??= {});\n variants[match.word] = (variants[match.word] ?? 0) + 1;\n }\n }\n }\n\n if (agentMessages > 0) {\n perAgent[adapter.name] = { messages: agentMessages, swears: agentSwears };\n }\n }\n\n spinner.stop();\n\n // Report\n console.log(\"\");\n console.log(` ${c.bold}${c.red}devrage${c.reset} ${c.dim}report${c.reset}`);\n console.log(` ${c.dim}${\"\u2500\".repeat(30)}${c.reset}`);\n console.log(\"\");\n console.log(` ${c.dim}messages scanned${c.reset} ${c.bold}${totalMessages}${c.reset}`);\n console.log(` ${c.dim}total swears${c.reset} ${c.bold}${c.red}${totalSwears}${c.reset}`);\n\n const activeAgents = Object.entries(perAgent);\n if (activeAgents.length > 1) {\n console.log(\"\");\n console.log(` ${c.bold}by agent${c.reset}`);\n for (const [name, stats] of activeAgents) {\n const rate = ((stats.swears / stats.messages) * 100).toFixed(1);\n console.log(\n ` ${c.cyan}${name.padEnd(10)}${c.reset} ${c.bold}${String(stats.swears).padStart(4)}${c.reset} ${c.dim}in ${stats.messages} messages (${rate}%)${c.reset}`,\n );\n }\n }\n\n if (totalSwears > 0) {\n const sorted = Object.entries(groupTally).sort(([, a], [, b]) => b - a);\n console.log(\"\");\n console.log(` ${c.bold}top words${c.reset}`);\n for (const [group, count] of sorted.slice(0, 10)) {\n const variants = variantTally[group] ?? {};\n const variantList = Object.entries(variants)\n .sort(([, a], [, b]) => b - a)\n .filter(([v]) => v !== group)\n .slice(0, 15)\n .map(([v, cnt]) => `${c.dim}${v}${c.reset} ${cnt}`)\n .join(`${c.dim},${c.reset} `);\n const suffix = variantList ? ` ${c.dim}(${c.reset}${variantList}${c.dim})${c.reset}` : \"\";\n console.log(\n ` ${c.yellow}${group.padEnd(12)}${c.reset} ${c.bold}${String(count).padStart(4)}${c.reset}${suffix}`,\n );\n }\n }\n\n console.log(\"\");\n if (totalSwears === 0) {\n console.log(` ${c.green}squeaky clean! not a single swear found.${c.reset}`);\n console.log(\"\");\n }\n}\n\n\n", "import { scan } from \"./commands/scan\";\n\nconst COMMANDS: Record<string, (args: string[]) => Promise<void>> = {\n scan,\n};\n\nfunction usage(): void {\n console.log(`devrage \u2014 count how many times you swear at your coding agents\n\nUsage:\n devrage <command> [options]\n\nCommands:\n scan Scan sessions for profanity\n\nOptions:\n --help, -h Show this help message\n --version Show version\n\nExamples:\n devrage scan\n devrage scan --agent claude\n devrage scan --since 2025-01-01`);\n}\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2);\n const command = args[0];\n\n if (command === \"--help\" || command === \"-h\") {\n usage();\n process.exit(0);\n }\n\n if (command === \"--version\") {\n console.log(\"0.0.4\");\n process.exit(0);\n }\n\n // If no command or not a known command, default to scan\n const handler = command ? COMMANDS[command] : undefined;\n if (handler) {\n await handler(args.slice(1));\n } else {\n // Pass all args through to scan (covers both no-arg and unknown-arg cases)\n await scan(args);\n }\n}\n\nmain().catch((err: unknown) => {\n console.error(err);\n process.exit(1);\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["readdir", "homedir", "join", "readdir", "readFile", "stat", "homedir", "join", "extractText", "createReadStream", "readdir", "stat", "createInterface", "homedir", "join", "extractText", "existsSync", "homedir", "join", "createReadStream", "readdir", "stat", "createInterface", "homedir", "join", "contentToString", "readdir", "readFile", "existsSync", "homedir", "join", "base", "c"]
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapters/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapters/index.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;CAC7D;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,IAAI,CAAC;CACd;AAYD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAQnD;AAED,wBAAgB,WAAW,IAAI,OAAO,EAAE,CAEvC"}
|
|
@@ -3,6 +3,7 @@ import { claudeAdapter } from "./claude";
|
|
|
3
3
|
import { clineAdapter } from "./cline";
|
|
4
4
|
import { codexAdapter } from "./codex";
|
|
5
5
|
import { opencodeAdapter } from "./opencode";
|
|
6
|
+
import { piAdapter } from "./pi";
|
|
6
7
|
import { zedAdapter } from "./zed";
|
|
7
8
|
const ADAPTERS = {
|
|
8
9
|
claude: claudeAdapter,
|
|
@@ -10,6 +11,7 @@ const ADAPTERS = {
|
|
|
10
11
|
opencode: opencodeAdapter,
|
|
11
12
|
amp: ampAdapter,
|
|
12
13
|
cline: clineAdapter,
|
|
14
|
+
pi: piAdapter,
|
|
13
15
|
zed: zedAdapter,
|
|
14
16
|
};
|
|
15
17
|
export function createAdapter(name) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAmBnC,MAAM,QAAQ,GAAkC;IAC9C,MAAM,EAAE,aAAa;IACrB,KAAK,EAAE,YAAY;IACnB,QAAQ,EAAE,eAAe;IACzB,GAAG,EAAE,UAAU;IACf,KAAK,EAAE,YAAY;IACnB,GAAG,EAAE,UAAU;CAChB,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,oBAAoB,IAAI,gBAAgB,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAC5E,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,EAAE,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AACjD,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAmBnC,MAAM,QAAQ,GAAkC;IAC9C,MAAM,EAAE,aAAa;IACrB,KAAK,EAAE,YAAY;IACnB,QAAQ,EAAE,eAAe;IACzB,GAAG,EAAE,UAAU;IACf,KAAK,EAAE,YAAY;IACnB,EAAE,EAAE,SAAS;IACb,GAAG,EAAE,UAAU;CAChB,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,oBAAoB,IAAI,gBAAgB,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAC5E,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,EAAE,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AACjD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pi.d.ts","sourceRoot":"","sources":["../../../src/adapters/pi.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAA2B,MAAM,SAAS,CAAC;AAehE,wBAAgB,SAAS,IAAI,OAAO,CAOnC"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { createReadStream } from "node:fs";
|
|
2
|
+
import { readdir, stat } from "node:fs/promises";
|
|
3
|
+
import { createInterface } from "node:readline";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
/**
|
|
7
|
+
* Pi Agent stores sessions as JSONL files at:
|
|
8
|
+
* ~/.pi/agent/sessions/<project>/<session-id>.jsonl
|
|
9
|
+
*
|
|
10
|
+
* Each line is a JSON object:
|
|
11
|
+
* { "type": "session", "cwd": "/path/to/project" } — session metadata
|
|
12
|
+
* { "type": "message", "timestamp": "...", "message": { "role": "user", "content": "..." } }
|
|
13
|
+
*
|
|
14
|
+
* Content can be a string or array of { type: "text", text: "..." } parts.
|
|
15
|
+
*/
|
|
16
|
+
const PI_SESSIONS_DIR = join(homedir(), ".pi", "agent", "sessions");
|
|
17
|
+
export function piAdapter() {
|
|
18
|
+
return {
|
|
19
|
+
name: "pi",
|
|
20
|
+
async *messages(options) {
|
|
21
|
+
yield* walkPiSessions(PI_SESSIONS_DIR, options);
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
async function* walkPiSessions(dir, options, project) {
|
|
26
|
+
let entries;
|
|
27
|
+
try {
|
|
28
|
+
entries = await readdir(dir);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
const fullPath = join(dir, entry);
|
|
35
|
+
const entryStat = await stat(fullPath).catch(() => null);
|
|
36
|
+
if (!entryStat)
|
|
37
|
+
continue;
|
|
38
|
+
if (entryStat.isDirectory()) {
|
|
39
|
+
yield* walkPiSessions(fullPath, options, project ?? entry);
|
|
40
|
+
}
|
|
41
|
+
else if (entry.endsWith(".jsonl")) {
|
|
42
|
+
const session = entry.replace(".jsonl", "");
|
|
43
|
+
yield* parsePiJsonl(fullPath, { session, project, since: options?.since });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function* parsePiJsonl(filePath, context) {
|
|
48
|
+
const rl = createInterface({
|
|
49
|
+
input: createReadStream(filePath, { encoding: "utf-8" }),
|
|
50
|
+
crlfDelay: Infinity,
|
|
51
|
+
});
|
|
52
|
+
let project = context.project;
|
|
53
|
+
for await (const line of rl) {
|
|
54
|
+
if (!line.trim())
|
|
55
|
+
continue;
|
|
56
|
+
try {
|
|
57
|
+
const entry = JSON.parse(line);
|
|
58
|
+
// Session metadata line carries cwd
|
|
59
|
+
if (entry.type === "session") {
|
|
60
|
+
project = entry.cwd ?? project;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (entry.type !== "message")
|
|
64
|
+
continue;
|
|
65
|
+
const message = entry.message;
|
|
66
|
+
if (!message || message.role !== "user")
|
|
67
|
+
continue;
|
|
68
|
+
const text = contentToString(message.content);
|
|
69
|
+
if (!text)
|
|
70
|
+
continue;
|
|
71
|
+
const timestamp = typeof entry.timestamp === "string"
|
|
72
|
+
? entry.timestamp
|
|
73
|
+
: typeof message.timestamp === "number"
|
|
74
|
+
? new Date(message.timestamp).toISOString()
|
|
75
|
+
: undefined;
|
|
76
|
+
if (context.since && timestamp) {
|
|
77
|
+
const ts = new Date(timestamp);
|
|
78
|
+
if (ts < context.since)
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
yield {
|
|
82
|
+
text,
|
|
83
|
+
timestamp,
|
|
84
|
+
session: context.session,
|
|
85
|
+
project,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Skip malformed lines
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function contentToString(content) {
|
|
94
|
+
if (typeof content === "string")
|
|
95
|
+
return content;
|
|
96
|
+
if (Array.isArray(content)) {
|
|
97
|
+
const parts = content
|
|
98
|
+
.filter((p) => typeof p === "object" &&
|
|
99
|
+
p !== null &&
|
|
100
|
+
p.type === "text" &&
|
|
101
|
+
typeof p.text === "string")
|
|
102
|
+
.map((p) => p.text);
|
|
103
|
+
return parts.length > 0 ? parts.join(" ") : null;
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=pi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pi.js","sourceRoot":"","sources":["../../../src/adapters/pi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC;;;;;;;;;GASG;AAEH,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;AAEpE,MAAM,UAAU,SAAS;IACvB,OAAO;QACL,IAAI,EAAE,IAAI;QACV,KAAK,CAAC,CAAC,QAAQ,CAAC,OAAwB;YACtC,KAAK,CAAC,CAAC,cAAc,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;QAClD,CAAC;KACF,CAAC;AACJ,CAAC;AAED,KAAK,SAAS,CAAC,CAAC,cAAc,CAC5B,GAAW,EACX,OAAwB,EACxB,OAAgB;IAEhB,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAClC,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QACzD,IAAI,CAAC,SAAS;YAAE,SAAS;QAEzB,IAAI,SAAS,CAAC,WAAW,EAAE,EAAE,CAAC;YAC5B,KAAK,CAAC,CAAC,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,IAAI,KAAK,CAAC,CAAC;QAC7D,CAAC;aAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YACpC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YAC5C,KAAK,CAAC,CAAC,YAAY,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QAC7E,CAAC;IACH,CAAC;AACH,CAAC;AAED,KAAK,SAAS,CAAC,CAAC,YAAY,CAC1B,QAAgB,EAChB,OAA4D;IAE5D,MAAM,EAAE,GAAG,eAAe,CAAC;QACzB,KAAK,EAAE,gBAAgB,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;QACxD,SAAS,EAAE,QAAQ;KACpB,CAAC,CAAC;IAEH,IAAI,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAE9B,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,EAAE,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YAAE,SAAS;QAE3B,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,CAAC;YAE1C,oCAAoC;YACpC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC7B,OAAO,GAAG,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC;gBAC/B,SAAS;YACX,CAAC;YAED,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;gBAAE,SAAS;YAEvC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;YAC9B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM;gBAAE,SAAS;YAElD,MAAM,IAAI,GAAG,eAAe,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAC9C,IAAI,CAAC,IAAI;gBAAE,SAAS;YAEpB,MAAM,SAAS,GACb,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ;gBACjC,CAAC,CAAC,KAAK,CAAC,SAAS;gBACjB,CAAC,CAAC,OAAO,OAAO,CAAC,SAAS,KAAK,QAAQ;oBACrC,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE;oBAC3C,CAAC,CAAC,SAAS,CAAC;YAElB,IAAI,OAAO,CAAC,KAAK,IAAI,SAAS,EAAE,CAAC;gBAC/B,MAAM,EAAE,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC;gBAC/B,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK;oBAAE,SAAS;YACnC,CAAC;YAED,MAAM;gBACJ,IAAI;gBACJ,SAAS;gBACT,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,OAAO;aACR,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,uBAAuB;QACzB,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,OAAgB;IACvC,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAChD,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,OAAO;aAClB,MAAM,CACL,CAAC,CAAC,EAAuC,EAAE,CACzC,OAAO,CAAC,KAAK,QAAQ;YACrB,CAAC,KAAK,IAAI;YACV,CAAC,CAAC,IAAI,KAAK,MAAM;YACjB,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAC7B;aACA,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACtB,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACnD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|