sensemaking 0.11.4 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +4 -3
  2. package/bin/cli.js +5 -2
  3. package/dist/cjs/cli/index.d.cts +3 -3
  4. package/dist/cjs/cli/index.d.ts +3 -3
  5. package/dist/cjs/cli/index.js +3 -3
  6. package/dist/cjs/cli/index.js.map +1 -1
  7. package/dist/cjs/cli/named.js +2 -2
  8. package/dist/cjs/cli/named.js.map +1 -1
  9. package/dist/cjs/cli/related.js +1 -1
  10. package/dist/cjs/cli/related.js.map +1 -1
  11. package/dist/cjs/cli/search.js +1 -1
  12. package/dist/cjs/cli/search.js.map +1 -1
  13. package/dist/cjs/cli/shared.d.cts +3 -1
  14. package/dist/cjs/cli/shared.d.ts +3 -1
  15. package/dist/cjs/cli/shared.js +35 -5
  16. package/dist/cjs/cli/shared.js.map +1 -1
  17. package/dist/cjs/cli/sql.js +1 -1
  18. package/dist/cjs/cli/sql.js.map +1 -1
  19. package/dist/cjs/cli.js +14 -1
  20. package/dist/cjs/cli.js.map +1 -1
  21. package/dist/cjs/commands.js +19 -5
  22. package/dist/cjs/commands.js.map +1 -1
  23. package/dist/cjs/config.d.cts +4 -0
  24. package/dist/cjs/config.d.ts +4 -0
  25. package/dist/cjs/config.js +39 -4
  26. package/dist/cjs/config.js.map +1 -1
  27. package/dist/cjs/db.d.cts +1 -1
  28. package/dist/cjs/db.d.ts +1 -1
  29. package/dist/cjs/db.js +158 -14
  30. package/dist/cjs/db.js.map +1 -1
  31. package/dist/cjs/output.d.cts +4 -1
  32. package/dist/cjs/output.d.ts +4 -1
  33. package/dist/cjs/output.js +158 -6
  34. package/dist/cjs/output.js.map +1 -1
  35. package/dist/cjs/segment.d.cts +2 -0
  36. package/dist/cjs/segment.d.ts +2 -0
  37. package/dist/cjs/segment.js +98 -0
  38. package/dist/cjs/segment.js.map +1 -0
  39. package/dist/esm/cli/index.d.ts +3 -3
  40. package/dist/esm/cli/index.js +3 -3
  41. package/dist/esm/cli/index.js.map +1 -1
  42. package/dist/esm/cli/named.js +3 -3
  43. package/dist/esm/cli/named.js.map +1 -1
  44. package/dist/esm/cli/related.js +2 -2
  45. package/dist/esm/cli/related.js.map +1 -1
  46. package/dist/esm/cli/search.js +2 -2
  47. package/dist/esm/cli/search.js.map +1 -1
  48. package/dist/esm/cli/shared.d.ts +3 -1
  49. package/dist/esm/cli/shared.js +32 -5
  50. package/dist/esm/cli/shared.js.map +1 -1
  51. package/dist/esm/cli/sql.js +2 -2
  52. package/dist/esm/cli/sql.js.map +1 -1
  53. package/dist/esm/cli.js +14 -1
  54. package/dist/esm/cli.js.map +1 -1
  55. package/dist/esm/commands.js +19 -5
  56. package/dist/esm/commands.js.map +1 -1
  57. package/dist/esm/config.d.ts +4 -0
  58. package/dist/esm/config.js +37 -4
  59. package/dist/esm/config.js.map +1 -1
  60. package/dist/esm/db.d.ts +1 -1
  61. package/dist/esm/db.js +140 -15
  62. package/dist/esm/db.js.map +1 -1
  63. package/dist/esm/output.d.ts +4 -1
  64. package/dist/esm/output.js +104 -4
  65. package/dist/esm/output.js.map +1 -1
  66. package/dist/esm/segment.d.ts +2 -0
  67. package/dist/esm/segment.js +65 -0
  68. package/dist/esm/segment.js.map +1 -0
  69. package/package.json +4 -1
  70. package/schema.json +12 -1
  71. package/skills/sense/SKILL.md +7 -6
  72. package/skills/sense-setup/SKILL.md +1 -0
@@ -4,12 +4,12 @@ export declare const USAGE: {
4
4
  readonly watch: "watch [--force] [--config path]";
5
5
  readonly status: "status [--format table|json] [--config path]";
6
6
  readonly download: "download [--config path]";
7
- readonly sql: "sql \"<statement>\" [params...] [--preset name] [--format table|json] [--config path]";
8
- readonly search: "search \"<terms>\" [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where \"<sql>\"] [--k n] [--format table|json] [--config path]";
7
+ readonly sql: "sql \"<statement>\" [params...] [--preset name] [--format table|json|csv] [--config path]";
8
+ readonly search: "search \"<terms>\" [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where \"<sql>\"] [--k n] [--format table|json|csv] [--config path]";
9
9
  readonly map: "map [--format table|json] [--config path]";
10
10
  readonly peek: "peek <path> [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where \"<sql>\"] [--format table|json] [--config path]";
11
11
  readonly path: "path <a> <b> [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where \"<sql>\"] [--max-depth n] [--format table|json] [--config path]";
12
- readonly related: "related <note> [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where \"<sql>\"] [--k n] [--format table|json] [--config path]";
12
+ readonly related: "related <note> [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where \"<sql>\"] [--k n] [--format table|json|csv] [--config path]";
13
13
  };
14
14
  export declare const COMMANDS: Record<string, () => Promise<{
15
15
  default: Command;
@@ -6,12 +6,12 @@ export const USAGE = {
6
6
  watch: 'watch [--force] [--config path]',
7
7
  status: 'status [--format table|json] [--config path]',
8
8
  download: 'download [--config path]',
9
- sql: 'sql "<statement>" [params...] [--preset name] [--format table|json] [--config path]',
10
- search: 'search "<terms>" [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where "<sql>"] [--k n] [--format table|json] [--config path]',
9
+ sql: 'sql "<statement>" [params...] [--preset name] [--format table|json|csv] [--config path]',
10
+ search: 'search "<terms>" [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where "<sql>"] [--k n] [--format table|json|csv] [--config path]',
11
11
  map: 'map [--format table|json] [--config path]',
12
12
  peek: 'peek <path> [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where "<sql>"] [--format table|json] [--config path]',
13
13
  path: 'path <a> <b> [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where "<sql>"] [--max-depth n] [--format table|json] [--config path]',
14
- related: 'related <note> [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where "<sql>"] [--k n] [--format table|json] [--config path]'
14
+ related: 'related <note> [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where "<sql>"] [--k n] [--format table|json|csv] [--config path]'
15
15
  };
16
16
  // Lazy registry: a command's imports load only when it runs, so a heavy dependency in one
17
17
  // command never taxes the others (or --version/--help). These names are reserved: a saved
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli/index.ts"],"sourcesContent":["import type { Command } from './types.ts';\n\n// Each command's usage line, minus the leading \"usage: <name> \" -- shared by cli.ts's\n// top-level usage() and the command's own parse() error/--help output, so there is one\n// copy of each line, not two that can drift.\nexport const USAGE = {\n init: 'init',\n watch: 'watch [--force] [--config path]',\n status: 'status [--format table|json] [--config path]',\n download: 'download [--config path]',\n sql: 'sql \"<statement>\" [params...] [--preset name] [--format table|json] [--config path]',\n search: 'search \"<terms>\" [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where \"<sql>\"] [--k n] [--format table|json] [--config path]',\n map: 'map [--format table|json] [--config path]',\n peek: 'peek <path> [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where \"<sql>\"] [--format table|json] [--config path]',\n path: 'path <a> <b> [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where \"<sql>\"] [--max-depth n] [--format table|json] [--config path]',\n related: 'related <note> [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where \"<sql>\"] [--k n] [--format table|json] [--config path]',\n} as const;\n\n// Lazy registry: a command's imports load only when it runs, so a heavy dependency in one\n// command never taxes the others (or --version/--help). These names are reserved: a saved\n// entry in config can never shadow them.\nexport const COMMANDS: Record<string, () => Promise<{ default: Command }>> = {\n init: () => import('./init.ts'),\n watch: () => import('./watch.ts'),\n status: () => import('./status.ts'),\n download: () => import('./download.ts'),\n sql: () => import('./sql.ts'),\n search: () => import('./search.ts'),\n map: () => import('./map.ts'),\n peek: () => import('./peek.ts'),\n path: () => import('./path.ts'),\n related: () => import('./related.ts'),\n};\n\nexport type { Command, Ctx } from './types.ts';\n"],"names":["USAGE","init","watch","status","download","sql","search","map","peek","path","related","COMMANDS"],"mappings":"AAEA,sFAAsF;AACtF,uFAAuF;AACvF,6CAA6C;AAC7C,OAAO,MAAMA,QAAQ;IACnBC,MAAM;IACNC,OAAO;IACPC,QAAQ;IACRC,UAAU;IACVC,KAAK;IACLC,QAAQ;IACRC,KAAK;IACLC,MAAM;IACNC,MAAM;IACNC,SAAS;AACX,EAAW;AAEX,0FAA0F;AAC1F,0FAA0F;AAC1F,yCAAyC;AACzC,OAAO,MAAMC,WAAgE;IAC3EV,MAAM,IAAM,MAAM,CAAC;IACnBC,OAAO,IAAM,MAAM,CAAC;IACpBC,QAAQ,IAAM,MAAM,CAAC;IACrBC,UAAU,IAAM,MAAM,CAAC;IACvBC,KAAK,IAAM,MAAM,CAAC;IAClBC,QAAQ,IAAM,MAAM,CAAC;IACrBC,KAAK,IAAM,MAAM,CAAC;IAClBC,MAAM,IAAM,MAAM,CAAC;IACnBC,MAAM,IAAM,MAAM,CAAC;IACnBC,SAAS,IAAM,MAAM,CAAC;AACxB,EAAE"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli/index.ts"],"sourcesContent":["import type { Command } from './types.ts';\n\n// Each command's usage line, minus the leading \"usage: <name> \" -- shared by cli.ts's\n// top-level usage() and the command's own parse() error/--help output, so there is one\n// copy of each line, not two that can drift.\nexport const USAGE = {\n init: 'init',\n watch: 'watch [--force] [--config path]',\n status: 'status [--format table|json] [--config path]',\n download: 'download [--config path]',\n sql: 'sql \"<statement>\" [params...] [--preset name] [--format table|json|csv] [--config path]',\n search: 'search \"<terms>\" [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where \"<sql>\"] [--k n] [--format table|json|csv] [--config path]',\n map: 'map [--format table|json] [--config path]',\n peek: 'peek <path> [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where \"<sql>\"] [--format table|json] [--config path]',\n path: 'path <a> <b> [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where \"<sql>\"] [--max-depth n] [--format table|json] [--config path]',\n related: 'related <note> [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where \"<sql>\"] [--k n] [--format table|json|csv] [--config path]',\n} as const;\n\n// Lazy registry: a command's imports load only when it runs, so a heavy dependency in one\n// command never taxes the others (or --version/--help). These names are reserved: a saved\n// entry in config can never shadow them.\nexport const COMMANDS: Record<string, () => Promise<{ default: Command }>> = {\n init: () => import('./init.ts'),\n watch: () => import('./watch.ts'),\n status: () => import('./status.ts'),\n download: () => import('./download.ts'),\n sql: () => import('./sql.ts'),\n search: () => import('./search.ts'),\n map: () => import('./map.ts'),\n peek: () => import('./peek.ts'),\n path: () => import('./path.ts'),\n related: () => import('./related.ts'),\n};\n\nexport type { Command, Ctx } from './types.ts';\n"],"names":["USAGE","init","watch","status","download","sql","search","map","peek","path","related","COMMANDS"],"mappings":"AAEA,sFAAsF;AACtF,uFAAuF;AACvF,6CAA6C;AAC7C,OAAO,MAAMA,QAAQ;IACnBC,MAAM;IACNC,OAAO;IACPC,QAAQ;IACRC,UAAU;IACVC,KAAK;IACLC,QAAQ;IACRC,KAAK;IACLC,MAAM;IACNC,MAAM;IACNC,SAAS;AACX,EAAW;AAEX,0FAA0F;AAC1F,0FAA0F;AAC1F,yCAAyC;AACzC,OAAO,MAAMC,WAAgE;IAC3EV,MAAM,IAAM,MAAM,CAAC;IACnBC,OAAO,IAAM,MAAM,CAAC;IACpBC,QAAQ,IAAM,MAAM,CAAC;IACrBC,UAAU,IAAM,MAAM,CAAC;IACvBC,KAAK,IAAM,MAAM,CAAC;IAClBC,QAAQ,IAAM,MAAM,CAAC;IACrBC,KAAK,IAAM,MAAM,CAAC;IAClBC,MAAM,IAAM,MAAM,CAAC;IACnBC,MAAM,IAAM,MAAM,CAAC;IACnBC,SAAS,IAAM,MAAM,CAAC;AACxB,EAAE"}
@@ -1,17 +1,17 @@
1
1
  import { search } from '../commands.js';
2
2
  import { printRows } from '../output.js';
3
- import { CONFIG, FORMAT, formatOf, parse, parseK, runSql, SEARCH_FLAGS, withDb } from './shared.js';
3
+ import { CONFIG, FORMAT, parse, parseK, rowFormatOf, runSql, SEARCH_FLAGS, withDb } from './shared.js';
4
4
  // Fallback when the first positional is not a command: a saved query, { sql } or { search }.
5
5
  // SEARCH_FLAGS override a saved search's fields the same way they override a preset's.
6
6
  export default async function named(ctx, queryName) {
7
7
  var _parseK, _values_where, _values_preset, _values_include, _values_exclude;
8
- const usage = `usage: ${ctx.name} ${queryName} [params...] [--format table|json] [--config path] [--where "<sql>"] [--k n] [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude]`;
8
+ const usage = `usage: ${ctx.name} ${queryName} [params...] [--format table|json|csv] [--config path] [--where "<sql>"] [--k n] [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude]`;
9
9
  const { values, positionals: params } = parse(ctx.argv, usage, {
10
10
  ...SEARCH_FLAGS,
11
11
  ...FORMAT,
12
12
  ...CONFIG
13
13
  });
14
- const format = formatOf(values);
14
+ const format = rowFormatOf(values);
15
15
  const configPath = values.config;
16
16
  const cfg = ctx.resolveConfig(configPath);
17
17
  const entry = cfg.queries[queryName];
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli/named.ts"],"sourcesContent":["import { search } from '../commands.ts';\nimport { printRows } from '../output.ts';\nimport { CONFIG, FORMAT, formatOf, parse, parseK, runSql, SEARCH_FLAGS, withDb } from './shared.ts';\nimport type { Ctx } from './types.ts';\n\n// Fallback when the first positional is not a command: a saved query, { sql } or { search }.\n// SEARCH_FLAGS override a saved search's fields the same way they override a preset's.\nexport default async function named(ctx: Ctx, queryName: string): Promise<void> {\n const usage = `usage: ${ctx.name} ${queryName} [params...] [--format table|json] [--config path] [--where \"<sql>\"] [--k n] [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude]`;\n const { values, positionals: params } = parse(ctx.argv, usage, { ...SEARCH_FLAGS, ...FORMAT, ...CONFIG });\n const format = formatOf(values);\n const configPath = values.config as string | undefined;\n\n const cfg = ctx.resolveConfig(configPath);\n const entry = cfg.queries[queryName];\n if (entry === undefined) {\n console.error(`unknown command or saved entry: \"${queryName}\"`);\n console.error(`saved queries: ${Object.keys(cfg.queries).sort().join(', ')}`);\n process.exit(2);\n }\n\n if ('sql' in entry) {\n // A saved statement written against `scope` is preset-agnostic, so the same entry can be\n // re-pointed at another layer from the command line instead of being copied per preset.\n runSql(cfg, entry.sql, params, format, `query \"${queryName}\"`, values.preset as string | undefined);\n return;\n }\n\n // A saved search's text is fixed in config; there is nowhere for a positional to bind.\n if (params.length > 0) {\n ctx.usageError(`\"${queryName}\" is a saved search and takes no positional parameters; edit its \"search\" in sense.config.json, or use \"${ctx.name} search\" directly`);\n }\n const k = parseK(values.k as string | undefined, ctx.usageError) ?? entry.k;\n const where = (values.where as string | undefined) ?? entry.where;\n const preset = (values.preset as string | undefined) ?? entry.preset;\n const include = (values.include as string[] | undefined) ?? entry.include;\n const exclude = (values.exclude as string[] | undefined) ?? entry.exclude;\n const noExclude = values['no-exclude'] === true;\n await withDb(ctx, configPath, async (db, resolvedCfg) => printRows(await search(db, resolvedCfg, entry.search, { k, where, preset, include, exclude, noExclude }), format));\n}\n"],"names":["search","printRows","CONFIG","FORMAT","formatOf","parse","parseK","runSql","SEARCH_FLAGS","withDb","named","ctx","queryName","values","usage","name","positionals","params","argv","format","configPath","config","cfg","resolveConfig","entry","queries","undefined","console","error","Object","keys","sort","join","process","exit","sql","preset","length","usageError","k","where","include","exclude","noExclude","db","resolvedCfg"],"mappings":"AAAA,SAASA,MAAM,QAAQ,iBAAiB;AACxC,SAASC,SAAS,QAAQ,eAAe;AACzC,SAASC,MAAM,EAAEC,MAAM,EAAEC,QAAQ,EAAEC,KAAK,EAAEC,MAAM,EAAEC,MAAM,EAAEC,YAAY,EAAEC,MAAM,QAAQ,cAAc;AAGpG,6FAA6F;AAC7F,uFAAuF;AACvF,eAAe,eAAeC,MAAMC,GAAQ,EAAEC,SAAiB;QAyBnDN,SACKO,eACCA,gBACCA,iBACAA;IA5BjB,MAAMC,QAAQ,CAAC,OAAO,EAAEH,IAAII,IAAI,CAAC,CAAC,EAAEH,UAAU,qJAAqJ,CAAC;IACpM,MAAM,EAAEC,MAAM,EAAEG,aAAaC,MAAM,EAAE,GAAGZ,MAAMM,IAAIO,IAAI,EAAEJ,OAAO;QAAE,GAAGN,YAAY;QAAE,GAAGL,MAAM;QAAE,GAAGD,MAAM;IAAC;IACvG,MAAMiB,SAASf,SAASS;IACxB,MAAMO,aAAaP,OAAOQ,MAAM;IAEhC,MAAMC,MAAMX,IAAIY,aAAa,CAACH;IAC9B,MAAMI,QAAQF,IAAIG,OAAO,CAACb,UAAU;IACpC,IAAIY,UAAUE,WAAW;QACvBC,QAAQC,KAAK,CAAC,CAAC,iCAAiC,EAAEhB,UAAU,CAAC,CAAC;QAC9De,QAAQC,KAAK,CAAC,CAAC,eAAe,EAAEC,OAAOC,IAAI,CAACR,IAAIG,OAAO,EAAEM,IAAI,GAAGC,IAAI,CAAC,OAAO;QAC5EC,QAAQC,IAAI,CAAC;IACf;IAEA,IAAI,SAASV,OAAO;QAClB,yFAAyF;QACzF,wFAAwF;QACxFjB,OAAOe,KAAKE,MAAMW,GAAG,EAAElB,QAAQE,QAAQ,CAAC,OAAO,EAAEP,UAAU,CAAC,CAAC,EAAEC,OAAOuB,MAAM;QAC5E;IACF;IAEA,uFAAuF;IACvF,IAAInB,OAAOoB,MAAM,GAAG,GAAG;QACrB1B,IAAI2B,UAAU,CAAC,CAAC,CAAC,EAAE1B,UAAU,wGAAwG,EAAED,IAAII,IAAI,CAAC,iBAAiB,CAAC;IACpK;IACA,MAAMwB,KAAIjC,UAAAA,OAAOO,OAAO0B,CAAC,EAAwB5B,IAAI2B,UAAU,eAArDhC,qBAAAA,UAA0DkB,MAAMe,CAAC;IAC3E,MAAMC,SAAS3B,gBAAAA,OAAO2B,KAAK,cAAZ3B,2BAAAA,gBAAuCW,MAAMgB,KAAK;IACjE,MAAMJ,UAAUvB,iBAAAA,OAAOuB,MAAM,cAAbvB,4BAAAA,iBAAwCW,MAAMY,MAAM;IACpE,MAAMK,WAAW5B,kBAAAA,OAAO4B,OAAO,cAAd5B,6BAAAA,kBAA2CW,MAAMiB,OAAO;IACzE,MAAMC,WAAW7B,kBAAAA,OAAO6B,OAAO,cAAd7B,6BAAAA,kBAA2CW,MAAMkB,OAAO;IACzE,MAAMC,YAAY9B,MAAM,CAAC,aAAa,KAAK;IAC3C,MAAMJ,OAAOE,KAAKS,YAAY,OAAOwB,IAAIC,cAAgB5C,UAAU,MAAMD,OAAO4C,IAAIC,aAAarB,MAAMxB,MAAM,EAAE;YAAEuC;YAAGC;YAAOJ;YAAQK;YAASC;YAASC;QAAU,IAAIxB;AACrK"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli/named.ts"],"sourcesContent":["import { search } from '../commands.ts';\nimport { printRows } from '../output.ts';\nimport { CONFIG, FORMAT, parse, parseK, rowFormatOf, runSql, SEARCH_FLAGS, withDb } from './shared.ts';\nimport type { Ctx } from './types.ts';\n\n// Fallback when the first positional is not a command: a saved query, { sql } or { search }.\n// SEARCH_FLAGS override a saved search's fields the same way they override a preset's.\nexport default async function named(ctx: Ctx, queryName: string): Promise<void> {\n const usage = `usage: ${ctx.name} ${queryName} [params...] [--format table|json|csv] [--config path] [--where \"<sql>\"] [--k n] [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude]`;\n const { values, positionals: params } = parse(ctx.argv, usage, { ...SEARCH_FLAGS, ...FORMAT, ...CONFIG });\n const format = rowFormatOf(values);\n const configPath = values.config as string | undefined;\n\n const cfg = ctx.resolveConfig(configPath);\n const entry = cfg.queries[queryName];\n if (entry === undefined) {\n console.error(`unknown command or saved entry: \"${queryName}\"`);\n console.error(`saved queries: ${Object.keys(cfg.queries).sort().join(', ')}`);\n process.exit(2);\n }\n\n if ('sql' in entry) {\n // A saved statement written against `scope` is preset-agnostic, so the same entry can be\n // re-pointed at another layer from the command line instead of being copied per preset.\n runSql(cfg, entry.sql, params, format, `query \"${queryName}\"`, values.preset as string | undefined);\n return;\n }\n\n // A saved search's text is fixed in config; there is nowhere for a positional to bind.\n if (params.length > 0) {\n ctx.usageError(`\"${queryName}\" is a saved search and takes no positional parameters; edit its \"search\" in sense.config.json, or use \"${ctx.name} search\" directly`);\n }\n const k = parseK(values.k as string | undefined, ctx.usageError) ?? entry.k;\n const where = (values.where as string | undefined) ?? entry.where;\n const preset = (values.preset as string | undefined) ?? entry.preset;\n const include = (values.include as string[] | undefined) ?? entry.include;\n const exclude = (values.exclude as string[] | undefined) ?? entry.exclude;\n const noExclude = values['no-exclude'] === true;\n await withDb(ctx, configPath, async (db, resolvedCfg) => printRows(await search(db, resolvedCfg, entry.search, { k, where, preset, include, exclude, noExclude }), format));\n}\n"],"names":["search","printRows","CONFIG","FORMAT","parse","parseK","rowFormatOf","runSql","SEARCH_FLAGS","withDb","named","ctx","queryName","values","usage","name","positionals","params","argv","format","configPath","config","cfg","resolveConfig","entry","queries","undefined","console","error","Object","keys","sort","join","process","exit","sql","preset","length","usageError","k","where","include","exclude","noExclude","db","resolvedCfg"],"mappings":"AAAA,SAASA,MAAM,QAAQ,iBAAiB;AACxC,SAASC,SAAS,QAAQ,eAAe;AACzC,SAASC,MAAM,EAAEC,MAAM,EAAEC,KAAK,EAAEC,MAAM,EAAEC,WAAW,EAAEC,MAAM,EAAEC,YAAY,EAAEC,MAAM,QAAQ,cAAc;AAGvG,6FAA6F;AAC7F,uFAAuF;AACvF,eAAe,eAAeC,MAAMC,GAAQ,EAAEC,SAAiB;QAyBnDP,SACKQ,eACCA,gBACCA,iBACAA;IA5BjB,MAAMC,QAAQ,CAAC,OAAO,EAAEH,IAAII,IAAI,CAAC,CAAC,EAAEH,UAAU,yJAAyJ,CAAC;IACxM,MAAM,EAAEC,MAAM,EAAEG,aAAaC,MAAM,EAAE,GAAGb,MAAMO,IAAIO,IAAI,EAAEJ,OAAO;QAAE,GAAGN,YAAY;QAAE,GAAGL,MAAM;QAAE,GAAGD,MAAM;IAAC;IACvG,MAAMiB,SAASb,YAAYO;IAC3B,MAAMO,aAAaP,OAAOQ,MAAM;IAEhC,MAAMC,MAAMX,IAAIY,aAAa,CAACH;IAC9B,MAAMI,QAAQF,IAAIG,OAAO,CAACb,UAAU;IACpC,IAAIY,UAAUE,WAAW;QACvBC,QAAQC,KAAK,CAAC,CAAC,iCAAiC,EAAEhB,UAAU,CAAC,CAAC;QAC9De,QAAQC,KAAK,CAAC,CAAC,eAAe,EAAEC,OAAOC,IAAI,CAACR,IAAIG,OAAO,EAAEM,IAAI,GAAGC,IAAI,CAAC,OAAO;QAC5EC,QAAQC,IAAI,CAAC;IACf;IAEA,IAAI,SAASV,OAAO;QAClB,yFAAyF;QACzF,wFAAwF;QACxFjB,OAAOe,KAAKE,MAAMW,GAAG,EAAElB,QAAQE,QAAQ,CAAC,OAAO,EAAEP,UAAU,CAAC,CAAC,EAAEC,OAAOuB,MAAM;QAC5E;IACF;IAEA,uFAAuF;IACvF,IAAInB,OAAOoB,MAAM,GAAG,GAAG;QACrB1B,IAAI2B,UAAU,CAAC,CAAC,CAAC,EAAE1B,UAAU,wGAAwG,EAAED,IAAII,IAAI,CAAC,iBAAiB,CAAC;IACpK;IACA,MAAMwB,KAAIlC,UAAAA,OAAOQ,OAAO0B,CAAC,EAAwB5B,IAAI2B,UAAU,eAArDjC,qBAAAA,UAA0DmB,MAAMe,CAAC;IAC3E,MAAMC,SAAS3B,gBAAAA,OAAO2B,KAAK,cAAZ3B,2BAAAA,gBAAuCW,MAAMgB,KAAK;IACjE,MAAMJ,UAAUvB,iBAAAA,OAAOuB,MAAM,cAAbvB,4BAAAA,iBAAwCW,MAAMY,MAAM;IACpE,MAAMK,WAAW5B,kBAAAA,OAAO4B,OAAO,cAAd5B,6BAAAA,kBAA2CW,MAAMiB,OAAO;IACzE,MAAMC,WAAW7B,kBAAAA,OAAO6B,OAAO,cAAd7B,6BAAAA,kBAA2CW,MAAMkB,OAAO;IACzE,MAAMC,YAAY9B,MAAM,CAAC,aAAa,KAAK;IAC3C,MAAMJ,OAAOE,KAAKS,YAAY,OAAOwB,IAAIC,cAAgB5C,UAAU,MAAMD,OAAO4C,IAAIC,aAAarB,MAAMxB,MAAM,EAAE;YAAEuC;YAAGC;YAAOJ;YAAQK;YAASC;YAASC;QAAU,IAAIxB;AACrK"}
@@ -1,7 +1,7 @@
1
1
  import { relatedNotes } from '../commands.js';
2
2
  import { printRows } from '../output.js';
3
3
  import { USAGE } from './index.js';
4
- import { CONFIG, FORMAT, formatOf, parse, parseK, SCOPE, scopeOf, withDb } from './shared.js';
4
+ import { CONFIG, FORMAT, parse, parseK, rowFormatOf, SCOPE, scopeOf, withDb } from './shared.js';
5
5
  const RELATED_DEFAULT_K = 5;
6
6
  const relatedCmd = (ctx)=>{
7
7
  var _parseK;
@@ -17,7 +17,7 @@ const relatedCmd = (ctx)=>{
17
17
  const [pathArg] = positionals;
18
18
  if (!pathArg) ctx.usageError(usage);
19
19
  const k = (_parseK = parseK(values.k, ctx.usageError)) !== null && _parseK !== void 0 ? _parseK : RELATED_DEFAULT_K;
20
- const format = formatOf(values);
20
+ const format = rowFormatOf(values);
21
21
  return withDb(ctx, values.config, async (db, cfg)=>{
22
22
  const overrides = scopeOf(values);
23
23
  printRows(await relatedNotes(db, cfg, pathArg, overrides, k), format);
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli/related.ts"],"sourcesContent":["import { relatedNotes } from '../commands.ts';\nimport type { Row } from '../output.ts';\nimport { printRows } from '../output.ts';\nimport { USAGE } from './index.ts';\nimport { CONFIG, FORMAT, formatOf, parse, parseK, SCOPE, scopeOf, withDb } from './shared.ts';\nimport type { Command } from './types.ts';\n\nconst RELATED_DEFAULT_K = 5;\n\nconst relatedCmd: Command = (ctx) => {\n const usage = `usage: ${ctx.name} ${USAGE.related}`;\n const { values, positionals } = parse(ctx.argv, usage, { ...SCOPE, ...FORMAT, ...CONFIG, k: { type: 'string' } });\n const [pathArg] = positionals;\n if (!pathArg) ctx.usageError(usage);\n const k = parseK(values.k as string | undefined, ctx.usageError) ?? RELATED_DEFAULT_K;\n const format = formatOf(values);\n return withDb(ctx, values.config as string | undefined, async (db, cfg) => {\n const overrides = scopeOf(values);\n printRows((await relatedNotes(db, cfg, pathArg, overrides, k)) as Row[], format);\n });\n};\nexport default relatedCmd;\n"],"names":["relatedNotes","printRows","USAGE","CONFIG","FORMAT","formatOf","parse","parseK","SCOPE","scopeOf","withDb","RELATED_DEFAULT_K","relatedCmd","ctx","usage","name","related","values","positionals","argv","k","type","pathArg","usageError","format","config","db","cfg","overrides"],"mappings":"AAAA,SAASA,YAAY,QAAQ,iBAAiB;AAE9C,SAASC,SAAS,QAAQ,eAAe;AACzC,SAASC,KAAK,QAAQ,aAAa;AACnC,SAASC,MAAM,EAAEC,MAAM,EAAEC,QAAQ,EAAEC,KAAK,EAAEC,MAAM,EAAEC,KAAK,EAAEC,OAAO,EAAEC,MAAM,QAAQ,cAAc;AAG9F,MAAMC,oBAAoB;AAE1B,MAAMC,aAAsB,CAACC;QAKjBN;IAJV,MAAMO,QAAQ,CAAC,OAAO,EAAED,IAAIE,IAAI,CAAC,CAAC,EAAEb,MAAMc,OAAO,EAAE;IACnD,MAAM,EAAEC,MAAM,EAAEC,WAAW,EAAE,GAAGZ,MAAMO,IAAIM,IAAI,EAAEL,OAAO;QAAE,GAAGN,KAAK;QAAE,GAAGJ,MAAM;QAAE,GAAGD,MAAM;QAAEiB,GAAG;YAAEC,MAAM;QAAS;IAAE;IAC/G,MAAM,CAACC,QAAQ,GAAGJ;IAClB,IAAI,CAACI,SAAST,IAAIU,UAAU,CAACT;IAC7B,MAAMM,KAAIb,UAAAA,OAAOU,OAAOG,CAAC,EAAwBP,IAAIU,UAAU,eAArDhB,qBAAAA,UAA0DI;IACpE,MAAMa,SAASnB,SAASY;IACxB,OAAOP,OAAOG,KAAKI,OAAOQ,MAAM,EAAwB,OAAOC,IAAIC;QACjE,MAAMC,YAAYnB,QAAQQ;QAC1BhB,UAAW,MAAMD,aAAa0B,IAAIC,KAAKL,SAASM,WAAWR,IAAcI;IAC3E;AACF;AACA,eAAeZ,WAAW"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli/related.ts"],"sourcesContent":["import { relatedNotes } from '../commands.ts';\nimport type { Row } from '../output.ts';\nimport { printRows } from '../output.ts';\nimport { USAGE } from './index.ts';\nimport { CONFIG, FORMAT, parse, parseK, rowFormatOf, SCOPE, scopeOf, withDb } from './shared.ts';\nimport type { Command } from './types.ts';\n\nconst RELATED_DEFAULT_K = 5;\n\nconst relatedCmd: Command = (ctx) => {\n const usage = `usage: ${ctx.name} ${USAGE.related}`;\n const { values, positionals } = parse(ctx.argv, usage, { ...SCOPE, ...FORMAT, ...CONFIG, k: { type: 'string' } });\n const [pathArg] = positionals;\n if (!pathArg) ctx.usageError(usage);\n const k = parseK(values.k as string | undefined, ctx.usageError) ?? RELATED_DEFAULT_K;\n const format = rowFormatOf(values);\n return withDb(ctx, values.config as string | undefined, async (db, cfg) => {\n const overrides = scopeOf(values);\n printRows((await relatedNotes(db, cfg, pathArg, overrides, k)) as Row[], format);\n });\n};\nexport default relatedCmd;\n"],"names":["relatedNotes","printRows","USAGE","CONFIG","FORMAT","parse","parseK","rowFormatOf","SCOPE","scopeOf","withDb","RELATED_DEFAULT_K","relatedCmd","ctx","usage","name","related","values","positionals","argv","k","type","pathArg","usageError","format","config","db","cfg","overrides"],"mappings":"AAAA,SAASA,YAAY,QAAQ,iBAAiB;AAE9C,SAASC,SAAS,QAAQ,eAAe;AACzC,SAASC,KAAK,QAAQ,aAAa;AACnC,SAASC,MAAM,EAAEC,MAAM,EAAEC,KAAK,EAAEC,MAAM,EAAEC,WAAW,EAAEC,KAAK,EAAEC,OAAO,EAAEC,MAAM,QAAQ,cAAc;AAGjG,MAAMC,oBAAoB;AAE1B,MAAMC,aAAsB,CAACC;QAKjBP;IAJV,MAAMQ,QAAQ,CAAC,OAAO,EAAED,IAAIE,IAAI,CAAC,CAAC,EAAEb,MAAMc,OAAO,EAAE;IACnD,MAAM,EAAEC,MAAM,EAAEC,WAAW,EAAE,GAAGb,MAAMQ,IAAIM,IAAI,EAAEL,OAAO;QAAE,GAAGN,KAAK;QAAE,GAAGJ,MAAM;QAAE,GAAGD,MAAM;QAAEiB,GAAG;YAAEC,MAAM;QAAS;IAAE;IAC/G,MAAM,CAACC,QAAQ,GAAGJ;IAClB,IAAI,CAACI,SAAST,IAAIU,UAAU,CAACT;IAC7B,MAAMM,KAAId,UAAAA,OAAOW,OAAOG,CAAC,EAAwBP,IAAIU,UAAU,eAArDjB,qBAAAA,UAA0DK;IACpE,MAAMa,SAASjB,YAAYU;IAC3B,OAAOP,OAAOG,KAAKI,OAAOQ,MAAM,EAAwB,OAAOC,IAAIC;QACjE,MAAMC,YAAYnB,QAAQQ;QAC1BhB,UAAW,MAAMD,aAAa0B,IAAIC,KAAKL,SAASM,WAAWR,IAAcI;IAC3E;AACF;AACA,eAAeZ,WAAW"}
@@ -1,7 +1,7 @@
1
1
  import { search } from '../commands.js';
2
2
  import { printRows } from '../output.js';
3
3
  import { USAGE } from './index.js';
4
- import { CONFIG, FORMAT, formatOf, parse, parseK, SEARCH_FLAGS, scopeOf, withDb } from './shared.js';
4
+ import { CONFIG, FORMAT, parse, parseK, rowFormatOf, SEARCH_FLAGS, scopeOf, withDb } from './shared.js';
5
5
  const searchCmd = async (ctx)=>{
6
6
  const usage = `usage: ${ctx.name} ${USAGE.search}`;
7
7
  const { values, positionals } = parse(ctx.argv, usage, {
@@ -12,7 +12,7 @@ const searchCmd = async (ctx)=>{
12
12
  const [terms] = positionals;
13
13
  if (!terms) ctx.usageError(usage);
14
14
  const k = parseK(values.k, ctx.usageError);
15
- const format = formatOf(values);
15
+ const format = rowFormatOf(values);
16
16
  await withDb(ctx, values.config, async (db, cfg)=>printRows(await search(db, cfg, terms, {
17
17
  k,
18
18
  ...scopeOf(values)
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli/search.ts"],"sourcesContent":["import { search } from '../commands.ts';\nimport { printRows } from '../output.ts';\nimport { USAGE } from './index.ts';\nimport { CONFIG, FORMAT, formatOf, parse, parseK, SEARCH_FLAGS, scopeOf, withDb } from './shared.ts';\nimport type { Command } from './types.ts';\n\nconst searchCmd: Command = async (ctx) => {\n const usage = `usage: ${ctx.name} ${USAGE.search}`;\n const { values, positionals } = parse(ctx.argv, usage, { ...SEARCH_FLAGS, ...FORMAT, ...CONFIG });\n const [terms] = positionals;\n if (!terms) ctx.usageError(usage);\n const k = parseK(values.k as string | undefined, ctx.usageError);\n const format = formatOf(values);\n await withDb(ctx, values.config as string | undefined, async (db, cfg) => printRows(await search(db, cfg, terms, { k, ...scopeOf(values) }), format));\n};\nexport default searchCmd;\n"],"names":["search","printRows","USAGE","CONFIG","FORMAT","formatOf","parse","parseK","SEARCH_FLAGS","scopeOf","withDb","searchCmd","ctx","usage","name","values","positionals","argv","terms","usageError","k","format","config","db","cfg"],"mappings":"AAAA,SAASA,MAAM,QAAQ,iBAAiB;AACxC,SAASC,SAAS,QAAQ,eAAe;AACzC,SAASC,KAAK,QAAQ,aAAa;AACnC,SAASC,MAAM,EAAEC,MAAM,EAAEC,QAAQ,EAAEC,KAAK,EAAEC,MAAM,EAAEC,YAAY,EAAEC,OAAO,EAAEC,MAAM,QAAQ,cAAc;AAGrG,MAAMC,YAAqB,OAAOC;IAChC,MAAMC,QAAQ,CAAC,OAAO,EAAED,IAAIE,IAAI,CAAC,CAAC,EAAEZ,MAAMF,MAAM,EAAE;IAClD,MAAM,EAAEe,MAAM,EAAEC,WAAW,EAAE,GAAGV,MAAMM,IAAIK,IAAI,EAAEJ,OAAO;QAAE,GAAGL,YAAY;QAAE,GAAGJ,MAAM;QAAE,GAAGD,MAAM;IAAC;IAC/F,MAAM,CAACe,MAAM,GAAGF;IAChB,IAAI,CAACE,OAAON,IAAIO,UAAU,CAACN;IAC3B,MAAMO,IAAIb,OAAOQ,OAAOK,CAAC,EAAwBR,IAAIO,UAAU;IAC/D,MAAME,SAAShB,SAASU;IACxB,MAAML,OAAOE,KAAKG,OAAOO,MAAM,EAAwB,OAAOC,IAAIC,MAAQvB,UAAU,MAAMD,OAAOuB,IAAIC,KAAKN,OAAO;YAAEE;YAAG,GAAGX,QAAQM,OAAO;QAAC,IAAIM;AAC/I;AACA,eAAeV,UAAU"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli/search.ts"],"sourcesContent":["import { search } from '../commands.ts';\nimport { printRows } from '../output.ts';\nimport { USAGE } from './index.ts';\nimport { CONFIG, FORMAT, parse, parseK, rowFormatOf, SEARCH_FLAGS, scopeOf, withDb } from './shared.ts';\nimport type { Command } from './types.ts';\n\nconst searchCmd: Command = async (ctx) => {\n const usage = `usage: ${ctx.name} ${USAGE.search}`;\n const { values, positionals } = parse(ctx.argv, usage, { ...SEARCH_FLAGS, ...FORMAT, ...CONFIG });\n const [terms] = positionals;\n if (!terms) ctx.usageError(usage);\n const k = parseK(values.k as string | undefined, ctx.usageError);\n const format = rowFormatOf(values);\n await withDb(ctx, values.config as string | undefined, async (db, cfg) => printRows(await search(db, cfg, terms, { k, ...scopeOf(values) }), format));\n};\nexport default searchCmd;\n"],"names":["search","printRows","USAGE","CONFIG","FORMAT","parse","parseK","rowFormatOf","SEARCH_FLAGS","scopeOf","withDb","searchCmd","ctx","usage","name","values","positionals","argv","terms","usageError","k","format","config","db","cfg"],"mappings":"AAAA,SAASA,MAAM,QAAQ,iBAAiB;AACxC,SAASC,SAAS,QAAQ,eAAe;AACzC,SAASC,KAAK,QAAQ,aAAa;AACnC,SAASC,MAAM,EAAEC,MAAM,EAAEC,KAAK,EAAEC,MAAM,EAAEC,WAAW,EAAEC,YAAY,EAAEC,OAAO,EAAEC,MAAM,QAAQ,cAAc;AAGxG,MAAMC,YAAqB,OAAOC;IAChC,MAAMC,QAAQ,CAAC,OAAO,EAAED,IAAIE,IAAI,CAAC,CAAC,EAAEZ,MAAMF,MAAM,EAAE;IAClD,MAAM,EAAEe,MAAM,EAAEC,WAAW,EAAE,GAAGX,MAAMO,IAAIK,IAAI,EAAEJ,OAAO;QAAE,GAAGL,YAAY;QAAE,GAAGJ,MAAM;QAAE,GAAGD,MAAM;IAAC;IAC/F,MAAM,CAACe,MAAM,GAAGF;IAChB,IAAI,CAACE,OAAON,IAAIO,UAAU,CAACN;IAC3B,MAAMO,IAAId,OAAOS,OAAOK,CAAC,EAAwBR,IAAIO,UAAU;IAC/D,MAAME,SAASd,YAAYQ;IAC3B,MAAML,OAAOE,KAAKG,OAAOO,MAAM,EAAwB,OAAOC,IAAIC,MAAQvB,UAAU,MAAMD,OAAOuB,IAAIC,KAAKN,OAAO;YAAEE;YAAG,GAAGX,QAAQM,OAAO;QAAC,IAAIM;AAC/I;AACA,eAAeV,UAAU"}
@@ -1,6 +1,7 @@
1
1
  import type { ParseArgsOptionsConfig } from 'node:util';
2
2
  import type { ResolvedConfig, SearchOverrides } from '../config.js';
3
3
  import type { OpenResult } from '../db.js';
4
+ import type { RowFormat } from '../output.js';
4
5
  import type { Ctx } from './types.js';
5
6
  export declare const FORMAT: ParseArgsOptionsConfig;
6
7
  export declare const CONFIG: ParseArgsOptionsConfig;
@@ -9,6 +10,7 @@ export declare const SEARCH_FLAGS: ParseArgsOptionsConfig;
9
10
  type Values = Record<string, string | boolean | string[] | undefined>;
10
11
  export declare function scopeOf(values: Values): SearchOverrides;
11
12
  export declare function formatOf(values: Values): 'table' | 'json';
13
+ export declare function rowFormatOf(values: Values): RowFormat;
12
14
  export declare function parse(argv: string[], usage: string, options: ParseArgsOptionsConfig): {
13
15
  values: Values;
14
16
  positionals: string[];
@@ -16,5 +18,5 @@ export declare function parse(argv: string[], usage: string, options: ParseArgsO
16
18
  export declare function parseK(k: string | undefined, usageError: (message: string) => never): number | undefined;
17
19
  export declare function printWarnings(warnings: string[]): void;
18
20
  export declare function withDb(ctx: Ctx, configPath: string | undefined, fn: (db: OpenResult['db'], cfg: ResolvedConfig) => void | Promise<void>): Promise<void>;
19
- export declare function runSql(cfg: ResolvedConfig, sql: string, params: string[], format: 'table' | 'json', label: string, preset?: string): void;
21
+ export declare function runSql(cfg: ResolvedConfig, sql: string, params: string[], format: RowFormat, label: string, preset?: string): void;
20
22
  export {};
@@ -1,7 +1,7 @@
1
1
  import { parseArgs } from 'node:util';
2
2
  import { resolvePreset } from '../config.js';
3
3
  import { open } from '../db.js';
4
- import { printRows } from '../output.js';
4
+ import { printRowStream } from '../output.js';
5
5
  import { searchError } from '../search-error.js';
6
6
  // Spreadable option fragments -- one flag name keeps one meaning across every command's table.
7
7
  export const FORMAT = {
@@ -57,8 +57,28 @@ export function scopeOf(values) {
57
57
  noExclude: values['no-exclude'] === true
58
58
  };
59
59
  }
60
+ // An unrecognised value used to fall through to `table`, so a typo looked like it worked.
61
+ // parseArgs is strict about flag names; this is the same strictness for the value.
62
+ function pickFormat(values, allowed) {
63
+ const format = String(values.format);
64
+ if (allowed.includes(format)) return format;
65
+ console.error(`unknown --format "${format}"; expected ${allowed.join(', ')}`);
66
+ process.exit(2);
67
+ }
60
68
  export function formatOf(values) {
61
- return values.format === 'json' ? 'json' : 'table';
69
+ return pickFormat(values, [
70
+ 'table',
71
+ 'json'
72
+ ]);
73
+ }
74
+ // csv is a row set rendered as rows; map, peek, status, and path render structures instead,
75
+ // and take formatOf.
76
+ export function rowFormatOf(values) {
77
+ return pickFormat(values, [
78
+ 'table',
79
+ 'json',
80
+ 'csv'
81
+ ]);
62
82
  }
63
83
  // Per-command parseArgs: strict (a foreign flag exits 2), and every table gets --help for free.
64
84
  export function parse(argv, usage, options) {
@@ -144,9 +164,17 @@ export function runSql(cfg, sql, params, format, label, preset) {
144
164
  const { db, warnings } = open(cfg);
145
165
  printWarnings(warnings);
146
166
  if (preset !== undefined) bindScope(db, cfg, preset);
147
- let rows;
167
+ // Streamed rather than collected: `sql` is the one caller whose result size is the
168
+ // statement's business, not this package's, so the rows are never held here in bulk.
169
+ // columns() reads the statement's own metadata, so a 0-row csv still has its header.
170
+ // A mid-stream SQLite error (e.g. SQLITE_BUSY outlasting busy_timeout) can still leave
171
+ // partial output; the nonzero exit code is the caller's signal, output completeness is not
172
+ // guaranteed on failure.
148
173
  try {
149
- rows = db.prepare(sql).all(...params);
174
+ const statement = db.prepare(sql);
175
+ statement.setReadBigInts(true); // int64 past 2^53 arrives as BigInt instead of throwing at step time
176
+ const columns = statement.columns().map((c)=>c.name);
177
+ printRowStream(statement.iterate(...params), format, columns);
150
178
  } catch (err) {
151
179
  db.close();
152
180
  // A saved query or ad-hoc SQL can carry `content MATCH ?` too, so the same FTS5
@@ -155,6 +183,5 @@ export function runSql(cfg, sql, params, format, label, preset) {
155
183
  if (/\bMATCH\b/i.test(sql)) throw searchError(err, params.join(' '));
156
184
  throw err;
157
185
  }
158
- printRows(rows, format);
159
186
  db.close();
160
187
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli/shared.ts"],"sourcesContent":["import type { ParseArgsOptionsConfig } from 'node:util';\nimport { parseArgs } from 'node:util';\nimport type { ResolvedConfig, SearchOverrides } from '../config.ts';\nimport { resolvePreset } from '../config.ts';\nimport type { OpenResult } from '../db.ts';\nimport { open } from '../db.ts';\nimport type { Row } from '../output.ts';\nimport { printRows } from '../output.ts';\nimport { searchError } from '../search-error.ts';\nimport type { Ctx } from './types.ts';\n\n// Spreadable option fragments -- one flag name keeps one meaning across every command's table.\nexport const FORMAT: ParseArgsOptionsConfig = { format: { type: 'string', default: 'table' } };\nexport const CONFIG: ParseArgsOptionsConfig = { config: { type: 'string' } };\n// The scope vocabulary every scoped command shares: named preset, ad hoc include/exclude\n// globs, a where SQL condition. search adds k on top.\nexport const SCOPE: ParseArgsOptionsConfig = {\n where: { type: 'string' },\n preset: { type: 'string' },\n include: { type: 'string', multiple: true },\n exclude: { type: 'string', multiple: true },\n // Widening, which no other flag can express: --include and --exclude each override their\n // own side of the preset, so neither can drop an exclusion the preset declares.\n 'no-exclude': { type: 'boolean', default: false },\n};\nexport const SEARCH_FLAGS: ParseArgsOptionsConfig = { ...SCOPE, k: { type: 'string' } };\n\ntype Values = Record<string, string | boolean | string[] | undefined>;\n\n// The SCOPE fragment's parsed values as the overrides every scoped command passes down. One\n// place to read them, so a new scope field is added to the fragment and here, not in each\n// command's call.\nexport function scopeOf(values: Values): SearchOverrides {\n return {\n preset: values.preset as string | undefined,\n include: values.include as string[] | undefined,\n exclude: values.exclude as string[] | undefined,\n where: values.where as string | undefined,\n noExclude: values['no-exclude'] === true,\n };\n}\n\nexport function formatOf(values: Values): 'table' | 'json' {\n return values.format === 'json' ? 'json' : 'table';\n}\n\n// Per-command parseArgs: strict (a foreign flag exits 2), and every table gets --help for free.\nexport function parse(argv: string[], usage: string, options: ParseArgsOptionsConfig): { values: Values; positionals: string[] } {\n let values: Values;\n let positionals: string[];\n try {\n ({ values, positionals } = parseArgs({\n args: argv,\n options: { ...options, help: { type: 'boolean', default: false, short: 'h' } },\n strict: true,\n allowPositionals: true,\n }));\n } catch (err) {\n console.error((err as Error).message);\n console.error(usage);\n process.exit(2);\n }\n if (values.help) {\n console.log(usage);\n process.exit(0);\n }\n return { values, positionals };\n}\n\n// --k must be a positive integer: SQLite reads a bound LIMIT of -1 as \"unlimited\" and 0 as\n// \"nothing\", and parseInt would silently truncate \"5.9\" -- all three are caller mistakes\n// worth a usage error, matching the config-level SavedSearch validation.\nexport function parseK(k: string | undefined, usageError: (message: string) => never): number | undefined {\n if (k === undefined) return undefined;\n const parsed = Number(k);\n if (!Number.isInteger(parsed) || parsed <= 0) usageError(`--k expects a positive integer, got \"${k}\"`);\n return parsed;\n}\n\n// Shared open-query-close envelope for commands that touch the tree.\n\nexport function printWarnings(warnings: string[]): void {\n for (const w of warnings) console.warn(w);\n}\n\nexport async function withDb(ctx: Ctx, configPath: string | undefined, fn: (db: OpenResult['db'], cfg: ResolvedConfig) => void | Promise<void>): Promise<void> {\n const cfg = ctx.resolveConfig(configPath);\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n try {\n await fn(db, cfg);\n } finally {\n db.close();\n }\n}\n\n// `--preset` on SQL binds the scope rather than applying it: the statement joins `scope`\n// itself. Filtering behind the query's back is not available -- the obvious version, temp views\n// shadowing the base tables, cannot cover `content`, because FTS5 `MATCH` uses the table name\n// as a hidden column and a view has none. A flag that silently scoped three tables of four\n// would look scoped and not be. See plans/vault-field-report-fixes.md item F.\nfunction bindScope(db: OpenResult['db'], cfg: ResolvedConfig, preset: string): void {\n const { name } = resolvePreset(cfg, preset); // unknown names throw, listing what is declared\n db.exec('DROP TABLE IF EXISTS temp.scope');\n db.exec('CREATE TEMP TABLE scope (\"path\" TEXT PRIMARY KEY)');\n db.prepare('INSERT INTO temp.scope (\"path\") SELECT \"path\" FROM preset_files WHERE preset = ?').run(name);\n}\n\n// An unbound `?` silently binds NULL, so mismatched param counts fail loudly instead.\nexport function runSql(cfg: ResolvedConfig, sql: string, params: string[], format: 'table' | 'json', label: string, preset?: string): void {\n const placeholderCount = (sql.match(/\\?/g) ?? []).length;\n if (params.length !== placeholderCount) {\n console.error(`${label} expects ${placeholderCount} parameter(s), got ${params.length}`);\n process.exit(2);\n }\n // Naming a preset and never joining it would return the whole index while reading as scoped,\n // which is the one hazard of binding rather than applying. Refuse instead.\n if (preset !== undefined && !/\\bscope\\b/i.test(sql)) {\n console.error(`--preset binds a temporary \"scope\" table of the preset's paths, and ${label} never joins it, so the preset would have no effect`);\n console.error(`add: JOIN scope ON scope.\"path\" = <table>.\"path\"`);\n process.exit(2);\n }\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n if (preset !== undefined) bindScope(db, cfg, preset);\n let rows: Row[];\n try {\n rows = db.prepare(sql).all(...params) as Row[];\n } catch (err) {\n db.close();\n // A saved query or ad-hoc SQL can carry `content MATCH ?` too, so the same FTS5\n // punctuation trap applies -- the bound parameters are the search terms there. SQL\n // without MATCH gets its error verbatim; search advice on a plain typo would mislead.\n if (/\\bMATCH\\b/i.test(sql)) throw searchError(err as Error, params.join(' '));\n throw err;\n }\n printRows(rows, format);\n db.close();\n}\n"],"names":["parseArgs","resolvePreset","open","printRows","searchError","FORMAT","format","type","default","CONFIG","config","SCOPE","where","preset","include","multiple","exclude","SEARCH_FLAGS","k","scopeOf","values","noExclude","formatOf","parse","argv","usage","options","positionals","args","help","short","strict","allowPositionals","err","console","error","message","process","exit","log","parseK","usageError","undefined","parsed","Number","isInteger","printWarnings","warnings","w","warn","withDb","ctx","configPath","fn","cfg","resolveConfig","db","close","bindScope","name","exec","prepare","run","runSql","sql","params","label","placeholderCount","match","length","test","rows","all","join"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli/shared.ts"],"sourcesContent":["import type { ParseArgsOptionsConfig } from 'node:util';\nimport { parseArgs } from 'node:util';\nimport type { ResolvedConfig, SearchOverrides } from '../config.ts';\nimport { resolvePreset } from '../config.ts';\nimport type { OpenResult } from '../db.ts';\nimport { open } from '../db.ts';\nimport type { Row, RowFormat } from '../output.ts';\nimport { printRowStream } from '../output.ts';\nimport { searchError } from '../search-error.ts';\nimport type { Ctx } from './types.ts';\n\n// Spreadable option fragments -- one flag name keeps one meaning across every command's table.\nexport const FORMAT: ParseArgsOptionsConfig = { format: { type: 'string', default: 'table' } };\nexport const CONFIG: ParseArgsOptionsConfig = { config: { type: 'string' } };\n// The scope vocabulary every scoped command shares: named preset, ad hoc include/exclude\n// globs, a where SQL condition. search adds k on top.\nexport const SCOPE: ParseArgsOptionsConfig = {\n where: { type: 'string' },\n preset: { type: 'string' },\n include: { type: 'string', multiple: true },\n exclude: { type: 'string', multiple: true },\n // Widening, which no other flag can express: --include and --exclude each override their\n // own side of the preset, so neither can drop an exclusion the preset declares.\n 'no-exclude': { type: 'boolean', default: false },\n};\nexport const SEARCH_FLAGS: ParseArgsOptionsConfig = { ...SCOPE, k: { type: 'string' } };\n\ntype Values = Record<string, string | boolean | string[] | undefined>;\n\n// The SCOPE fragment's parsed values as the overrides every scoped command passes down. One\n// place to read them, so a new scope field is added to the fragment and here, not in each\n// command's call.\nexport function scopeOf(values: Values): SearchOverrides {\n return {\n preset: values.preset as string | undefined,\n include: values.include as string[] | undefined,\n exclude: values.exclude as string[] | undefined,\n where: values.where as string | undefined,\n noExclude: values['no-exclude'] === true,\n };\n}\n\n// An unrecognised value used to fall through to `table`, so a typo looked like it worked.\n// parseArgs is strict about flag names; this is the same strictness for the value.\nfunction pickFormat<T extends string>(values: Values, allowed: readonly T[]): T {\n const format = String(values.format);\n if ((allowed as readonly string[]).includes(format)) return format as T;\n console.error(`unknown --format \"${format}\"; expected ${allowed.join(', ')}`);\n process.exit(2);\n}\n\nexport function formatOf(values: Values): 'table' | 'json' {\n return pickFormat(values, ['table', 'json'] as const);\n}\n\n// csv is a row set rendered as rows; map, peek, status, and path render structures instead,\n// and take formatOf.\nexport function rowFormatOf(values: Values): RowFormat {\n return pickFormat(values, ['table', 'json', 'csv'] as const);\n}\n\n// Per-command parseArgs: strict (a foreign flag exits 2), and every table gets --help for free.\nexport function parse(argv: string[], usage: string, options: ParseArgsOptionsConfig): { values: Values; positionals: string[] } {\n let values: Values;\n let positionals: string[];\n try {\n ({ values, positionals } = parseArgs({\n args: argv,\n options: { ...options, help: { type: 'boolean', default: false, short: 'h' } },\n strict: true,\n allowPositionals: true,\n }));\n } catch (err) {\n console.error((err as Error).message);\n console.error(usage);\n process.exit(2);\n }\n if (values.help) {\n console.log(usage);\n process.exit(0);\n }\n return { values, positionals };\n}\n\n// --k must be a positive integer: SQLite reads a bound LIMIT of -1 as \"unlimited\" and 0 as\n// \"nothing\", and parseInt would silently truncate \"5.9\" -- all three are caller mistakes\n// worth a usage error, matching the config-level SavedSearch validation.\nexport function parseK(k: string | undefined, usageError: (message: string) => never): number | undefined {\n if (k === undefined) return undefined;\n const parsed = Number(k);\n if (!Number.isInteger(parsed) || parsed <= 0) usageError(`--k expects a positive integer, got \"${k}\"`);\n return parsed;\n}\n\n// Shared open-query-close envelope for commands that touch the tree.\n\nexport function printWarnings(warnings: string[]): void {\n for (const w of warnings) console.warn(w);\n}\n\nexport async function withDb(ctx: Ctx, configPath: string | undefined, fn: (db: OpenResult['db'], cfg: ResolvedConfig) => void | Promise<void>): Promise<void> {\n const cfg = ctx.resolveConfig(configPath);\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n try {\n await fn(db, cfg);\n } finally {\n db.close();\n }\n}\n\n// `--preset` on SQL binds the scope rather than applying it: the statement joins `scope`\n// itself. Filtering behind the query's back is not available -- the obvious version, temp views\n// shadowing the base tables, cannot cover `content`, because FTS5 `MATCH` uses the table name\n// as a hidden column and a view has none. A flag that silently scoped three tables of four\n// would look scoped and not be. See plans/vault-field-report-fixes.md item F.\nfunction bindScope(db: OpenResult['db'], cfg: ResolvedConfig, preset: string): void {\n const { name } = resolvePreset(cfg, preset); // unknown names throw, listing what is declared\n db.exec('DROP TABLE IF EXISTS temp.scope');\n db.exec('CREATE TEMP TABLE scope (\"path\" TEXT PRIMARY KEY)');\n db.prepare('INSERT INTO temp.scope (\"path\") SELECT \"path\" FROM preset_files WHERE preset = ?').run(name);\n}\n\n// An unbound `?` silently binds NULL, so mismatched param counts fail loudly instead.\nexport function runSql(cfg: ResolvedConfig, sql: string, params: string[], format: RowFormat, label: string, preset?: string): void {\n const placeholderCount = (sql.match(/\\?/g) ?? []).length;\n if (params.length !== placeholderCount) {\n console.error(`${label} expects ${placeholderCount} parameter(s), got ${params.length}`);\n process.exit(2);\n }\n // Naming a preset and never joining it would return the whole index while reading as scoped,\n // which is the one hazard of binding rather than applying. Refuse instead.\n if (preset !== undefined && !/\\bscope\\b/i.test(sql)) {\n console.error(`--preset binds a temporary \"scope\" table of the preset's paths, and ${label} never joins it, so the preset would have no effect`);\n console.error(`add: JOIN scope ON scope.\"path\" = <table>.\"path\"`);\n process.exit(2);\n }\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n if (preset !== undefined) bindScope(db, cfg, preset);\n // Streamed rather than collected: `sql` is the one caller whose result size is the\n // statement's business, not this package's, so the rows are never held here in bulk.\n // columns() reads the statement's own metadata, so a 0-row csv still has its header.\n // A mid-stream SQLite error (e.g. SQLITE_BUSY outlasting busy_timeout) can still leave\n // partial output; the nonzero exit code is the caller's signal, output completeness is not\n // guaranteed on failure.\n try {\n const statement = db.prepare(sql);\n statement.setReadBigInts(true); // int64 past 2^53 arrives as BigInt instead of throwing at step time\n const columns = statement.columns().map((c) => c.name);\n printRowStream(statement.iterate(...params) as Iterable<Row>, format, columns);\n } catch (err) {\n db.close();\n // A saved query or ad-hoc SQL can carry `content MATCH ?` too, so the same FTS5\n // punctuation trap applies -- the bound parameters are the search terms there. SQL\n // without MATCH gets its error verbatim; search advice on a plain typo would mislead.\n if (/\\bMATCH\\b/i.test(sql)) throw searchError(err as Error, params.join(' '));\n throw err;\n }\n db.close();\n}\n"],"names":["parseArgs","resolvePreset","open","printRowStream","searchError","FORMAT","format","type","default","CONFIG","config","SCOPE","where","preset","include","multiple","exclude","SEARCH_FLAGS","k","scopeOf","values","noExclude","pickFormat","allowed","String","includes","console","error","join","process","exit","formatOf","rowFormatOf","parse","argv","usage","options","positionals","args","help","short","strict","allowPositionals","err","message","log","parseK","usageError","undefined","parsed","Number","isInteger","printWarnings","warnings","w","warn","withDb","ctx","configPath","fn","cfg","resolveConfig","db","close","bindScope","name","exec","prepare","run","runSql","sql","params","label","placeholderCount","match","length","test","statement","setReadBigInts","columns","map","c","iterate"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { USAGE } from './index.js';
2
- import { CONFIG, FORMAT, formatOf, parse, runSql } from './shared.js';
2
+ import { CONFIG, FORMAT, parse, rowFormatOf, runSql } from './shared.js';
3
3
  const sqlCmd = (ctx)=>{
4
4
  const usage = `usage: ${ctx.name} ${USAGE.sql}`;
5
5
  const { values, positionals } = parse(ctx.argv, usage, {
@@ -11,7 +11,7 @@ const sqlCmd = (ctx)=>{
11
11
  });
12
12
  const [sql, ...params] = positionals;
13
13
  if (!sql) ctx.usageError(usage);
14
- const format = formatOf(values);
14
+ const format = rowFormatOf(values);
15
15
  runSql(ctx.resolveConfig(values.config), sql, params, format, 'ad-hoc statement', values.preset);
16
16
  };
17
17
  export default sqlCmd;
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli/sql.ts"],"sourcesContent":["import { USAGE } from './index.ts';\nimport { CONFIG, FORMAT, formatOf, parse, runSql } from './shared.ts';\nimport type { Command } from './types.ts';\n\nconst sqlCmd: Command = (ctx) => {\n const usage = `usage: ${ctx.name} ${USAGE.sql}`;\n const { values, positionals } = parse(ctx.argv, usage, { ...FORMAT, ...CONFIG, preset: { type: 'string' } });\n const [sql, ...params] = positionals;\n if (!sql) ctx.usageError(usage);\n const format = formatOf(values);\n runSql(ctx.resolveConfig(values.config as string | undefined), sql, params, format, 'ad-hoc statement', values.preset as string | undefined);\n};\nexport default sqlCmd;\n"],"names":["USAGE","CONFIG","FORMAT","formatOf","parse","runSql","sqlCmd","ctx","usage","name","sql","values","positionals","argv","preset","type","params","usageError","format","resolveConfig","config"],"mappings":"AAAA,SAASA,KAAK,QAAQ,aAAa;AACnC,SAASC,MAAM,EAAEC,MAAM,EAAEC,QAAQ,EAAEC,KAAK,EAAEC,MAAM,QAAQ,cAAc;AAGtE,MAAMC,SAAkB,CAACC;IACvB,MAAMC,QAAQ,CAAC,OAAO,EAAED,IAAIE,IAAI,CAAC,CAAC,EAAET,MAAMU,GAAG,EAAE;IAC/C,MAAM,EAAEC,MAAM,EAAEC,WAAW,EAAE,GAAGR,MAAMG,IAAIM,IAAI,EAAEL,OAAO;QAAE,GAAGN,MAAM;QAAE,GAAGD,MAAM;QAAEa,QAAQ;YAAEC,MAAM;QAAS;IAAE;IAC1G,MAAM,CAACL,KAAK,GAAGM,OAAO,GAAGJ;IACzB,IAAI,CAACF,KAAKH,IAAIU,UAAU,CAACT;IACzB,MAAMU,SAASf,SAASQ;IACxBN,OAAOE,IAAIY,aAAa,CAACR,OAAOS,MAAM,GAAyBV,KAAKM,QAAQE,QAAQ,oBAAoBP,OAAOG,MAAM;AACvH;AACA,eAAeR,OAAO"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli/sql.ts"],"sourcesContent":["import { USAGE } from './index.ts';\nimport { CONFIG, FORMAT, parse, rowFormatOf, runSql } from './shared.ts';\nimport type { Command } from './types.ts';\n\nconst sqlCmd: Command = (ctx) => {\n const usage = `usage: ${ctx.name} ${USAGE.sql}`;\n const { values, positionals } = parse(ctx.argv, usage, { ...FORMAT, ...CONFIG, preset: { type: 'string' } });\n const [sql, ...params] = positionals;\n if (!sql) ctx.usageError(usage);\n const format = rowFormatOf(values);\n runSql(ctx.resolveConfig(values.config as string | undefined), sql, params, format, 'ad-hoc statement', values.preset as string | undefined);\n};\nexport default sqlCmd;\n"],"names":["USAGE","CONFIG","FORMAT","parse","rowFormatOf","runSql","sqlCmd","ctx","usage","name","sql","values","positionals","argv","preset","type","params","usageError","format","resolveConfig","config"],"mappings":"AAAA,SAASA,KAAK,QAAQ,aAAa;AACnC,SAASC,MAAM,EAAEC,MAAM,EAAEC,KAAK,EAAEC,WAAW,EAAEC,MAAM,QAAQ,cAAc;AAGzE,MAAMC,SAAkB,CAACC;IACvB,MAAMC,QAAQ,CAAC,OAAO,EAAED,IAAIE,IAAI,CAAC,CAAC,EAAET,MAAMU,GAAG,EAAE;IAC/C,MAAM,EAAEC,MAAM,EAAEC,WAAW,EAAE,GAAGT,MAAMI,IAAIM,IAAI,EAAEL,OAAO;QAAE,GAAGN,MAAM;QAAE,GAAGD,MAAM;QAAEa,QAAQ;YAAEC,MAAM;QAAS;IAAE;IAC1G,MAAM,CAACL,KAAK,GAAGM,OAAO,GAAGJ;IACzB,IAAI,CAACF,KAAKH,IAAIU,UAAU,CAACT;IACzB,MAAMU,SAASd,YAAYO;IAC3BN,OAAOE,IAAIY,aAAa,CAACR,OAAOS,MAAM,GAAyBV,KAAKM,QAAQE,QAAQ,oBAAoBP,OAAOG,MAAM;AACvH;AACA,eAAeR,OAAO"}
package/dist/esm/cli.js CHANGED
@@ -22,7 +22,7 @@ function packageVersion() {
22
22
  function usage(name) {
23
23
  const lines = Object.values(USAGE).map((u)=>` ${name} ${u}`);
24
24
  return [
25
- `usage: ${name} <name> [params...] [--format table|json] [--config path]`,
25
+ `usage: ${name} <name> [params...] [--format table|json|csv] [--config path]`,
26
26
  ...lines,
27
27
  ` ${name} --list`,
28
28
  ` ${name} --version`
@@ -40,6 +40,19 @@ function resolveConfigFor(name, configPath) {
40
40
  }
41
41
  // Thrown errors -> exit 1 with the message verbatim; usage errors exit 2 directly.
42
42
  export default async function cli(argv, name) {
43
+ // stdout is a pipe whenever the caller pipes or redirects, and a reader that goes away
44
+ // (`| head`) makes every later write fail. The failure arrives asynchronously, after the
45
+ // write loop has already returned: console.log absorbs it, process.stdout.write (what the
46
+ // streaming formats use) does not, so with no listener the process dies on an unhandled
47
+ // 'error' event having already done its work. Neither throwing nor exiting is safe from
48
+ // here. The event is async, so a throw is an uncaught exception rather than something
49
+ // bin/cli.js can catch, and process.exit() would skip the cleanup `sense watch` runs on the
50
+ // way out, leaving a heartbeat behind that makes a dead watcher look alive.
51
+ process.stdout.on('error', (err)=>{
52
+ if (err.code === 'EPIPE') return;
53
+ console.error(`sense: ${err.message}`);
54
+ process.exitCode = 1;
55
+ });
43
56
  const first = argv[0];
44
57
  // No command word, or the first token looks like a flag: the only flags this level knows.
45
58
  if (!first || first.startsWith('-')) {
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { parseArgs } from 'node:util';\nimport { COMMANDS, USAGE } from './cli/index.ts';\nimport type { Ctx } from './cli/types.ts';\nimport { loadConfig, SUPPORTED_CONFIG_VERSION } from './config.ts';\n\n// Parsing and dispatch only; commands lazy-load from src/cli/, so nothing heavy is imported\n// here. Flags parse per command -- this file handles only --version/--help/--list/--config.\n\n// Works from dist/cjs and dist/esm alike: walk up past the dist type-marker package.json.\nfunction packageVersion(): string {\n const load = createRequire(import.meta.url);\n for (const rel of ['../package.json', '../../package.json', '../../../package.json']) {\n try {\n const pkg = load(rel) as { name?: string; version?: string };\n if (pkg.name === 'sensemaking' && pkg.version) return pkg.version;\n } catch {}\n }\n return 'unknown';\n}\n\nfunction usage(name: string): string {\n const lines = Object.values(USAGE).map((u) => ` ${name} ${u}`);\n return [`usage: ${name} <name> [params...] [--format table|json] [--config path]`, ...lines, ` ${name} --list`, ` ${name} --version`].join('\\n');\n}\n\nfunction resolveConfigFor(name: string, configPath: string | undefined) {\n const cfg = loadConfig(configPath);\n if (cfg.migratedFrom !== undefined) {\n console.warn(`${name}: migrated ${cfg.configPath} from config version ${cfg.migratedFrom} to ${SUPPORTED_CONFIG_VERSION}`);\n }\n if (cfg.unknownKeys !== undefined) {\n console.warn(`${name}: ${cfg.configPath} sets ${cfg.unknownKeys.join(', ')}, which this build does not read (no effect); see schema.json for the keys it does`);\n }\n return cfg;\n}\n\n// Thrown errors -> exit 1 with the message verbatim; usage errors exit 2 directly.\nexport default async function cli(argv: string[], name: string): Promise<void> {\n const first = argv[0];\n\n // No command word, or the first token looks like a flag: the only flags this level knows.\n if (!first || first.startsWith('-')) {\n let values: Record<string, string | boolean | undefined>;\n try {\n ({ values } = parseArgs({\n args: argv,\n options: {\n version: { type: 'boolean', default: false, short: 'v' },\n help: { type: 'boolean', default: false, short: 'h' },\n list: { type: 'boolean', default: false },\n config: { type: 'string' },\n },\n allowPositionals: true,\n }));\n } catch (err) {\n console.error((err as Error).message);\n console.error(usage(name));\n process.exit(2);\n }\n\n try {\n if (values.version) {\n console.log(packageVersion());\n return;\n }\n if (values.help) {\n console.log(usage(name));\n return;\n }\n if (values.list) {\n const queries = resolveConfigFor(name, values.config as string | undefined).queries;\n for (const queryName of Object.keys(queries).sort()) {\n // Every entry names its verb, so --list can label both kinds rather than only\n // marking the exception.\n console.log(`${queryName} (${'search' in queries[queryName] ? 'search' : 'sql'})`);\n }\n return;\n }\n } catch (err) {\n console.error((err as Error).message);\n process.exit(1);\n }\n\n console.error(usage(name));\n process.exit(2);\n }\n\n // Breaking renames get one release of a pointer instead of an \"unknown\" error: find ->\n // search, and query -> sql (which took its name back from the search sense of \"query\").\n const RENAMED: Record<string, string> = { find: 'search', query: 'sql' };\n if (RENAMED[first]) {\n console.error(`${name}: ${first} is now ${RENAMED[first]}`);\n console.error(usage(name));\n process.exit(2);\n }\n\n const ctx: Ctx = {\n name,\n argv: argv.slice(1),\n resolveConfig: (configPath) => resolveConfigFor(name, configPath),\n usageError(message) {\n console.error(message);\n process.exit(2);\n },\n };\n\n try {\n const load = COMMANDS[first];\n if (load) await (await load()).default(ctx);\n else await (await import('./cli/named.ts')).default(ctx, first);\n } catch (err) {\n console.error((err as Error).message);\n process.exit(1);\n }\n}\n"],"names":["createRequire","parseArgs","COMMANDS","USAGE","loadConfig","SUPPORTED_CONFIG_VERSION","packageVersion","load","url","rel","pkg","name","version","usage","lines","Object","values","map","u","join","resolveConfigFor","configPath","cfg","migratedFrom","undefined","console","warn","unknownKeys","cli","argv","first","startsWith","args","options","type","default","short","help","list","config","allowPositionals","err","error","message","process","exit","log","queries","queryName","keys","sort","RENAMED","find","query","ctx","slice","resolveConfig","usageError"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { parseArgs } from 'node:util';\nimport { COMMANDS, USAGE } from './cli/index.ts';\nimport type { Ctx } from './cli/types.ts';\nimport { loadConfig, SUPPORTED_CONFIG_VERSION } from './config.ts';\n\n// Parsing and dispatch only; commands lazy-load from src/cli/, so nothing heavy is imported\n// here. Flags parse per command -- this file handles only --version/--help/--list/--config.\n\n// Works from dist/cjs and dist/esm alike: walk up past the dist type-marker package.json.\nfunction packageVersion(): string {\n const load = createRequire(import.meta.url);\n for (const rel of ['../package.json', '../../package.json', '../../../package.json']) {\n try {\n const pkg = load(rel) as { name?: string; version?: string };\n if (pkg.name === 'sensemaking' && pkg.version) return pkg.version;\n } catch {}\n }\n return 'unknown';\n}\n\nfunction usage(name: string): string {\n const lines = Object.values(USAGE).map((u) => ` ${name} ${u}`);\n return [`usage: ${name} <name> [params...] [--format table|json|csv] [--config path]`, ...lines, ` ${name} --list`, ` ${name} --version`].join('\\n');\n}\n\nfunction resolveConfigFor(name: string, configPath: string | undefined) {\n const cfg = loadConfig(configPath);\n if (cfg.migratedFrom !== undefined) {\n console.warn(`${name}: migrated ${cfg.configPath} from config version ${cfg.migratedFrom} to ${SUPPORTED_CONFIG_VERSION}`);\n }\n if (cfg.unknownKeys !== undefined) {\n console.warn(`${name}: ${cfg.configPath} sets ${cfg.unknownKeys.join(', ')}, which this build does not read (no effect); see schema.json for the keys it does`);\n }\n return cfg;\n}\n\n// Thrown errors -> exit 1 with the message verbatim; usage errors exit 2 directly.\nexport default async function cli(argv: string[], name: string): Promise<void> {\n // stdout is a pipe whenever the caller pipes or redirects, and a reader that goes away\n // (`| head`) makes every later write fail. The failure arrives asynchronously, after the\n // write loop has already returned: console.log absorbs it, process.stdout.write (what the\n // streaming formats use) does not, so with no listener the process dies on an unhandled\n // 'error' event having already done its work. Neither throwing nor exiting is safe from\n // here. The event is async, so a throw is an uncaught exception rather than something\n // bin/cli.js can catch, and process.exit() would skip the cleanup `sense watch` runs on the\n // way out, leaving a heartbeat behind that makes a dead watcher look alive.\n process.stdout.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EPIPE') return;\n console.error(`sense: ${err.message}`);\n process.exitCode = 1;\n });\n\n const first = argv[0];\n\n // No command word, or the first token looks like a flag: the only flags this level knows.\n if (!first || first.startsWith('-')) {\n let values: Record<string, string | boolean | undefined>;\n try {\n ({ values } = parseArgs({\n args: argv,\n options: {\n version: { type: 'boolean', default: false, short: 'v' },\n help: { type: 'boolean', default: false, short: 'h' },\n list: { type: 'boolean', default: false },\n config: { type: 'string' },\n },\n allowPositionals: true,\n }));\n } catch (err) {\n console.error((err as Error).message);\n console.error(usage(name));\n process.exit(2);\n }\n\n try {\n if (values.version) {\n console.log(packageVersion());\n return;\n }\n if (values.help) {\n console.log(usage(name));\n return;\n }\n if (values.list) {\n const queries = resolveConfigFor(name, values.config as string | undefined).queries;\n for (const queryName of Object.keys(queries).sort()) {\n // Every entry names its verb, so --list can label both kinds rather than only\n // marking the exception.\n console.log(`${queryName} (${'search' in queries[queryName] ? 'search' : 'sql'})`);\n }\n return;\n }\n } catch (err) {\n console.error((err as Error).message);\n process.exit(1);\n }\n\n console.error(usage(name));\n process.exit(2);\n }\n\n // Breaking renames get one release of a pointer instead of an \"unknown\" error: find ->\n // search, and query -> sql (which took its name back from the search sense of \"query\").\n const RENAMED: Record<string, string> = { find: 'search', query: 'sql' };\n if (RENAMED[first]) {\n console.error(`${name}: ${first} is now ${RENAMED[first]}`);\n console.error(usage(name));\n process.exit(2);\n }\n\n const ctx: Ctx = {\n name,\n argv: argv.slice(1),\n resolveConfig: (configPath) => resolveConfigFor(name, configPath),\n usageError(message) {\n console.error(message);\n process.exit(2);\n },\n };\n\n try {\n const load = COMMANDS[first];\n if (load) await (await load()).default(ctx);\n else await (await import('./cli/named.ts')).default(ctx, first);\n } catch (err) {\n console.error((err as Error).message);\n process.exit(1);\n }\n}\n"],"names":["createRequire","parseArgs","COMMANDS","USAGE","loadConfig","SUPPORTED_CONFIG_VERSION","packageVersion","load","url","rel","pkg","name","version","usage","lines","Object","values","map","u","join","resolveConfigFor","configPath","cfg","migratedFrom","undefined","console","warn","unknownKeys","cli","argv","process","stdout","on","err","code","error","message","exitCode","first","startsWith","args","options","type","default","short","help","list","config","allowPositionals","exit","log","queries","queryName","keys","sort","RENAMED","find","query","ctx","slice","resolveConfig","usageError"],"mappings":"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"}
@@ -1,15 +1,18 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { join, matchesGlob } from 'node:path';
3
3
  import posix from 'node:path/posix';
4
- import { anyPresetEmbeds, embedEnabled, featureEnabled, featureStates, presetNames, presetSemanticEnabled, resolveSearch } from './config.js';
4
+ import { anyPresetEmbeds, contentTokenize, embedEnabled, featureEnabled, featureStates, presetNames, presetSemanticEnabled, resolveSearch } from './config.js';
5
5
  import { SenseError } from './errors.js';
6
6
  import { embedPending, hasEmbedding, modelPresent, semanticCandidates, similarNotes } from './features/embed.js';
7
7
  import { linkEdges } from './features/index.js';
8
8
  import { personalizedRank } from './graph.js';
9
9
  import { searchError } from './search-error.js';
10
+ import { segmentMatch } from './segment.js';
10
11
  // The three commands: mapTree (orient), search (locate), peek (structure).
11
12
  // Each returns data; cli.ts renders. All of them degrade when a feature is off.
12
- const WEIGHTED_BM25 = 'bm25(content, 10.0, 5.0, 1.0)';
13
+ // Mirrors the main columns onto the sidecars, so a title match found through title_seg ranks
14
+ // like a title match found through title, not like a body match.
15
+ const WEIGHTED_BM25 = 'bm25(content, 10.0, 5.0, 1.0, 0, 10.0, 5.0, 1.0)';
13
16
  const RRF_K = 60;
14
17
  // snippet() re-tokenizes each candidate doc, superlinearly: ~10s for one 1MB doc
15
18
  // (BENCHMARKING.md). Past this bound, rows get the linear JS excerpt instead.
@@ -142,7 +145,14 @@ export async function search(db, cfg, terms, opts = {}) {
142
145
  throw new SenseError('EMBED_MODEL_MISSING', `preset "${effective.presetName}" searches with vectors, but the embedding model is not available; run \`sense download\`, or set "semantic": false on that preset to search on words and links`);
143
146
  }
144
147
  const semanticEnabled = wantsVectors && scopeHasEmbeddings(db, cfg, scopedPaths);
145
- // Terms pass verbatim to MATCH; invalid syntax errors rather than being rewritten. --where
148
+ // Terms pass to MATCH as written, with one transform: a run of text whose language marks no
149
+ // word boundaries becomes a quoted grapheme phrase (and a title:/summary:/text: qualifier
150
+ // ahead of it retargets its _seg column), matching how it was indexed. Text that already
151
+ // carries boundaries comes through untouched, so this is a no-op for the languages that
152
+ // never needed it. Gated on the same predicate that decided whether the sidecars were
153
+ // populated (contentTokenize(cfg) === undefined), so index and query can't disagree. Invalid
154
+ // syntax still errors rather than being rewritten.
155
+ // --where
146
156
  // applies inside the candidate query (a post-filter would drop matches ranked past the pool)
147
157
  // and again on the final select, for link-derived rows.
148
158
  const scope = effective.where;
@@ -150,10 +160,14 @@ export async function search(db, cfg, terms, opts = {}) {
150
160
  const whereCond = scope ? `AND (${scope})` : '';
151
161
  // Docs past SNIPPET_BOUND skip snippet() entirely -- SQLite
152
162
  // short-circuits the untaken CASE branch, so it's never invoked on them.
153
- const matchSql = `SELECT content.path AS path, CASE WHEN length(content.text) <= ${SNIPPET_BOUND} THEN snippet(content, -1, '«', '»', '…', 10) ELSE NULL END AS hit FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ORDER BY ${WEIGHTED_BM25} LIMIT ${fetch}`;
163
+ // Column 2 (text), not -1 (best column): -1 could surface a _seg sidecar as the excerpt,
164
+ // which is machine-spaced and not what the author wrote. A row that matches only in a
165
+ // sidecar gets an unhighlighted text excerpt below instead -- a stated cost.
166
+ const matchSql = `SELECT content.path AS path, CASE WHEN length(content.text) <= ${SNIPPET_BOUND} THEN snippet(content, 2, '«', '»', '…', 10) ELSE NULL END AS hit FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ORDER BY ${WEIGHTED_BM25} LIMIT ${fetch}`;
167
+ const query = contentTokenize(cfg) === undefined ? segmentMatch(terms) : terms;
154
168
  let matchRows;
155
169
  try {
156
- matchRows = db.prepare(matchSql).all(terms);
170
+ matchRows = db.prepare(matchSql).all(query);
157
171
  } catch (err) {
158
172
  throw searchError(err, terms, scope);
159
173
  }