@askexenow/exe-os 0.8.32 → 0.8.36
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/bin/backfill-conversations.js +332 -348
- package/dist/bin/backfill-responses.js +72 -12
- package/dist/bin/backfill-vectors.js +72 -12
- package/dist/bin/cleanup-stale-review-tasks.js +63 -3
- package/dist/bin/cli.js +1518 -1122
- package/dist/bin/exe-agent.js +4 -4
- package/dist/bin/exe-assign.js +80 -18
- package/dist/bin/exe-boot.js +408 -89
- package/dist/bin/exe-call.js +83 -24
- package/dist/bin/exe-dispatch.js +18 -10
- package/dist/bin/exe-doctor.js +63 -3
- package/dist/bin/exe-export-behaviors.js +64 -3
- package/dist/bin/exe-forget.js +69 -4
- package/dist/bin/exe-gateway.js +121 -36
- package/dist/bin/exe-heartbeat.js +77 -13
- package/dist/bin/exe-kill.js +64 -3
- package/dist/bin/exe-launch-agent.js +162 -35
- package/dist/bin/exe-link.js +946 -0
- package/dist/bin/exe-new-employee.js +121 -36
- package/dist/bin/exe-pending-messages.js +72 -7
- package/dist/bin/exe-pending-notifications.js +63 -3
- package/dist/bin/exe-pending-reviews.js +75 -10
- package/dist/bin/exe-rename.js +1287 -0
- package/dist/bin/exe-review.js +64 -4
- package/dist/bin/exe-search.js +79 -13
- package/dist/bin/exe-session-cleanup.js +91 -26
- package/dist/bin/exe-status.js +64 -4
- package/dist/bin/exe-team.js +64 -4
- package/dist/bin/git-sweep.js +71 -4
- package/dist/bin/graph-backfill.js +64 -3
- package/dist/bin/graph-export.js +64 -3
- package/dist/bin/install.js +3 -3
- package/dist/bin/scan-tasks.js +71 -4
- package/dist/bin/setup.js +156 -38
- package/dist/bin/shard-migrate.js +64 -3
- package/dist/bin/wiki-sync.js +64 -3
- package/dist/gateway/index.js +122 -37
- package/dist/hooks/bug-report-worker.js +209 -23
- package/dist/hooks/commit-complete.js +71 -4
- package/dist/hooks/error-recall.js +79 -13
- package/dist/hooks/ingest-worker.js +129 -43
- package/dist/hooks/instructions-loaded.js +71 -4
- package/dist/hooks/notification.js +71 -4
- package/dist/hooks/post-compact.js +71 -4
- package/dist/hooks/pre-compact.js +71 -4
- package/dist/hooks/pre-tool-use.js +413 -194
- package/dist/hooks/prompt-ingest-worker.js +82 -22
- package/dist/hooks/prompt-submit.js +103 -37
- package/dist/hooks/response-ingest-worker.js +87 -22
- package/dist/hooks/session-end.js +71 -4
- package/dist/hooks/session-start.js +79 -13
- package/dist/hooks/stop.js +71 -4
- package/dist/hooks/subagent-stop.js +71 -4
- package/dist/hooks/summary-worker.js +303 -50
- package/dist/index.js +134 -46
- package/dist/lib/cloud-sync.js +209 -15
- package/dist/lib/consolidation.js +4 -4
- package/dist/lib/database.js +64 -2
- package/dist/lib/device-registry.js +70 -3
- package/dist/lib/employee-templates.js +48 -22
- package/dist/lib/employees.js +34 -1
- package/dist/lib/exe-daemon.js +136 -53
- package/dist/lib/hybrid-search.js +79 -13
- package/dist/lib/identity-templates.js +57 -6
- package/dist/lib/identity.js +3 -3
- package/dist/lib/messaging.js +22 -14
- package/dist/lib/reminders.js +3 -3
- package/dist/lib/schedules.js +63 -3
- package/dist/lib/skill-learning.js +3 -3
- package/dist/lib/status-brief.js +63 -5
- package/dist/lib/store.js +64 -3
- package/dist/lib/task-router.js +4 -2
- package/dist/lib/tasks.js +48 -21
- package/dist/lib/tmux-routing.js +47 -20
- package/dist/mcp/server.js +727 -58
- package/dist/mcp/tools/complete-reminder.js +3 -3
- package/dist/mcp/tools/create-reminder.js +3 -3
- package/dist/mcp/tools/create-task.js +151 -24
- package/dist/mcp/tools/deactivate-behavior.js +3 -3
- package/dist/mcp/tools/list-reminders.js +3 -3
- package/dist/mcp/tools/list-tasks.js +17 -8
- package/dist/mcp/tools/send-message.js +24 -16
- package/dist/mcp/tools/update-task.js +25 -16
- package/dist/runtime/index.js +112 -24
- package/dist/tui/App.js +139 -36
- package/package.json +6 -2
- package/src/commands/exe/rename.md +12 -0
|
@@ -262,7 +262,7 @@ function listShards() {
|
|
|
262
262
|
}
|
|
263
263
|
async function ensureShardSchema(client) {
|
|
264
264
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
265
|
-
await client.execute("PRAGMA busy_timeout =
|
|
265
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
266
266
|
try {
|
|
267
267
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
268
268
|
} catch {
|
|
@@ -446,124 +446,79 @@ var init_shard_manager = __esm({
|
|
|
446
446
|
}
|
|
447
447
|
});
|
|
448
448
|
|
|
449
|
-
// src/lib/employees.ts
|
|
450
|
-
var employees_exports = {};
|
|
451
|
-
__export(employees_exports, {
|
|
452
|
-
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
453
|
-
addEmployee: () => addEmployee,
|
|
454
|
-
getEmployee: () => getEmployee,
|
|
455
|
-
loadEmployees: () => loadEmployees,
|
|
456
|
-
registerBinSymlinks: () => registerBinSymlinks,
|
|
457
|
-
saveEmployees: () => saveEmployees,
|
|
458
|
-
validateEmployeeName: () => validateEmployeeName
|
|
459
|
-
});
|
|
460
|
-
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
461
|
-
import { existsSync as existsSync5, symlinkSync, readlinkSync } from "fs";
|
|
462
|
-
import { execSync } from "child_process";
|
|
463
|
-
import path5 from "path";
|
|
464
|
-
function validateEmployeeName(name) {
|
|
465
|
-
if (!name) {
|
|
466
|
-
return { valid: false, error: "Name is required" };
|
|
467
|
-
}
|
|
468
|
-
if (name.length > 32) {
|
|
469
|
-
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
470
|
-
}
|
|
471
|
-
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
472
|
-
return {
|
|
473
|
-
valid: false,
|
|
474
|
-
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
return { valid: true };
|
|
478
|
-
}
|
|
479
|
-
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
480
|
-
if (!existsSync5(employeesPath)) {
|
|
481
|
-
return [];
|
|
482
|
-
}
|
|
483
|
-
const raw = await readFile3(employeesPath, "utf-8");
|
|
484
|
-
try {
|
|
485
|
-
return JSON.parse(raw);
|
|
486
|
-
} catch {
|
|
487
|
-
return [];
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
491
|
-
await mkdir3(path5.dirname(employeesPath), { recursive: true });
|
|
492
|
-
await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
493
|
-
}
|
|
494
|
-
function getEmployee(employees, name) {
|
|
495
|
-
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
496
|
-
}
|
|
497
|
-
function addEmployee(employees, employee) {
|
|
498
|
-
const normalized = { ...employee, name: employee.name.toLowerCase() };
|
|
499
|
-
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
500
|
-
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
501
|
-
}
|
|
502
|
-
return [...employees, normalized];
|
|
503
|
-
}
|
|
504
|
-
function registerBinSymlinks(name) {
|
|
505
|
-
const created = [];
|
|
506
|
-
const skipped = [];
|
|
507
|
-
const errors = [];
|
|
508
|
-
let exeBinPath;
|
|
509
|
-
try {
|
|
510
|
-
exeBinPath = execSync("which exe", { encoding: "utf-8" }).trim();
|
|
511
|
-
} catch {
|
|
512
|
-
errors.push("Could not find 'exe' in PATH");
|
|
513
|
-
return { created, skipped, errors };
|
|
514
|
-
}
|
|
515
|
-
const binDir = path5.dirname(exeBinPath);
|
|
516
|
-
let target;
|
|
517
|
-
try {
|
|
518
|
-
target = readlinkSync(exeBinPath);
|
|
519
|
-
} catch {
|
|
520
|
-
errors.push("Could not read 'exe' symlink");
|
|
521
|
-
return { created, skipped, errors };
|
|
522
|
-
}
|
|
523
|
-
for (const suffix of ["", "-opencode"]) {
|
|
524
|
-
const linkName = `${name}${suffix}`;
|
|
525
|
-
const linkPath = path5.join(binDir, linkName);
|
|
526
|
-
if (existsSync5(linkPath)) {
|
|
527
|
-
skipped.push(linkName);
|
|
528
|
-
continue;
|
|
529
|
-
}
|
|
530
|
-
try {
|
|
531
|
-
symlinkSync(target, linkPath);
|
|
532
|
-
created.push(linkName);
|
|
533
|
-
} catch (err) {
|
|
534
|
-
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
return { created, skipped, errors };
|
|
538
|
-
}
|
|
539
|
-
var EMPLOYEES_PATH;
|
|
540
|
-
var init_employees = __esm({
|
|
541
|
-
"src/lib/employees.ts"() {
|
|
542
|
-
"use strict";
|
|
543
|
-
init_config();
|
|
544
|
-
EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
|
|
545
|
-
}
|
|
546
|
-
});
|
|
547
|
-
|
|
548
449
|
// src/bin/backfill-conversations.ts
|
|
549
450
|
import crypto2 from "crypto";
|
|
550
451
|
import { createReadStream } from "fs";
|
|
551
452
|
import { readdir, stat } from "fs/promises";
|
|
552
|
-
import
|
|
453
|
+
import path5 from "path";
|
|
553
454
|
import { createInterface } from "readline";
|
|
554
455
|
import { homedir } from "os";
|
|
456
|
+
import { parseArgs } from "util";
|
|
555
457
|
|
|
556
458
|
// src/types/memory.ts
|
|
557
459
|
var EMBEDDING_DIM = 1024;
|
|
558
460
|
|
|
559
461
|
// src/lib/database.ts
|
|
560
462
|
import { createClient } from "@libsql/client";
|
|
463
|
+
|
|
464
|
+
// src/lib/db-retry.ts
|
|
465
|
+
var MAX_RETRIES = 3;
|
|
466
|
+
var BASE_DELAY_MS = 200;
|
|
467
|
+
var MAX_JITTER_MS = 300;
|
|
468
|
+
function isBusyError(err) {
|
|
469
|
+
if (err instanceof Error) {
|
|
470
|
+
const msg = err.message.toLowerCase();
|
|
471
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
472
|
+
}
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
function delay(ms) {
|
|
476
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
477
|
+
}
|
|
478
|
+
async function retryOnBusy(fn, label) {
|
|
479
|
+
let lastError;
|
|
480
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
481
|
+
try {
|
|
482
|
+
return await fn();
|
|
483
|
+
} catch (err) {
|
|
484
|
+
lastError = err;
|
|
485
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
486
|
+
throw err;
|
|
487
|
+
}
|
|
488
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
489
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
490
|
+
process.stderr.write(
|
|
491
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
492
|
+
`
|
|
493
|
+
);
|
|
494
|
+
await delay(backoff + jitter);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
throw lastError;
|
|
498
|
+
}
|
|
499
|
+
function wrapWithRetry(client) {
|
|
500
|
+
return new Proxy(client, {
|
|
501
|
+
get(target, prop, receiver) {
|
|
502
|
+
if (prop === "execute") {
|
|
503
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
504
|
+
}
|
|
505
|
+
if (prop === "batch") {
|
|
506
|
+
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
507
|
+
}
|
|
508
|
+
return Reflect.get(target, prop, receiver);
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// src/lib/database.ts
|
|
561
514
|
var _client = null;
|
|
515
|
+
var _resilientClient = null;
|
|
562
516
|
var initTurso = initDatabase;
|
|
563
517
|
async function initDatabase(config) {
|
|
564
518
|
if (_client) {
|
|
565
519
|
_client.close();
|
|
566
520
|
_client = null;
|
|
521
|
+
_resilientClient = null;
|
|
567
522
|
}
|
|
568
523
|
const opts = {
|
|
569
524
|
url: `file:${config.dbPath}`
|
|
@@ -572,17 +527,24 @@ async function initDatabase(config) {
|
|
|
572
527
|
opts.encryptionKey = config.encryptionKey;
|
|
573
528
|
}
|
|
574
529
|
_client = createClient(opts);
|
|
530
|
+
_resilientClient = wrapWithRetry(_client);
|
|
575
531
|
}
|
|
576
532
|
function getClient() {
|
|
533
|
+
if (!_resilientClient) {
|
|
534
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
535
|
+
}
|
|
536
|
+
return _resilientClient;
|
|
537
|
+
}
|
|
538
|
+
function getRawClient() {
|
|
577
539
|
if (!_client) {
|
|
578
540
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
579
541
|
}
|
|
580
542
|
return _client;
|
|
581
543
|
}
|
|
582
544
|
async function ensureSchema() {
|
|
583
|
-
const client =
|
|
545
|
+
const client = getRawClient();
|
|
584
546
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
585
|
-
await client.execute("PRAGMA busy_timeout =
|
|
547
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
586
548
|
try {
|
|
587
549
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
588
550
|
} catch {
|
|
@@ -1831,11 +1793,11 @@ async function connectEmbedDaemon() {
|
|
|
1831
1793
|
}
|
|
1832
1794
|
}
|
|
1833
1795
|
const start = Date.now();
|
|
1834
|
-
let
|
|
1796
|
+
let delay2 = 100;
|
|
1835
1797
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1836
|
-
await new Promise((r) => setTimeout(r,
|
|
1798
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
1837
1799
|
if (await connectToSocket()) return true;
|
|
1838
|
-
|
|
1800
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
1839
1801
|
}
|
|
1840
1802
|
return false;
|
|
1841
1803
|
}
|
|
@@ -1927,11 +1889,11 @@ async function embedViaClient(text, priority = "high") {
|
|
|
1927
1889
|
`);
|
|
1928
1890
|
killAndRespawnDaemon();
|
|
1929
1891
|
const start = Date.now();
|
|
1930
|
-
let
|
|
1892
|
+
let delay2 = 200;
|
|
1931
1893
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1932
|
-
await new Promise((r) => setTimeout(r,
|
|
1894
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
1933
1895
|
if (await connectToSocket()) break;
|
|
1934
|
-
|
|
1896
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
1935
1897
|
}
|
|
1936
1898
|
if (!_connected) return null;
|
|
1937
1899
|
}
|
|
@@ -1943,11 +1905,11 @@ async function embedViaClient(text, priority = "high") {
|
|
|
1943
1905
|
`);
|
|
1944
1906
|
killAndRespawnDaemon();
|
|
1945
1907
|
const start = Date.now();
|
|
1946
|
-
let
|
|
1908
|
+
let delay2 = 200;
|
|
1947
1909
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1948
|
-
await new Promise((r) => setTimeout(r,
|
|
1910
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
1949
1911
|
if (await connectToSocket()) break;
|
|
1950
|
-
|
|
1912
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
1951
1913
|
}
|
|
1952
1914
|
if (!_connected) return null;
|
|
1953
1915
|
const retry = await sendRequest([text], priority);
|
|
@@ -1973,9 +1935,11 @@ function isMainModule(importMetaUrl) {
|
|
|
1973
1935
|
}
|
|
1974
1936
|
|
|
1975
1937
|
// src/bin/backfill-conversations.ts
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1938
|
+
var TOOL_NAME = "backfill-conversation";
|
|
1939
|
+
var MIN_MESSAGES = 3;
|
|
1940
|
+
var MAX_SUMMARY_LENGTH = 4e3;
|
|
1941
|
+
async function findJsonlFiles(sinceDate, projectFilter) {
|
|
1942
|
+
const projectsDir = path5.join(homedir(), ".claude", "projects");
|
|
1979
1943
|
const files = [];
|
|
1980
1944
|
async function walk(dir) {
|
|
1981
1945
|
let entries;
|
|
@@ -1985,61 +1949,67 @@ async function findJsonlFiles(cutoffMs) {
|
|
|
1985
1949
|
return;
|
|
1986
1950
|
}
|
|
1987
1951
|
for (const entry of entries) {
|
|
1988
|
-
const full =
|
|
1952
|
+
const full = path5.join(dir, entry.name);
|
|
1989
1953
|
if (entry.isDirectory()) {
|
|
1954
|
+
if (entry.name === "subagents" || entry.name === "tool-results") continue;
|
|
1990
1955
|
await walk(full);
|
|
1991
1956
|
} else if (entry.name.endsWith(".jsonl")) {
|
|
1992
1957
|
try {
|
|
1993
1958
|
const s = await stat(full);
|
|
1994
|
-
if (s.mtimeMs
|
|
1959
|
+
if (sinceDate && s.mtimeMs < sinceDate.getTime()) continue;
|
|
1960
|
+
files.push(full);
|
|
1995
1961
|
} catch {
|
|
1996
1962
|
}
|
|
1997
1963
|
}
|
|
1998
1964
|
}
|
|
1999
1965
|
}
|
|
2000
|
-
|
|
1966
|
+
if (projectFilter) {
|
|
1967
|
+
let projectDirs;
|
|
1968
|
+
try {
|
|
1969
|
+
projectDirs = await readdir(projectsDir, { withFileTypes: true });
|
|
1970
|
+
} catch {
|
|
1971
|
+
return files;
|
|
1972
|
+
}
|
|
1973
|
+
for (const entry of projectDirs) {
|
|
1974
|
+
if (!entry.isDirectory()) continue;
|
|
1975
|
+
const decoded = decodeProjectDir(entry.name);
|
|
1976
|
+
if (decoded.toLowerCase().includes(projectFilter.toLowerCase())) {
|
|
1977
|
+
await walk(path5.join(projectsDir, entry.name));
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
} else {
|
|
1981
|
+
await walk(projectsDir);
|
|
1982
|
+
}
|
|
2001
1983
|
return files;
|
|
2002
1984
|
}
|
|
2003
|
-
function
|
|
2004
|
-
const projectsDir = path6.join(homedir(), ".claude", "projects");
|
|
2005
|
-
const relative = path6.relative(projectsDir, filePath);
|
|
2006
|
-
const projectDir = relative.split(path6.sep)[0] ?? relative;
|
|
1985
|
+
function decodeProjectDir(dirName) {
|
|
2007
1986
|
const homeEncoded = homedir().replaceAll("/", "-");
|
|
2008
|
-
if (
|
|
2009
|
-
return
|
|
1987
|
+
if (dirName.startsWith(homeEncoded + "-")) {
|
|
1988
|
+
return dirName.slice(homeEncoded.length + 1);
|
|
2010
1989
|
}
|
|
2011
|
-
if (
|
|
2012
|
-
return
|
|
1990
|
+
if (dirName === homeEncoded) return "home";
|
|
1991
|
+
return dirName;
|
|
2013
1992
|
}
|
|
2014
|
-
function
|
|
2015
|
-
|
|
2016
|
-
const
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
texts.push(block.text);
|
|
2020
|
-
}
|
|
2021
|
-
}
|
|
2022
|
-
return texts.join("\n");
|
|
2023
|
-
}
|
|
2024
|
-
function extractUserText(content) {
|
|
2025
|
-
if (typeof content === "string") return content;
|
|
2026
|
-
const texts = [];
|
|
2027
|
-
for (const block of content) {
|
|
2028
|
-
if (block.type === "text" && block.text) {
|
|
2029
|
-
texts.push(block.text);
|
|
2030
|
-
}
|
|
2031
|
-
}
|
|
2032
|
-
return texts.join("\n");
|
|
1993
|
+
function projectNameFromPath(filePath) {
|
|
1994
|
+
const projectsDir = path5.join(homedir(), ".claude", "projects");
|
|
1995
|
+
const relative = path5.relative(projectsDir, filePath);
|
|
1996
|
+
const projectDir = relative.split(path5.sep)[0] ?? "unknown";
|
|
1997
|
+
return decodeProjectDir(projectDir);
|
|
2033
1998
|
}
|
|
2034
|
-
async function
|
|
2035
|
-
const
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
1999
|
+
async function parseConversation(filePath) {
|
|
2000
|
+
const conv = {
|
|
2001
|
+
sessionId: path5.basename(filePath, ".jsonl"),
|
|
2002
|
+
projectName: projectNameFromPath(filePath),
|
|
2003
|
+
cwd: void 0,
|
|
2004
|
+
startTime: void 0,
|
|
2005
|
+
endTime: void 0,
|
|
2006
|
+
userMessages: [],
|
|
2007
|
+
toolCounts: {},
|
|
2008
|
+
filesTouched: /* @__PURE__ */ new Set(),
|
|
2009
|
+
errorCount: 0,
|
|
2010
|
+
totalMessages: 0,
|
|
2011
|
+
agentId: "default"
|
|
2012
|
+
};
|
|
2043
2013
|
const rl = createInterface({
|
|
2044
2014
|
input: createReadStream(filePath, { encoding: "utf8" }),
|
|
2045
2015
|
crlfDelay: Infinity
|
|
@@ -2052,251 +2022,265 @@ async function extractConversationPairs(filePath, projectFilter) {
|
|
|
2052
2022
|
} catch {
|
|
2053
2023
|
continue;
|
|
2054
2024
|
}
|
|
2055
|
-
if (entry.cwd
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2025
|
+
if (entry.cwd && typeof entry.cwd === "string") {
|
|
2026
|
+
conv.cwd = entry.cwd;
|
|
2027
|
+
}
|
|
2028
|
+
const ts = entry.timestamp;
|
|
2029
|
+
if (ts) {
|
|
2030
|
+
if (!conv.startTime || ts < conv.startTime) conv.startTime = ts;
|
|
2031
|
+
if (!conv.endTime || ts > conv.endTime) conv.endTime = ts;
|
|
2032
|
+
}
|
|
2033
|
+
const entryType = entry.type;
|
|
2034
|
+
if (entryType === "user") {
|
|
2035
|
+
conv.totalMessages++;
|
|
2036
|
+
const message = entry.message;
|
|
2037
|
+
if (message?.content) {
|
|
2038
|
+
const text = extractUserText(message.content);
|
|
2039
|
+
if (text && text.length > 10) {
|
|
2040
|
+
conv.userMessages.push(text);
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
} else if (entryType === "assistant") {
|
|
2044
|
+
conv.totalMessages++;
|
|
2045
|
+
const message = entry.message;
|
|
2046
|
+
if (message?.content && Array.isArray(message.content)) {
|
|
2047
|
+
for (const block of message.content) {
|
|
2048
|
+
if (typeof block !== "object" || block === null) continue;
|
|
2049
|
+
const b = block;
|
|
2050
|
+
if (b.type === "tool_use") {
|
|
2051
|
+
const toolName = b.name;
|
|
2052
|
+
conv.toolCounts[toolName] = (conv.toolCounts[toolName] || 0) + 1;
|
|
2053
|
+
const input = b.input;
|
|
2054
|
+
if (input?.file_path && typeof input.file_path === "string") {
|
|
2055
|
+
if (toolName === "Write" || toolName === "Edit") {
|
|
2056
|
+
conv.filesTouched.add(input.file_path);
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2065
2061
|
}
|
|
2066
|
-
} else if (entry.type === "assistant" && entry.message?.content) {
|
|
2067
|
-
const assistantText = extractAssistantText(entry.message.content);
|
|
2068
|
-
if (!assistantText.trim()) continue;
|
|
2069
|
-
const resolvedProject = fileCwd ? path6.basename(fileCwd) : fallbackProject;
|
|
2070
|
-
const userText = pendingUser?.text ?? "";
|
|
2071
|
-
const timestamp = entry.timestamp ?? pendingUser?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2072
|
-
pairs.push({
|
|
2073
|
-
userText,
|
|
2074
|
-
assistantText,
|
|
2075
|
-
timestamp,
|
|
2076
|
-
sessionId: fileSessionId || path6.basename(filePath, ".jsonl"),
|
|
2077
|
-
project: resolvedProject,
|
|
2078
|
-
cwd: fileCwd || fallbackProject
|
|
2079
|
-
});
|
|
2080
|
-
pendingUser = null;
|
|
2081
2062
|
}
|
|
2082
2063
|
}
|
|
2083
|
-
|
|
2064
|
+
if (conv.cwd) {
|
|
2065
|
+
conv.projectName = path5.basename(conv.cwd);
|
|
2066
|
+
const worktreeMatch = conv.cwd.match(/\.worktrees\/([^/]+)/);
|
|
2067
|
+
if (worktreeMatch?.[1]) {
|
|
2068
|
+
conv.agentId = worktreeMatch[1];
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
return conv;
|
|
2084
2072
|
}
|
|
2085
|
-
function
|
|
2086
|
-
|
|
2073
|
+
function extractUserText(content) {
|
|
2074
|
+
if (typeof content === "string") return content;
|
|
2075
|
+
if (Array.isArray(content)) {
|
|
2076
|
+
const parts = [];
|
|
2077
|
+
for (const block of content) {
|
|
2078
|
+
if (typeof block === "string") {
|
|
2079
|
+
parts.push(block);
|
|
2080
|
+
} else if (typeof block === "object" && block !== null) {
|
|
2081
|
+
const b = block;
|
|
2082
|
+
if (b.type === "text" && typeof b.text === "string") {
|
|
2083
|
+
parts.push(b.text);
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
return parts.join("\n");
|
|
2088
|
+
}
|
|
2089
|
+
return "";
|
|
2087
2090
|
}
|
|
2088
|
-
|
|
2091
|
+
function buildSummary(conv) {
|
|
2092
|
+
const parts = [];
|
|
2093
|
+
parts.push(`Session: ${conv.sessionId}`);
|
|
2094
|
+
parts.push(`Project: ${conv.projectName}`);
|
|
2095
|
+
if (conv.startTime) {
|
|
2096
|
+
parts.push(`Time: ${conv.startTime}${conv.endTime ? ` \u2192 ${conv.endTime}` : ""}`);
|
|
2097
|
+
}
|
|
2098
|
+
parts.push(`Messages: ${conv.totalMessages}`);
|
|
2099
|
+
if (conv.agentId !== "default") {
|
|
2100
|
+
parts.push(`Agent: ${conv.agentId}`);
|
|
2101
|
+
}
|
|
2102
|
+
parts.push("");
|
|
2103
|
+
if (conv.userMessages.length > 0) {
|
|
2104
|
+
parts.push("## What was asked");
|
|
2105
|
+
const prompts = conv.userMessages.slice(0, 5);
|
|
2106
|
+
for (const prompt of prompts) {
|
|
2107
|
+
const truncated = prompt.length > 300 ? prompt.slice(0, 300) + "..." : prompt;
|
|
2108
|
+
const cleaned = truncated.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
|
|
2109
|
+
if (cleaned) parts.push(`- ${cleaned}`);
|
|
2110
|
+
}
|
|
2111
|
+
if (conv.userMessages.length > 5) {
|
|
2112
|
+
parts.push(`- ... and ${conv.userMessages.length - 5} more prompts`);
|
|
2113
|
+
}
|
|
2114
|
+
parts.push("");
|
|
2115
|
+
}
|
|
2116
|
+
const toolEntries = Object.entries(conv.toolCounts).sort((a, b) => b[1] - a[1]);
|
|
2117
|
+
if (toolEntries.length > 0) {
|
|
2118
|
+
parts.push("## Tools used");
|
|
2119
|
+
for (const [tool, count] of toolEntries) {
|
|
2120
|
+
parts.push(`- ${tool}: ${count}`);
|
|
2121
|
+
}
|
|
2122
|
+
parts.push("");
|
|
2123
|
+
}
|
|
2124
|
+
if (conv.filesTouched.size > 0) {
|
|
2125
|
+
parts.push("## Files modified");
|
|
2126
|
+
const fileList = [...conv.filesTouched].sort();
|
|
2127
|
+
const shown = fileList.slice(0, 20);
|
|
2128
|
+
for (const f of shown) {
|
|
2129
|
+
parts.push(`- ${f}`);
|
|
2130
|
+
}
|
|
2131
|
+
if (fileList.length > 20) {
|
|
2132
|
+
parts.push(`- ... and ${fileList.length - 20} more files`);
|
|
2133
|
+
}
|
|
2134
|
+
parts.push("");
|
|
2135
|
+
}
|
|
2136
|
+
if (conv.errorCount > 0) {
|
|
2137
|
+
parts.push(`Errors: ${conv.errorCount}`);
|
|
2138
|
+
}
|
|
2139
|
+
let summary = parts.join("\n");
|
|
2140
|
+
if (summary.length > MAX_SUMMARY_LENGTH) {
|
|
2141
|
+
summary = summary.slice(0, MAX_SUMMARY_LENGTH);
|
|
2142
|
+
}
|
|
2143
|
+
return summary;
|
|
2144
|
+
}
|
|
2145
|
+
async function loadExistingSourcePaths() {
|
|
2089
2146
|
const client = getClient();
|
|
2090
|
-
const
|
|
2147
|
+
const paths = /* @__PURE__ */ new Set();
|
|
2091
2148
|
let offset = 0;
|
|
2092
2149
|
const batchSize = 500;
|
|
2093
2150
|
while (true) {
|
|
2094
2151
|
const result = await client.execute({
|
|
2095
|
-
sql: `SELECT
|
|
2096
|
-
WHERE
|
|
2152
|
+
sql: `SELECT source_path FROM memories
|
|
2153
|
+
WHERE tool_name = ? AND source_path IS NOT NULL
|
|
2097
2154
|
ORDER BY id LIMIT ? OFFSET ?`,
|
|
2098
|
-
args: [
|
|
2155
|
+
args: [TOOL_NAME, batchSize, offset]
|
|
2099
2156
|
});
|
|
2100
2157
|
if (result.rows.length === 0) break;
|
|
2101
2158
|
for (const row of result.rows) {
|
|
2102
|
-
|
|
2103
|
-
hashPair(
|
|
2104
|
-
row.content_text ?? "",
|
|
2105
|
-
row.agent_response ?? ""
|
|
2106
|
-
)
|
|
2107
|
-
);
|
|
2159
|
+
paths.add(row.source_path);
|
|
2108
2160
|
}
|
|
2109
2161
|
offset += batchSize;
|
|
2110
2162
|
}
|
|
2111
|
-
return
|
|
2112
|
-
}
|
|
2113
|
-
var MAX_CONTENT_LENGTH = 8e3;
|
|
2114
|
-
var MIN_ASSISTANT_LENGTH = 50;
|
|
2115
|
-
async function storePair(pair, daemonConnected, stats, agentId) {
|
|
2116
|
-
const client = getClient();
|
|
2117
|
-
const id = crypto2.randomUUID();
|
|
2118
|
-
const userTrunc = pair.userText.length > MAX_CONTENT_LENGTH ? pair.userText.slice(0, MAX_CONTENT_LENGTH) : pair.userText;
|
|
2119
|
-
const assistTrunc = pair.assistantText.length > MAX_CONTENT_LENGTH ? pair.assistantText.slice(0, MAX_CONTENT_LENGTH) : pair.assistantText;
|
|
2120
|
-
await client.execute({
|
|
2121
|
-
sql: `INSERT INTO conversations
|
|
2122
|
-
(id, platform, external_id, sender_id, sender_name, sender_phone, sender_email,
|
|
2123
|
-
recipient_id, channel_id, thread_id, reply_to_id,
|
|
2124
|
-
content_text, content_media, agent_response, agent_name,
|
|
2125
|
-
timestamp, ingested_at)
|
|
2126
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2127
|
-
args: [
|
|
2128
|
-
id,
|
|
2129
|
-
"claude-code",
|
|
2130
|
-
null,
|
|
2131
|
-
"user",
|
|
2132
|
-
"user",
|
|
2133
|
-
null,
|
|
2134
|
-
null,
|
|
2135
|
-
null,
|
|
2136
|
-
pair.project,
|
|
2137
|
-
pair.sessionId,
|
|
2138
|
-
null,
|
|
2139
|
-
userTrunc,
|
|
2140
|
-
null,
|
|
2141
|
-
assistTrunc,
|
|
2142
|
-
"claude",
|
|
2143
|
-
pair.timestamp,
|
|
2144
|
-
(/* @__PURE__ */ new Date()).toISOString()
|
|
2145
|
-
]
|
|
2146
|
-
});
|
|
2147
|
-
stats.conversationsStored++;
|
|
2148
|
-
const rawText = [
|
|
2149
|
-
`[claude-code] Conversation in ${pair.project}`,
|
|
2150
|
-
userTrunc ? `User: ${userTrunc}` : null,
|
|
2151
|
-
`Assistant: ${assistTrunc}`
|
|
2152
|
-
].filter(Boolean).join("\n");
|
|
2153
|
-
let vector = null;
|
|
2154
|
-
if (daemonConnected) {
|
|
2155
|
-
try {
|
|
2156
|
-
vector = await embedViaClient(rawText, "low");
|
|
2157
|
-
if (!vector) stats.embedFailed++;
|
|
2158
|
-
} catch {
|
|
2159
|
-
stats.embedFailed++;
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
await writeMemory({
|
|
2163
|
-
id: crypto2.randomUUID(),
|
|
2164
|
-
agent_id: agentId,
|
|
2165
|
-
agent_role: "coo",
|
|
2166
|
-
session_id: pair.sessionId,
|
|
2167
|
-
timestamp: pair.timestamp,
|
|
2168
|
-
tool_name: "ConversationBackfill",
|
|
2169
|
-
project_name: pair.project,
|
|
2170
|
-
has_error: false,
|
|
2171
|
-
raw_text: rawText,
|
|
2172
|
-
vector,
|
|
2173
|
-
importance: 3
|
|
2174
|
-
});
|
|
2175
|
-
stats.memoriesStored++;
|
|
2163
|
+
return paths;
|
|
2176
2164
|
}
|
|
2177
2165
|
async function backfillConversations(options) {
|
|
2178
2166
|
const stats = {
|
|
2179
2167
|
filesScanned: 0,
|
|
2180
2168
|
conversationsStored: 0,
|
|
2181
|
-
memoriesStored: 0,
|
|
2182
2169
|
skippedDedup: 0,
|
|
2183
|
-
|
|
2170
|
+
skippedTooShort: 0,
|
|
2184
2171
|
embedFailed: 0
|
|
2185
2172
|
};
|
|
2186
|
-
const
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
try {
|
|
2190
|
-
const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
2191
|
-
const employees = await loadEmployees2();
|
|
2192
|
-
const coo = employees.find((e) => e.role === "COO");
|
|
2193
|
-
if (coo) cooAgentId = coo.name.toLowerCase();
|
|
2194
|
-
} catch {
|
|
2173
|
+
const sinceDate = options.since ? new Date(options.since) : void 0;
|
|
2174
|
+
if (sinceDate && isNaN(sinceDate.getTime())) {
|
|
2175
|
+
throw new Error(`Invalid --since date: ${options.since}`);
|
|
2195
2176
|
}
|
|
2196
|
-
process.stderr.write(
|
|
2197
|
-
|
|
2177
|
+
process.stderr.write("[backfill-conversations] Initializing store...\n");
|
|
2178
|
+
await initStore();
|
|
2179
|
+
let daemonConnected = false;
|
|
2198
2180
|
if (!options.dryRun) {
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
sql: "SELECT COUNT(*) as cnt FROM memories WHERE agent_id = 'backfill' OR (tool_name = 'ConversationBackfill' AND agent_id != ?)",
|
|
2205
|
-
args: [cooAgentId]
|
|
2206
|
-
});
|
|
2207
|
-
const count = Number(old.rows[0]?.cnt ?? 0);
|
|
2208
|
-
if (count > 0) {
|
|
2209
|
-
await client.execute({
|
|
2210
|
-
sql: "UPDATE memories SET agent_id = ?, agent_role = 'coo' WHERE agent_id = 'backfill' OR (tool_name = 'ConversationBackfill' AND agent_id != ?)",
|
|
2211
|
-
args: [cooAgentId, cooAgentId]
|
|
2212
|
-
});
|
|
2213
|
-
process.stderr.write(`[backfill-conversations] Migrated ${count} records \u2192 agent_id='${cooAgentId}'
|
|
2214
|
-
`);
|
|
2215
|
-
}
|
|
2216
|
-
} catch {
|
|
2181
|
+
daemonConnected = await connectEmbedDaemon();
|
|
2182
|
+
if (!daemonConnected) {
|
|
2183
|
+
process.stderr.write(
|
|
2184
|
+
"[backfill-conversations] WARNING: Daemon unavailable \u2014 vectors will be NULL (backfill-vectors can fix later)\n"
|
|
2185
|
+
);
|
|
2217
2186
|
}
|
|
2218
2187
|
}
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
"[backfill-conversations] WARNING: Daemon unavailable \u2014 vectors will be NULL (backfill-vectors can fix later)\n"
|
|
2223
|
-
);
|
|
2224
|
-
}
|
|
2225
|
-
let seenHashes = /* @__PURE__ */ new Set();
|
|
2226
|
-
if (!options.dryRun) {
|
|
2227
|
-
process.stderr.write("[backfill-conversations] Loading existing hashes for dedup...\n");
|
|
2228
|
-
seenHashes = await loadExistingHashes(cutoffIso);
|
|
2229
|
-
process.stderr.write(`[backfill-conversations] ${seenHashes.size} existing conversations loaded
|
|
2188
|
+
process.stderr.write("[backfill-conversations] Loading already-ingested conversations...\n");
|
|
2189
|
+
const existingPaths = options.dryRun ? /* @__PURE__ */ new Set() : await loadExistingSourcePaths();
|
|
2190
|
+
process.stderr.write(`[backfill-conversations] ${existingPaths.size} conversations already ingested
|
|
2230
2191
|
`);
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
process.stderr.write(`[backfill-conversations] Found ${files.length} JSONL files
|
|
2192
|
+
const files = await findJsonlFiles(sinceDate, options.project);
|
|
2193
|
+
process.stderr.write(`[backfill-conversations] Found ${files.length} JSONL files to process
|
|
2234
2194
|
`);
|
|
2195
|
+
process.env.EXE_EMBED_PRIORITY = "low";
|
|
2235
2196
|
for (const file of files) {
|
|
2236
|
-
const pairs = await extractConversationPairs(file, options.projectFilter);
|
|
2237
2197
|
stats.filesScanned++;
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2198
|
+
if (existingPaths.has(file)) {
|
|
2199
|
+
stats.skippedDedup++;
|
|
2200
|
+
continue;
|
|
2201
|
+
}
|
|
2202
|
+
const conv = await parseConversation(file);
|
|
2203
|
+
if (conv.totalMessages < MIN_MESSAGES) {
|
|
2204
|
+
stats.skippedTooShort++;
|
|
2205
|
+
continue;
|
|
2206
|
+
}
|
|
2207
|
+
const summary = buildSummary(conv);
|
|
2208
|
+
if (options.dryRun) {
|
|
2209
|
+
process.stdout.write(`
|
|
2210
|
+
\u2500\u2500\u2500 ${file} \u2500\u2500\u2500
|
|
2211
|
+
`);
|
|
2212
|
+
process.stdout.write(`Project: ${conv.projectName} | Messages: ${conv.totalMessages}`);
|
|
2213
|
+
process.stdout.write(` | Tools: ${Object.keys(conv.toolCounts).length}`);
|
|
2214
|
+
process.stdout.write(` | Files: ${conv.filesTouched.size}
|
|
2215
|
+
`);
|
|
2216
|
+
const firstPrompt = conv.userMessages[0];
|
|
2217
|
+
if (firstPrompt) {
|
|
2218
|
+
process.stdout.write(`First prompt: ${firstPrompt.slice(0, 120)}
|
|
2219
|
+
`);
|
|
2247
2220
|
}
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2221
|
+
stats.conversationsStored++;
|
|
2222
|
+
continue;
|
|
2223
|
+
}
|
|
2224
|
+
let vector = null;
|
|
2225
|
+
if (daemonConnected) {
|
|
2226
|
+
try {
|
|
2227
|
+
vector = await embedViaClient(summary, "low");
|
|
2228
|
+
if (!vector) stats.embedFailed++;
|
|
2229
|
+
} catch {
|
|
2230
|
+
stats.embedFailed++;
|
|
2254
2231
|
}
|
|
2255
2232
|
}
|
|
2256
|
-
|
|
2233
|
+
await writeMemory({
|
|
2234
|
+
id: crypto2.randomUUID(),
|
|
2235
|
+
agent_id: conv.agentId,
|
|
2236
|
+
agent_role: conv.agentId === "exe" ? "COO" : "specialist",
|
|
2237
|
+
session_id: conv.sessionId,
|
|
2238
|
+
timestamp: conv.startTime ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2239
|
+
tool_name: TOOL_NAME,
|
|
2240
|
+
project_name: conv.projectName,
|
|
2241
|
+
has_error: conv.errorCount > 0,
|
|
2242
|
+
raw_text: summary,
|
|
2243
|
+
vector,
|
|
2244
|
+
source_path: file,
|
|
2245
|
+
source_type: "conversation"
|
|
2246
|
+
});
|
|
2247
|
+
existingPaths.add(file);
|
|
2248
|
+
stats.conversationsStored++;
|
|
2249
|
+
if (stats.filesScanned % 50 === 0) {
|
|
2257
2250
|
process.stderr.write(
|
|
2258
2251
|
`[backfill-conversations] Progress: ${stats.filesScanned}/${files.length} files, ${stats.conversationsStored} stored
|
|
2259
2252
|
`
|
|
2260
2253
|
);
|
|
2261
|
-
|
|
2254
|
+
await flushBatch();
|
|
2262
2255
|
}
|
|
2263
2256
|
}
|
|
2264
|
-
if (!options.dryRun)
|
|
2257
|
+
if (!options.dryRun) {
|
|
2258
|
+
await flushBatch();
|
|
2259
|
+
}
|
|
2265
2260
|
process.stderr.write(
|
|
2266
|
-
`[backfill-conversations] Done.
|
|
2261
|
+
`[backfill-conversations] Done. Scanned: ${stats.filesScanned}, Stored: ${stats.conversationsStored}, Dedup: ${stats.skippedDedup}, TooShort: ${stats.skippedTooShort}, EmbedFail: ${stats.embedFailed}
|
|
2267
2262
|
`
|
|
2268
2263
|
);
|
|
2269
2264
|
return stats;
|
|
2270
2265
|
}
|
|
2271
|
-
function parseArgs(argv) {
|
|
2272
|
-
let days = 30;
|
|
2273
|
-
let projectFilter;
|
|
2274
|
-
let dryRun = false;
|
|
2275
|
-
for (let i = 0; i < argv.length; i++) {
|
|
2276
|
-
switch (argv[i]) {
|
|
2277
|
-
case "--days":
|
|
2278
|
-
days = parseInt(argv[++i] ?? "30", 10) || 30;
|
|
2279
|
-
break;
|
|
2280
|
-
case "--project":
|
|
2281
|
-
projectFilter = argv[++i] ?? void 0;
|
|
2282
|
-
break;
|
|
2283
|
-
case "--dry-run":
|
|
2284
|
-
dryRun = true;
|
|
2285
|
-
break;
|
|
2286
|
-
}
|
|
2287
|
-
}
|
|
2288
|
-
return { days, projectFilter, dryRun };
|
|
2289
|
-
}
|
|
2290
2266
|
if (isMainModule(import.meta.url)) {
|
|
2291
|
-
const
|
|
2292
|
-
|
|
2267
|
+
const { values } = parseArgs({
|
|
2268
|
+
options: {
|
|
2269
|
+
since: { type: "string" },
|
|
2270
|
+
project: { type: "string" },
|
|
2271
|
+
"dry-run": { type: "boolean", default: false }
|
|
2272
|
+
},
|
|
2273
|
+
strict: true
|
|
2274
|
+
});
|
|
2275
|
+
backfillConversations({
|
|
2276
|
+
since: values.since,
|
|
2277
|
+
project: values.project,
|
|
2278
|
+
dryRun: values["dry-run"]
|
|
2279
|
+
}).then((result) => {
|
|
2293
2280
|
console.log(JSON.stringify(result, null, 2));
|
|
2294
2281
|
process.exit(0);
|
|
2295
2282
|
}).catch((err) => {
|
|
2296
|
-
console.error(
|
|
2297
|
-
"Backfill failed:",
|
|
2298
|
-
err instanceof Error ? err.message : String(err)
|
|
2299
|
-
);
|
|
2283
|
+
console.error("Backfill failed:", err instanceof Error ? err.message : String(err));
|
|
2300
2284
|
process.exit(1);
|
|
2301
2285
|
});
|
|
2302
2286
|
}
|