@c3-oss/prosa 0.2.0 → 0.3.1
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/prosa.js +368 -216
- package/dist/bin/prosa.js.map +1 -1
- package/dist/cli/main.js +368 -216
- package/dist/cli/main.js.map +1 -1
- package/dist/index.d.ts +59 -7
- package/dist/index.js +2273 -2136
- package/dist/index.js.map +1 -1
- package/package.json +18 -2
package/dist/bin/prosa.js
CHANGED
|
@@ -12,8 +12,8 @@ var __export = (target, all) => {
|
|
|
12
12
|
|
|
13
13
|
// src/core/db.ts
|
|
14
14
|
import Database from "better-sqlite3";
|
|
15
|
-
function openDb(
|
|
16
|
-
const db = new Database(
|
|
15
|
+
function openDb(path20) {
|
|
16
|
+
const db = new Database(path20);
|
|
17
17
|
db.pragma("journal_mode = WAL");
|
|
18
18
|
db.pragma("foreign_keys = ON");
|
|
19
19
|
db.pragma("synchronous = NORMAL");
|
|
@@ -48,6 +48,15 @@ var init_db = __esm({
|
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
+
// src/core/errors.ts
|
|
52
|
+
var getErrorMessage;
|
|
53
|
+
var init_errors = __esm({
|
|
54
|
+
"src/core/errors.ts"() {
|
|
55
|
+
"use strict";
|
|
56
|
+
getErrorMessage = (err) => err instanceof Error ? err.message : String(err);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
51
60
|
// src/core/cas/compress.ts
|
|
52
61
|
import { compress as zstdCompress, decompress as zstdDecompress } from "zstd-napi";
|
|
53
62
|
function compressBytes(input) {
|
|
@@ -393,7 +402,7 @@ function rebuildFts5Index(bundle) {
|
|
|
393
402
|
status: "failed",
|
|
394
403
|
sourceDocCount: countSearchDocs(bundle),
|
|
395
404
|
indexedDocCount: countFts5Docs(bundle),
|
|
396
|
-
errorMessage:
|
|
405
|
+
errorMessage: getErrorMessage(error)
|
|
397
406
|
});
|
|
398
407
|
throw error;
|
|
399
408
|
}
|
|
@@ -466,7 +475,7 @@ async function rebuildTantivyIndex(bundle) {
|
|
|
466
475
|
status: "failed",
|
|
467
476
|
sourceDocCount: countSearchDocs(bundle),
|
|
468
477
|
indexedDocCount: 0,
|
|
469
|
-
errorMessage:
|
|
478
|
+
errorMessage: getErrorMessage(error)
|
|
470
479
|
});
|
|
471
480
|
throw error;
|
|
472
481
|
}
|
|
@@ -514,6 +523,7 @@ var init_indexing = __esm({
|
|
|
514
523
|
"src/services/indexing.ts"() {
|
|
515
524
|
"use strict";
|
|
516
525
|
init_db();
|
|
526
|
+
init_errors();
|
|
517
527
|
FTS5_TRIGGER_SQL = `
|
|
518
528
|
CREATE TRIGGER IF NOT EXISTS search_docs_ai AFTER INSERT ON search_docs BEGIN
|
|
519
529
|
INSERT INTO search_docs_fts(rowid, text, role, tool_name, field_kind)
|
|
@@ -535,6 +545,16 @@ END;
|
|
|
535
545
|
}
|
|
536
546
|
});
|
|
537
547
|
|
|
548
|
+
// src/core/limits.ts
|
|
549
|
+
function clampLimit(value, opts) {
|
|
550
|
+
return Math.max(opts.min ?? 1, Math.min(opts.max, value ?? opts.fallback));
|
|
551
|
+
}
|
|
552
|
+
var init_limits = __esm({
|
|
553
|
+
"src/core/limits.ts"() {
|
|
554
|
+
"use strict";
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
|
|
538
558
|
// src/services/search.ts
|
|
539
559
|
import { existsSync } from "fs";
|
|
540
560
|
import { createRequire } from "module";
|
|
@@ -545,7 +565,7 @@ function searchFullText(bundle, options) {
|
|
|
545
565
|
if (options.engine === "tantivy") {
|
|
546
566
|
return searchTantivy(bundle, options);
|
|
547
567
|
}
|
|
548
|
-
const limit =
|
|
568
|
+
const limit = clampLimit(options.limit, { max: 500, fallback: 50 });
|
|
549
569
|
const sql = `
|
|
550
570
|
SELECT d.doc_id,
|
|
551
571
|
d.entity_type,
|
|
@@ -576,7 +596,7 @@ function searchTantivy(bundle, options) {
|
|
|
576
596
|
`tantivy index is ${status?.status ?? "missing"}; run \`prosa index tantivy\` first`
|
|
577
597
|
);
|
|
578
598
|
}
|
|
579
|
-
const limit =
|
|
599
|
+
const limit = clampLimit(options.limit, { max: 500, fallback: 50 });
|
|
580
600
|
const queryText = options.query.trim();
|
|
581
601
|
if (!queryText) return [];
|
|
582
602
|
const tantivy = requireTantivy();
|
|
@@ -610,9 +630,7 @@ function requireTantivy() {
|
|
|
610
630
|
try {
|
|
611
631
|
return require2("@oxdev03/node-tantivy-binding");
|
|
612
632
|
} catch (error) {
|
|
613
|
-
throw new Error(
|
|
614
|
-
`tantivy engine is unavailable: ${error instanceof Error ? error.message : String(error)}`
|
|
615
|
-
);
|
|
633
|
+
throw new Error(`tantivy engine is unavailable: ${getErrorMessage(error)}`);
|
|
616
634
|
}
|
|
617
635
|
}
|
|
618
636
|
function getStoredText(doc, field) {
|
|
@@ -641,6 +659,8 @@ var require2;
|
|
|
641
659
|
var init_search = __esm({
|
|
642
660
|
"src/services/search.ts"() {
|
|
643
661
|
"use strict";
|
|
662
|
+
init_errors();
|
|
663
|
+
init_limits();
|
|
644
664
|
init_indexing();
|
|
645
665
|
require2 = createRequire(import.meta.url);
|
|
646
666
|
}
|
|
@@ -669,7 +689,7 @@ function sessionFilterWhere(filters) {
|
|
|
669
689
|
}
|
|
670
690
|
function listSessions(bundle, filters = {}) {
|
|
671
691
|
const { where, params } = sessionFilterWhere(filters);
|
|
672
|
-
const limit =
|
|
692
|
+
const limit = clampLimit(filters.limit, { max: 1e3, fallback: 50 });
|
|
673
693
|
const sql = `
|
|
674
694
|
SELECT s.session_id,
|
|
675
695
|
s.source_tool,
|
|
@@ -743,6 +763,7 @@ function getSession(bundle, sessionId2) {
|
|
|
743
763
|
var init_sessions = __esm({
|
|
744
764
|
"src/services/sessions.ts"() {
|
|
745
765
|
"use strict";
|
|
766
|
+
init_limits();
|
|
746
767
|
}
|
|
747
768
|
});
|
|
748
769
|
|
|
@@ -1120,8 +1141,6 @@ var PROSA_PARSER_VERSION = "0.1.0";
|
|
|
1120
1141
|
var PROSA_SCHEMA_VERSION = 2;
|
|
1121
1142
|
|
|
1122
1143
|
// src/cli/commands/compile.ts
|
|
1123
|
-
import os2 from "os";
|
|
1124
|
-
import path14 from "path";
|
|
1125
1144
|
import { Command } from "commander";
|
|
1126
1145
|
|
|
1127
1146
|
// src/core/bundle.ts
|
|
@@ -1627,6 +1646,11 @@ function closeBundle(bundle) {
|
|
|
1627
1646
|
closeDb(bundle.db);
|
|
1628
1647
|
}
|
|
1629
1648
|
|
|
1649
|
+
// src/services/compile.ts
|
|
1650
|
+
init_errors();
|
|
1651
|
+
import os2 from "os";
|
|
1652
|
+
import path14 from "path";
|
|
1653
|
+
|
|
1630
1654
|
// src/importers/claude/index.ts
|
|
1631
1655
|
init_cas();
|
|
1632
1656
|
init_db();
|
|
@@ -1676,6 +1700,9 @@ function importBatchId(sourceTool, startedAtIso) {
|
|
|
1676
1700
|
return tupleId(["import_batch", sourceTool, startedAtIso, String(Math.random())]);
|
|
1677
1701
|
}
|
|
1678
1702
|
|
|
1703
|
+
// src/importers/claude/index.ts
|
|
1704
|
+
init_errors();
|
|
1705
|
+
|
|
1679
1706
|
// src/core/ingest/batch.ts
|
|
1680
1707
|
init_cas();
|
|
1681
1708
|
init_db();
|
|
@@ -1965,7 +1992,7 @@ async function compileClaude(bundle, root, options = {}) {
|
|
|
1965
1992
|
);
|
|
1966
1993
|
await recordError(bundle, batch.batch_id, {
|
|
1967
1994
|
kind: "claude_file_failed",
|
|
1968
|
-
message:
|
|
1995
|
+
message: getErrorMessage(error),
|
|
1969
1996
|
payload: { path: file.filePath }
|
|
1970
1997
|
});
|
|
1971
1998
|
}
|
|
@@ -2880,6 +2907,7 @@ init_cas();
|
|
|
2880
2907
|
init_db();
|
|
2881
2908
|
import { readFile as readFile5 } from "fs/promises";
|
|
2882
2909
|
import path7 from "path";
|
|
2910
|
+
init_errors();
|
|
2883
2911
|
|
|
2884
2912
|
// src/importers/codex/discover.ts
|
|
2885
2913
|
import { readdir as readdir2 } from "fs/promises";
|
|
@@ -2929,7 +2957,7 @@ async function compileCodex(bundle, root, options = {}) {
|
|
|
2929
2957
|
);
|
|
2930
2958
|
await recordError(bundle, batch.batch_id, {
|
|
2931
2959
|
kind: "codex_file_failed",
|
|
2932
|
-
message:
|
|
2960
|
+
message: getErrorMessage(error),
|
|
2933
2961
|
payload: { path: filePath }
|
|
2934
2962
|
});
|
|
2935
2963
|
}
|
|
@@ -4020,6 +4048,7 @@ init_cas();
|
|
|
4020
4048
|
init_db();
|
|
4021
4049
|
import path9 from "path";
|
|
4022
4050
|
import Database2 from "better-sqlite3";
|
|
4051
|
+
init_errors();
|
|
4023
4052
|
|
|
4024
4053
|
// src/importers/cursor/discover.ts
|
|
4025
4054
|
import { readdir as readdir3 } from "fs/promises";
|
|
@@ -4084,7 +4113,7 @@ async function compileCursor(bundle, root, options = {}) {
|
|
|
4084
4113
|
);
|
|
4085
4114
|
await recordError(bundle, batch.batch_id, {
|
|
4086
4115
|
kind: "cursor_store_failed",
|
|
4087
|
-
message:
|
|
4116
|
+
message: getErrorMessage(error),
|
|
4088
4117
|
payload: { path: store.filePath }
|
|
4089
4118
|
});
|
|
4090
4119
|
}
|
|
@@ -4739,6 +4768,7 @@ init_cas();
|
|
|
4739
4768
|
init_db();
|
|
4740
4769
|
import { readFile as readFile7 } from "fs/promises";
|
|
4741
4770
|
import path11 from "path";
|
|
4771
|
+
init_errors();
|
|
4742
4772
|
|
|
4743
4773
|
// src/importers/gemini/discover.ts
|
|
4744
4774
|
import { readFile as readFile6, readdir as readdir4 } from "fs/promises";
|
|
@@ -4810,7 +4840,7 @@ async function compileGemini(bundle, root, options = {}) {
|
|
|
4810
4840
|
);
|
|
4811
4841
|
await recordError(bundle, batch.batch_id, {
|
|
4812
4842
|
kind: "gemini_file_failed",
|
|
4813
|
-
message:
|
|
4843
|
+
message: getErrorMessage(error),
|
|
4814
4844
|
payload: { path: file.filePath }
|
|
4815
4845
|
});
|
|
4816
4846
|
}
|
|
@@ -5496,6 +5526,7 @@ function flushPending4(bundle, pending) {
|
|
|
5496
5526
|
import { mkdir as mkdir3, rm, writeFile as writeFile4 } from "fs/promises";
|
|
5497
5527
|
import path12 from "path";
|
|
5498
5528
|
import { DuckDBConnection } from "@duckdb/node-api";
|
|
5529
|
+
init_errors();
|
|
5499
5530
|
var PARQUET_TABLES = [
|
|
5500
5531
|
"objects",
|
|
5501
5532
|
"source_files",
|
|
@@ -5593,7 +5624,7 @@ async function attachSqlite(connection, dbPath) {
|
|
|
5593
5624
|
await connection.run(`ATTACH ${sqlString(dbPath)} AS prosa (TYPE sqlite)`);
|
|
5594
5625
|
} catch (error) {
|
|
5595
5626
|
throw new Error(
|
|
5596
|
-
`DuckDB could not attach prosa.sqlite via the sqlite extension: ${
|
|
5627
|
+
`DuckDB could not attach prosa.sqlite via the sqlite extension: ${getErrorMessage(error)}`
|
|
5597
5628
|
);
|
|
5598
5629
|
}
|
|
5599
5630
|
}
|
|
@@ -5624,39 +5655,13 @@ function sqlString(value) {
|
|
|
5624
5655
|
return `'${value.replace(/'/g, "''")}'`;
|
|
5625
5656
|
}
|
|
5626
5657
|
function isMissingParquetError(error) {
|
|
5627
|
-
const message =
|
|
5658
|
+
const message = getErrorMessage(error);
|
|
5628
5659
|
return /No files found|does not exist|not found/i.test(message) && /\.parquet/i.test(message);
|
|
5629
5660
|
}
|
|
5630
5661
|
|
|
5631
|
-
// src/
|
|
5662
|
+
// src/services/compile.ts
|
|
5632
5663
|
init_indexing();
|
|
5633
|
-
|
|
5634
|
-
// src/cli/logger.ts
|
|
5635
|
-
import pino from "pino";
|
|
5636
|
-
import pretty from "pino-pretty";
|
|
5637
|
-
function createCliLogger(options) {
|
|
5638
|
-
const loggerOptions = {
|
|
5639
|
-
base: void 0,
|
|
5640
|
-
level: options.verbose === true ? "debug" : "info"
|
|
5641
|
-
};
|
|
5642
|
-
if (options.jsonLogs === true) {
|
|
5643
|
-
return pino(loggerOptions, pino.destination({ dest: 2, sync: true }));
|
|
5644
|
-
}
|
|
5645
|
-
return pino(
|
|
5646
|
-
loggerOptions,
|
|
5647
|
-
pretty({
|
|
5648
|
-
colorize: process.stderr.isTTY,
|
|
5649
|
-
destination: 2,
|
|
5650
|
-
ignore: "pid,hostname",
|
|
5651
|
-
singleLine: true,
|
|
5652
|
-
sync: true,
|
|
5653
|
-
translateTime: "SYS:yyyy-mm-dd HH:MM:ss.l"
|
|
5654
|
-
})
|
|
5655
|
-
);
|
|
5656
|
-
}
|
|
5657
|
-
|
|
5658
|
-
// src/cli/commands/compile.ts
|
|
5659
|
-
var PROVIDERS = [
|
|
5664
|
+
var COMPILE_PROVIDERS = [
|
|
5660
5665
|
{
|
|
5661
5666
|
name: "codex",
|
|
5662
5667
|
description: "Import Codex CLI session histories into the bundle.",
|
|
@@ -5686,13 +5691,129 @@ var PROVIDERS = [
|
|
|
5686
5691
|
compile: compileCursor
|
|
5687
5692
|
}
|
|
5688
5693
|
];
|
|
5694
|
+
function getCompileProvider(source) {
|
|
5695
|
+
const provider = COMPILE_PROVIDERS.find((p) => p.name === source);
|
|
5696
|
+
if (!provider) {
|
|
5697
|
+
throw new Error(`unknown compile source: ${source}`);
|
|
5698
|
+
}
|
|
5699
|
+
return provider;
|
|
5700
|
+
}
|
|
5701
|
+
function resolveCompilePath(p) {
|
|
5702
|
+
if (p === "~") return os2.homedir();
|
|
5703
|
+
if (p.startsWith("~/")) return path14.join(os2.homedir(), p.slice(2));
|
|
5704
|
+
return path14.resolve(p);
|
|
5705
|
+
}
|
|
5706
|
+
async function runCompileImports(options) {
|
|
5707
|
+
const { bundle, providers, deferIndex, logger } = options;
|
|
5708
|
+
let importedAny = false;
|
|
5709
|
+
const summaries = [];
|
|
5710
|
+
let tantivy = null;
|
|
5711
|
+
let tantivyError = null;
|
|
5712
|
+
try {
|
|
5713
|
+
if (deferIndex) {
|
|
5714
|
+
logger?.info("disabling FTS5 triggers for deferred indexing");
|
|
5715
|
+
disableFts5Triggers(bundle);
|
|
5716
|
+
}
|
|
5717
|
+
for (const provider of providers) {
|
|
5718
|
+
const sourcePath = resolveCompilePath(options.sessionsPath ?? provider.defaultSessionsPath());
|
|
5719
|
+
const providerLogger = logger?.child({
|
|
5720
|
+
source_tool: provider.name,
|
|
5721
|
+
source_path: sourcePath
|
|
5722
|
+
});
|
|
5723
|
+
providerLogger?.info("starting compile");
|
|
5724
|
+
const r = await provider.compile(bundle, sourcePath, { logger: providerLogger });
|
|
5725
|
+
importedAny ||= r.counts.source_files_imported > 0;
|
|
5726
|
+
providerLogger?.info(
|
|
5727
|
+
{
|
|
5728
|
+
batch_id: r.batch.batch_id,
|
|
5729
|
+
counts: r.counts
|
|
5730
|
+
},
|
|
5731
|
+
"compile finished"
|
|
5732
|
+
);
|
|
5733
|
+
const summary = {
|
|
5734
|
+
source: provider.name,
|
|
5735
|
+
sourcePath,
|
|
5736
|
+
batchId: r.batch.batch_id,
|
|
5737
|
+
batch: r.batch,
|
|
5738
|
+
counts: r.counts
|
|
5739
|
+
};
|
|
5740
|
+
summaries.push(summary);
|
|
5741
|
+
options.onProviderComplete?.(summary);
|
|
5742
|
+
}
|
|
5743
|
+
logger?.info({ changed: importedAny, fts5_deferred: deferIndex }, "marking indexes");
|
|
5744
|
+
markIndexesAfterImport(bundle, {
|
|
5745
|
+
changed: importedAny,
|
|
5746
|
+
fts5Deferred: deferIndex
|
|
5747
|
+
});
|
|
5748
|
+
if (importedAny) {
|
|
5749
|
+
try {
|
|
5750
|
+
logger?.info("rebuilding tantivy index");
|
|
5751
|
+
const status = await rebuildTantivyIndex(bundle);
|
|
5752
|
+
tantivy = { indexedDocCount: status.indexed_doc_count };
|
|
5753
|
+
options.onTantivyComplete?.(tantivy);
|
|
5754
|
+
} catch (error) {
|
|
5755
|
+
tantivyError = getErrorMessage(error);
|
|
5756
|
+
logger?.error({ err: error }, "tantivy rebuild failed; SQLite data is intact");
|
|
5757
|
+
}
|
|
5758
|
+
}
|
|
5759
|
+
} finally {
|
|
5760
|
+
if (deferIndex) {
|
|
5761
|
+
logger?.info("re-enabling FTS5 triggers");
|
|
5762
|
+
enableFts5Triggers(bundle);
|
|
5763
|
+
}
|
|
5764
|
+
}
|
|
5765
|
+
return {
|
|
5766
|
+
providers: summaries,
|
|
5767
|
+
importedAny,
|
|
5768
|
+
tantivy,
|
|
5769
|
+
tantivyError
|
|
5770
|
+
};
|
|
5771
|
+
}
|
|
5772
|
+
async function exportCompileParquet(options) {
|
|
5773
|
+
const storePath = resolveCompilePath(options.storePath);
|
|
5774
|
+
options.logger?.info({ store_path: storePath }, "exporting parquet");
|
|
5775
|
+
const result = await exportBundleParquet({ bundlePath: storePath });
|
|
5776
|
+
return {
|
|
5777
|
+
outDir: result.outDir,
|
|
5778
|
+
manifestPath: result.manifestPath,
|
|
5779
|
+
tableCount: Object.keys(result.files).length,
|
|
5780
|
+
files: result.files,
|
|
5781
|
+
counts: result.counts
|
|
5782
|
+
};
|
|
5783
|
+
}
|
|
5784
|
+
|
|
5785
|
+
// src/cli/logger.ts
|
|
5786
|
+
import pino from "pino";
|
|
5787
|
+
import pretty from "pino-pretty";
|
|
5788
|
+
function createCliLogger(options) {
|
|
5789
|
+
const loggerOptions = {
|
|
5790
|
+
base: void 0,
|
|
5791
|
+
level: options.verbose ? "debug" : "info"
|
|
5792
|
+
};
|
|
5793
|
+
if (options.jsonLogs) {
|
|
5794
|
+
return pino(loggerOptions, pino.destination({ dest: 2, sync: true }));
|
|
5795
|
+
}
|
|
5796
|
+
return pino(
|
|
5797
|
+
loggerOptions,
|
|
5798
|
+
pretty({
|
|
5799
|
+
colorize: process.stderr.isTTY,
|
|
5800
|
+
destination: 2,
|
|
5801
|
+
ignore: "pid,hostname",
|
|
5802
|
+
singleLine: true,
|
|
5803
|
+
sync: true,
|
|
5804
|
+
translateTime: "SYS:yyyy-mm-dd HH:MM:ss.l"
|
|
5805
|
+
})
|
|
5806
|
+
);
|
|
5807
|
+
}
|
|
5808
|
+
|
|
5809
|
+
// src/cli/commands/compile.ts
|
|
5689
5810
|
function compileCommand() {
|
|
5690
5811
|
const command = addCompileLogOptions(
|
|
5691
5812
|
new Command("compile").description(
|
|
5692
5813
|
"Import session histories from one agent CLI into the bundle."
|
|
5693
5814
|
)
|
|
5694
5815
|
);
|
|
5695
|
-
for (const provider of
|
|
5816
|
+
for (const provider of COMPILE_PROVIDERS) {
|
|
5696
5817
|
command.addCommand(providerCompileCommand(provider));
|
|
5697
5818
|
}
|
|
5698
5819
|
command.action(() => {
|
|
@@ -5703,9 +5824,9 @@ function compileCommand() {
|
|
|
5703
5824
|
function compileAllCommand() {
|
|
5704
5825
|
return addCompileLogOptions(new Command("compile-all")).description("Import all agent CLI session histories using default source paths.").option("--defer-index", "skip immediate FTS5 updates; run `prosa index fts5` later").action(async (options) => {
|
|
5705
5826
|
await runCompiles({
|
|
5706
|
-
providers:
|
|
5827
|
+
providers: COMPILE_PROVIDERS,
|
|
5707
5828
|
storePath: defaultBundlePath(),
|
|
5708
|
-
deferIndex: options.deferIndex
|
|
5829
|
+
deferIndex: options.deferIndex ?? false,
|
|
5709
5830
|
logOptions: options
|
|
5710
5831
|
});
|
|
5711
5832
|
});
|
|
@@ -5720,7 +5841,7 @@ function providerCompileCommand(provider) {
|
|
|
5720
5841
|
await runCompiles({
|
|
5721
5842
|
providers: [provider],
|
|
5722
5843
|
storePath: options.store,
|
|
5723
|
-
deferIndex: options.deferIndex
|
|
5844
|
+
deferIndex: options.deferIndex ?? false,
|
|
5724
5845
|
sessionsPath: options.sessionsPath,
|
|
5725
5846
|
logOptions: command.optsWithGlobals()
|
|
5726
5847
|
});
|
|
@@ -5732,76 +5853,42 @@ function addCompileLogOptions(command) {
|
|
|
5732
5853
|
}
|
|
5733
5854
|
async function runCompiles(options) {
|
|
5734
5855
|
const logger = createCliLogger(options.logOptions);
|
|
5735
|
-
const storePath =
|
|
5856
|
+
const storePath = resolveCompilePath(options.storePath);
|
|
5736
5857
|
logger.info({ store_path: storePath }, "opening bundle");
|
|
5737
5858
|
const bundle = await openBundle(storePath);
|
|
5738
5859
|
let importedAny = false;
|
|
5739
5860
|
try {
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
});
|
|
5750
|
-
providerLogger.info("starting compile");
|
|
5751
|
-
const r = await provider.compile(bundle, sourcePath, { logger: providerLogger });
|
|
5752
|
-
importedAny ||= r.counts.source_files_imported > 0;
|
|
5753
|
-
providerLogger.info(
|
|
5754
|
-
{
|
|
5755
|
-
batch_id: r.batch.batch_id,
|
|
5756
|
-
counts: r.counts
|
|
5757
|
-
},
|
|
5758
|
-
"compile finished"
|
|
5759
|
-
);
|
|
5760
|
-
printCounts(provider.name, r.batch.batch_id, r.counts);
|
|
5761
|
-
}
|
|
5762
|
-
logger.info({ changed: importedAny, fts5_deferred: options.deferIndex }, "marking indexes");
|
|
5763
|
-
markIndexesAfterImport(bundle, {
|
|
5764
|
-
changed: importedAny,
|
|
5765
|
-
fts5Deferred: options.deferIndex
|
|
5766
|
-
});
|
|
5767
|
-
if (importedAny) {
|
|
5768
|
-
try {
|
|
5769
|
-
logger.info("rebuilding tantivy index");
|
|
5770
|
-
const status = await rebuildTantivyIndex(bundle);
|
|
5771
|
-
process.stdout.write(`tantivy: indexed ${status.indexed_doc_count} docs
|
|
5861
|
+
const result = await runCompileImports({
|
|
5862
|
+
bundle,
|
|
5863
|
+
providers: options.providers,
|
|
5864
|
+
deferIndex: options.deferIndex,
|
|
5865
|
+
sessionsPath: options.sessionsPath,
|
|
5866
|
+
logger,
|
|
5867
|
+
onProviderComplete: printCounts,
|
|
5868
|
+
onTantivyComplete: (status) => {
|
|
5869
|
+
process.stdout.write(`tantivy: indexed ${status.indexedDocCount} docs
|
|
5772
5870
|
`);
|
|
5773
|
-
} catch (error) {
|
|
5774
|
-
logger.error({ err: error }, "tantivy rebuild failed; SQLite data is intact");
|
|
5775
5871
|
}
|
|
5776
|
-
}
|
|
5872
|
+
});
|
|
5873
|
+
importedAny = result.importedAny;
|
|
5777
5874
|
} finally {
|
|
5778
|
-
if (options.deferIndex) {
|
|
5779
|
-
logger.info("re-enabling FTS5 triggers");
|
|
5780
|
-
enableFts5Triggers(bundle);
|
|
5781
|
-
}
|
|
5782
5875
|
closeBundle(bundle);
|
|
5783
5876
|
logger.info({ store_path: storePath }, "bundle closed");
|
|
5784
5877
|
}
|
|
5785
5878
|
if (importedAny) {
|
|
5786
5879
|
try {
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
const tableCount = Object.keys(result.files).length;
|
|
5790
|
-
process.stdout.write(`parquet: wrote ${tableCount} tables to ${result.outDir}
|
|
5880
|
+
const result = await exportCompileParquet({ storePath, logger });
|
|
5881
|
+
process.stdout.write(`parquet: wrote ${result.tableCount} tables to ${result.outDir}
|
|
5791
5882
|
`);
|
|
5792
5883
|
} catch (error) {
|
|
5793
5884
|
logger.error({ err: error }, "parquet export failed; SQLite data is intact");
|
|
5794
5885
|
}
|
|
5795
5886
|
}
|
|
5796
5887
|
}
|
|
5797
|
-
function
|
|
5798
|
-
|
|
5799
|
-
if (p.startsWith("~/")) return path14.join(os2.homedir(), p.slice(2));
|
|
5800
|
-
return path14.resolve(p);
|
|
5801
|
-
}
|
|
5802
|
-
function printCounts(label, batchId, c) {
|
|
5888
|
+
function printCounts(summary) {
|
|
5889
|
+
const c = summary.counts;
|
|
5803
5890
|
process.stdout.write(
|
|
5804
|
-
`${
|
|
5891
|
+
`${summary.source} import: batch=${summary.batchId}
|
|
5805
5892
|
source_files seen=${c.source_files_seen} imported=${c.source_files_imported} skipped=${c.source_files_skipped}
|
|
5806
5893
|
sessions=${c.sessions} turns=${c.turns} messages=${c.messages} blocks=${c.content_blocks}
|
|
5807
5894
|
events=${c.events} tool_calls=${c.tool_calls} tool_results=${c.tool_results}
|
|
@@ -5812,7 +5899,7 @@ function printCounts(label, batchId, c) {
|
|
|
5812
5899
|
|
|
5813
5900
|
// src/cli/commands/export.ts
|
|
5814
5901
|
import { writeFile as writeFile6 } from "fs/promises";
|
|
5815
|
-
import
|
|
5902
|
+
import path16 from "path";
|
|
5816
5903
|
import { Command as Command2 } from "commander";
|
|
5817
5904
|
|
|
5818
5905
|
// src/services/export/markdown.ts
|
|
@@ -5926,30 +6013,38 @@ function renderToolCall(c) {
|
|
|
5926
6013
|
return lines.join("\n");
|
|
5927
6014
|
}
|
|
5928
6015
|
|
|
6016
|
+
// src/cli/bundle.ts
|
|
6017
|
+
import path15 from "path";
|
|
6018
|
+
async function withBundle(storePath, fn) {
|
|
6019
|
+
const bundle = await openBundle(path15.resolve(storePath));
|
|
6020
|
+
try {
|
|
6021
|
+
return await fn(bundle);
|
|
6022
|
+
} finally {
|
|
6023
|
+
closeBundle(bundle);
|
|
6024
|
+
}
|
|
6025
|
+
}
|
|
6026
|
+
|
|
5929
6027
|
// src/cli/commands/export.ts
|
|
5930
6028
|
function exportCommand() {
|
|
5931
6029
|
const session = new Command2("session").description("Export a single session to a human-readable format.").argument("<session-id>", "prosa session_id").requiredOption("--format <fmt>", 'currently only "markdown" is supported').option("--out <path>", "write to file instead of stdout").option("--store <path>", "bundle directory", defaultBundlePath()).action(async (sessionId2, options) => {
|
|
5932
6030
|
if (options.format !== "markdown") {
|
|
5933
6031
|
throw new Error(`unsupported format: ${options.format} (try --format markdown)`);
|
|
5934
6032
|
}
|
|
5935
|
-
|
|
5936
|
-
try {
|
|
6033
|
+
await withBundle(options.store, async (bundle) => {
|
|
5937
6034
|
const markdown = await exportSessionMarkdown(bundle, sessionId2);
|
|
5938
6035
|
if (options.out) {
|
|
5939
|
-
await writeFile6(
|
|
5940
|
-
process.stdout.write(`wrote ${
|
|
6036
|
+
await writeFile6(path16.resolve(options.out), markdown, "utf8");
|
|
6037
|
+
process.stdout.write(`wrote ${path16.resolve(options.out)}
|
|
5941
6038
|
`);
|
|
5942
6039
|
} else {
|
|
5943
6040
|
process.stdout.write(markdown);
|
|
5944
6041
|
}
|
|
5945
|
-
}
|
|
5946
|
-
closeBundle(bundle);
|
|
5947
|
-
}
|
|
6042
|
+
});
|
|
5948
6043
|
});
|
|
5949
6044
|
const parquet = new Command2("parquet").description("Export canonical tables to derived Parquet files for analytics.").option("--store <path>", "bundle directory", defaultBundlePath()).option("--out <path>", "output directory (default: <store>/parquet)").action(async (options) => {
|
|
5950
6045
|
const result = await exportBundleParquet({
|
|
5951
|
-
bundlePath:
|
|
5952
|
-
outDir: options.out ?
|
|
6046
|
+
bundlePath: path16.resolve(options.store),
|
|
6047
|
+
outDir: options.out ? path16.resolve(options.out) : void 0
|
|
5953
6048
|
});
|
|
5954
6049
|
process.stdout.write(`wrote parquet export to ${result.outDir}
|
|
5955
6050
|
`);
|
|
@@ -5960,12 +6055,13 @@ function exportCommand() {
|
|
|
5960
6055
|
}
|
|
5961
6056
|
|
|
5962
6057
|
// src/cli/commands/index.ts
|
|
5963
|
-
import path16 from "path";
|
|
5964
6058
|
import { Command as Command3 } from "commander";
|
|
5965
6059
|
init_indexing();
|
|
5966
6060
|
|
|
5967
6061
|
// src/cli/output.ts
|
|
5968
6062
|
var OUTPUT_FORMATS = ["interactive", "table", "json", "csv"];
|
|
6063
|
+
var COL_SEPARATOR = " ";
|
|
6064
|
+
var RULE_CHAR = "-";
|
|
5969
6065
|
function parseOutputFormat(value, fallback) {
|
|
5970
6066
|
if (value === void 0) return fallback;
|
|
5971
6067
|
if (OUTPUT_FORMATS.includes(value)) return value;
|
|
@@ -5993,11 +6089,12 @@ function printJson(rows, opts) {
|
|
|
5993
6089
|
`);
|
|
5994
6090
|
}
|
|
5995
6091
|
function printCsv(rows, opts) {
|
|
5996
|
-
const
|
|
5997
|
-
process.stdout.write(`${
|
|
6092
|
+
const columns = opts.columns;
|
|
6093
|
+
process.stdout.write(`${columns.map(csvField).join(",")}
|
|
5998
6094
|
`);
|
|
5999
6095
|
for (const row of rows) {
|
|
6000
|
-
const
|
|
6096
|
+
const record = row;
|
|
6097
|
+
const line = columns.map((column) => csvField(formatCell(record[column]))).join(",");
|
|
6001
6098
|
process.stdout.write(`${line}
|
|
6002
6099
|
`);
|
|
6003
6100
|
}
|
|
@@ -6007,23 +6104,24 @@ function csvField(value) {
|
|
|
6007
6104
|
return value;
|
|
6008
6105
|
}
|
|
6009
6106
|
function printTable(rows, opts) {
|
|
6010
|
-
const
|
|
6011
|
-
const widths =
|
|
6012
|
-
const cells = rows.map(
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
const
|
|
6016
|
-
|
|
6107
|
+
const columns = opts.columns;
|
|
6108
|
+
const widths = columns.map((column) => column.length);
|
|
6109
|
+
const cells = rows.map((row) => {
|
|
6110
|
+
const record = row;
|
|
6111
|
+
return columns.map((column, index) => {
|
|
6112
|
+
const text = formatCell(record[column]);
|
|
6113
|
+
const width = widths[index] ?? 0;
|
|
6114
|
+
if (text.length > width) widths[index] = text.length;
|
|
6017
6115
|
return text;
|
|
6018
|
-
})
|
|
6019
|
-
);
|
|
6020
|
-
const header =
|
|
6021
|
-
const
|
|
6116
|
+
});
|
|
6117
|
+
});
|
|
6118
|
+
const header = columns.map((column, index) => column.padEnd(widths[index] ?? 0)).join(COL_SEPARATOR);
|
|
6119
|
+
const rule = columns.map((_, index) => RULE_CHAR.repeat(widths[index] ?? 0)).join(COL_SEPARATOR);
|
|
6022
6120
|
process.stdout.write(`${header}
|
|
6023
|
-
${
|
|
6121
|
+
${rule}
|
|
6024
6122
|
`);
|
|
6025
6123
|
for (const cellRow of cells) {
|
|
6026
|
-
const line = cellRow.map((
|
|
6124
|
+
const line = cellRow.map((cell, index) => cell.padEnd(widths[index] ?? 0)).join(COL_SEPARATOR);
|
|
6027
6125
|
process.stdout.write(`${line}
|
|
6028
6126
|
`);
|
|
6029
6127
|
}
|
|
@@ -6038,27 +6136,18 @@ function formatCell(value) {
|
|
|
6038
6136
|
// src/cli/commands/index.ts
|
|
6039
6137
|
function indexCommand() {
|
|
6040
6138
|
const fts5 = new Command3("fts5").description("Rebuild the SQLite FTS5 index from search_docs.").option("--store <path>", "bundle directory", defaultBundlePath()).action(async (options) => {
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
printIndexStatus(status2);
|
|
6045
|
-
} finally {
|
|
6046
|
-
closeBundle(bundle);
|
|
6047
|
-
}
|
|
6139
|
+
await withBundle(options.store, (bundle) => {
|
|
6140
|
+
printIndexStatus(rebuildFts5Index(bundle));
|
|
6141
|
+
});
|
|
6048
6142
|
});
|
|
6049
6143
|
const tantivy = new Command3("tantivy").description("Rebuild the Tantivy sidecar index from search_docs.").option("--store <path>", "bundle directory", defaultBundlePath()).action(async (options) => {
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
printIndexStatus(status2);
|
|
6054
|
-
} finally {
|
|
6055
|
-
closeBundle(bundle);
|
|
6056
|
-
}
|
|
6144
|
+
await withBundle(options.store, async (bundle) => {
|
|
6145
|
+
printIndexStatus(await rebuildTantivyIndex(bundle));
|
|
6146
|
+
});
|
|
6057
6147
|
});
|
|
6058
6148
|
const status = new Command3("status").description("Show derived search index status.").option("--store <path>", "bundle directory", defaultBundlePath()).option("--output-format <fmt>", "interactive|table|json|csv", "table").action(async (options) => {
|
|
6059
6149
|
const format = parseOutputFormat(options.outputFormat, "table");
|
|
6060
|
-
|
|
6061
|
-
try {
|
|
6150
|
+
await withBundle(options.store, (bundle) => {
|
|
6062
6151
|
const rows = getSearchIndexStatuses(bundle);
|
|
6063
6152
|
printRows(rows, {
|
|
6064
6153
|
format,
|
|
@@ -6071,9 +6160,7 @@ function indexCommand() {
|
|
|
6071
6160
|
"error_message"
|
|
6072
6161
|
]
|
|
6073
6162
|
});
|
|
6074
|
-
}
|
|
6075
|
-
closeBundle(bundle);
|
|
6076
|
-
}
|
|
6163
|
+
});
|
|
6077
6164
|
});
|
|
6078
6165
|
return new Command3("index").description("Build or inspect derived search indexes.").addCommand(fts5).addCommand(tantivy).addCommand(status);
|
|
6079
6166
|
}
|
|
@@ -6120,6 +6207,7 @@ import path18 from "path";
|
|
|
6120
6207
|
import { Command as Command5 } from "commander";
|
|
6121
6208
|
|
|
6122
6209
|
// src/mcp/server.ts
|
|
6210
|
+
init_errors();
|
|
6123
6211
|
import { randomUUID } from "crypto";
|
|
6124
6212
|
import http from "http";
|
|
6125
6213
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -6128,10 +6216,13 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
6128
6216
|
|
|
6129
6217
|
// src/mcp/guidance.ts
|
|
6130
6218
|
var PROSA_MCP_INSTRUCTIONS = `
|
|
6131
|
-
prosa is a
|
|
6132
|
-
commands, decisions, file touches, and full transcripts before answering from
|
|
6219
|
+
prosa is a local memory over local agent session histories. Use it to import recent sessions,
|
|
6220
|
+
find prior work, commands, decisions, file touches, and full transcripts before answering from
|
|
6221
|
+
memory.
|
|
6133
6222
|
|
|
6134
6223
|
Recommended workflow:
|
|
6224
|
+
- Use compile to refresh the bundle when recent local sessions may not be indexed yet. With no
|
|
6225
|
+
input it imports all supported providers from default paths.
|
|
6135
6226
|
- For open-ended questions, start with search_sessions using 2-5 concrete terms.
|
|
6136
6227
|
- For questions about a file or path, start with find_touched_files, then inspect the returned sessions.
|
|
6137
6228
|
- After search results, call get_session for the most relevant session_ids before drawing conclusions.
|
|
@@ -6176,19 +6267,95 @@ Use this workflow:
|
|
|
6176
6267
|
|
|
6177
6268
|
// src/mcp/tools.ts
|
|
6178
6269
|
import { z } from "zod";
|
|
6270
|
+
|
|
6271
|
+
// src/core/domain/types.ts
|
|
6272
|
+
var SOURCE_TOOLS = ["cursor", "codex", "claude", "gemini"];
|
|
6273
|
+
|
|
6274
|
+
// src/mcp/tools.ts
|
|
6275
|
+
init_errors();
|
|
6276
|
+
init_limits();
|
|
6179
6277
|
init_indexing();
|
|
6180
6278
|
init_search();
|
|
6181
6279
|
init_sessions();
|
|
6182
6280
|
function registerProsaTools(server, bundle, options = {}) {
|
|
6183
6281
|
const searchEngine = options.searchEngine ?? "fts5";
|
|
6282
|
+
const storePath = options.storePath ?? bundle.path;
|
|
6184
6283
|
registerProsaPrompts(server);
|
|
6284
|
+
server.registerTool(
|
|
6285
|
+
"compile",
|
|
6286
|
+
{
|
|
6287
|
+
title: "Compile sessions",
|
|
6288
|
+
description: "Import local agent session histories into the active prosa bundle. With no input, compiles all providers from default paths. With source, compiles that provider; sessions_path may override that provider path.",
|
|
6289
|
+
inputSchema: {
|
|
6290
|
+
source: z.enum(SOURCE_TOOLS).optional(),
|
|
6291
|
+
sessions_path: z.string().min(1).optional()
|
|
6292
|
+
},
|
|
6293
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true }
|
|
6294
|
+
},
|
|
6295
|
+
async ({ source, sessions_path }) => {
|
|
6296
|
+
if (sessions_path && !source) {
|
|
6297
|
+
return {
|
|
6298
|
+
content: [
|
|
6299
|
+
{
|
|
6300
|
+
type: "text",
|
|
6301
|
+
text: "sessions_path requires source because providers use incompatible source layouts"
|
|
6302
|
+
}
|
|
6303
|
+
],
|
|
6304
|
+
isError: true
|
|
6305
|
+
};
|
|
6306
|
+
}
|
|
6307
|
+
try {
|
|
6308
|
+
const result = await runCompileImports({
|
|
6309
|
+
bundle,
|
|
6310
|
+
providers: source ? [getCompileProvider(source)] : COMPILE_PROVIDERS,
|
|
6311
|
+
deferIndex: false,
|
|
6312
|
+
sessionsPath: sessions_path
|
|
6313
|
+
});
|
|
6314
|
+
const parquet = result.importedAny ? await exportCompileParquet({ storePath }) : null;
|
|
6315
|
+
return {
|
|
6316
|
+
content: [
|
|
6317
|
+
{
|
|
6318
|
+
type: "text",
|
|
6319
|
+
text: JSON.stringify(
|
|
6320
|
+
{
|
|
6321
|
+
providers: result.providers.map((provider) => ({
|
|
6322
|
+
source: provider.source,
|
|
6323
|
+
source_path: provider.sourcePath,
|
|
6324
|
+
batch_id: provider.batchId,
|
|
6325
|
+
counts: provider.counts
|
|
6326
|
+
})),
|
|
6327
|
+
imported_any: result.importedAny,
|
|
6328
|
+
tantivy: result.tantivy ? { indexed_doc_count: result.tantivy.indexedDocCount } : null,
|
|
6329
|
+
tantivy_error: result.tantivyError,
|
|
6330
|
+
parquet: parquet ? {
|
|
6331
|
+
out_dir: parquet.outDir,
|
|
6332
|
+
manifest_path: parquet.manifestPath,
|
|
6333
|
+
table_count: parquet.tableCount,
|
|
6334
|
+
files: parquet.files,
|
|
6335
|
+
counts: parquet.counts
|
|
6336
|
+
} : null
|
|
6337
|
+
},
|
|
6338
|
+
null,
|
|
6339
|
+
2
|
|
6340
|
+
)
|
|
6341
|
+
}
|
|
6342
|
+
]
|
|
6343
|
+
};
|
|
6344
|
+
} catch (error) {
|
|
6345
|
+
return {
|
|
6346
|
+
content: [{ type: "text", text: getErrorMessage(error) }],
|
|
6347
|
+
isError: true
|
|
6348
|
+
};
|
|
6349
|
+
}
|
|
6350
|
+
}
|
|
6351
|
+
);
|
|
6185
6352
|
server.registerTool(
|
|
6186
6353
|
"list_sessions",
|
|
6187
6354
|
{
|
|
6188
6355
|
title: "List sessions",
|
|
6189
6356
|
description: "List recent sessions when you need candidates by source/date before deeper inspection. Next step: call get_session for relevant session_id values.",
|
|
6190
6357
|
inputSchema: {
|
|
6191
|
-
source: z.enum(
|
|
6358
|
+
source: z.enum(SOURCE_TOOLS).optional(),
|
|
6192
6359
|
since: z.string().optional().describe("ISO timestamp lower bound (inclusive)"),
|
|
6193
6360
|
until: z.string().optional().describe("ISO timestamp upper bound (exclusive)"),
|
|
6194
6361
|
limit: z.number().int().min(1).max(500).optional().default(50)
|
|
@@ -6274,7 +6441,7 @@ function registerProsaTools(server, bundle, options = {}) {
|
|
|
6274
6441
|
return { content: [{ type: "text", text: md }] };
|
|
6275
6442
|
} catch (error) {
|
|
6276
6443
|
return {
|
|
6277
|
-
content: [{ type: "text", text:
|
|
6444
|
+
content: [{ type: "text", text: getErrorMessage(error) }],
|
|
6278
6445
|
isError: true
|
|
6279
6446
|
};
|
|
6280
6447
|
}
|
|
@@ -6333,7 +6500,7 @@ function registerProsaTools(server, bundle, options = {}) {
|
|
|
6333
6500
|
LEFT JOIN tool_results tr ON tr.tool_call_id = tc.tool_call_id
|
|
6334
6501
|
${where}
|
|
6335
6502
|
ORDER BY tc.timestamp_start DESC
|
|
6336
|
-
LIMIT ${
|
|
6503
|
+
LIMIT ${clampLimit(limit, { max: 500, fallback: 100 })}
|
|
6337
6504
|
`;
|
|
6338
6505
|
const rows = bundle.db.prepare(sql).all(...params);
|
|
6339
6506
|
return {
|
|
@@ -6364,7 +6531,7 @@ function registerProsaTools(server, bundle, options = {}) {
|
|
|
6364
6531
|
FROM artifacts a
|
|
6365
6532
|
WHERE a.path IS NOT NULL AND a.path LIKE ?
|
|
6366
6533
|
ORDER BY timestamp_start DESC
|
|
6367
|
-
LIMIT ${
|
|
6534
|
+
LIMIT ${clampLimit(limit, { max: 500, fallback: 100 })}
|
|
6368
6535
|
`;
|
|
6369
6536
|
const like = `%${path_substring}%`;
|
|
6370
6537
|
const rows = bundle.db.prepare(sql).all(like, like);
|
|
@@ -6452,14 +6619,14 @@ function registerProsaPrompts(server) {
|
|
|
6452
6619
|
path: z.string().min(1).describe("File path, directory, or distinctive path suffix")
|
|
6453
6620
|
}
|
|
6454
6621
|
},
|
|
6455
|
-
({ path:
|
|
6622
|
+
({ path: path20 }) => ({
|
|
6456
6623
|
description: "Find sessions that touched a path and summarize the evidence.",
|
|
6457
6624
|
messages: [
|
|
6458
6625
|
{
|
|
6459
6626
|
role: "user",
|
|
6460
6627
|
content: {
|
|
6461
6628
|
type: "text",
|
|
6462
|
-
text: FIND_FILE_HISTORY_PROMPT.replace("{{path}}",
|
|
6629
|
+
text: FIND_FILE_HISTORY_PROMPT.replace("{{path}}", path20)
|
|
6463
6630
|
}
|
|
6464
6631
|
}
|
|
6465
6632
|
]
|
|
@@ -6494,7 +6661,7 @@ function registerProsaPrompts(server) {
|
|
|
6494
6661
|
|
|
6495
6662
|
// src/mcp/server.ts
|
|
6496
6663
|
async function listenMcpStdioServer(bundle, options = {}) {
|
|
6497
|
-
const server = createMcpServer(bundle, options.searchEngine ?? "fts5");
|
|
6664
|
+
const server = createMcpServer(bundle, options.searchEngine ?? "fts5", options.storePath);
|
|
6498
6665
|
const transport = new StdioServerTransport();
|
|
6499
6666
|
await server.connect(transport);
|
|
6500
6667
|
return {
|
|
@@ -6508,10 +6675,13 @@ async function listenMcpServer(bundle, options) {
|
|
|
6508
6675
|
const mcpPath = options.path ?? "/mcp";
|
|
6509
6676
|
const sessions = /* @__PURE__ */ new Map();
|
|
6510
6677
|
const searchEngine = options.searchEngine ?? "fts5";
|
|
6678
|
+
const storePath = options.storePath ?? bundle.path;
|
|
6511
6679
|
const httpServer = http.createServer((req, res) => {
|
|
6512
|
-
handleRequest(req, res, mcpPath, sessions, bundle, searchEngine).catch(
|
|
6513
|
-
|
|
6514
|
-
|
|
6680
|
+
handleRequest(req, res, mcpPath, sessions, bundle, searchEngine, storePath).catch(
|
|
6681
|
+
(error) => {
|
|
6682
|
+
writeError(res, error);
|
|
6683
|
+
}
|
|
6684
|
+
);
|
|
6515
6685
|
});
|
|
6516
6686
|
await new Promise((resolve, reject) => {
|
|
6517
6687
|
httpServer.once("error", reject);
|
|
@@ -6534,7 +6704,7 @@ async function listenMcpServer(bundle, options) {
|
|
|
6534
6704
|
}
|
|
6535
6705
|
};
|
|
6536
6706
|
}
|
|
6537
|
-
async function handleRequest(req, res, mcpPath, sessions, bundle, searchEngine) {
|
|
6707
|
+
async function handleRequest(req, res, mcpPath, sessions, bundle, searchEngine, storePath) {
|
|
6538
6708
|
if (!req.url || !req.url.startsWith(mcpPath)) {
|
|
6539
6709
|
res.writeHead(404).end();
|
|
6540
6710
|
return;
|
|
@@ -6556,14 +6726,14 @@ async function handleRequest(req, res, mcpPath, sessions, bundle, searchEngine)
|
|
|
6556
6726
|
res.writeHead(404).end();
|
|
6557
6727
|
return;
|
|
6558
6728
|
}
|
|
6559
|
-
entry = await openSession(bundle, sessions, searchEngine);
|
|
6729
|
+
entry = await openSession(bundle, sessions, searchEngine, storePath);
|
|
6560
6730
|
}
|
|
6561
6731
|
const bodyText = await readBody(req);
|
|
6562
6732
|
const body = bodyText.length > 0 ? safeJsonParse(bodyText) : void 0;
|
|
6563
6733
|
await entry.transport.handleRequest(req, res, body);
|
|
6564
6734
|
}
|
|
6565
|
-
async function openSession(bundle, store, searchEngine) {
|
|
6566
|
-
const server = createMcpServer(bundle, searchEngine);
|
|
6735
|
+
async function openSession(bundle, store, searchEngine, storePath) {
|
|
6736
|
+
const server = createMcpServer(bundle, searchEngine, storePath);
|
|
6567
6737
|
const transport = new StreamableHTTPServerTransport({
|
|
6568
6738
|
sessionIdGenerator: () => randomUUID(),
|
|
6569
6739
|
onsessioninitialized: (id) => {
|
|
@@ -6581,7 +6751,7 @@ async function openSession(bundle, store, searchEngine) {
|
|
|
6581
6751
|
await server.connect(transport);
|
|
6582
6752
|
return { server, transport };
|
|
6583
6753
|
}
|
|
6584
|
-
function createMcpServer(bundle, searchEngine) {
|
|
6754
|
+
function createMcpServer(bundle, searchEngine, storePath) {
|
|
6585
6755
|
const server = new McpServer(
|
|
6586
6756
|
{
|
|
6587
6757
|
name: "prosa",
|
|
@@ -6589,7 +6759,7 @@ function createMcpServer(bundle, searchEngine) {
|
|
|
6589
6759
|
},
|
|
6590
6760
|
{ instructions: PROSA_MCP_INSTRUCTIONS }
|
|
6591
6761
|
);
|
|
6592
|
-
registerProsaTools(server, bundle, { searchEngine });
|
|
6762
|
+
registerProsaTools(server, bundle, { searchEngine, storePath });
|
|
6593
6763
|
return server;
|
|
6594
6764
|
}
|
|
6595
6765
|
async function readBody(req) {
|
|
@@ -6620,17 +6790,33 @@ function writeError(res, error) {
|
|
|
6620
6790
|
res.end(
|
|
6621
6791
|
JSON.stringify({
|
|
6622
6792
|
jsonrpc: "2.0",
|
|
6623
|
-
error: { code: -32603, message:
|
|
6793
|
+
error: { code: -32603, message: getErrorMessage(error) },
|
|
6624
6794
|
id: null
|
|
6625
6795
|
})
|
|
6626
6796
|
);
|
|
6627
6797
|
}
|
|
6628
6798
|
|
|
6799
|
+
// src/cli/parsers.ts
|
|
6800
|
+
function parseSearchEngine(value) {
|
|
6801
|
+
if (value === "fts5" || value === "tantivy") return value;
|
|
6802
|
+
throw new Error(`invalid search engine: ${value} (expected fts5 or tantivy)`);
|
|
6803
|
+
}
|
|
6804
|
+
function parseMcpTransport(value) {
|
|
6805
|
+
if (value === "stdio" || value === "http") return value;
|
|
6806
|
+
throw new Error(`invalid transport: ${value} (expected stdio or http)`);
|
|
6807
|
+
}
|
|
6808
|
+
function parseSourceTool(value) {
|
|
6809
|
+
if (value === void 0) return void 0;
|
|
6810
|
+
if (SOURCE_TOOLS.includes(value)) return value;
|
|
6811
|
+
throw new Error(`invalid source tool: ${value} (expected one of ${SOURCE_TOOLS.join(", ")})`);
|
|
6812
|
+
}
|
|
6813
|
+
|
|
6629
6814
|
// src/cli/commands/mcp.ts
|
|
6630
6815
|
function mcpCommand() {
|
|
6631
6816
|
const serve = new Command5("serve").description("Start a local MCP server over the prosa bundle.").option("--store <path>", "bundle directory", defaultBundlePath()).option("--transport <transport>", "MCP transport: stdio|http", "stdio").option("--host <host>", "bind host", "127.0.0.1").option("--port <port>", "bind port", "7331").option("--path <path>", "HTTP path", "/mcp").option("--search-engine <engine>", "search engine: fts5|tantivy", "fts5").action(
|
|
6632
6817
|
async (options) => {
|
|
6633
|
-
const
|
|
6818
|
+
const storePath = path18.resolve(options.store);
|
|
6819
|
+
const bundle = await openBundle(storePath);
|
|
6634
6820
|
try {
|
|
6635
6821
|
const transport = parseMcpTransport(options.transport);
|
|
6636
6822
|
const searchEngine = parseSearchEngine(options.searchEngine);
|
|
@@ -6643,7 +6829,8 @@ function mcpCommand() {
|
|
|
6643
6829
|
host: options.host,
|
|
6644
6830
|
port,
|
|
6645
6831
|
path: options.path,
|
|
6646
|
-
searchEngine
|
|
6832
|
+
searchEngine,
|
|
6833
|
+
storePath
|
|
6647
6834
|
});
|
|
6648
6835
|
process.stdout.write(`prosa mcp server listening at ${server2.url}
|
|
6649
6836
|
`);
|
|
@@ -6651,7 +6838,7 @@ function mcpCommand() {
|
|
|
6651
6838
|
registerShutdown(server2.close, bundle);
|
|
6652
6839
|
return;
|
|
6653
6840
|
}
|
|
6654
|
-
const server = await listenMcpStdioServer(bundle, { searchEngine });
|
|
6841
|
+
const server = await listenMcpStdioServer(bundle, { searchEngine, storePath });
|
|
6655
6842
|
registerShutdown(server.close, bundle);
|
|
6656
6843
|
} catch (error) {
|
|
6657
6844
|
closeBundle(bundle);
|
|
@@ -6661,14 +6848,6 @@ function mcpCommand() {
|
|
|
6661
6848
|
);
|
|
6662
6849
|
return new Command5("mcp").description("MCP server commands.").addCommand(serve);
|
|
6663
6850
|
}
|
|
6664
|
-
function parseMcpTransport(value) {
|
|
6665
|
-
if (value === "stdio" || value === "http") return value;
|
|
6666
|
-
throw new Error(`invalid --transport: ${value} (expected stdio or http)`);
|
|
6667
|
-
}
|
|
6668
|
-
function parseSearchEngine(value) {
|
|
6669
|
-
if (value === "fts5" || value === "tantivy") return value;
|
|
6670
|
-
throw new Error(`invalid --search-engine: ${value} (expected fts5 or tantivy)`);
|
|
6671
|
-
}
|
|
6672
6851
|
function registerShutdown(closeServer, bundle) {
|
|
6673
6852
|
const shutdown = async () => {
|
|
6674
6853
|
await closeServer();
|
|
@@ -6690,7 +6869,7 @@ function queryCommand() {
|
|
|
6690
6869
|
const duckdb = new Command6("duckdb").description("Run a DuckDB SQL query over exported Parquet tables.").argument("<sql>", "DuckDB SQL query").option("--store <path>", "bundle directory", defaultBundlePath()).option("--parquet-dir <path>", "Parquet directory (default: <store>/parquet)").option("--output-format <fmt>", "interactive|table|json|csv", "table").action(
|
|
6691
6870
|
async (sql, options) => {
|
|
6692
6871
|
const format = parseOutputFormat(options.outputFormat, "table");
|
|
6693
|
-
const parquetDir = options.parquetDir ? path19.resolve(options.parquetDir) : await
|
|
6872
|
+
const parquetDir = options.parquetDir ? path19.resolve(options.parquetDir) : await withBundle(options.store, (bundle) => bundle.paths.parquet);
|
|
6694
6873
|
const result = await queryDuckDbParquet({ parquetDir, sql });
|
|
6695
6874
|
printRows(result.rows, {
|
|
6696
6875
|
format,
|
|
@@ -6701,26 +6880,16 @@ function queryCommand() {
|
|
|
6701
6880
|
);
|
|
6702
6881
|
return new Command6("query").description("Run derived analytical queries.").addCommand(duckdb);
|
|
6703
6882
|
}
|
|
6704
|
-
async function defaultParquetDir(storePath) {
|
|
6705
|
-
const bundle = await openBundle(storePath);
|
|
6706
|
-
try {
|
|
6707
|
-
return bundle.paths.parquet;
|
|
6708
|
-
} finally {
|
|
6709
|
-
closeBundle(bundle);
|
|
6710
|
-
}
|
|
6711
|
-
}
|
|
6712
6883
|
|
|
6713
6884
|
// src/cli/commands/search.ts
|
|
6714
|
-
import path20 from "path";
|
|
6715
6885
|
import { Command as Command7 } from "commander";
|
|
6716
6886
|
init_search();
|
|
6717
6887
|
function searchCommand() {
|
|
6718
6888
|
return new Command7("search").description("Full-text search across messages, tool calls and tool outputs.").argument("<query>", "FTS5 query string (supports MATCH syntax)").option("--store <path>", "bundle directory", defaultBundlePath()).option("--limit <n>", "maximum hits", "50").option("--engine <engine>", "search engine: fts5|tantivy", "fts5").option("--output-format <fmt>", "interactive|table|json|csv", "table").action(
|
|
6719
6889
|
async (query, options) => {
|
|
6720
|
-
const engine =
|
|
6890
|
+
const engine = parseSearchEngine(options.engine);
|
|
6721
6891
|
const format = parseOutputFormat(options.outputFormat, "table");
|
|
6722
|
-
|
|
6723
|
-
try {
|
|
6892
|
+
await withBundle(options.store, (bundle) => {
|
|
6724
6893
|
const hits = searchFullText(bundle, {
|
|
6725
6894
|
query,
|
|
6726
6895
|
limit: Number.parseInt(options.limit, 10),
|
|
@@ -6731,29 +6900,21 @@ function searchCommand() {
|
|
|
6731
6900
|
columns: ["timestamp", "role", "tool_name", "session_id", "snippet"],
|
|
6732
6901
|
meta: { query, engine, count: hits.length }
|
|
6733
6902
|
});
|
|
6734
|
-
}
|
|
6735
|
-
closeBundle(bundle);
|
|
6736
|
-
}
|
|
6903
|
+
});
|
|
6737
6904
|
}
|
|
6738
6905
|
);
|
|
6739
6906
|
}
|
|
6740
|
-
function parseSearchEngine2(value) {
|
|
6741
|
-
if (value === "fts5" || value === "tantivy") return value;
|
|
6742
|
-
throw new Error(`invalid --engine: ${value} (expected fts5 or tantivy)`);
|
|
6743
|
-
}
|
|
6744
6907
|
|
|
6745
6908
|
// src/cli/commands/sessions.ts
|
|
6746
|
-
import path21 from "path";
|
|
6747
6909
|
import { Command as Command8 } from "commander";
|
|
6748
6910
|
init_sessions();
|
|
6749
6911
|
function sessionsCommand() {
|
|
6750
6912
|
const command = new Command8("sessions").description("List sessions in the bundle, with filters.").enablePositionalOptions().option("--store <path>", "bundle directory", defaultBundlePath()).option("--source <tool>", "filter by source tool: cursor|codex|claude|gemini").option("--since <iso>", "sessions starting on/after this ISO timestamp").option("--until <iso>", "sessions starting before this ISO timestamp").option("--limit <n>", "maximum rows", "50").option("--output-format <fmt>", "interactive|table|json|csv", "table").action(
|
|
6751
6913
|
async (options) => {
|
|
6752
6914
|
const format = parseOutputFormat(options.outputFormat, "table");
|
|
6753
|
-
|
|
6754
|
-
try {
|
|
6915
|
+
await withBundle(options.store, (bundle) => {
|
|
6755
6916
|
const rows = listSessions(bundle, {
|
|
6756
|
-
sourceTool: options.source,
|
|
6917
|
+
sourceTool: parseSourceTool(options.source),
|
|
6757
6918
|
sinceIso: options.since,
|
|
6758
6919
|
untilIso: options.until,
|
|
6759
6920
|
limit: Number.parseInt(options.limit, 10)
|
|
@@ -6771,26 +6932,21 @@ function sessionsCommand() {
|
|
|
6771
6932
|
"title"
|
|
6772
6933
|
]
|
|
6773
6934
|
});
|
|
6774
|
-
}
|
|
6775
|
-
closeBundle(bundle);
|
|
6776
|
-
}
|
|
6935
|
+
});
|
|
6777
6936
|
}
|
|
6778
6937
|
);
|
|
6779
6938
|
command.addCommand(
|
|
6780
6939
|
new Command8("count").description("Count sessions in the bundle, with filters.").option("--store <path>", "bundle directory", defaultBundlePath()).option("--source <tool>", "filter by source tool: cursor|codex|claude|gemini").option("--since <iso>", "sessions starting on/after this ISO timestamp").option("--until <iso>", "sessions starting before this ISO timestamp").action(
|
|
6781
6940
|
async (options) => {
|
|
6782
|
-
|
|
6783
|
-
try {
|
|
6941
|
+
await withBundle(options.store, (bundle) => {
|
|
6784
6942
|
const count = countSessions(bundle, {
|
|
6785
|
-
sourceTool: options.source,
|
|
6943
|
+
sourceTool: parseSourceTool(options.source),
|
|
6786
6944
|
sinceIso: options.since,
|
|
6787
6945
|
untilIso: options.until
|
|
6788
6946
|
});
|
|
6789
6947
|
process.stdout.write(`${count}
|
|
6790
6948
|
`);
|
|
6791
|
-
}
|
|
6792
|
-
closeBundle(bundle);
|
|
6793
|
-
}
|
|
6949
|
+
});
|
|
6794
6950
|
}
|
|
6795
6951
|
)
|
|
6796
6952
|
);
|
|
@@ -6798,7 +6954,6 @@ function sessionsCommand() {
|
|
|
6798
6954
|
}
|
|
6799
6955
|
|
|
6800
6956
|
// src/cli/commands/tui.ts
|
|
6801
|
-
import path22 from "path";
|
|
6802
6957
|
import { Command as Command9 } from "commander";
|
|
6803
6958
|
function tuiCommand() {
|
|
6804
6959
|
return new Command9("tui").description("Open the interactive Ink-based explorer.").option("--store <path>", "bundle directory", defaultBundlePath()).action(async (options) => {
|
|
@@ -6807,14 +6962,11 @@ function tuiCommand() {
|
|
|
6807
6962
|
import("react"),
|
|
6808
6963
|
Promise.resolve().then(() => (init_App(), App_exports))
|
|
6809
6964
|
]);
|
|
6810
|
-
|
|
6811
|
-
try {
|
|
6965
|
+
await withBundle(options.store, async (bundle) => {
|
|
6812
6966
|
console.clear();
|
|
6813
6967
|
const app = render(React.createElement(App2, { bundle }));
|
|
6814
6968
|
await app.waitUntilExit();
|
|
6815
|
-
}
|
|
6816
|
-
closeBundle(bundle);
|
|
6817
|
-
}
|
|
6969
|
+
});
|
|
6818
6970
|
});
|
|
6819
6971
|
}
|
|
6820
6972
|
|