@c3-oss/prosa 0.3.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/cli/main.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(path23) {
16
- const db = new Database(path23);
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: error instanceof Error ? error.message : String(error)
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: error instanceof Error ? error.message : String(error)
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 = Math.max(1, Math.min(500, options.limit ?? 50));
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 = Math.max(1, Math.min(500, options.limit ?? 50));
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 = Math.max(1, Math.min(1e3, filters.limit ?? 50));
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
 
@@ -1626,6 +1647,7 @@ function closeBundle(bundle) {
1626
1647
  }
1627
1648
 
1628
1649
  // src/services/compile.ts
1650
+ init_errors();
1629
1651
  import os2 from "os";
1630
1652
  import path14 from "path";
1631
1653
 
@@ -1678,6 +1700,9 @@ function importBatchId(sourceTool, startedAtIso) {
1678
1700
  return tupleId(["import_batch", sourceTool, startedAtIso, String(Math.random())]);
1679
1701
  }
1680
1702
 
1703
+ // src/importers/claude/index.ts
1704
+ init_errors();
1705
+
1681
1706
  // src/core/ingest/batch.ts
1682
1707
  init_cas();
1683
1708
  init_db();
@@ -1967,7 +1992,7 @@ async function compileClaude(bundle, root, options = {}) {
1967
1992
  );
1968
1993
  await recordError(bundle, batch.batch_id, {
1969
1994
  kind: "claude_file_failed",
1970
- message: error instanceof Error ? error.message : String(error),
1995
+ message: getErrorMessage(error),
1971
1996
  payload: { path: file.filePath }
1972
1997
  });
1973
1998
  }
@@ -2882,6 +2907,7 @@ init_cas();
2882
2907
  init_db();
2883
2908
  import { readFile as readFile5 } from "fs/promises";
2884
2909
  import path7 from "path";
2910
+ init_errors();
2885
2911
 
2886
2912
  // src/importers/codex/discover.ts
2887
2913
  import { readdir as readdir2 } from "fs/promises";
@@ -2931,7 +2957,7 @@ async function compileCodex(bundle, root, options = {}) {
2931
2957
  );
2932
2958
  await recordError(bundle, batch.batch_id, {
2933
2959
  kind: "codex_file_failed",
2934
- message: error instanceof Error ? error.message : String(error),
2960
+ message: getErrorMessage(error),
2935
2961
  payload: { path: filePath }
2936
2962
  });
2937
2963
  }
@@ -4022,6 +4048,7 @@ init_cas();
4022
4048
  init_db();
4023
4049
  import path9 from "path";
4024
4050
  import Database2 from "better-sqlite3";
4051
+ init_errors();
4025
4052
 
4026
4053
  // src/importers/cursor/discover.ts
4027
4054
  import { readdir as readdir3 } from "fs/promises";
@@ -4086,7 +4113,7 @@ async function compileCursor(bundle, root, options = {}) {
4086
4113
  );
4087
4114
  await recordError(bundle, batch.batch_id, {
4088
4115
  kind: "cursor_store_failed",
4089
- message: error instanceof Error ? error.message : String(error),
4116
+ message: getErrorMessage(error),
4090
4117
  payload: { path: store.filePath }
4091
4118
  });
4092
4119
  }
@@ -4741,6 +4768,7 @@ init_cas();
4741
4768
  init_db();
4742
4769
  import { readFile as readFile7 } from "fs/promises";
4743
4770
  import path11 from "path";
4771
+ init_errors();
4744
4772
 
4745
4773
  // src/importers/gemini/discover.ts
4746
4774
  import { readFile as readFile6, readdir as readdir4 } from "fs/promises";
@@ -4812,7 +4840,7 @@ async function compileGemini(bundle, root, options = {}) {
4812
4840
  );
4813
4841
  await recordError(bundle, batch.batch_id, {
4814
4842
  kind: "gemini_file_failed",
4815
- message: error instanceof Error ? error.message : String(error),
4843
+ message: getErrorMessage(error),
4816
4844
  payload: { path: file.filePath }
4817
4845
  });
4818
4846
  }
@@ -5498,6 +5526,7 @@ function flushPending4(bundle, pending) {
5498
5526
  import { mkdir as mkdir3, rm, writeFile as writeFile4 } from "fs/promises";
5499
5527
  import path12 from "path";
5500
5528
  import { DuckDBConnection } from "@duckdb/node-api";
5529
+ init_errors();
5501
5530
  var PARQUET_TABLES = [
5502
5531
  "objects",
5503
5532
  "source_files",
@@ -5595,7 +5624,7 @@ async function attachSqlite(connection, dbPath) {
5595
5624
  await connection.run(`ATTACH ${sqlString(dbPath)} AS prosa (TYPE sqlite)`);
5596
5625
  } catch (error) {
5597
5626
  throw new Error(
5598
- `DuckDB could not attach prosa.sqlite via the sqlite extension: ${error instanceof Error ? error.message : String(error)}`
5627
+ `DuckDB could not attach prosa.sqlite via the sqlite extension: ${getErrorMessage(error)}`
5599
5628
  );
5600
5629
  }
5601
5630
  }
@@ -5626,7 +5655,7 @@ function sqlString(value) {
5626
5655
  return `'${value.replace(/'/g, "''")}'`;
5627
5656
  }
5628
5657
  function isMissingParquetError(error) {
5629
- const message = error instanceof Error ? error.message : String(error);
5658
+ const message = getErrorMessage(error);
5630
5659
  return /No files found|does not exist|not found/i.test(message) && /\.parquet/i.test(message);
5631
5660
  }
5632
5661
 
@@ -5723,7 +5752,7 @@ async function runCompileImports(options) {
5723
5752
  tantivy = { indexedDocCount: status.indexed_doc_count };
5724
5753
  options.onTantivyComplete?.(tantivy);
5725
5754
  } catch (error) {
5726
- tantivyError = error instanceof Error ? error.message : String(error);
5755
+ tantivyError = getErrorMessage(error);
5727
5756
  logger?.error({ err: error }, "tantivy rebuild failed; SQLite data is intact");
5728
5757
  }
5729
5758
  }
@@ -5759,9 +5788,9 @@ import pretty from "pino-pretty";
5759
5788
  function createCliLogger(options) {
5760
5789
  const loggerOptions = {
5761
5790
  base: void 0,
5762
- level: options.verbose === true ? "debug" : "info"
5791
+ level: options.verbose ? "debug" : "info"
5763
5792
  };
5764
- if (options.jsonLogs === true) {
5793
+ if (options.jsonLogs) {
5765
5794
  return pino(loggerOptions, pino.destination({ dest: 2, sync: true }));
5766
5795
  }
5767
5796
  return pino(
@@ -5797,7 +5826,7 @@ function compileAllCommand() {
5797
5826
  await runCompiles({
5798
5827
  providers: COMPILE_PROVIDERS,
5799
5828
  storePath: defaultBundlePath(),
5800
- deferIndex: options.deferIndex === true,
5829
+ deferIndex: options.deferIndex ?? false,
5801
5830
  logOptions: options
5802
5831
  });
5803
5832
  });
@@ -5812,7 +5841,7 @@ function providerCompileCommand(provider) {
5812
5841
  await runCompiles({
5813
5842
  providers: [provider],
5814
5843
  storePath: options.store,
5815
- deferIndex: options.deferIndex === true,
5844
+ deferIndex: options.deferIndex ?? false,
5816
5845
  sessionsPath: options.sessionsPath,
5817
5846
  logOptions: command.optsWithGlobals()
5818
5847
  });
@@ -5870,7 +5899,7 @@ function printCounts(summary) {
5870
5899
 
5871
5900
  // src/cli/commands/export.ts
5872
5901
  import { writeFile as writeFile6 } from "fs/promises";
5873
- import path15 from "path";
5902
+ import path16 from "path";
5874
5903
  import { Command as Command2 } from "commander";
5875
5904
 
5876
5905
  // src/services/export/markdown.ts
@@ -5984,30 +6013,38 @@ function renderToolCall(c) {
5984
6013
  return lines.join("\n");
5985
6014
  }
5986
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
+
5987
6027
  // src/cli/commands/export.ts
5988
6028
  function exportCommand() {
5989
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) => {
5990
6030
  if (options.format !== "markdown") {
5991
6031
  throw new Error(`unsupported format: ${options.format} (try --format markdown)`);
5992
6032
  }
5993
- const bundle = await openBundle(path15.resolve(options.store));
5994
- try {
6033
+ await withBundle(options.store, async (bundle) => {
5995
6034
  const markdown = await exportSessionMarkdown(bundle, sessionId2);
5996
6035
  if (options.out) {
5997
- await writeFile6(path15.resolve(options.out), markdown, "utf8");
5998
- process.stdout.write(`wrote ${path15.resolve(options.out)}
6036
+ await writeFile6(path16.resolve(options.out), markdown, "utf8");
6037
+ process.stdout.write(`wrote ${path16.resolve(options.out)}
5999
6038
  `);
6000
6039
  } else {
6001
6040
  process.stdout.write(markdown);
6002
6041
  }
6003
- } finally {
6004
- closeBundle(bundle);
6005
- }
6042
+ });
6006
6043
  });
6007
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) => {
6008
6045
  const result = await exportBundleParquet({
6009
- bundlePath: path15.resolve(options.store),
6010
- outDir: options.out ? path15.resolve(options.out) : void 0
6046
+ bundlePath: path16.resolve(options.store),
6047
+ outDir: options.out ? path16.resolve(options.out) : void 0
6011
6048
  });
6012
6049
  process.stdout.write(`wrote parquet export to ${result.outDir}
6013
6050
  `);
@@ -6018,12 +6055,13 @@ function exportCommand() {
6018
6055
  }
6019
6056
 
6020
6057
  // src/cli/commands/index.ts
6021
- import path16 from "path";
6022
6058
  import { Command as Command3 } from "commander";
6023
6059
  init_indexing();
6024
6060
 
6025
6061
  // src/cli/output.ts
6026
6062
  var OUTPUT_FORMATS = ["interactive", "table", "json", "csv"];
6063
+ var COL_SEPARATOR = " ";
6064
+ var RULE_CHAR = "-";
6027
6065
  function parseOutputFormat(value, fallback) {
6028
6066
  if (value === void 0) return fallback;
6029
6067
  if (OUTPUT_FORMATS.includes(value)) return value;
@@ -6051,11 +6089,12 @@ function printJson(rows, opts) {
6051
6089
  `);
6052
6090
  }
6053
6091
  function printCsv(rows, opts) {
6054
- const cols = opts.columns;
6055
- process.stdout.write(`${cols.map(csvField).join(",")}
6092
+ const columns = opts.columns;
6093
+ process.stdout.write(`${columns.map(csvField).join(",")}
6056
6094
  `);
6057
6095
  for (const row of rows) {
6058
- const line = cols.map((c) => csvField(formatCell(row[c]))).join(",");
6096
+ const record = row;
6097
+ const line = columns.map((column) => csvField(formatCell(record[column]))).join(",");
6059
6098
  process.stdout.write(`${line}
6060
6099
  `);
6061
6100
  }
@@ -6065,23 +6104,24 @@ function csvField(value) {
6065
6104
  return value;
6066
6105
  }
6067
6106
  function printTable(rows, opts) {
6068
- const cols = opts.columns;
6069
- const widths = cols.map((c) => c.length);
6070
- const cells = rows.map(
6071
- (row) => cols.map((col, i) => {
6072
- const text = formatCell(row[col]);
6073
- const w = widths[i] ?? 0;
6074
- if (text.length > w) widths[i] = text.length;
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;
6075
6115
  return text;
6076
- })
6077
- );
6078
- const header = cols.map((c, i) => c.padEnd(widths[i] ?? 0)).join(" ");
6079
- const sep = cols.map((_, i) => "-".repeat(widths[i] ?? 0)).join(" ");
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);
6080
6120
  process.stdout.write(`${header}
6081
- ${sep}
6121
+ ${rule}
6082
6122
  `);
6083
6123
  for (const cellRow of cells) {
6084
- const line = cellRow.map((c, i) => c.padEnd(widths[i] ?? 0)).join(" ");
6124
+ const line = cellRow.map((cell, index) => cell.padEnd(widths[index] ?? 0)).join(COL_SEPARATOR);
6085
6125
  process.stdout.write(`${line}
6086
6126
  `);
6087
6127
  }
@@ -6096,27 +6136,18 @@ function formatCell(value) {
6096
6136
  // src/cli/commands/index.ts
6097
6137
  function indexCommand() {
6098
6138
  const fts5 = new Command3("fts5").description("Rebuild the SQLite FTS5 index from search_docs.").option("--store <path>", "bundle directory", defaultBundlePath()).action(async (options) => {
6099
- const bundle = await openBundle(path16.resolve(options.store));
6100
- try {
6101
- const status2 = rebuildFts5Index(bundle);
6102
- printIndexStatus(status2);
6103
- } finally {
6104
- closeBundle(bundle);
6105
- }
6139
+ await withBundle(options.store, (bundle) => {
6140
+ printIndexStatus(rebuildFts5Index(bundle));
6141
+ });
6106
6142
  });
6107
6143
  const tantivy = new Command3("tantivy").description("Rebuild the Tantivy sidecar index from search_docs.").option("--store <path>", "bundle directory", defaultBundlePath()).action(async (options) => {
6108
- const bundle = await openBundle(path16.resolve(options.store));
6109
- try {
6110
- const status2 = await rebuildTantivyIndex(bundle);
6111
- printIndexStatus(status2);
6112
- } finally {
6113
- closeBundle(bundle);
6114
- }
6144
+ await withBundle(options.store, async (bundle) => {
6145
+ printIndexStatus(await rebuildTantivyIndex(bundle));
6146
+ });
6115
6147
  });
6116
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) => {
6117
6149
  const format = parseOutputFormat(options.outputFormat, "table");
6118
- const bundle = await openBundle(path16.resolve(options.store));
6119
- try {
6150
+ await withBundle(options.store, (bundle) => {
6120
6151
  const rows = getSearchIndexStatuses(bundle);
6121
6152
  printRows(rows, {
6122
6153
  format,
@@ -6129,9 +6160,7 @@ function indexCommand() {
6129
6160
  "error_message"
6130
6161
  ]
6131
6162
  });
6132
- } finally {
6133
- closeBundle(bundle);
6134
- }
6163
+ });
6135
6164
  });
6136
6165
  return new Command3("index").description("Build or inspect derived search indexes.").addCommand(fts5).addCommand(tantivy).addCommand(status);
6137
6166
  }
@@ -6178,6 +6207,7 @@ import path18 from "path";
6178
6207
  import { Command as Command5 } from "commander";
6179
6208
 
6180
6209
  // src/mcp/server.ts
6210
+ init_errors();
6181
6211
  import { randomUUID } from "crypto";
6182
6212
  import http from "http";
6183
6213
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -6237,6 +6267,13 @@ Use this workflow:
6237
6267
 
6238
6268
  // src/mcp/tools.ts
6239
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();
6240
6277
  init_indexing();
6241
6278
  init_search();
6242
6279
  init_sessions();
@@ -6250,7 +6287,7 @@ function registerProsaTools(server, bundle, options = {}) {
6250
6287
  title: "Compile sessions",
6251
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.",
6252
6289
  inputSchema: {
6253
- source: z.enum(["cursor", "codex", "claude", "gemini"]).optional(),
6290
+ source: z.enum(SOURCE_TOOLS).optional(),
6254
6291
  sessions_path: z.string().min(1).optional()
6255
6292
  },
6256
6293
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true }
@@ -6306,7 +6343,7 @@ function registerProsaTools(server, bundle, options = {}) {
6306
6343
  };
6307
6344
  } catch (error) {
6308
6345
  return {
6309
- content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
6346
+ content: [{ type: "text", text: getErrorMessage(error) }],
6310
6347
  isError: true
6311
6348
  };
6312
6349
  }
@@ -6318,7 +6355,7 @@ function registerProsaTools(server, bundle, options = {}) {
6318
6355
  title: "List sessions",
6319
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.",
6320
6357
  inputSchema: {
6321
- source: z.enum(["cursor", "codex", "claude", "gemini"]).optional(),
6358
+ source: z.enum(SOURCE_TOOLS).optional(),
6322
6359
  since: z.string().optional().describe("ISO timestamp lower bound (inclusive)"),
6323
6360
  until: z.string().optional().describe("ISO timestamp upper bound (exclusive)"),
6324
6361
  limit: z.number().int().min(1).max(500).optional().default(50)
@@ -6404,7 +6441,7 @@ function registerProsaTools(server, bundle, options = {}) {
6404
6441
  return { content: [{ type: "text", text: md }] };
6405
6442
  } catch (error) {
6406
6443
  return {
6407
- content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
6444
+ content: [{ type: "text", text: getErrorMessage(error) }],
6408
6445
  isError: true
6409
6446
  };
6410
6447
  }
@@ -6463,7 +6500,7 @@ function registerProsaTools(server, bundle, options = {}) {
6463
6500
  LEFT JOIN tool_results tr ON tr.tool_call_id = tc.tool_call_id
6464
6501
  ${where}
6465
6502
  ORDER BY tc.timestamp_start DESC
6466
- LIMIT ${Math.max(1, Math.min(500, limit ?? 100))}
6503
+ LIMIT ${clampLimit(limit, { max: 500, fallback: 100 })}
6467
6504
  `;
6468
6505
  const rows = bundle.db.prepare(sql).all(...params);
6469
6506
  return {
@@ -6494,7 +6531,7 @@ function registerProsaTools(server, bundle, options = {}) {
6494
6531
  FROM artifacts a
6495
6532
  WHERE a.path IS NOT NULL AND a.path LIKE ?
6496
6533
  ORDER BY timestamp_start DESC
6497
- LIMIT ${Math.max(1, Math.min(500, limit ?? 100))}
6534
+ LIMIT ${clampLimit(limit, { max: 500, fallback: 100 })}
6498
6535
  `;
6499
6536
  const like = `%${path_substring}%`;
6500
6537
  const rows = bundle.db.prepare(sql).all(like, like);
@@ -6582,14 +6619,14 @@ function registerProsaPrompts(server) {
6582
6619
  path: z.string().min(1).describe("File path, directory, or distinctive path suffix")
6583
6620
  }
6584
6621
  },
6585
- ({ path: path23 }) => ({
6622
+ ({ path: path20 }) => ({
6586
6623
  description: "Find sessions that touched a path and summarize the evidence.",
6587
6624
  messages: [
6588
6625
  {
6589
6626
  role: "user",
6590
6627
  content: {
6591
6628
  type: "text",
6592
- text: FIND_FILE_HISTORY_PROMPT.replace("{{path}}", path23)
6629
+ text: FIND_FILE_HISTORY_PROMPT.replace("{{path}}", path20)
6593
6630
  }
6594
6631
  }
6595
6632
  ]
@@ -6753,12 +6790,27 @@ function writeError(res, error) {
6753
6790
  res.end(
6754
6791
  JSON.stringify({
6755
6792
  jsonrpc: "2.0",
6756
- error: { code: -32603, message: error instanceof Error ? error.message : String(error) },
6793
+ error: { code: -32603, message: getErrorMessage(error) },
6757
6794
  id: null
6758
6795
  })
6759
6796
  );
6760
6797
  }
6761
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
+
6762
6814
  // src/cli/commands/mcp.ts
6763
6815
  function mcpCommand() {
6764
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(
@@ -6796,14 +6848,6 @@ function mcpCommand() {
6796
6848
  );
6797
6849
  return new Command5("mcp").description("MCP server commands.").addCommand(serve);
6798
6850
  }
6799
- function parseMcpTransport(value) {
6800
- if (value === "stdio" || value === "http") return value;
6801
- throw new Error(`invalid --transport: ${value} (expected stdio or http)`);
6802
- }
6803
- function parseSearchEngine(value) {
6804
- if (value === "fts5" || value === "tantivy") return value;
6805
- throw new Error(`invalid --search-engine: ${value} (expected fts5 or tantivy)`);
6806
- }
6807
6851
  function registerShutdown(closeServer, bundle) {
6808
6852
  const shutdown = async () => {
6809
6853
  await closeServer();
@@ -6825,7 +6869,7 @@ function queryCommand() {
6825
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(
6826
6870
  async (sql, options) => {
6827
6871
  const format = parseOutputFormat(options.outputFormat, "table");
6828
- const parquetDir = options.parquetDir ? path19.resolve(options.parquetDir) : await defaultParquetDir(path19.resolve(options.store));
6872
+ const parquetDir = options.parquetDir ? path19.resolve(options.parquetDir) : await withBundle(options.store, (bundle) => bundle.paths.parquet);
6829
6873
  const result = await queryDuckDbParquet({ parquetDir, sql });
6830
6874
  printRows(result.rows, {
6831
6875
  format,
@@ -6836,26 +6880,16 @@ function queryCommand() {
6836
6880
  );
6837
6881
  return new Command6("query").description("Run derived analytical queries.").addCommand(duckdb);
6838
6882
  }
6839
- async function defaultParquetDir(storePath) {
6840
- const bundle = await openBundle(storePath);
6841
- try {
6842
- return bundle.paths.parquet;
6843
- } finally {
6844
- closeBundle(bundle);
6845
- }
6846
- }
6847
6883
 
6848
6884
  // src/cli/commands/search.ts
6849
- import path20 from "path";
6850
6885
  import { Command as Command7 } from "commander";
6851
6886
  init_search();
6852
6887
  function searchCommand() {
6853
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(
6854
6889
  async (query, options) => {
6855
- const engine = parseSearchEngine2(options.engine);
6890
+ const engine = parseSearchEngine(options.engine);
6856
6891
  const format = parseOutputFormat(options.outputFormat, "table");
6857
- const bundle = await openBundle(path20.resolve(options.store));
6858
- try {
6892
+ await withBundle(options.store, (bundle) => {
6859
6893
  const hits = searchFullText(bundle, {
6860
6894
  query,
6861
6895
  limit: Number.parseInt(options.limit, 10),
@@ -6866,29 +6900,21 @@ function searchCommand() {
6866
6900
  columns: ["timestamp", "role", "tool_name", "session_id", "snippet"],
6867
6901
  meta: { query, engine, count: hits.length }
6868
6902
  });
6869
- } finally {
6870
- closeBundle(bundle);
6871
- }
6903
+ });
6872
6904
  }
6873
6905
  );
6874
6906
  }
6875
- function parseSearchEngine2(value) {
6876
- if (value === "fts5" || value === "tantivy") return value;
6877
- throw new Error(`invalid --engine: ${value} (expected fts5 or tantivy)`);
6878
- }
6879
6907
 
6880
6908
  // src/cli/commands/sessions.ts
6881
- import path21 from "path";
6882
6909
  import { Command as Command8 } from "commander";
6883
6910
  init_sessions();
6884
6911
  function sessionsCommand() {
6885
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(
6886
6913
  async (options) => {
6887
6914
  const format = parseOutputFormat(options.outputFormat, "table");
6888
- const bundle = await openBundle(path21.resolve(options.store));
6889
- try {
6915
+ await withBundle(options.store, (bundle) => {
6890
6916
  const rows = listSessions(bundle, {
6891
- sourceTool: options.source,
6917
+ sourceTool: parseSourceTool(options.source),
6892
6918
  sinceIso: options.since,
6893
6919
  untilIso: options.until,
6894
6920
  limit: Number.parseInt(options.limit, 10)
@@ -6906,26 +6932,21 @@ function sessionsCommand() {
6906
6932
  "title"
6907
6933
  ]
6908
6934
  });
6909
- } finally {
6910
- closeBundle(bundle);
6911
- }
6935
+ });
6912
6936
  }
6913
6937
  );
6914
6938
  command.addCommand(
6915
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(
6916
6940
  async (options) => {
6917
- const bundle = await openBundle(path21.resolve(options.store));
6918
- try {
6941
+ await withBundle(options.store, (bundle) => {
6919
6942
  const count = countSessions(bundle, {
6920
- sourceTool: options.source,
6943
+ sourceTool: parseSourceTool(options.source),
6921
6944
  sinceIso: options.since,
6922
6945
  untilIso: options.until
6923
6946
  });
6924
6947
  process.stdout.write(`${count}
6925
6948
  `);
6926
- } finally {
6927
- closeBundle(bundle);
6928
- }
6949
+ });
6929
6950
  }
6930
6951
  )
6931
6952
  );
@@ -6933,7 +6954,6 @@ function sessionsCommand() {
6933
6954
  }
6934
6955
 
6935
6956
  // src/cli/commands/tui.ts
6936
- import path22 from "path";
6937
6957
  import { Command as Command9 } from "commander";
6938
6958
  function tuiCommand() {
6939
6959
  return new Command9("tui").description("Open the interactive Ink-based explorer.").option("--store <path>", "bundle directory", defaultBundlePath()).action(async (options) => {
@@ -6942,14 +6962,11 @@ function tuiCommand() {
6942
6962
  import("react"),
6943
6963
  Promise.resolve().then(() => (init_App(), App_exports))
6944
6964
  ]);
6945
- const bundle = await openBundle(path22.resolve(options.store));
6946
- try {
6965
+ await withBundle(options.store, async (bundle) => {
6947
6966
  console.clear();
6948
6967
  const app = render(React.createElement(App2, { bundle }));
6949
6968
  await app.waitUntilExit();
6950
- } finally {
6951
- closeBundle(bundle);
6952
- }
6969
+ });
6953
6970
  });
6954
6971
  }
6955
6972