@agent-native/core 0.7.21 → 0.7.23

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 (163) hide show
  1. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  2. package/dist/agent/engine/ai-sdk-engine.js +43 -1
  3. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  4. package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
  5. package/dist/agent/engine/anthropic-engine.js +8 -0
  6. package/dist/agent/engine/anthropic-engine.js.map +1 -1
  7. package/dist/agent/engine/builder-engine.d.ts +1 -1
  8. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  9. package/dist/agent/engine/builder-engine.js +9 -4
  10. package/dist/agent/engine/builder-engine.js.map +1 -1
  11. package/dist/agent/engine/translate-ai-sdk.d.ts.map +1 -1
  12. package/dist/agent/engine/translate-ai-sdk.js +31 -1
  13. package/dist/agent/engine/translate-ai-sdk.js.map +1 -1
  14. package/dist/agent/engine/translate-anthropic.d.ts.map +1 -1
  15. package/dist/agent/engine/translate-anthropic.js +16 -0
  16. package/dist/agent/engine/translate-anthropic.js.map +1 -1
  17. package/dist/agent/engine/types.d.ts +16 -1
  18. package/dist/agent/engine/types.d.ts.map +1 -1
  19. package/dist/agent/engine/types.js.map +1 -1
  20. package/dist/agent/production-agent.d.ts +4 -0
  21. package/dist/agent/production-agent.d.ts.map +1 -1
  22. package/dist/agent/production-agent.js +96 -4
  23. package/dist/agent/production-agent.js.map +1 -1
  24. package/dist/agent/types.d.ts +3 -0
  25. package/dist/agent/types.d.ts.map +1 -1
  26. package/dist/agent/types.js.map +1 -1
  27. package/dist/cli/create.d.ts +2 -1
  28. package/dist/cli/create.d.ts.map +1 -1
  29. package/dist/cli/create.js +21 -14
  30. package/dist/cli/create.js.map +1 -1
  31. package/dist/client/AgentPanel.d.ts.map +1 -1
  32. package/dist/client/AgentPanel.js +5 -5
  33. package/dist/client/AgentPanel.js.map +1 -1
  34. package/dist/client/AssistantChat.d.ts +5 -0
  35. package/dist/client/AssistantChat.d.ts.map +1 -1
  36. package/dist/client/AssistantChat.js +54 -2
  37. package/dist/client/AssistantChat.js.map +1 -1
  38. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  39. package/dist/client/MultiTabAssistantChat.js +33 -2
  40. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  41. package/dist/client/NewWorkspaceAppFlow.d.ts.map +1 -1
  42. package/dist/client/NewWorkspaceAppFlow.js +10 -1
  43. package/dist/client/NewWorkspaceAppFlow.js.map +1 -1
  44. package/dist/client/agent-chat-adapter.d.ts +4 -0
  45. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  46. package/dist/client/agent-chat-adapter.js +5 -1
  47. package/dist/client/agent-chat-adapter.js.map +1 -1
  48. package/dist/client/composer/TiptapComposer.d.ts +6 -1
  49. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  50. package/dist/client/composer/TiptapComposer.js +25 -17
  51. package/dist/client/composer/TiptapComposer.js.map +1 -1
  52. package/dist/client/composer/useVoiceDictation.d.ts +6 -5
  53. package/dist/client/composer/useVoiceDictation.d.ts.map +1 -1
  54. package/dist/client/composer/useVoiceDictation.js +54 -21
  55. package/dist/client/composer/useVoiceDictation.js.map +1 -1
  56. package/dist/client/org/OrgSwitcher.d.ts +3 -1
  57. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  58. package/dist/client/org/OrgSwitcher.js +12 -7
  59. package/dist/client/org/OrgSwitcher.js.map +1 -1
  60. package/dist/client/settings/AutomationsSection.d.ts.map +1 -1
  61. package/dist/client/settings/AutomationsSection.js +2 -2
  62. package/dist/client/settings/AutomationsSection.js.map +1 -1
  63. package/dist/client/settings/VoiceTranscriptionSection.d.ts.map +1 -1
  64. package/dist/client/settings/VoiceTranscriptionSection.js +46 -15
  65. package/dist/client/settings/VoiceTranscriptionSection.js.map +1 -1
  66. package/dist/client/tools/ToolViewer.d.ts.map +1 -1
  67. package/dist/client/tools/ToolViewer.js +2 -2
  68. package/dist/client/tools/ToolViewer.js.map +1 -1
  69. package/dist/client/tools/ToolsListPage.d.ts.map +1 -1
  70. package/dist/client/tools/ToolsListPage.js +4 -4
  71. package/dist/client/tools/ToolsListPage.js.map +1 -1
  72. package/dist/client/tools/ToolsSidebarSection.d.ts.map +1 -1
  73. package/dist/client/tools/ToolsSidebarSection.js +2 -2
  74. package/dist/client/tools/ToolsSidebarSection.js.map +1 -1
  75. package/dist/client/transcription/use-live-transcription.d.ts +1 -0
  76. package/dist/client/transcription/use-live-transcription.d.ts.map +1 -1
  77. package/dist/client/transcription/use-live-transcription.js +41 -0
  78. package/dist/client/transcription/use-live-transcription.js.map +1 -1
  79. package/dist/integrations/adapters/email.js +81 -5
  80. package/dist/integrations/adapters/email.js.map +1 -1
  81. package/dist/integrations/adapters/slack.d.ts.map +1 -1
  82. package/dist/integrations/adapters/slack.js +4 -1
  83. package/dist/integrations/adapters/slack.js.map +1 -1
  84. package/dist/integrations/plugin.d.ts.map +1 -1
  85. package/dist/integrations/plugin.js +2 -1
  86. package/dist/integrations/plugin.js.map +1 -1
  87. package/dist/integrations/types.d.ts +2 -0
  88. package/dist/integrations/types.d.ts.map +1 -1
  89. package/dist/integrations/types.js.map +1 -1
  90. package/dist/integrations/webhook-handler.js +12 -2
  91. package/dist/integrations/webhook-handler.js.map +1 -1
  92. package/dist/oauth-tokens/store.d.ts.map +1 -1
  93. package/dist/oauth-tokens/store.js +34 -16
  94. package/dist/oauth-tokens/store.js.map +1 -1
  95. package/dist/scripts/db/exec.d.ts.map +1 -1
  96. package/dist/scripts/db/exec.js +32 -23
  97. package/dist/scripts/db/exec.js.map +1 -1
  98. package/dist/scripts/db/patch.d.ts.map +1 -1
  99. package/dist/scripts/db/patch.js +48 -35
  100. package/dist/scripts/db/patch.js.map +1 -1
  101. package/dist/scripts/db/query.d.ts.map +1 -1
  102. package/dist/scripts/db/query.js +22 -13
  103. package/dist/scripts/db/query.js.map +1 -1
  104. package/dist/scripts/db/safety.d.ts +2 -0
  105. package/dist/scripts/db/safety.d.ts.map +1 -0
  106. package/dist/scripts/db/safety.js +67 -0
  107. package/dist/scripts/db/safety.js.map +1 -0
  108. package/dist/scripts/db/scoping.js +4 -4
  109. package/dist/scripts/db/scoping.js.map +1 -1
  110. package/dist/server/email-template.d.ts +5 -0
  111. package/dist/server/email-template.d.ts.map +1 -1
  112. package/dist/server/email-template.js +7 -4
  113. package/dist/server/email-template.js.map +1 -1
  114. package/dist/server/google-auth-plugin.d.ts.map +1 -1
  115. package/dist/server/google-auth-plugin.js +1 -8
  116. package/dist/server/google-auth-plugin.js.map +1 -1
  117. package/dist/server/index.d.ts +3 -2
  118. package/dist/server/index.d.ts.map +1 -1
  119. package/dist/server/index.js +3 -2
  120. package/dist/server/index.js.map +1 -1
  121. package/dist/server/onboarding-html.d.ts.map +1 -1
  122. package/dist/server/onboarding-html.js +3 -10
  123. package/dist/server/onboarding-html.js.map +1 -1
  124. package/dist/server/transcribe-voice.d.ts +9 -9
  125. package/dist/server/transcribe-voice.d.ts.map +1 -1
  126. package/dist/server/transcribe-voice.js +405 -51
  127. package/dist/server/transcribe-voice.js.map +1 -1
  128. package/dist/server/voice-providers-status.d.ts.map +1 -1
  129. package/dist/server/voice-providers-status.js +13 -1
  130. package/dist/server/voice-providers-status.js.map +1 -1
  131. package/dist/settings/store.d.ts.map +1 -1
  132. package/dist/settings/store.js +14 -6
  133. package/dist/settings/store.js.map +1 -1
  134. package/dist/shared/reasoning-effort.d.ts +8 -0
  135. package/dist/shared/reasoning-effort.d.ts.map +1 -0
  136. package/dist/shared/reasoning-effort.js +94 -0
  137. package/dist/shared/reasoning-effort.js.map +1 -0
  138. package/dist/templates/default/public/favicon.svg +1 -13
  139. package/dist/templates/default/public/icon-180.svg +1 -13
  140. package/dist/templates/default/public/icon-192.svg +1 -13
  141. package/dist/templates/default/public/icon-512.svg +1 -13
  142. package/dist/templates/workspace-root/.github/workflows/ci.yml +32 -0
  143. package/dist/templates/workspace-root/.prettierignore +19 -0
  144. package/dist/templates/workspace-root/_gitignore +5 -0
  145. package/dist/templates/workspace-root/package.json +13 -2
  146. package/dist/templates/workspace-root/pnpm-workspace.yaml +1 -0
  147. package/dist/templates/workspace-root/scripts/workspace-dev.ts +2 -0
  148. package/dist/transcription/builder-transcription.d.ts +2 -0
  149. package/dist/transcription/builder-transcription.d.ts.map +1 -1
  150. package/dist/transcription/builder-transcription.js +4 -0
  151. package/dist/transcription/builder-transcription.js.map +1 -1
  152. package/docs/content/voice-input.md +14 -13
  153. package/package.json +1 -1
  154. package/src/templates/default/public/favicon.svg +1 -13
  155. package/src/templates/default/public/icon-180.svg +1 -13
  156. package/src/templates/default/public/icon-192.svg +1 -13
  157. package/src/templates/default/public/icon-512.svg +1 -13
  158. package/src/templates/workspace-root/.github/workflows/ci.yml +32 -0
  159. package/src/templates/workspace-root/.prettierignore +19 -0
  160. package/src/templates/workspace-root/_gitignore +5 -0
  161. package/src/templates/workspace-root/package.json +13 -2
  162. package/src/templates/workspace-root/pnpm-workspace.yaml +1 -0
  163. package/src/templates/workspace-root/scripts/workspace-dev.ts +2 -0
@@ -50,6 +50,7 @@ import path from "path";
50
50
  import { createClient } from "@libsql/client";
51
51
  import { getDatabaseUrl, getDatabaseAuthToken } from "../../db/client.js";
52
52
  import { parseArgs, fail } from "../utils.js";
53
+ import { assertNoSensitiveFrameworkTables } from "./safety.js";
53
54
  import { buildScopingPostgres, buildScopingSqlite } from "./scoping.js";
54
55
  function isPostgresUrl(url) {
55
56
  return url.startsWith("postgres://") || url.startsWith("postgresql://");
@@ -456,41 +457,51 @@ async function runPostgres(opts) {
456
457
  const { default: pg } = await import("postgres");
457
458
  const pgSql = pg(opts.url);
458
459
  try {
459
- // Same temp-view scoping db-exec uses — SELECT and UPDATE both go through
460
- // the scoped view, so cross-user access is impossible even if --where is
461
- // permissive.
462
- const scoping = await buildScopingPostgres(pgSql);
463
- for (const stmt of scoping.setup) {
464
- await pgSql.unsafe(stmt);
465
- }
466
- const selectSql = `SELECT "${opts.column}" AS __val FROM "${opts.table}" WHERE ${opts.where}`;
467
- const selected = Array.from(await pgSql.unsafe(selectSql));
468
- if (selected.length === 0) {
469
- fail(`No rows matched: ${opts.table} WHERE ${opts.where}. ` +
470
- `(In production, data scoping filters results to the current user — the row may exist but be owned by someone else.)`);
471
- }
472
- if (selected.length > 1) {
473
- fail(`WHERE matched ${selected.length} rows in ${opts.table}. db-patch expects exactly one row narrow the WHERE clause (usually by primary key).`);
474
- }
475
- const original = (selected[0].__val ?? "");
476
- if (typeof original !== "string") {
477
- fail(`Column ${opts.table}.${opts.column} is not a text column (got ${typeof original}).`);
478
- }
479
- const { content, results, applied, total } = applyEither(original, opts);
480
- if (applied > 0) {
481
- await pgSql.unsafe(`UPDATE "${opts.table}" SET "${opts.column}" = $1 WHERE ${opts.where}`, [content]);
482
- }
483
- printResult({
484
- table: opts.table,
485
- column: opts.column,
486
- applied,
487
- total,
488
- bytesBefore: original.length,
489
- bytesAfter: content.length,
490
- results,
491
- }, opts.format);
492
- for (const stmt of scoping.teardown) {
493
- await pgSql.unsafe(stmt).catch(() => { });
460
+ let result;
461
+ await pgSql.begin(async (tx) => {
462
+ // Same temp-view scoping db-exec uses — SELECT and UPDATE both go
463
+ // through the scoped view. Keep setup and teardown transaction-local
464
+ // so pooled Postgres backends never retain the temp views.
465
+ const scoping = await buildScopingPostgres(tx);
466
+ try {
467
+ for (const stmt of scoping.setup) {
468
+ await tx.unsafe(stmt);
469
+ }
470
+ const selectSql = `SELECT "${opts.column}" AS __val FROM "${opts.table}" WHERE ${opts.where}`;
471
+ const selected = Array.from(await tx.unsafe(selectSql));
472
+ if (selected.length === 0) {
473
+ fail(`No rows matched: ${opts.table} WHERE ${opts.where}. ` +
474
+ `(In production, data scoping filters results to the current user — the row may exist but be owned by someone else.)`);
475
+ }
476
+ if (selected.length > 1) {
477
+ fail(`WHERE matched ${selected.length} rows in ${opts.table}. db-patch expects exactly one row — narrow the WHERE clause (usually by primary key).`);
478
+ }
479
+ const original = (selected[0].__val ?? "");
480
+ if (typeof original !== "string") {
481
+ fail(`Column ${opts.table}.${opts.column} is not a text column (got ${typeof original}).`);
482
+ }
483
+ const { content, results, applied, total } = applyEither(original, opts);
484
+ if (applied > 0) {
485
+ await tx.unsafe(`UPDATE "${opts.table}" SET "${opts.column}" = $1 WHERE ${opts.where}`, [content]);
486
+ }
487
+ result = {
488
+ table: opts.table,
489
+ column: opts.column,
490
+ applied,
491
+ total,
492
+ bytesBefore: original.length,
493
+ bytesAfter: content.length,
494
+ results,
495
+ };
496
+ }
497
+ finally {
498
+ for (const stmt of scoping.teardown) {
499
+ await tx.unsafe(stmt).catch(() => { });
500
+ }
501
+ }
502
+ });
503
+ if (result) {
504
+ printResult(result, opts.format);
494
505
  }
495
506
  }
496
507
  finally {
@@ -609,6 +620,8 @@ When to use db-patch vs other tools:
609
620
  fail(`Invalid --table: "${table}". Must be a plain identifier (letters, digits, underscore).`);
610
621
  if (!isValidIdentifier(column))
611
622
  fail(`Invalid --column: "${column}". Must be a plain identifier (letters, digits, underscore).`);
623
+ assertNoSensitiveFrameworkTables(table, "patch");
624
+ assertNoSensitiveFrameworkTables(where, "read");
612
625
  validateWhere(where);
613
626
  const jsonOps = parseJsonOps(parsed);
614
627
  // Edit parsing only runs when json-ops isn't provided — otherwise the
@@ -1 +1 @@
1
- {"version":3,"file":"patch.js","sourceRoot":"","sources":["../../../src/scripts/db/patch.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Core script: db-patch\n *\n * Surgical search-and-replace on a text column in any SQL table. Instead of\n * re-sending the full column value (as `db-exec UPDATE` would require), the\n * agent sends one or more `{find, replace}` pairs. The script reads the row,\n * applies the edits in memory, and writes the result back in a single UPDATE.\n *\n * ## When to use which tool\n *\n * Large text field, small slice to change → db-patch (this)\n * e.g. fix one paragraph in a 50KB document, tweak one key in a dashboard\n * JSON blob, rename a label in a slide HTML string.\n *\n * Short field, set outright → db-exec UPDATE\n * e.g. `UPDATE forms SET status = 'published' WHERE id = '...'`.\n *\n * Multiple columns / computed values → db-exec UPDATE\n * e.g. `UPDATE meals SET calories = calories + 50, ...`.\n *\n * Domain-specific action exists → use that action\n * e.g. `edit-document` or `update-slide` — they also push live Yjs\n * updates to any open collaborative editor. db-patch is the generic\n * fallback for tables without a bespoke action.\n *\n * ## Why it's faster\n *\n * The agent only has to transmit the diff (the `find` + `replace`\n * strings), not the full new value. For large text fields — multi-kilobyte\n * markdown documents, slide HTML, dashboard/form JSON — this dramatically\n * reduces tokens per edit and keeps concurrent edits composable.\n *\n * ## Security\n *\n * In production mode, the same per-user / per-org temp view scoping that\n * `db-exec` uses applies here: the SELECT and UPDATE both go through the\n * scoped view, so you can never read or write rows outside the current\n * user's data. The WHERE clause is validated against a keyword denylist\n * (no ;, no chained statements, no DDL).\n *\n * ## Usage\n *\n * pnpm action db-patch --table <t> --column <c> --where \"<clause>\" \\\n * --find \"old\" --replace \"new\"\n *\n * pnpm action db-patch --table decks --column data --where \"id='d1'\" \\\n * --edits '[{\"find\":\"Q3\",\"replace\":\"Q4\"},{\"find\":\"$1M\",\"replace\":\"$1.2M\"}]'\n */\n\nimport path from \"path\";\nimport { createClient } from \"@libsql/client\";\nimport { getDatabaseUrl, getDatabaseAuthToken } from \"../../db/client.js\";\nimport { parseArgs, fail } from \"../utils.js\";\nimport { buildScopingPostgres, buildScopingSqlite } from \"./scoping.js\";\n\ninterface TextEdit {\n find: string;\n replace: string;\n}\n\n/**\n * JSON patch operation — a subset of RFC 6902 plus a convenience `move-before`\n * that's rare in the spec but common for list reordering. The agent ends up\n * needing this all the time (reordering dashboard panels, form fields, slide\n * layers) and without it has to do multi-step string surgery.\n */\ninterface JsonOp {\n op: \"set\" | \"replace\" | \"remove\" | \"move\" | \"move-before\" | \"insert\";\n /** JSON Pointer-style path, e.g. \"/panels/3/title\". \"\" = root. */\n path?: string;\n /** For move / move-before: source path. */\n from?: string;\n /** For set / replace / insert: value to write. */\n value?: unknown;\n}\n\ninterface EditResult {\n index: number;\n status: \"replaced\" | \"deleted\" | \"not-found\";\n detail: string;\n occurrences: number;\n}\n\ninterface PatchOutput {\n table: string;\n column: string;\n applied: number;\n total: number;\n bytesBefore: number;\n bytesAfter: number;\n results: EditResult[];\n}\n\nfunction isPostgresUrl(url: string): boolean {\n return url.startsWith(\"postgres://\") || url.startsWith(\"postgresql://\");\n}\n\n/** Only unquoted [A-Za-z_][A-Za-z0-9_]* identifiers are allowed — no spaces,\n * no quoting, no dotted names. This is deliberately strict: it stops the\n * agent from sneaking SQL into the table/column slots. */\nfunction isValidIdentifier(s: string): boolean {\n return /^[A-Za-z_][A-Za-z0-9_]*$/.test(s);\n}\n\n/** Reject WHERE clauses that could chain statements or hide DDL. This isn't\n * a full SQL parser — just a keyword/character denylist to keep the surface\n * area equivalent to what db-exec already allows. */\nfunction validateWhere(where: string): void {\n if (where.includes(\";\")) {\n fail(\"--where must not contain ';' (no statement chaining)\");\n }\n // Strip inline strings before keyword scanning so \"WHERE name = 'DROP TABLE'\"\n // doesn't trip the denylist.\n const stripped = where\n .replace(/'(?:''|[^'])*'/g, \"''\")\n .replace(/\"(?:\"\"|[^\"])*\"/g, '\"\"')\n .toUpperCase();\n\n const blocked = [\n \" INSERT \",\n \" UPDATE \",\n \" DELETE \",\n \" DROP \",\n \" ALTER \",\n \" CREATE \",\n \" ATTACH \",\n \" DETACH \",\n \" PRAGMA \",\n \" VACUUM \",\n \"--\",\n \"/*\",\n ];\n const padded = \" \" + stripped + \" \";\n for (const kw of blocked) {\n if (padded.includes(kw)) {\n fail(`--where must not contain \"${kw.trim()}\"`);\n }\n }\n}\n\nfunction parseEdits(parsed: Record<string, string>): TextEdit[] {\n let edits: TextEdit[];\n\n if (parsed.edits) {\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(parsed.edits);\n } catch (e: any) {\n fail(`Invalid --edits JSON: ${e.message}`);\n }\n if (!Array.isArray(parsedJson) || parsedJson.length === 0) {\n fail(\"--edits must be a non-empty JSON array of {find, replace} objects\");\n }\n edits = parsedJson as TextEdit[];\n } else if (parsed.find !== undefined) {\n if (parsed.find === \"\") fail(\"--find cannot be empty\");\n edits = [{ find: parsed.find, replace: parsed.replace ?? \"\" }];\n } else {\n fail(\"Either --find/--replace or --edits is required\");\n }\n\n for (const edit of edits!) {\n if (typeof edit.find !== \"string\" || edit.find === \"\") {\n fail(\"Each edit must have a non-empty 'find' string\");\n }\n if (edit.replace === undefined || edit.replace === null) {\n edit.replace = \"\";\n }\n if (typeof edit.replace !== \"string\") {\n fail(\"Each edit's 'replace' field must be a string\");\n }\n }\n\n return edits!;\n}\n\nfunction preview(s: string): string {\n const max = 60;\n const trimmed = s.replace(/\\s+/g, \" \");\n return trimmed.length > max ? trimmed.slice(0, max) + \"...\" : trimmed;\n}\n\n// ─── JSON patch helpers ─────────────────────────────────────────────────────\n\n/** Parse a JSON Pointer (\"/panels/3/title\") into path segments. \"\" = root. */\nfunction parsePointer(pointer: string): string[] {\n if (pointer === \"\" || pointer === \"/\") return [];\n if (!pointer.startsWith(\"/\")) {\n fail(`JSON path must start with '/' (got: ${pointer})`);\n }\n return pointer\n .slice(1)\n .split(\"/\")\n .map((s) => s.replace(/~1/g, \"/\").replace(/~0/g, \"~\"));\n}\n\n/** Walk to the parent container of the given path. Returns [parent, lastKey]. */\nfunction resolveParent(\n root: unknown,\n segments: string[],\n): [any, string | number] {\n if (segments.length === 0) {\n fail(\"Root path is not supported for this operation\");\n }\n let node: any = root;\n for (let i = 0; i < segments.length - 1; i++) {\n const seg = segments[i];\n if (Array.isArray(node)) {\n const idx = parseInt(seg, 10);\n if (isNaN(idx) || idx < 0 || idx >= node.length) {\n fail(\n `Path segment \"${seg}\" is out of bounds for array of length ${node.length}`,\n );\n }\n node = node[idx];\n } else if (node && typeof node === \"object\") {\n if (!(seg in node)) {\n fail(`Path segment \"${seg}\" not found in object`);\n }\n node = node[seg];\n } else {\n fail(`Cannot descend into ${typeof node} at segment \"${seg}\"`);\n }\n }\n const last = segments[segments.length - 1];\n if (Array.isArray(node)) {\n const idx = last === \"-\" ? node.length : parseInt(last, 10);\n if (isNaN(idx)) fail(`Expected numeric index, got \"${last}\"`);\n return [node, idx];\n }\n return [node, last];\n}\n\n/** Apply one JSON op, mutating `root` in place. Returns a short detail string. */\nfunction applyJsonOp(root: any, op: JsonOp): string {\n switch (op.op) {\n case \"set\":\n case \"replace\": {\n if (op.path === undefined) fail(`${op.op} requires 'path'`);\n const [parent, key] = resolveParent(root, parsePointer(op.path));\n parent[key as any] = op.value;\n return `${op.op} ${op.path}`;\n }\n case \"remove\": {\n if (op.path === undefined) fail(\"remove requires 'path'\");\n const [parent, key] = resolveParent(root, parsePointer(op.path));\n if (Array.isArray(parent)) {\n parent.splice(key as number, 1);\n } else {\n delete parent[key as string];\n }\n return `remove ${op.path}`;\n }\n case \"insert\": {\n if (op.path === undefined) fail(\"insert requires 'path'\");\n const [parent, key] = resolveParent(root, parsePointer(op.path));\n if (!Array.isArray(parent)) fail(`insert target must be an array`);\n parent.splice(key as number, 0, op.value);\n return `insert at ${op.path}`;\n }\n case \"move\":\n case \"move-before\": {\n if (!op.from || op.path === undefined) {\n fail(`${op.op} requires 'from' and 'path'`);\n }\n const fromSeg = parsePointer(op.from);\n const toSeg = parsePointer(op.path);\n const [fromParent, fromKey] = resolveParent(root, fromSeg);\n // Extract the value\n let value: unknown;\n if (Array.isArray(fromParent)) {\n value = fromParent[fromKey as number];\n fromParent.splice(fromKey as number, 1);\n } else {\n value = fromParent[fromKey as string];\n delete fromParent[fromKey as string];\n }\n // For array moves where the destination is in the same array and\n // after the removed index, shift the target index down by one so\n // \"move /panels/7 to /panels/3\" lands exactly at index 3 even after\n // the earlier splice shifted indices.\n let [toParent, toKey] = resolveParent(root, toSeg);\n if (\n Array.isArray(toParent) &&\n Array.isArray(fromParent) &&\n toParent === fromParent\n ) {\n const fromIdx = fromKey as number;\n const toIdx = toKey as number;\n if (toIdx > fromIdx) {\n toKey = toIdx - 1;\n }\n }\n if (Array.isArray(toParent)) {\n toParent.splice(toKey as number, 0, value);\n } else {\n toParent[toKey as string] = value;\n }\n return `${op.op} ${op.from} → ${op.path}`;\n }\n default:\n fail(`Unknown JSON op: ${(op as any).op}`);\n }\n return \"\";\n}\n\nfunction parseJsonOps(parsed: Record<string, string>): JsonOp[] | null {\n if (!parsed.jsonOps && !parsed[\"json-ops\"]) return null;\n const raw = parsed.jsonOps ?? parsed[\"json-ops\"];\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(raw);\n } catch (e: any) {\n fail(`Invalid --json-ops JSON: ${e.message}`);\n }\n if (!Array.isArray(parsedJson) || parsedJson.length === 0) {\n fail(\"--json-ops must be a non-empty JSON array\");\n }\n for (const op of parsedJson as any[]) {\n if (!op || typeof op !== \"object\" || typeof op.op !== \"string\") {\n fail(\"Each op must be an object with an 'op' field\");\n }\n }\n return parsedJson as JsonOp[];\n}\n\nfunction countOccurrences(haystack: string, needle: string): number {\n if (!needle) return 0;\n let count = 0;\n let idx = 0;\n while ((idx = haystack.indexOf(needle, idx)) !== -1) {\n count++;\n idx += needle.length;\n }\n return count;\n}\n\n/** Find all match positions (up to a cap so we don't explode memory). */\nfunction findAll(haystack: string, needle: string, cap = 10): number[] {\n const out: number[] = [];\n if (!needle) return out;\n let idx = 0;\n while ((idx = haystack.indexOf(needle, idx)) !== -1 && out.length < cap) {\n out.push(idx);\n idx += needle.length;\n }\n return out;\n}\n\n/** Format a single match with ~40 chars of surrounding context so the agent\n * can widen its `find` string to disambiguate ambiguous matches. */\nfunction formatContext(\n content: string,\n matchIdx: number,\n matchLen: number,\n radius = 40,\n): string {\n const start = Math.max(0, matchIdx - radius);\n const end = Math.min(content.length, matchIdx + matchLen + radius);\n const before = content.slice(start, matchIdx).replace(/\\s+/g, \" \");\n const middle = content\n .slice(matchIdx, matchIdx + matchLen)\n .replace(/\\s+/g, \" \");\n const after = content.slice(matchIdx + matchLen, end).replace(/\\s+/g, \" \");\n const prefix = start > 0 ? \"…\" : \"\";\n const suffix = end < content.length ? \"…\" : \"\";\n return `${prefix}${before}⟨${middle}⟩${after}${suffix}`;\n}\n\n/** Build a \"string not unique\" error message showing each match in\n * context — matches Claude Code's Edit-tool UX so the agent can\n * widen the find string and retry. */\nfunction buildAmbiguousMessage(\n findStr: string,\n content: string,\n count: number,\n): string {\n const positions = findAll(content, findStr, 6);\n const lines = [\n `Found ${count} occurrences of the 'find' string — db-patch requires exactly one match by default.`,\n `Widen 'find' with unique surrounding context, or pass --all to replace every occurrence.`,\n `'find' preview: \"${preview(findStr)}\"`,\n \"Matches:\",\n ];\n for (let i = 0; i < positions.length; i++) {\n lines.push(\n ` [${i + 1}] ${formatContext(content, positions[i], findStr.length)}`,\n );\n }\n if (count > positions.length) {\n lines.push(` … and ${count - positions.length} more`);\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * Apply edits sequentially.\n *\n * Default behavior matches Claude Code's Edit tool: the `find` string must\n * match exactly one occurrence. If 0 → \"not found\". If >1 → error with\n * surrounding context for each match so the agent can widen `find` and\n * retry. Pass `replaceAll` (`--all`) to allow replacing every occurrence.\n *\n * This strict-uniqueness default is a deliberate reliability upgrade — 9×\n * fewer silent wrong-match bugs at the cost of slightly more verbose finds.\n */\nfunction applyEdits(\n content: string,\n edits: TextEdit[],\n replaceAll: boolean,\n): { content: string; results: EditResult[]; applied: number } {\n let out = content;\n const results: EditResult[] = [];\n let applied = 0;\n\n for (let i = 0; i < edits.length; i++) {\n const edit = edits[i];\n const occurrences = countOccurrences(out, edit.find);\n\n if (occurrences === 0) {\n results.push({\n index: i,\n status: \"not-found\",\n detail: `NOT FOUND: \"${preview(edit.find)}\"`,\n occurrences: 0,\n });\n continue;\n }\n\n if (replaceAll) {\n // Literal replaceAll via split/join — no regex, no special chars.\n out = out.split(edit.find).join(edit.replace);\n applied++;\n results.push({\n index: i,\n status: edit.replace === \"\" ? \"deleted\" : \"replaced\",\n detail: `${edit.replace === \"\" ? \"deleted\" : \"replaced\"} ${occurrences}×: \"${preview(edit.find)}\"`,\n occurrences,\n });\n } else if (occurrences > 1) {\n results.push({\n index: i,\n status: \"not-found\",\n detail: buildAmbiguousMessage(edit.find, out, occurrences),\n occurrences,\n });\n } else {\n const idx = out.indexOf(edit.find);\n out =\n out.slice(0, idx) + edit.replace + out.slice(idx + edit.find.length);\n applied++;\n results.push({\n index: i,\n status: edit.replace === \"\" ? \"deleted\" : \"replaced\",\n detail: `${edit.replace === \"\" ? \"deleted\" : \"replaced\"}: \"${preview(edit.find)}\"`,\n occurrences: 1,\n });\n }\n }\n\n return { content: out, results, applied };\n}\n\nfunction printResult(out: PatchOutput, format?: string): void {\n if (format === \"json\") {\n console.log(JSON.stringify(out, null, 2));\n return;\n }\n console.log(`db-patch: ${out.table}.${out.column}`);\n console.log(` Applied: ${out.applied}/${out.total}`);\n console.log(` Bytes: ${out.bytesBefore} → ${out.bytesAfter}`);\n for (const r of out.results) {\n console.log(` - ${r.detail}`);\n }\n}\n\ninterface RunOpts {\n url: string;\n table: string;\n column: string;\n where: string;\n edits: TextEdit[];\n jsonOps?: JsonOp[];\n replaceAll: boolean;\n format?: string;\n}\n\nfunction applyEither(\n original: string,\n opts: RunOpts,\n): {\n content: string;\n results: EditResult[];\n applied: number;\n total: number;\n} {\n if (opts.jsonOps && opts.jsonOps.length > 0) {\n let root: unknown;\n try {\n root = JSON.parse(original);\n } catch (e: any) {\n fail(\n `--json-ops requires the column value to be valid JSON. Parse failed: ${e.message}`,\n );\n }\n const results: EditResult[] = [];\n let applied = 0;\n for (let i = 0; i < opts.jsonOps.length; i++) {\n const op = opts.jsonOps[i];\n try {\n const detail = applyJsonOp(root, op);\n results.push({\n index: i,\n status: \"replaced\",\n detail,\n occurrences: 1,\n });\n applied++;\n } catch (e: any) {\n results.push({\n index: i,\n status: \"not-found\",\n detail: `FAILED: ${e?.message ?? String(e)}`,\n occurrences: 0,\n });\n }\n }\n return {\n content: JSON.stringify(root),\n results,\n applied,\n total: opts.jsonOps.length,\n };\n }\n const out = applyEdits(original, opts.edits, opts.replaceAll);\n return { ...out, total: opts.edits.length };\n}\n\n// ─── Postgres path ──────────────────────────────────────────────────────────\n\nasync function runPostgres(opts: RunOpts): Promise<void> {\n const { default: pg } = await import(\"postgres\");\n const pgSql = pg(opts.url);\n try {\n // Same temp-view scoping db-exec uses — SELECT and UPDATE both go through\n // the scoped view, so cross-user access is impossible even if --where is\n // permissive.\n const scoping = await buildScopingPostgres(pgSql);\n for (const stmt of scoping.setup) {\n await pgSql.unsafe(stmt);\n }\n\n const selectSql = `SELECT \"${opts.column}\" AS __val FROM \"${opts.table}\" WHERE ${opts.where}`;\n const selected: any[] = Array.from(await pgSql.unsafe(selectSql));\n\n if (selected.length === 0) {\n fail(\n `No rows matched: ${opts.table} WHERE ${opts.where}. ` +\n `(In production, data scoping filters results to the current user — the row may exist but be owned by someone else.)`,\n );\n }\n if (selected.length > 1) {\n fail(\n `WHERE matched ${selected.length} rows in ${opts.table}. db-patch expects exactly one row — narrow the WHERE clause (usually by primary key).`,\n );\n }\n\n const original = (selected[0].__val ?? \"\") as string;\n if (typeof original !== \"string\") {\n fail(\n `Column ${opts.table}.${opts.column} is not a text column (got ${typeof original}).`,\n );\n }\n\n const { content, results, applied, total } = applyEither(original, opts);\n\n if (applied > 0) {\n await pgSql.unsafe(\n `UPDATE \"${opts.table}\" SET \"${opts.column}\" = $1 WHERE ${opts.where}`,\n [content],\n );\n }\n\n printResult(\n {\n table: opts.table,\n column: opts.column,\n applied,\n total,\n bytesBefore: original.length,\n bytesAfter: content.length,\n results,\n },\n opts.format,\n );\n\n for (const stmt of scoping.teardown) {\n await pgSql.unsafe(stmt).catch(() => {});\n }\n } finally {\n await pgSql.end();\n }\n}\n\n// ─── SQLite / libSQL path ───────────────────────────────────────────────────\n\nasync function runSqlite(opts: RunOpts): Promise<void> {\n const client = createClient({\n url: opts.url,\n authToken: getDatabaseAuthToken(),\n });\n try {\n const scoping = await buildScopingSqlite(client);\n for (const stmt of scoping.setup) {\n await client.execute(stmt);\n }\n\n const selectSql = `SELECT \"${opts.column}\" AS __val FROM \"${opts.table}\" WHERE ${opts.where}`;\n const selectRes = await client.execute(selectSql);\n\n if (selectRes.rows.length === 0) {\n fail(\n `No rows matched: ${opts.table} WHERE ${opts.where}. ` +\n `(In production, data scoping filters results to the current user — the row may exist but be owned by someone else.)`,\n );\n }\n if (selectRes.rows.length > 1) {\n fail(\n `WHERE matched ${selectRes.rows.length} rows in ${opts.table}. db-patch expects exactly one row — narrow the WHERE clause (usually by primary key).`,\n );\n }\n\n const row = selectRes.rows[0] as any;\n const original = (row.__val ?? row[0] ?? \"\") as string;\n if (typeof original !== \"string\") {\n fail(\n `Column ${opts.table}.${opts.column} is not a text column (got ${typeof original}).`,\n );\n }\n\n const { content, results, applied, total } = applyEither(original, opts);\n\n if (applied > 0) {\n await client.execute({\n sql: `UPDATE \"${opts.table}\" SET \"${opts.column}\" = ? WHERE ${opts.where}`,\n args: [content],\n });\n }\n\n printResult(\n {\n table: opts.table,\n column: opts.column,\n applied,\n total,\n bytesBefore: original.length,\n bytesAfter: content.length,\n results,\n },\n opts.format,\n );\n\n for (const stmt of scoping.teardown) {\n await client.execute(stmt).catch(() => {});\n }\n } finally {\n client.close();\n }\n}\n\n// ─── Entry point ────────────────────────────────────────────────────────────\n\nexport default async function dbPatch(args: string[]): Promise<void> {\n const parsed = parseArgs(args);\n\n if (parsed.help === \"true\") {\n console.log(`Usage: pnpm action db-patch --table <t> --column <c> --where \"<clause>\" [edit flags]\n\nSurgical search-and-replace on a text column. Avoids re-sending the full\ncolumn value — ideal for large strings (documents, slides, dashboards, JSON).\n\nRequired:\n --table <name> Target table (identifier; no quoting)\n --column <name> Target text column (identifier; no quoting)\n --where \"<clause>\" SQL WHERE clause that matches exactly one row\n\nEdit mode (pick one):\n --find <text> Text to find (single edit; default replace = \"\")\n --replace <text> Replacement text (used with --find)\n --edits <json> Batch: JSON array of {find, replace} objects\n --json-ops <json> Structural JSON edits on a JSON column — array of ops:\n { op: \"set\", path, value } → set/replace at path\n { op: \"remove\", path } → delete at path\n { op: \"insert\", path, value } → insert into array\n { op: \"move\", from, path } → move node\n { op: \"move-before\", from, path } → move, stable indexing\n Paths use JSON Pointer (\"/panels/3/title\").\n Much safer than string patches for JSON columns\n (dashboards, forms, slide decks).\n\nOptions:\n --all Replace every occurrence of each 'find' (default: first only)\n --format json Output as JSON\n --help Show this help\n\nExamples:\n # Fix a typo in one document\n pnpm action db-patch --table documents --column content \\\\\n --where \"id='abc'\" --find \"teh\" --replace \"the\"\n\n # Batch edits on a deck's JSON blob\n pnpm action db-patch --table decks --column data --where \"id='d1'\" \\\\\n --edits '[{\"find\":\"\\\\\"Q3\\\\\"\",\"replace\":\"\\\\\"Q4\\\\\"\"},{\"find\":\"$1M\",\"replace\":\"$1.2M\"}]'\n\nWhen to use db-patch vs other tools:\n Large text field, small edit → db-patch (this)\n Short field or multi-column set → db-exec UPDATE\n Domain action exists (edit-document, ...) → use that action (syncs live\n to open collaborative editors)\n`);\n return;\n }\n\n const table = parsed.table;\n const column = parsed.column;\n const where = parsed.where;\n\n if (!table) fail(\"--table is required\");\n if (!column) fail(\"--column is required\");\n if (!where) fail(\"--where is required\");\n if (!isValidIdentifier(table))\n fail(\n `Invalid --table: \"${table}\". Must be a plain identifier (letters, digits, underscore).`,\n );\n if (!isValidIdentifier(column))\n fail(\n `Invalid --column: \"${column}\". Must be a plain identifier (letters, digits, underscore).`,\n );\n validateWhere(where);\n\n const jsonOps = parseJsonOps(parsed);\n // Edit parsing only runs when json-ops isn't provided — otherwise the\n // find/replace args are irrelevant and would error if missing.\n const edits = jsonOps ? [] : parseEdits(parsed);\n const replaceAll = parsed.all === \"true\";\n\n // Resolve database URL: --db flag → DATABASE_URL env → default file path\n let url: string;\n if (parsed.db) {\n url = \"file:\" + path.resolve(parsed.db);\n } else if (getDatabaseUrl()) {\n url = getDatabaseUrl();\n } else {\n url = \"file:\" + path.resolve(process.cwd(), \"data\", \"app.db\");\n }\n\n if (isPostgresUrl(url)) {\n await runPostgres({\n url,\n table,\n column,\n where,\n edits,\n jsonOps: jsonOps ?? undefined,\n replaceAll,\n format: parsed.format,\n });\n } else {\n await runSqlite({\n url,\n table,\n column,\n where,\n edits,\n jsonOps: jsonOps ?? undefined,\n replaceAll,\n format: parsed.format,\n });\n }\n}\n"]}
1
+ {"version":3,"file":"patch.js","sourceRoot":"","sources":["../../../src/scripts/db/patch.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Core script: db-patch\n *\n * Surgical search-and-replace on a text column in any SQL table. Instead of\n * re-sending the full column value (as `db-exec UPDATE` would require), the\n * agent sends one or more `{find, replace}` pairs. The script reads the row,\n * applies the edits in memory, and writes the result back in a single UPDATE.\n *\n * ## When to use which tool\n *\n * Large text field, small slice to change → db-patch (this)\n * e.g. fix one paragraph in a 50KB document, tweak one key in a dashboard\n * JSON blob, rename a label in a slide HTML string.\n *\n * Short field, set outright → db-exec UPDATE\n * e.g. `UPDATE forms SET status = 'published' WHERE id = '...'`.\n *\n * Multiple columns / computed values → db-exec UPDATE\n * e.g. `UPDATE meals SET calories = calories + 50, ...`.\n *\n * Domain-specific action exists → use that action\n * e.g. `edit-document` or `update-slide` — they also push live Yjs\n * updates to any open collaborative editor. db-patch is the generic\n * fallback for tables without a bespoke action.\n *\n * ## Why it's faster\n *\n * The agent only has to transmit the diff (the `find` + `replace`\n * strings), not the full new value. For large text fields — multi-kilobyte\n * markdown documents, slide HTML, dashboard/form JSON — this dramatically\n * reduces tokens per edit and keeps concurrent edits composable.\n *\n * ## Security\n *\n * In production mode, the same per-user / per-org temp view scoping that\n * `db-exec` uses applies here: the SELECT and UPDATE both go through the\n * scoped view, so you can never read or write rows outside the current\n * user's data. The WHERE clause is validated against a keyword denylist\n * (no ;, no chained statements, no DDL).\n *\n * ## Usage\n *\n * pnpm action db-patch --table <t> --column <c> --where \"<clause>\" \\\n * --find \"old\" --replace \"new\"\n *\n * pnpm action db-patch --table decks --column data --where \"id='d1'\" \\\n * --edits '[{\"find\":\"Q3\",\"replace\":\"Q4\"},{\"find\":\"$1M\",\"replace\":\"$1.2M\"}]'\n */\n\nimport path from \"path\";\nimport { createClient } from \"@libsql/client\";\nimport { getDatabaseUrl, getDatabaseAuthToken } from \"../../db/client.js\";\nimport { parseArgs, fail } from \"../utils.js\";\nimport { assertNoSensitiveFrameworkTables } from \"./safety.js\";\nimport { buildScopingPostgres, buildScopingSqlite } from \"./scoping.js\";\n\ninterface TextEdit {\n find: string;\n replace: string;\n}\n\n/**\n * JSON patch operation — a subset of RFC 6902 plus a convenience `move-before`\n * that's rare in the spec but common for list reordering. The agent ends up\n * needing this all the time (reordering dashboard panels, form fields, slide\n * layers) and without it has to do multi-step string surgery.\n */\ninterface JsonOp {\n op: \"set\" | \"replace\" | \"remove\" | \"move\" | \"move-before\" | \"insert\";\n /** JSON Pointer-style path, e.g. \"/panels/3/title\". \"\" = root. */\n path?: string;\n /** For move / move-before: source path. */\n from?: string;\n /** For set / replace / insert: value to write. */\n value?: unknown;\n}\n\ninterface EditResult {\n index: number;\n status: \"replaced\" | \"deleted\" | \"not-found\";\n detail: string;\n occurrences: number;\n}\n\ninterface PatchOutput {\n table: string;\n column: string;\n applied: number;\n total: number;\n bytesBefore: number;\n bytesAfter: number;\n results: EditResult[];\n}\n\nfunction isPostgresUrl(url: string): boolean {\n return url.startsWith(\"postgres://\") || url.startsWith(\"postgresql://\");\n}\n\n/** Only unquoted [A-Za-z_][A-Za-z0-9_]* identifiers are allowed — no spaces,\n * no quoting, no dotted names. This is deliberately strict: it stops the\n * agent from sneaking SQL into the table/column slots. */\nfunction isValidIdentifier(s: string): boolean {\n return /^[A-Za-z_][A-Za-z0-9_]*$/.test(s);\n}\n\n/** Reject WHERE clauses that could chain statements or hide DDL. This isn't\n * a full SQL parser — just a keyword/character denylist to keep the surface\n * area equivalent to what db-exec already allows. */\nfunction validateWhere(where: string): void {\n if (where.includes(\";\")) {\n fail(\"--where must not contain ';' (no statement chaining)\");\n }\n // Strip inline strings before keyword scanning so \"WHERE name = 'DROP TABLE'\"\n // doesn't trip the denylist.\n const stripped = where\n .replace(/'(?:''|[^'])*'/g, \"''\")\n .replace(/\"(?:\"\"|[^\"])*\"/g, '\"\"')\n .toUpperCase();\n\n const blocked = [\n \" INSERT \",\n \" UPDATE \",\n \" DELETE \",\n \" DROP \",\n \" ALTER \",\n \" CREATE \",\n \" ATTACH \",\n \" DETACH \",\n \" PRAGMA \",\n \" VACUUM \",\n \"--\",\n \"/*\",\n ];\n const padded = \" \" + stripped + \" \";\n for (const kw of blocked) {\n if (padded.includes(kw)) {\n fail(`--where must not contain \"${kw.trim()}\"`);\n }\n }\n}\n\nfunction parseEdits(parsed: Record<string, string>): TextEdit[] {\n let edits: TextEdit[];\n\n if (parsed.edits) {\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(parsed.edits);\n } catch (e: any) {\n fail(`Invalid --edits JSON: ${e.message}`);\n }\n if (!Array.isArray(parsedJson) || parsedJson.length === 0) {\n fail(\"--edits must be a non-empty JSON array of {find, replace} objects\");\n }\n edits = parsedJson as TextEdit[];\n } else if (parsed.find !== undefined) {\n if (parsed.find === \"\") fail(\"--find cannot be empty\");\n edits = [{ find: parsed.find, replace: parsed.replace ?? \"\" }];\n } else {\n fail(\"Either --find/--replace or --edits is required\");\n }\n\n for (const edit of edits!) {\n if (typeof edit.find !== \"string\" || edit.find === \"\") {\n fail(\"Each edit must have a non-empty 'find' string\");\n }\n if (edit.replace === undefined || edit.replace === null) {\n edit.replace = \"\";\n }\n if (typeof edit.replace !== \"string\") {\n fail(\"Each edit's 'replace' field must be a string\");\n }\n }\n\n return edits!;\n}\n\nfunction preview(s: string): string {\n const max = 60;\n const trimmed = s.replace(/\\s+/g, \" \");\n return trimmed.length > max ? trimmed.slice(0, max) + \"...\" : trimmed;\n}\n\n// ─── JSON patch helpers ─────────────────────────────────────────────────────\n\n/** Parse a JSON Pointer (\"/panels/3/title\") into path segments. \"\" = root. */\nfunction parsePointer(pointer: string): string[] {\n if (pointer === \"\" || pointer === \"/\") return [];\n if (!pointer.startsWith(\"/\")) {\n fail(`JSON path must start with '/' (got: ${pointer})`);\n }\n return pointer\n .slice(1)\n .split(\"/\")\n .map((s) => s.replace(/~1/g, \"/\").replace(/~0/g, \"~\"));\n}\n\n/** Walk to the parent container of the given path. Returns [parent, lastKey]. */\nfunction resolveParent(\n root: unknown,\n segments: string[],\n): [any, string | number] {\n if (segments.length === 0) {\n fail(\"Root path is not supported for this operation\");\n }\n let node: any = root;\n for (let i = 0; i < segments.length - 1; i++) {\n const seg = segments[i];\n if (Array.isArray(node)) {\n const idx = parseInt(seg, 10);\n if (isNaN(idx) || idx < 0 || idx >= node.length) {\n fail(\n `Path segment \"${seg}\" is out of bounds for array of length ${node.length}`,\n );\n }\n node = node[idx];\n } else if (node && typeof node === \"object\") {\n if (!(seg in node)) {\n fail(`Path segment \"${seg}\" not found in object`);\n }\n node = node[seg];\n } else {\n fail(`Cannot descend into ${typeof node} at segment \"${seg}\"`);\n }\n }\n const last = segments[segments.length - 1];\n if (Array.isArray(node)) {\n const idx = last === \"-\" ? node.length : parseInt(last, 10);\n if (isNaN(idx)) fail(`Expected numeric index, got \"${last}\"`);\n return [node, idx];\n }\n return [node, last];\n}\n\n/** Apply one JSON op, mutating `root` in place. Returns a short detail string. */\nfunction applyJsonOp(root: any, op: JsonOp): string {\n switch (op.op) {\n case \"set\":\n case \"replace\": {\n if (op.path === undefined) fail(`${op.op} requires 'path'`);\n const [parent, key] = resolveParent(root, parsePointer(op.path));\n parent[key as any] = op.value;\n return `${op.op} ${op.path}`;\n }\n case \"remove\": {\n if (op.path === undefined) fail(\"remove requires 'path'\");\n const [parent, key] = resolveParent(root, parsePointer(op.path));\n if (Array.isArray(parent)) {\n parent.splice(key as number, 1);\n } else {\n delete parent[key as string];\n }\n return `remove ${op.path}`;\n }\n case \"insert\": {\n if (op.path === undefined) fail(\"insert requires 'path'\");\n const [parent, key] = resolveParent(root, parsePointer(op.path));\n if (!Array.isArray(parent)) fail(`insert target must be an array`);\n parent.splice(key as number, 0, op.value);\n return `insert at ${op.path}`;\n }\n case \"move\":\n case \"move-before\": {\n if (!op.from || op.path === undefined) {\n fail(`${op.op} requires 'from' and 'path'`);\n }\n const fromSeg = parsePointer(op.from);\n const toSeg = parsePointer(op.path);\n const [fromParent, fromKey] = resolveParent(root, fromSeg);\n // Extract the value\n let value: unknown;\n if (Array.isArray(fromParent)) {\n value = fromParent[fromKey as number];\n fromParent.splice(fromKey as number, 1);\n } else {\n value = fromParent[fromKey as string];\n delete fromParent[fromKey as string];\n }\n // For array moves where the destination is in the same array and\n // after the removed index, shift the target index down by one so\n // \"move /panels/7 to /panels/3\" lands exactly at index 3 even after\n // the earlier splice shifted indices.\n let [toParent, toKey] = resolveParent(root, toSeg);\n if (\n Array.isArray(toParent) &&\n Array.isArray(fromParent) &&\n toParent === fromParent\n ) {\n const fromIdx = fromKey as number;\n const toIdx = toKey as number;\n if (toIdx > fromIdx) {\n toKey = toIdx - 1;\n }\n }\n if (Array.isArray(toParent)) {\n toParent.splice(toKey as number, 0, value);\n } else {\n toParent[toKey as string] = value;\n }\n return `${op.op} ${op.from} → ${op.path}`;\n }\n default:\n fail(`Unknown JSON op: ${(op as any).op}`);\n }\n return \"\";\n}\n\nfunction parseJsonOps(parsed: Record<string, string>): JsonOp[] | null {\n if (!parsed.jsonOps && !parsed[\"json-ops\"]) return null;\n const raw = parsed.jsonOps ?? parsed[\"json-ops\"];\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(raw);\n } catch (e: any) {\n fail(`Invalid --json-ops JSON: ${e.message}`);\n }\n if (!Array.isArray(parsedJson) || parsedJson.length === 0) {\n fail(\"--json-ops must be a non-empty JSON array\");\n }\n for (const op of parsedJson as any[]) {\n if (!op || typeof op !== \"object\" || typeof op.op !== \"string\") {\n fail(\"Each op must be an object with an 'op' field\");\n }\n }\n return parsedJson as JsonOp[];\n}\n\nfunction countOccurrences(haystack: string, needle: string): number {\n if (!needle) return 0;\n let count = 0;\n let idx = 0;\n while ((idx = haystack.indexOf(needle, idx)) !== -1) {\n count++;\n idx += needle.length;\n }\n return count;\n}\n\n/** Find all match positions (up to a cap so we don't explode memory). */\nfunction findAll(haystack: string, needle: string, cap = 10): number[] {\n const out: number[] = [];\n if (!needle) return out;\n let idx = 0;\n while ((idx = haystack.indexOf(needle, idx)) !== -1 && out.length < cap) {\n out.push(idx);\n idx += needle.length;\n }\n return out;\n}\n\n/** Format a single match with ~40 chars of surrounding context so the agent\n * can widen its `find` string to disambiguate ambiguous matches. */\nfunction formatContext(\n content: string,\n matchIdx: number,\n matchLen: number,\n radius = 40,\n): string {\n const start = Math.max(0, matchIdx - radius);\n const end = Math.min(content.length, matchIdx + matchLen + radius);\n const before = content.slice(start, matchIdx).replace(/\\s+/g, \" \");\n const middle = content\n .slice(matchIdx, matchIdx + matchLen)\n .replace(/\\s+/g, \" \");\n const after = content.slice(matchIdx + matchLen, end).replace(/\\s+/g, \" \");\n const prefix = start > 0 ? \"…\" : \"\";\n const suffix = end < content.length ? \"…\" : \"\";\n return `${prefix}${before}⟨${middle}⟩${after}${suffix}`;\n}\n\n/** Build a \"string not unique\" error message showing each match in\n * context — matches Claude Code's Edit-tool UX so the agent can\n * widen the find string and retry. */\nfunction buildAmbiguousMessage(\n findStr: string,\n content: string,\n count: number,\n): string {\n const positions = findAll(content, findStr, 6);\n const lines = [\n `Found ${count} occurrences of the 'find' string — db-patch requires exactly one match by default.`,\n `Widen 'find' with unique surrounding context, or pass --all to replace every occurrence.`,\n `'find' preview: \"${preview(findStr)}\"`,\n \"Matches:\",\n ];\n for (let i = 0; i < positions.length; i++) {\n lines.push(\n ` [${i + 1}] ${formatContext(content, positions[i], findStr.length)}`,\n );\n }\n if (count > positions.length) {\n lines.push(` … and ${count - positions.length} more`);\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * Apply edits sequentially.\n *\n * Default behavior matches Claude Code's Edit tool: the `find` string must\n * match exactly one occurrence. If 0 → \"not found\". If >1 → error with\n * surrounding context for each match so the agent can widen `find` and\n * retry. Pass `replaceAll` (`--all`) to allow replacing every occurrence.\n *\n * This strict-uniqueness default is a deliberate reliability upgrade — 9×\n * fewer silent wrong-match bugs at the cost of slightly more verbose finds.\n */\nfunction applyEdits(\n content: string,\n edits: TextEdit[],\n replaceAll: boolean,\n): { content: string; results: EditResult[]; applied: number } {\n let out = content;\n const results: EditResult[] = [];\n let applied = 0;\n\n for (let i = 0; i < edits.length; i++) {\n const edit = edits[i];\n const occurrences = countOccurrences(out, edit.find);\n\n if (occurrences === 0) {\n results.push({\n index: i,\n status: \"not-found\",\n detail: `NOT FOUND: \"${preview(edit.find)}\"`,\n occurrences: 0,\n });\n continue;\n }\n\n if (replaceAll) {\n // Literal replaceAll via split/join — no regex, no special chars.\n out = out.split(edit.find).join(edit.replace);\n applied++;\n results.push({\n index: i,\n status: edit.replace === \"\" ? \"deleted\" : \"replaced\",\n detail: `${edit.replace === \"\" ? \"deleted\" : \"replaced\"} ${occurrences}×: \"${preview(edit.find)}\"`,\n occurrences,\n });\n } else if (occurrences > 1) {\n results.push({\n index: i,\n status: \"not-found\",\n detail: buildAmbiguousMessage(edit.find, out, occurrences),\n occurrences,\n });\n } else {\n const idx = out.indexOf(edit.find);\n out =\n out.slice(0, idx) + edit.replace + out.slice(idx + edit.find.length);\n applied++;\n results.push({\n index: i,\n status: edit.replace === \"\" ? \"deleted\" : \"replaced\",\n detail: `${edit.replace === \"\" ? \"deleted\" : \"replaced\"}: \"${preview(edit.find)}\"`,\n occurrences: 1,\n });\n }\n }\n\n return { content: out, results, applied };\n}\n\nfunction printResult(out: PatchOutput, format?: string): void {\n if (format === \"json\") {\n console.log(JSON.stringify(out, null, 2));\n return;\n }\n console.log(`db-patch: ${out.table}.${out.column}`);\n console.log(` Applied: ${out.applied}/${out.total}`);\n console.log(` Bytes: ${out.bytesBefore} → ${out.bytesAfter}`);\n for (const r of out.results) {\n console.log(` - ${r.detail}`);\n }\n}\n\ninterface RunOpts {\n url: string;\n table: string;\n column: string;\n where: string;\n edits: TextEdit[];\n jsonOps?: JsonOp[];\n replaceAll: boolean;\n format?: string;\n}\n\nfunction applyEither(\n original: string,\n opts: RunOpts,\n): {\n content: string;\n results: EditResult[];\n applied: number;\n total: number;\n} {\n if (opts.jsonOps && opts.jsonOps.length > 0) {\n let root: unknown;\n try {\n root = JSON.parse(original);\n } catch (e: any) {\n fail(\n `--json-ops requires the column value to be valid JSON. Parse failed: ${e.message}`,\n );\n }\n const results: EditResult[] = [];\n let applied = 0;\n for (let i = 0; i < opts.jsonOps.length; i++) {\n const op = opts.jsonOps[i];\n try {\n const detail = applyJsonOp(root, op);\n results.push({\n index: i,\n status: \"replaced\",\n detail,\n occurrences: 1,\n });\n applied++;\n } catch (e: any) {\n results.push({\n index: i,\n status: \"not-found\",\n detail: `FAILED: ${e?.message ?? String(e)}`,\n occurrences: 0,\n });\n }\n }\n return {\n content: JSON.stringify(root),\n results,\n applied,\n total: opts.jsonOps.length,\n };\n }\n const out = applyEdits(original, opts.edits, opts.replaceAll);\n return { ...out, total: opts.edits.length };\n}\n\n// ─── Postgres path ──────────────────────────────────────────────────────────\n\nasync function runPostgres(opts: RunOpts): Promise<void> {\n const { default: pg } = await import(\"postgres\");\n const pgSql = pg(opts.url);\n try {\n let result:\n | {\n table: string;\n column: string;\n applied: number;\n total: number;\n bytesBefore: number;\n bytesAfter: number;\n results: EditResult[];\n }\n | undefined;\n\n await pgSql.begin(async (tx: any) => {\n // Same temp-view scoping db-exec uses — SELECT and UPDATE both go\n // through the scoped view. Keep setup and teardown transaction-local\n // so pooled Postgres backends never retain the temp views.\n const scoping = await buildScopingPostgres(tx);\n try {\n for (const stmt of scoping.setup) {\n await tx.unsafe(stmt);\n }\n\n const selectSql = `SELECT \"${opts.column}\" AS __val FROM \"${opts.table}\" WHERE ${opts.where}`;\n const selected: any[] = Array.from(await tx.unsafe(selectSql));\n\n if (selected.length === 0) {\n fail(\n `No rows matched: ${opts.table} WHERE ${opts.where}. ` +\n `(In production, data scoping filters results to the current user — the row may exist but be owned by someone else.)`,\n );\n }\n if (selected.length > 1) {\n fail(\n `WHERE matched ${selected.length} rows in ${opts.table}. db-patch expects exactly one row — narrow the WHERE clause (usually by primary key).`,\n );\n }\n\n const original = (selected[0].__val ?? \"\") as string;\n if (typeof original !== \"string\") {\n fail(\n `Column ${opts.table}.${opts.column} is not a text column (got ${typeof original}).`,\n );\n }\n\n const { content, results, applied, total } = applyEither(\n original,\n opts,\n );\n\n if (applied > 0) {\n await tx.unsafe(\n `UPDATE \"${opts.table}\" SET \"${opts.column}\" = $1 WHERE ${opts.where}`,\n [content],\n );\n }\n\n result = {\n table: opts.table,\n column: opts.column,\n applied,\n total,\n bytesBefore: original.length,\n bytesAfter: content.length,\n results,\n };\n } finally {\n for (const stmt of scoping.teardown) {\n await tx.unsafe(stmt).catch(() => {});\n }\n }\n });\n\n if (result) {\n printResult(result, opts.format);\n }\n } finally {\n await pgSql.end();\n }\n}\n\n// ─── SQLite / libSQL path ───────────────────────────────────────────────────\n\nasync function runSqlite(opts: RunOpts): Promise<void> {\n const client = createClient({\n url: opts.url,\n authToken: getDatabaseAuthToken(),\n });\n try {\n const scoping = await buildScopingSqlite(client);\n for (const stmt of scoping.setup) {\n await client.execute(stmt);\n }\n\n const selectSql = `SELECT \"${opts.column}\" AS __val FROM \"${opts.table}\" WHERE ${opts.where}`;\n const selectRes = await client.execute(selectSql);\n\n if (selectRes.rows.length === 0) {\n fail(\n `No rows matched: ${opts.table} WHERE ${opts.where}. ` +\n `(In production, data scoping filters results to the current user — the row may exist but be owned by someone else.)`,\n );\n }\n if (selectRes.rows.length > 1) {\n fail(\n `WHERE matched ${selectRes.rows.length} rows in ${opts.table}. db-patch expects exactly one row — narrow the WHERE clause (usually by primary key).`,\n );\n }\n\n const row = selectRes.rows[0] as any;\n const original = (row.__val ?? row[0] ?? \"\") as string;\n if (typeof original !== \"string\") {\n fail(\n `Column ${opts.table}.${opts.column} is not a text column (got ${typeof original}).`,\n );\n }\n\n const { content, results, applied, total } = applyEither(original, opts);\n\n if (applied > 0) {\n await client.execute({\n sql: `UPDATE \"${opts.table}\" SET \"${opts.column}\" = ? WHERE ${opts.where}`,\n args: [content],\n });\n }\n\n printResult(\n {\n table: opts.table,\n column: opts.column,\n applied,\n total,\n bytesBefore: original.length,\n bytesAfter: content.length,\n results,\n },\n opts.format,\n );\n\n for (const stmt of scoping.teardown) {\n await client.execute(stmt).catch(() => {});\n }\n } finally {\n client.close();\n }\n}\n\n// ─── Entry point ────────────────────────────────────────────────────────────\n\nexport default async function dbPatch(args: string[]): Promise<void> {\n const parsed = parseArgs(args);\n\n if (parsed.help === \"true\") {\n console.log(`Usage: pnpm action db-patch --table <t> --column <c> --where \"<clause>\" [edit flags]\n\nSurgical search-and-replace on a text column. Avoids re-sending the full\ncolumn value — ideal for large strings (documents, slides, dashboards, JSON).\n\nRequired:\n --table <name> Target table (identifier; no quoting)\n --column <name> Target text column (identifier; no quoting)\n --where \"<clause>\" SQL WHERE clause that matches exactly one row\n\nEdit mode (pick one):\n --find <text> Text to find (single edit; default replace = \"\")\n --replace <text> Replacement text (used with --find)\n --edits <json> Batch: JSON array of {find, replace} objects\n --json-ops <json> Structural JSON edits on a JSON column — array of ops:\n { op: \"set\", path, value } → set/replace at path\n { op: \"remove\", path } → delete at path\n { op: \"insert\", path, value } → insert into array\n { op: \"move\", from, path } → move node\n { op: \"move-before\", from, path } → move, stable indexing\n Paths use JSON Pointer (\"/panels/3/title\").\n Much safer than string patches for JSON columns\n (dashboards, forms, slide decks).\n\nOptions:\n --all Replace every occurrence of each 'find' (default: first only)\n --format json Output as JSON\n --help Show this help\n\nExamples:\n # Fix a typo in one document\n pnpm action db-patch --table documents --column content \\\\\n --where \"id='abc'\" --find \"teh\" --replace \"the\"\n\n # Batch edits on a deck's JSON blob\n pnpm action db-patch --table decks --column data --where \"id='d1'\" \\\\\n --edits '[{\"find\":\"\\\\\"Q3\\\\\"\",\"replace\":\"\\\\\"Q4\\\\\"\"},{\"find\":\"$1M\",\"replace\":\"$1.2M\"}]'\n\nWhen to use db-patch vs other tools:\n Large text field, small edit → db-patch (this)\n Short field or multi-column set → db-exec UPDATE\n Domain action exists (edit-document, ...) → use that action (syncs live\n to open collaborative editors)\n`);\n return;\n }\n\n const table = parsed.table;\n const column = parsed.column;\n const where = parsed.where;\n\n if (!table) fail(\"--table is required\");\n if (!column) fail(\"--column is required\");\n if (!where) fail(\"--where is required\");\n if (!isValidIdentifier(table))\n fail(\n `Invalid --table: \"${table}\". Must be a plain identifier (letters, digits, underscore).`,\n );\n if (!isValidIdentifier(column))\n fail(\n `Invalid --column: \"${column}\". Must be a plain identifier (letters, digits, underscore).`,\n );\n assertNoSensitiveFrameworkTables(table, \"patch\");\n assertNoSensitiveFrameworkTables(where, \"read\");\n validateWhere(where);\n\n const jsonOps = parseJsonOps(parsed);\n // Edit parsing only runs when json-ops isn't provided — otherwise the\n // find/replace args are irrelevant and would error if missing.\n const edits = jsonOps ? [] : parseEdits(parsed);\n const replaceAll = parsed.all === \"true\";\n\n // Resolve database URL: --db flag → DATABASE_URL env → default file path\n let url: string;\n if (parsed.db) {\n url = \"file:\" + path.resolve(parsed.db);\n } else if (getDatabaseUrl()) {\n url = getDatabaseUrl();\n } else {\n url = \"file:\" + path.resolve(process.cwd(), \"data\", \"app.db\");\n }\n\n if (isPostgresUrl(url)) {\n await runPostgres({\n url,\n table,\n column,\n where,\n edits,\n jsonOps: jsonOps ?? undefined,\n replaceAll,\n format: parsed.format,\n });\n } else {\n await runSqlite({\n url,\n table,\n column,\n where,\n edits,\n jsonOps: jsonOps ?? undefined,\n replaceAll,\n format: parsed.format,\n });\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../../src/scripts/db/query.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAwJH,wBAA8B,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAmInE"}
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../../src/scripts/db/query.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAyJH,wBAA8B,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAyInE"}
@@ -15,6 +15,7 @@ import path from "path";
15
15
  import { createClient } from "@libsql/client";
16
16
  import { getDatabaseUrl, getDatabaseAuthToken } from "../../db/client.js";
17
17
  import { parseArgs, fail } from "../utils.js";
18
+ import { assertNoSensitiveFrameworkTables } from "./safety.js";
18
19
  import { buildScopingPostgres, buildScopingSqlite } from "./scoping.js";
19
20
  function isPostgresUrl(url) {
20
21
  return url.startsWith("postgres://") || url.startsWith("postgresql://");
@@ -175,6 +176,7 @@ Options:
175
176
  !upper.startsWith("PRAGMA")) {
176
177
  fail("Only SELECT, WITH, EXPLAIN, and PRAGMA queries are allowed. Use db-exec for writes.");
177
178
  }
179
+ assertNoSensitiveFrameworkTables(stripped, "read");
178
180
  // Resolve database URL: --db flag → DATABASE_URL env → default file path
179
181
  let url;
180
182
  if (parsed.db) {
@@ -200,22 +202,29 @@ Options:
200
202
  const { default: pg } = await import("postgres");
201
203
  const pgSql = pg(url);
202
204
  try {
203
- // Set up user-scoped temp views in production
204
- const scoping = await buildScopingPostgres(pgSql);
205
- for (const stmt of scoping.setup) {
206
- await pgSql.unsafe(stmt);
207
- }
208
205
  const pgSqlText = normalizePostgresSql(finalSql, sqlArgs);
209
- const result = sqlArgs.length > 0
210
- ? await pgSql.unsafe(pgSqlText, sqlArgs)
211
- : await pgSql.unsafe(pgSqlText);
212
- const rows = Array.from(result);
206
+ let rows = [];
207
+ await pgSql.begin(async (tx) => {
208
+ // Temp views are session state. Keep setup/query/teardown on one
209
+ // transaction-bound backend so pooled Postgres never retains them.
210
+ const scoping = await buildScopingPostgres(tx);
211
+ try {
212
+ for (const stmt of scoping.setup) {
213
+ await tx.unsafe(stmt);
214
+ }
215
+ const result = sqlArgs.length > 0
216
+ ? await tx.unsafe(pgSqlText, sqlArgs)
217
+ : await tx.unsafe(pgSqlText);
218
+ rows = Array.from(result);
219
+ }
220
+ finally {
221
+ for (const stmt of scoping.teardown) {
222
+ await tx.unsafe(stmt).catch(() => { });
223
+ }
224
+ }
225
+ });
213
226
  const keys = rows.length > 0 ? Object.keys(rows[0]) : [];
214
227
  printTable(rows.length > 0 ? rows : keys.length > 0 ? rows : [], pgSqlText, parsed.format);
215
- // Tear down temp views
216
- for (const stmt of scoping.teardown) {
217
- await pgSql.unsafe(stmt).catch(() => { });
218
- }
219
228
  }
220
229
  finally {
221
230
  await pgSql.end();
@@ -1 +1 @@
1
- {"version":3,"file":"query.js","sourceRoot":"","sources":["../../../src/scripts/db/query.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Core script: db-query\n *\n * Run a read-only SQL query against a SQLite or Postgres database.\n *\n * In production mode, temporary views are created to scope data to the\n * current user (AGENT_USER_EMAIL). Tables with an `owner_email` column\n * and core tables (settings, application_state, etc.) are automatically\n * filtered so queries only return the current user's data.\n *\n * Usage:\n * pnpm action db-query --sql \"SELECT * FROM forms WHERE id = ?\" [--args '[\"abc\"]'] [--db path] [--format json] [--limit N]\n */\n\nimport path from \"path\";\nimport { createClient } from \"@libsql/client\";\nimport { getDatabaseUrl, getDatabaseAuthToken } from \"../../db/client.js\";\nimport { parseArgs, fail } from \"../utils.js\";\nimport { buildScopingPostgres, buildScopingSqlite } from \"./scoping.js\";\n\nfunction isPostgresUrl(url: string): boolean {\n return url.startsWith(\"postgres://\") || url.startsWith(\"postgresql://\");\n}\n\nfunction parseSqlArgs(raw: string | undefined): unknown[] {\n if (!raw) return [];\n try {\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed)) return parsed;\n } catch {\n // Fall through to the shared error below.\n }\n fail(\"--args must be a JSON array\");\n}\n\nfunction convertQuestionMarksToPostgresParams(sql: string): string {\n let index = 0;\n let out = \"\";\n let state: \"normal\" | \"single\" | \"double\" | \"line-comment\" | \"block-comment\" =\n \"normal\";\n\n for (let i = 0; i < sql.length; i++) {\n const ch = sql[i];\n const next = sql[i + 1];\n\n if (state === \"line-comment\") {\n out += ch;\n if (ch === \"\\n\") state = \"normal\";\n continue;\n }\n\n if (state === \"block-comment\") {\n out += ch;\n if (ch === \"*\" && next === \"/\") {\n out += next;\n i++;\n state = \"normal\";\n }\n continue;\n }\n\n if (state === \"single\") {\n out += ch;\n if (ch === \"'\" && next === \"'\") {\n out += next;\n i++;\n } else if (ch === \"'\") {\n state = \"normal\";\n }\n continue;\n }\n\n if (state === \"double\") {\n out += ch;\n if (ch === '\"' && next === '\"') {\n out += next;\n i++;\n } else if (ch === '\"') {\n state = \"normal\";\n }\n continue;\n }\n\n if (ch === \"-\" && next === \"-\") {\n out += ch + next;\n i++;\n state = \"line-comment\";\n continue;\n }\n if (ch === \"/\" && next === \"*\") {\n out += ch + next;\n i++;\n state = \"block-comment\";\n continue;\n }\n if (ch === \"'\") {\n out += ch;\n state = \"single\";\n continue;\n }\n if (ch === '\"') {\n out += ch;\n state = \"double\";\n continue;\n }\n if (ch === \"?\") {\n index++;\n out += `$${index}`;\n continue;\n }\n out += ch;\n }\n\n return out;\n}\n\nfunction normalizePostgresSql(sql: string, args: unknown[]): string {\n if (args.length === 0 || /\\$\\d+\\b/.test(sql)) return sql;\n return convertQuestionMarksToPostgresParams(sql);\n}\n\nfunction printTable(\n rows: Record<string, unknown>[],\n finalSql: string,\n format?: string,\n) {\n if (format === \"json\") {\n console.log(\n JSON.stringify({ query: finalSql, rows, count: rows.length }, null, 2),\n );\n return;\n }\n\n console.log(`Query: ${finalSql}`);\n console.log(`Rows: ${rows.length}\\n`);\n\n if (rows.length === 0) {\n console.log(\"(no results)\");\n return;\n }\n\n const keys = Object.keys(rows[0]);\n const widths = keys.map((k) => {\n const maxVal = Math.max(...rows.map((r) => String(r[k] ?? \"NULL\").length));\n return Math.max(k.length, Math.min(maxVal, 60));\n });\n\n const header = keys.map((k, i) => k.padEnd(widths[i])).join(\" | \");\n console.log(header);\n console.log(widths.map((w) => \"-\".repeat(w)).join(\"-+-\"));\n\n for (const row of rows) {\n const line = keys\n .map((k, i) => {\n const val = String(row[k] ?? \"NULL\");\n return val.length > 60\n ? val.slice(0, 57) + \"...\"\n : val.padEnd(widths[i]);\n })\n .join(\" | \");\n console.log(line);\n }\n}\n\nexport default async function dbQuery(args: string[]): Promise<void> {\n const parsed = parseArgs(args);\n\n if (parsed.help === \"true\") {\n console.log(`Usage: pnpm action db-query --sql \"<query>\" [options]\n\nOptions:\n --sql <query> SQL SELECT query to run (required)\n --args <json> JSON array of positional SQL bind parameters\n --db <path> Path to SQLite database (default: data/app.db)\n --format json Output as JSON instead of a table\n --limit N Append LIMIT N if not already present\n --help Show this help message`);\n return;\n }\n\n const sql = parsed.sql;\n if (!sql) {\n fail('--sql is required. Example: --sql \"SELECT * FROM forms\"');\n }\n const sqlArgs = parseSqlArgs(parsed.args);\n\n // Safety: only allow read-only statements.\n // Strip leading SQL comments before checking the prefix.\n const stripped = sql\n .replace(/^\\s*--[^\\n]*\\n/gm, \"\")\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\")\n .trim();\n const upper = stripped.toUpperCase();\n if (\n !upper.startsWith(\"SELECT\") &&\n !upper.startsWith(\"WITH\") &&\n !upper.startsWith(\"EXPLAIN\") &&\n !upper.startsWith(\"PRAGMA\")\n ) {\n fail(\n \"Only SELECT, WITH, EXPLAIN, and PRAGMA queries are allowed. Use db-exec for writes.\",\n );\n }\n\n // Resolve database URL: --db flag → DATABASE_URL env → default file path\n let url: string;\n if (parsed.db) {\n url = \"file:\" + path.resolve(parsed.db);\n } else if (getDatabaseUrl()) {\n url = getDatabaseUrl();\n } else {\n url = \"file:\" + path.resolve(process.cwd(), \"data\", \"app.db\");\n }\n\n let finalSql = sql;\n if (\n parsed.limit &&\n (upper.startsWith(\"SELECT\") || upper.startsWith(\"WITH\")) &&\n !/\\bLIMIT\\b/i.test(stripped)\n ) {\n const limitVal = parseInt(parsed.limit, 10);\n if (isNaN(limitVal) || limitVal < 1)\n fail(\"--limit must be a positive integer\");\n finalSql = `${sql} LIMIT ${limitVal}`;\n }\n\n // Postgres path\n if (isPostgresUrl(url)) {\n const { default: pg } = await import(\"postgres\");\n const pgSql = pg(url);\n try {\n // Set up user-scoped temp views in production\n const scoping = await buildScopingPostgres(pgSql);\n for (const stmt of scoping.setup) {\n await pgSql.unsafe(stmt);\n }\n\n const pgSqlText = normalizePostgresSql(finalSql, sqlArgs);\n const result =\n sqlArgs.length > 0\n ? await pgSql.unsafe(pgSqlText, sqlArgs as any[])\n : await pgSql.unsafe(pgSqlText);\n const rows: Record<string, unknown>[] = Array.from(result);\n const keys = rows.length > 0 ? Object.keys(rows[0]) : [];\n\n printTable(\n rows.length > 0 ? rows : keys.length > 0 ? rows : [],\n pgSqlText,\n parsed.format,\n );\n\n // Tear down temp views\n for (const stmt of scoping.teardown) {\n await pgSql.unsafe(stmt).catch(() => {});\n }\n } finally {\n await pgSql.end();\n }\n return;\n }\n\n // libsql / SQLite path\n const client = createClient({\n url,\n authToken: getDatabaseAuthToken(),\n });\n\n try {\n // Set up user-scoped temp views in production\n const scoping = await buildScopingSqlite(client);\n for (const stmt of scoping.setup) {\n await client.execute(stmt);\n }\n\n const result =\n sqlArgs.length > 0\n ? await client.execute({ sql: finalSql, args: sqlArgs as any[] })\n : await client.execute(finalSql);\n const rows: Record<string, unknown>[] = result.rows.map((row) => {\n const obj: Record<string, unknown> = {};\n for (let i = 0; i < result.columns.length; i++) {\n obj[result.columns[i]] = row[i];\n }\n return obj;\n });\n\n printTable(rows, finalSql, parsed.format);\n\n // Tear down temp views\n for (const stmt of scoping.teardown) {\n await client.execute(stmt).catch(() => {});\n }\n } finally {\n client.close();\n }\n}\n"]}
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../../../src/scripts/db/query.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Core script: db-query\n *\n * Run a read-only SQL query against a SQLite or Postgres database.\n *\n * In production mode, temporary views are created to scope data to the\n * current user (AGENT_USER_EMAIL). Tables with an `owner_email` column\n * and core tables (settings, application_state, etc.) are automatically\n * filtered so queries only return the current user's data.\n *\n * Usage:\n * pnpm action db-query --sql \"SELECT * FROM forms WHERE id = ?\" [--args '[\"abc\"]'] [--db path] [--format json] [--limit N]\n */\n\nimport path from \"path\";\nimport { createClient } from \"@libsql/client\";\nimport { getDatabaseUrl, getDatabaseAuthToken } from \"../../db/client.js\";\nimport { parseArgs, fail } from \"../utils.js\";\nimport { assertNoSensitiveFrameworkTables } from \"./safety.js\";\nimport { buildScopingPostgres, buildScopingSqlite } from \"./scoping.js\";\n\nfunction isPostgresUrl(url: string): boolean {\n return url.startsWith(\"postgres://\") || url.startsWith(\"postgresql://\");\n}\n\nfunction parseSqlArgs(raw: string | undefined): unknown[] {\n if (!raw) return [];\n try {\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed)) return parsed;\n } catch {\n // Fall through to the shared error below.\n }\n fail(\"--args must be a JSON array\");\n}\n\nfunction convertQuestionMarksToPostgresParams(sql: string): string {\n let index = 0;\n let out = \"\";\n let state: \"normal\" | \"single\" | \"double\" | \"line-comment\" | \"block-comment\" =\n \"normal\";\n\n for (let i = 0; i < sql.length; i++) {\n const ch = sql[i];\n const next = sql[i + 1];\n\n if (state === \"line-comment\") {\n out += ch;\n if (ch === \"\\n\") state = \"normal\";\n continue;\n }\n\n if (state === \"block-comment\") {\n out += ch;\n if (ch === \"*\" && next === \"/\") {\n out += next;\n i++;\n state = \"normal\";\n }\n continue;\n }\n\n if (state === \"single\") {\n out += ch;\n if (ch === \"'\" && next === \"'\") {\n out += next;\n i++;\n } else if (ch === \"'\") {\n state = \"normal\";\n }\n continue;\n }\n\n if (state === \"double\") {\n out += ch;\n if (ch === '\"' && next === '\"') {\n out += next;\n i++;\n } else if (ch === '\"') {\n state = \"normal\";\n }\n continue;\n }\n\n if (ch === \"-\" && next === \"-\") {\n out += ch + next;\n i++;\n state = \"line-comment\";\n continue;\n }\n if (ch === \"/\" && next === \"*\") {\n out += ch + next;\n i++;\n state = \"block-comment\";\n continue;\n }\n if (ch === \"'\") {\n out += ch;\n state = \"single\";\n continue;\n }\n if (ch === '\"') {\n out += ch;\n state = \"double\";\n continue;\n }\n if (ch === \"?\") {\n index++;\n out += `$${index}`;\n continue;\n }\n out += ch;\n }\n\n return out;\n}\n\nfunction normalizePostgresSql(sql: string, args: unknown[]): string {\n if (args.length === 0 || /\\$\\d+\\b/.test(sql)) return sql;\n return convertQuestionMarksToPostgresParams(sql);\n}\n\nfunction printTable(\n rows: Record<string, unknown>[],\n finalSql: string,\n format?: string,\n) {\n if (format === \"json\") {\n console.log(\n JSON.stringify({ query: finalSql, rows, count: rows.length }, null, 2),\n );\n return;\n }\n\n console.log(`Query: ${finalSql}`);\n console.log(`Rows: ${rows.length}\\n`);\n\n if (rows.length === 0) {\n console.log(\"(no results)\");\n return;\n }\n\n const keys = Object.keys(rows[0]);\n const widths = keys.map((k) => {\n const maxVal = Math.max(...rows.map((r) => String(r[k] ?? \"NULL\").length));\n return Math.max(k.length, Math.min(maxVal, 60));\n });\n\n const header = keys.map((k, i) => k.padEnd(widths[i])).join(\" | \");\n console.log(header);\n console.log(widths.map((w) => \"-\".repeat(w)).join(\"-+-\"));\n\n for (const row of rows) {\n const line = keys\n .map((k, i) => {\n const val = String(row[k] ?? \"NULL\");\n return val.length > 60\n ? val.slice(0, 57) + \"...\"\n : val.padEnd(widths[i]);\n })\n .join(\" | \");\n console.log(line);\n }\n}\n\nexport default async function dbQuery(args: string[]): Promise<void> {\n const parsed = parseArgs(args);\n\n if (parsed.help === \"true\") {\n console.log(`Usage: pnpm action db-query --sql \"<query>\" [options]\n\nOptions:\n --sql <query> SQL SELECT query to run (required)\n --args <json> JSON array of positional SQL bind parameters\n --db <path> Path to SQLite database (default: data/app.db)\n --format json Output as JSON instead of a table\n --limit N Append LIMIT N if not already present\n --help Show this help message`);\n return;\n }\n\n const sql = parsed.sql;\n if (!sql) {\n fail('--sql is required. Example: --sql \"SELECT * FROM forms\"');\n }\n const sqlArgs = parseSqlArgs(parsed.args);\n\n // Safety: only allow read-only statements.\n // Strip leading SQL comments before checking the prefix.\n const stripped = sql\n .replace(/^\\s*--[^\\n]*\\n/gm, \"\")\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\")\n .trim();\n const upper = stripped.toUpperCase();\n if (\n !upper.startsWith(\"SELECT\") &&\n !upper.startsWith(\"WITH\") &&\n !upper.startsWith(\"EXPLAIN\") &&\n !upper.startsWith(\"PRAGMA\")\n ) {\n fail(\n \"Only SELECT, WITH, EXPLAIN, and PRAGMA queries are allowed. Use db-exec for writes.\",\n );\n }\n assertNoSensitiveFrameworkTables(stripped, \"read\");\n\n // Resolve database URL: --db flag → DATABASE_URL env → default file path\n let url: string;\n if (parsed.db) {\n url = \"file:\" + path.resolve(parsed.db);\n } else if (getDatabaseUrl()) {\n url = getDatabaseUrl();\n } else {\n url = \"file:\" + path.resolve(process.cwd(), \"data\", \"app.db\");\n }\n\n let finalSql = sql;\n if (\n parsed.limit &&\n (upper.startsWith(\"SELECT\") || upper.startsWith(\"WITH\")) &&\n !/\\bLIMIT\\b/i.test(stripped)\n ) {\n const limitVal = parseInt(parsed.limit, 10);\n if (isNaN(limitVal) || limitVal < 1)\n fail(\"--limit must be a positive integer\");\n finalSql = `${sql} LIMIT ${limitVal}`;\n }\n\n // Postgres path\n if (isPostgresUrl(url)) {\n const { default: pg } = await import(\"postgres\");\n const pgSql = pg(url);\n try {\n const pgSqlText = normalizePostgresSql(finalSql, sqlArgs);\n let rows: Record<string, unknown>[] = [];\n await pgSql.begin(async (tx: any) => {\n // Temp views are session state. Keep setup/query/teardown on one\n // transaction-bound backend so pooled Postgres never retains them.\n const scoping = await buildScopingPostgres(tx);\n try {\n for (const stmt of scoping.setup) {\n await tx.unsafe(stmt);\n }\n\n const result =\n sqlArgs.length > 0\n ? await tx.unsafe(pgSqlText, sqlArgs as any[])\n : await tx.unsafe(pgSqlText);\n rows = Array.from(result);\n } finally {\n for (const stmt of scoping.teardown) {\n await tx.unsafe(stmt).catch(() => {});\n }\n }\n });\n const keys = rows.length > 0 ? Object.keys(rows[0]) : [];\n\n printTable(\n rows.length > 0 ? rows : keys.length > 0 ? rows : [],\n pgSqlText,\n parsed.format,\n );\n } finally {\n await pgSql.end();\n }\n return;\n }\n\n // libsql / SQLite path\n const client = createClient({\n url,\n authToken: getDatabaseAuthToken(),\n });\n\n try {\n // Set up user-scoped temp views in production\n const scoping = await buildScopingSqlite(client);\n for (const stmt of scoping.setup) {\n await client.execute(stmt);\n }\n\n const result =\n sqlArgs.length > 0\n ? await client.execute({ sql: finalSql, args: sqlArgs as any[] })\n : await client.execute(finalSql);\n const rows: Record<string, unknown>[] = result.rows.map((row) => {\n const obj: Record<string, unknown> = {};\n for (let i = 0; i < result.columns.length; i++) {\n obj[result.columns[i]] = row[i];\n }\n return obj;\n });\n\n printTable(rows, finalSql, parsed.format);\n\n // Tear down temp views\n for (const stmt of scoping.teardown) {\n await client.execute(stmt).catch(() => {});\n }\n } finally {\n client.close();\n }\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export declare function assertNoSensitiveFrameworkTables(sql: string, operation: "read" | "write" | "patch"): void;
2
+ //# sourceMappingURL=safety.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safety.d.ts","sourceRoot":"","sources":["../../../src/scripts/db/safety.ts"],"names":[],"mappings":"AA+DA,wBAAgB,gCAAgC,CAC9C,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,GACpC,IAAI,CAcN"}
@@ -0,0 +1,67 @@
1
+ import { fail } from "../utils.js";
2
+ // Credential and identity tables are deliberately off-limits to the generic
3
+ // agent DB tools. They contain OAuth tokens, encrypted API keys, sessions, or
4
+ // auth identity data; use the framework stores/actions instead.
5
+ const SENSITIVE_FRAMEWORK_TABLE_RE = /\b(app_secrets|oauth_tokens|user|users|session|sessions|account|accounts|verification|jwks|organization|member|invitation|org_members|org_invitations|pg_catalog|information_schema|pg_class|pg_proc|pg_namespace|pg_user|pg_roles|pg_authid|pg_shadow)\b/i;
6
+ function stripSqlNonIdentifiers(sql) {
7
+ let out = "";
8
+ let state = "normal";
9
+ for (let i = 0; i < sql.length; i++) {
10
+ const ch = sql[i];
11
+ const next = sql[i + 1];
12
+ if (state === "line-comment") {
13
+ if (ch === "\n") {
14
+ out += " ";
15
+ state = "normal";
16
+ }
17
+ continue;
18
+ }
19
+ if (state === "block-comment") {
20
+ if (ch === "*" && next === "/") {
21
+ i++;
22
+ out += " ";
23
+ state = "normal";
24
+ }
25
+ continue;
26
+ }
27
+ if (state === "single") {
28
+ if (ch === "'" && next === "'") {
29
+ i++;
30
+ }
31
+ else if (ch === "'") {
32
+ out += " ";
33
+ state = "normal";
34
+ }
35
+ continue;
36
+ }
37
+ if (ch === "-" && next === "-") {
38
+ i++;
39
+ state = "line-comment";
40
+ continue;
41
+ }
42
+ if (ch === "/" && next === "*") {
43
+ i++;
44
+ state = "block-comment";
45
+ continue;
46
+ }
47
+ if (ch === "'") {
48
+ state = "single";
49
+ continue;
50
+ }
51
+ out += ch;
52
+ }
53
+ return out;
54
+ }
55
+ export function assertNoSensitiveFrameworkTables(sql, operation) {
56
+ const cleanSql = stripSqlNonIdentifiers(sql);
57
+ const match = cleanSql.match(SENSITIVE_FRAMEWORK_TABLE_RE);
58
+ if (!match)
59
+ return;
60
+ const verb = operation === "read"
61
+ ? "readable"
62
+ : operation === "write"
63
+ ? "writable"
64
+ : "patchable";
65
+ fail(`Sensitive framework table "${match[1]}" is not ${verb} through raw DB tools. Use the framework auth, secrets, or OAuth APIs instead.`);
66
+ }
67
+ //# sourceMappingURL=safety.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safety.js","sourceRoot":"","sources":["../../../src/scripts/db/safety.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEnC,4EAA4E;AAC5E,8EAA8E;AAC9E,gEAAgE;AAChE,MAAM,4BAA4B,GAChC,4PAA4P,CAAC;AAE/P,SAAS,sBAAsB,CAAC,GAAW;IACzC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,KAAK,GAA2D,QAAQ,CAAC;IAE7E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QAClB,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAExB,IAAI,KAAK,KAAK,cAAc,EAAE,CAAC;YAC7B,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;gBAChB,GAAG,IAAI,GAAG,CAAC;gBACX,KAAK,GAAG,QAAQ,CAAC;YACnB,CAAC;YACD,SAAS;QACX,CAAC;QAED,IAAI,KAAK,KAAK,eAAe,EAAE,CAAC;YAC9B,IAAI,EAAE,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBAC/B,CAAC,EAAE,CAAC;gBACJ,GAAG,IAAI,GAAG,CAAC;gBACX,KAAK,GAAG,QAAQ,CAAC;YACnB,CAAC;YACD,SAAS;QACX,CAAC;QAED,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;YACvB,IAAI,EAAE,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBAC/B,CAAC,EAAE,CAAC;YACN,CAAC;iBAAM,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACtB,GAAG,IAAI,GAAG,CAAC;gBACX,KAAK,GAAG,QAAQ,CAAC;YACnB,CAAC;YACD,SAAS;QACX,CAAC;QAED,IAAI,EAAE,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YAC/B,CAAC,EAAE,CAAC;YACJ,KAAK,GAAG,cAAc,CAAC;YACvB,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YAC/B,CAAC,EAAE,CAAC;YACJ,KAAK,GAAG,eAAe,CAAC;YACxB,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,KAAK,GAAG,QAAQ,CAAC;YACjB,SAAS;QACX,CAAC;QACD,GAAG,IAAI,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,gCAAgC,CAC9C,GAAW,EACX,SAAqC;IAErC,MAAM,QAAQ,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAC3D,IAAI,CAAC,KAAK;QAAE,OAAO;IAEnB,MAAM,IAAI,GACR,SAAS,KAAK,MAAM;QAClB,CAAC,CAAC,UAAU;QACZ,CAAC,CAAC,SAAS,KAAK,OAAO;YACrB,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,WAAW,CAAC;IACpB,IAAI,CACF,8BAA8B,KAAK,CAAC,CAAC,CAAC,YAAY,IAAI,gFAAgF,CACvI,CAAC;AACJ,CAAC","sourcesContent":["import { fail } from \"../utils.js\";\n\n// Credential and identity tables are deliberately off-limits to the generic\n// agent DB tools. They contain OAuth tokens, encrypted API keys, sessions, or\n// auth identity data; use the framework stores/actions instead.\nconst SENSITIVE_FRAMEWORK_TABLE_RE =\n /\\b(app_secrets|oauth_tokens|user|users|session|sessions|account|accounts|verification|jwks|organization|member|invitation|org_members|org_invitations|pg_catalog|information_schema|pg_class|pg_proc|pg_namespace|pg_user|pg_roles|pg_authid|pg_shadow)\\b/i;\n\nfunction stripSqlNonIdentifiers(sql: string): string {\n let out = \"\";\n let state: \"normal\" | \"single\" | \"line-comment\" | \"block-comment\" = \"normal\";\n\n for (let i = 0; i < sql.length; i++) {\n const ch = sql[i];\n const next = sql[i + 1];\n\n if (state === \"line-comment\") {\n if (ch === \"\\n\") {\n out += \" \";\n state = \"normal\";\n }\n continue;\n }\n\n if (state === \"block-comment\") {\n if (ch === \"*\" && next === \"/\") {\n i++;\n out += \" \";\n state = \"normal\";\n }\n continue;\n }\n\n if (state === \"single\") {\n if (ch === \"'\" && next === \"'\") {\n i++;\n } else if (ch === \"'\") {\n out += \" \";\n state = \"normal\";\n }\n continue;\n }\n\n if (ch === \"-\" && next === \"-\") {\n i++;\n state = \"line-comment\";\n continue;\n }\n if (ch === \"/\" && next === \"*\") {\n i++;\n state = \"block-comment\";\n continue;\n }\n if (ch === \"'\") {\n state = \"single\";\n continue;\n }\n out += ch;\n }\n\n return out;\n}\n\nexport function assertNoSensitiveFrameworkTables(\n sql: string,\n operation: \"read\" | \"write\" | \"patch\",\n): void {\n const cleanSql = stripSqlNonIdentifiers(sql);\n const match = cleanSql.match(SENSITIVE_FRAMEWORK_TABLE_RE);\n if (!match) return;\n\n const verb =\n operation === \"read\"\n ? \"readable\"\n : operation === \"write\"\n ? \"writable\"\n : \"patchable\";\n fail(\n `Sensitive framework table \"${match[1]}\" is not ${verb} through raw DB tools. Use the framework auth, secrets, or OAuth APIs instead.`,\n );\n}\n"]}
@@ -106,7 +106,7 @@ function buildScopedTables(allColumns, userEmail, orgId, isPostgres) {
106
106
  }
107
107
  scoped.push({
108
108
  name: table,
109
- viewSql: `CREATE TEMPORARY VIEW "${table}" AS SELECT * FROM ${realTable} WHERE ${whereSql}${checkOption}`,
109
+ viewSql: `${isPostgres ? "CREATE OR REPLACE TEMPORARY" : "CREATE TEMPORARY"} VIEW "${table}" AS SELECT * FROM ${realTable} WHERE ${whereSql}${checkOption}`,
110
110
  });
111
111
  continue;
112
112
  }
@@ -120,7 +120,7 @@ function buildScopedTables(allColumns, userEmail, orgId, isPostgres) {
120
120
  : "";
121
121
  scoped.push({
122
122
  name: table,
123
- viewSql: `CREATE TEMPORARY VIEW "${table}" AS SELECT * FROM ${realTable} WHERE (("scope" = 'user' AND "${OWNER_COLUMN}" = '${safeEmail}')${orgClause})${checkOption}`,
123
+ viewSql: `${isPostgres ? "CREATE OR REPLACE TEMPORARY" : "CREATE TEMPORARY"} VIEW "${table}" AS SELECT * FROM ${realTable} WHERE (("scope" = 'user' AND "${OWNER_COLUMN}" = '${safeEmail}')${orgClause})${checkOption}`,
124
124
  });
125
125
  continue;
126
126
  }
@@ -138,7 +138,7 @@ function buildScopedTables(allColumns, userEmail, orgId, isPostgres) {
138
138
  const realTable = `${qualifiedPrefix}"${table}"`;
139
139
  scoped.push({
140
140
  name: table,
141
- viewSql: `CREATE TEMPORARY VIEW "${table}" AS SELECT * FROM ${realTable} WHERE ${clauses.join(" AND ")}${checkOption}`,
141
+ viewSql: `${isPostgres ? "CREATE OR REPLACE TEMPORARY" : "CREATE TEMPORARY"} VIEW "${table}" AS SELECT * FROM ${realTable} WHERE ${clauses.join(" AND ")}${checkOption}`,
142
142
  });
143
143
  }
144
144
  }
@@ -184,7 +184,7 @@ export async function buildScopingPostgres(pgSql) {
184
184
  }
185
185
  return {
186
186
  setup: scoped.map((s) => s.viewSql),
187
- teardown: scoped.map((s) => `DROP VIEW IF EXISTS "${s.name}"`),
187
+ teardown: scoped.map((s) => `DROP VIEW IF EXISTS pg_temp."${s.name}"`),
188
188
  active: scoped.length > 0,
189
189
  userEmail,
190
190
  orgId,