@xopcai/xopc 0.0.101 → 0.0.102

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 (246) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/feishu/src/outbound/media-load.js +1 -1
  3. package/dist/extensions/feishu/src/workflow-progress.js +1 -1
  4. package/dist/extensions/telegram/src/adapters/streaming-adapter.js +1 -1
  5. package/dist/extensions/telegram/src/inbound-processor.js +1 -1
  6. package/dist/extensions/telegram/src/plugin.js +1 -1
  7. package/dist/extensions/telegram/src/routing-integration.js +2 -2
  8. package/dist/extensions/telegram/src/update-offset-store.js +1 -1
  9. package/dist/extensions/telegram/src/workflow-progress.js +1 -1
  10. package/dist/extensions/telegram/xopc.extension.json +1 -1
  11. package/dist/extensions/weixin/src/api/api.js +2 -2
  12. package/dist/extensions/weixin/src/auth/accounts.js +1 -1
  13. package/dist/extensions/weixin/src/cdn/upload.js +1 -1
  14. package/dist/extensions/weixin/src/media/data-url.js +1 -1
  15. package/dist/extensions/weixin/src/messaging/debug-mode.js +1 -1
  16. package/dist/extensions/weixin/src/messaging/inbound.js +1 -1
  17. package/dist/extensions/weixin/src/messaging/process-message.js +1 -1
  18. package/dist/extensions/weixin/src/plugin.js +1 -1
  19. package/dist/extensions/weixin/src/storage/sync-buf.js +1 -1
  20. package/dist/extensions/weixin/src/workflow-progress.js +1 -1
  21. package/dist/gateway/static/root/assets/{agents-u8cmUfGQ.js → agents-DQ0mQxIA.js} +1 -1
  22. package/dist/gateway/static/root/assets/{apps-page-DF8m9cUE.js → apps-page-Uv4kOmDE.js} +1 -1
  23. package/dist/gateway/static/root/assets/{channels-settings-BM3doRvi.js → channels-settings-BEtP53V6.js} +1 -1
  24. package/dist/gateway/static/root/assets/{channels-status-swr-ah4zWEIP.js → channels-status-swr-CxTtS1hI.js} +1 -1
  25. package/dist/gateway/static/root/assets/{cron-api-DgMuL_2b.js → cron-api-CznlPIZT.js} +1 -1
  26. package/dist/gateway/static/root/assets/{cron-page-COfUPKfq.js → cron-page-Dx6dE3eD.js} +1 -1
  27. package/dist/gateway/static/root/assets/{dist-Dzy3Q0ZH.js → dist-DH4IuRV0.js} +1 -1
  28. package/dist/gateway/static/root/assets/{extension-debug-page-kyxxefKh.js → extension-debug-page-BNkhvRQW.js} +1 -1
  29. package/dist/gateway/static/root/assets/{extension-page-BVbCBHoI.js → extension-page-CcIGQah2.js} +1 -1
  30. package/dist/gateway/static/root/assets/{extension-settings-page-t1lb2A7v.js → extension-settings-page-DuDCSb3J.js} +1 -1
  31. package/dist/gateway/static/root/assets/{field-primitives-6Vo1Yga4.js → field-primitives-BUwUiDf5.js} +1 -1
  32. package/dist/gateway/static/root/assets/{heartbeat-config-api-BF3psu4O.js → heartbeat-config-api-BjFW1cl-.js} +1 -1
  33. package/dist/gateway/static/root/assets/{index-WAyKAilR.js → index-DKgHC1Ta.js} +4 -4
  34. package/dist/gateway/static/root/assets/{logs-page-BTVPXcHV.js → logs-page-CiG0m81R.js} +1 -1
  35. package/dist/gateway/static/root/assets/{note-detail-page-DqSAg_5Z.js → note-detail-page-DG3DVFTf.js} +2 -2
  36. package/dist/gateway/static/root/assets/{note-time-FSOjchic.js → note-time-DjlllCF3.js} +1 -1
  37. package/dist/gateway/static/root/assets/{notes-page-DkRX7BE2.js → notes-page-D40ZpS0Y.js} +1 -1
  38. package/dist/gateway/static/root/assets/{sessions-page-Khdw35Zk.js → sessions-page-BvtdbmW8.js} +1 -1
  39. package/dist/gateway/static/root/assets/{settings-advanced-gate-DspC8oEm.js → settings-advanced-gate-BQamRjkV.js} +1 -1
  40. package/dist/gateway/static/root/assets/{settings-form-section-DuxvUuur.js → settings-form-section-C3_sEFfD.js} +1 -1
  41. package/dist/gateway/static/root/assets/{settings-page-DN8JBZ8F.js → settings-page-D972_jaS.js} +1 -1
  42. package/dist/gateway/static/root/assets/{share-preview-page-UNAcbiMb.js → share-preview-page-CKCTMo0_.js} +1 -1
  43. package/dist/gateway/static/root/assets/{skills-page-ByYT0fXZ.js → skills-page-QwFCB4yV.js} +1 -1
  44. package/dist/gateway/static/root/assets/{utils-DK_1H47k.js → utils-DCm11_KW.js} +1 -1
  45. package/dist/gateway/static/root/assets/{voice-api-key-field-BYN2EIMs.js → voice-api-key-field-CywiB7Dw.js} +1 -1
  46. package/dist/gateway/static/root/assets/{workflow-page.utils-Bi19YNTr.js → workflow-page.utils-g57BRfEd.js} +1 -1
  47. package/dist/gateway/static/root/assets/{workflows-page-DP57FEKW.js → workflows-page-Be7l_SWw.js} +1 -1
  48. package/dist/gateway/static/root/index.html +1 -1
  49. package/dist/package.js +1 -1
  50. package/dist/src/agent/agent-manager.js +7 -7
  51. package/dist/src/agent/agent-scope.js +1 -1
  52. package/dist/src/agent/bootstrap/load-bootstrap-files.js +1 -1
  53. package/dist/src/agent/context/workspace-seed.js +2 -2
  54. package/dist/src/agent/context/workspace-state.js +1 -1
  55. package/dist/src/agent/goals/goal-run-store.js +4 -4
  56. package/dist/src/agent/goals/persistent-goal-service.js +1 -1
  57. package/dist/src/agent/goals/post-turn.js +2 -2
  58. package/dist/src/agent/image/load-image-media.js +2 -2
  59. package/dist/src/agent/ipc/bus.js +1 -1
  60. package/dist/src/agent/ipc/inbox.js +2 -2
  61. package/dist/src/agent/ipc/socket.js +1 -1
  62. package/dist/src/agent/mcp/bundle-mcp-materialize.js +1 -1
  63. package/dist/src/agent/mcp/bundle-mcp-runtime.js +1 -1
  64. package/dist/src/agent/mcp/mcp-transport-config.js +1 -1
  65. package/dist/src/agent/mcp/mcp-transport.js +1 -1
  66. package/dist/src/agent/memory/builtin-memory-store.js +1 -1
  67. package/dist/src/agent/memory/dreaming/deep-promotion.js +1 -1
  68. package/dist/src/agent/memory/dreaming/events.js +1 -1
  69. package/dist/src/agent/memory/dreaming/last-run.js +1 -1
  70. package/dist/src/agent/memory/dreaming/light-sweep.js +1 -1
  71. package/dist/src/agent/memory/dreaming/preview.js +1 -1
  72. package/dist/src/agent/memory/dreaming/rem-patterns.js +1 -1
  73. package/dist/src/agent/memory/dreaming/short-term-store.js +1 -1
  74. package/dist/src/agent/memory/dreaming/utils.js +1 -1
  75. package/dist/src/agent/memory/memory-flush.js +1 -1
  76. package/dist/src/agent/memory/plugin-discovery.js +1 -1
  77. package/dist/src/agent/models/manager.js +1 -1
  78. package/dist/src/agent/prompt/service-prompt-builder.js +2 -2
  79. package/dist/src/agent/reply/post-compaction-context.js +1 -1
  80. package/dist/src/agent/reply/workspace-boundary-read.js +1 -1
  81. package/dist/src/agent/sandbox/path-policy.js +2 -2
  82. package/dist/src/agent/service.js +4 -4
  83. package/dist/src/agent/session/session-inspector.js +1 -1
  84. package/dist/src/agent/skills/config.js +1 -1
  85. package/dist/src/agent/skills/hub-hash.js +2 -2
  86. package/dist/src/agent/skills/hub-lock.js +1 -1
  87. package/dist/src/agent/skills/hub-pull.js +2 -2
  88. package/dist/src/agent/skills/index.js +1 -1
  89. package/dist/src/agent/skills/managed-store.js +1 -1
  90. package/dist/src/agent/skills/scanner.js +1 -1
  91. package/dist/src/agent/skills/skill-manage-ops.js +1 -1
  92. package/dist/src/agent/skills/skill-manager.js +1 -1
  93. package/dist/src/agent/tools/dreaming-tool.js +1 -1
  94. package/dist/src/agent/tools/factory.js +1 -1
  95. package/dist/src/agent/tools/image-generate-tool.js +1 -1
  96. package/dist/src/agent/tools/send-media.js +1 -1
  97. package/dist/src/agent/tools/skill-manage-tool.js +1 -1
  98. package/dist/src/agent/tools/workflow-tool.js +1 -1
  99. package/dist/src/agent/tools/write.js +1 -1
  100. package/dist/src/agent/workflow/catalog.js +1 -1
  101. package/dist/src/auth/credentials.js +3 -3
  102. package/dist/src/auth/profiles/store.js +1 -1
  103. package/dist/src/auth/sync-provider-auth.js +1 -1
  104. package/dist/src/browser/cache-dir-policy.js +1 -1
  105. package/dist/src/browser/cdp-local-launcher.js +2 -2
  106. package/dist/src/browser/providers/browser-ext-install.js +3 -3
  107. package/dist/src/browser/providers/cloakbrowser.js +4 -4
  108. package/dist/src/browser/providers/playwright-doctor.js +1 -1
  109. package/dist/src/browser/stealth.js +1 -1
  110. package/dist/src/channels/outbound/persist-store.js +1 -1
  111. package/dist/src/channels/pairing/allow-from-file.js +1 -1
  112. package/dist/src/channels/pairing/pairing-store.js +2 -2
  113. package/dist/src/chat-commands/agent-edit.js +2 -2
  114. package/dist/src/chat-commands/builtins/config.js +2 -2
  115. package/dist/src/chat-commands/context.js +1 -1
  116. package/dist/src/cli/commands/config.js +1 -1
  117. package/dist/src/cli/commands/doctor/checks/config-health.js +1 -1
  118. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  119. package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
  120. package/dist/src/cli/commands/doctor/checks/state-integrity.js +1 -1
  121. package/dist/src/cli/commands/doctor/checks/workspace-status.js +1 -1
  122. package/dist/src/cli/commands/extension-dev.js +1 -1
  123. package/dist/src/cli/commands/extension-marketplace.js +1 -1
  124. package/dist/src/cli/commands/extension-pack.js +1 -1
  125. package/dist/src/cli/commands/gateway/logs.js +1 -1
  126. package/dist/src/cli/commands/image.js +1 -1
  127. package/dist/src/cli/commands/init.js +3 -3
  128. package/dist/src/cli/utils/init-workspace-core.js +2 -2
  129. package/dist/src/commands/agents.config.js +1 -1
  130. package/dist/src/config/agent-profile.js +1 -1
  131. package/dist/src/config/gateway-bind.js +1 -1
  132. package/dist/src/config/index.js +5 -5
  133. package/dist/src/config/loader.js +2 -2
  134. package/dist/src/config/models-json.js +2 -2
  135. package/dist/src/config/paths-state.js +1 -1
  136. package/dist/src/config/profile.js +2 -2
  137. package/dist/src/config/workspace-path.js +1 -1
  138. package/dist/src/cron/executor.js +2 -2
  139. package/dist/src/cron/persistence.js +1 -1
  140. package/dist/src/daemon/constants.js +1 -1
  141. package/dist/src/daemon/install-plan.js +2 -2
  142. package/dist/src/daemon/launchd.js +2 -2
  143. package/dist/src/daemon/schtasks.js +2 -2
  144. package/dist/src/daemon/systemd.js +2 -2
  145. package/dist/src/extensions/bundle-mcp.js +1 -1
  146. package/dist/src/extensions/discover-extensions.js +1 -1
  147. package/dist/src/extensions/health.js +1 -1
  148. package/dist/src/extensions/loader.js +1 -1
  149. package/dist/src/extensions/lockfile.js +2 -2
  150. package/dist/src/extensions/update.js +1 -1
  151. package/dist/src/gateway/agents-admin.js +3 -3
  152. package/dist/src/gateway/file-path-classifier.js +2 -2
  153. package/dist/src/gateway/heartbeat/service.js +1 -1
  154. package/dist/src/gateway/hono/lib/config-payload.js +1 -1
  155. package/dist/src/gateway/hono/lib/extension-store.js +2 -2
  156. package/dist/src/gateway/hono/lib/static-ui.js +2 -2
  157. package/dist/src/gateway/hono/oauth-async.js +1 -1
  158. package/dist/src/gateway/hono/oauth.js +1 -1
  159. package/dist/src/gateway/hono/routes/agents.js +1 -1
  160. package/dist/src/gateway/hono/routes/auth-registry-extensions.js +1 -1
  161. package/dist/src/gateway/hono/routes/config-patch/misc.js +1 -1
  162. package/dist/src/gateway/hono/routes/dreaming.js +1 -1
  163. package/dist/src/gateway/hono/routes/host-fs.js +2 -2
  164. package/dist/src/gateway/hono/routes/models.js +1 -1
  165. package/dist/src/gateway/hono/routes/notes.js +9 -22
  166. package/dist/src/gateway/hono/routes/notes.js.map +1 -1
  167. package/dist/src/gateway/hono/routes/shares.js +1 -1
  168. package/dist/src/gateway/hono/routes/workspace.js +2 -2
  169. package/dist/src/gateway/lock.js +3 -3
  170. package/dist/src/gateway/ports.js +1 -1
  171. package/dist/src/gateway/service/agent-runner.js +2 -2
  172. package/dist/src/gateway/service/marketplace-service.js +2 -2
  173. package/dist/src/gateway/service.js +1 -1
  174. package/dist/src/gateway/workspace-fs-file-list.js +1 -1
  175. package/dist/src/heartbeat/index.js +1 -1
  176. package/dist/src/infra/brew.js +1 -1
  177. package/dist/src/infra/package-json.js +1 -1
  178. package/dist/src/infra/package-update-steps.js +1 -1
  179. package/dist/src/infra/path-env.js +2 -2
  180. package/dist/src/infra/restart.js +2 -2
  181. package/dist/src/infra/stable-node-path.js +1 -1
  182. package/dist/src/infra/update-check.js +1 -1
  183. package/dist/src/infra/update-global.js +1 -1
  184. package/dist/src/infra/update-lock.js +3 -3
  185. package/dist/src/infra/update-runner.js +1 -1
  186. package/dist/src/infra/update-startup.js +2 -2
  187. package/dist/src/infra/write-file-atomic.js +2 -2
  188. package/dist/src/media/store.js +1 -1
  189. package/dist/src/notes/note-attachment-sync.d.ts +2 -3
  190. package/dist/src/notes/note-attachment-sync.js +2 -27
  191. package/dist/src/notes/note-attachment-sync.js.map +1 -1
  192. package/dist/src/notes/note-index-meta.d.ts +6 -2
  193. package/dist/src/notes/note-index-meta.js +11 -16
  194. package/dist/src/notes/note-index-meta.js.map +1 -1
  195. package/dist/src/notes/note-markdown.d.ts +61 -0
  196. package/dist/src/notes/note-markdown.js +177 -0
  197. package/dist/src/notes/note-markdown.js.map +1 -0
  198. package/dist/src/notes/service.d.ts +9 -7
  199. package/dist/src/notes/service.js +163 -312
  200. package/dist/src/notes/service.js.map +1 -1
  201. package/dist/src/notes/store.js +4 -5
  202. package/dist/src/notes/store.js.map +1 -1
  203. package/dist/src/notes/types.d.ts +29 -58
  204. package/dist/src/providers/auth-runtime/auth-profile-store.js +1 -1
  205. package/dist/src/providers/index.js +2 -2
  206. package/dist/src/providers/model-registry.js +1 -1
  207. package/dist/src/session/init-session-turn.js +2 -2
  208. package/dist/src/session/resolve-session.js +3 -3
  209. package/dist/src/session/session-title.js +2 -2
  210. package/dist/src/session/store.js +2 -2
  211. package/dist/src/share/share-auto.js +2 -2
  212. package/dist/src/share/share-store.js +3 -3
  213. package/dist/src/share/share-thumbnail.js +2 -2
  214. package/dist/src/share/share-zip.js +1 -1
  215. package/dist/src/share/site-share-store.js +3 -3
  216. package/dist/src/share/site-static-serve.js +1 -1
  217. package/dist/src/storage/sqlite/connection.js +2 -2
  218. package/dist/src/storage/sqlite/memory-index-repository.js +2 -2
  219. package/dist/src/storage/sqlite/migrations/003_add_note_markdown_meta.sql +4 -0
  220. package/dist/src/storage/sqlite/migrations/discover.js +1 -1
  221. package/dist/src/storage/sqlite/migrations/runner.d.ts +2 -2
  222. package/dist/src/storage/sqlite/migrations/runner.js +5 -5
  223. package/dist/src/storage/sqlite/migrations/runner.js.map +1 -1
  224. package/dist/src/storage/sqlite/migrations/runner.ts +2 -2
  225. package/dist/src/storage/sqlite/notes-repository.js +18 -6
  226. package/dist/src/storage/sqlite/notes-repository.js.map +1 -1
  227. package/dist/src/storage/sqlite/schema.js +4 -4
  228. package/dist/src/storage/sqlite/schema.sql +5 -1
  229. package/dist/src/tui/clipboard-image.js +3 -3
  230. package/dist/src/tui/theme-manager.js +1 -1
  231. package/dist/src/tui/tui-keybindings-file.js +1 -1
  232. package/dist/src/tui/tui-scoped-models.js +2 -2
  233. package/dist/src/tui/tui-settings.js +1 -1
  234. package/dist/src/tui/tui.js +3 -3
  235. package/dist/src/tunnel/frpc-binary.js +3 -3
  236. package/dist/src/tunnel/frpc-config.js +1 -1
  237. package/dist/src/tunnel/frpc-extract.js +1 -1
  238. package/dist/src/tunnel/tunnel-state.js +1 -1
  239. package/dist/src/utils/logger/audit.js +1 -1
  240. package/dist/src/utils/logger/log-store.js +1 -1
  241. package/dist/src/utils/logger/rotation.js +1 -1
  242. package/dist/src/voice/tts/audio.js +1 -1
  243. package/dist/src/voice/tts/providers/edge-speech.js +2 -2
  244. package/dist/src/workflows/store/event-store.js +1 -1
  245. package/dist/src/workflows/store/run-store.js +1 -1
  246. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
- import { join } from "node:path";
2
1
  import { existsSync, readFileSync, readdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
3
  //#region src/storage/sqlite/migrations/discover.ts
4
4
  /** Matches `002_add_notes_index.sql` → target version 2. */
5
5
  const MIGRATION_FILENAME = /^(\d{3})_[\w-]+\.sql$/;
@@ -1,9 +1,9 @@
1
1
  import type { DatabaseSync } from 'node:sqlite';
2
2
  import type { ApplyMigrationsOptions } from './types.js';
3
3
  /** Baseline schema version applied from schema.sql on first open. */
4
- export declare const XOPC_DB_BASELINE_SCHEMA_VERSION = 2;
4
+ export declare const XOPC_DB_BASELINE_SCHEMA_VERSION = 3;
5
5
  /** Latest schema version this release supports (increment when adding migrations). */
6
- export declare const XOPC_DB_SCHEMA_VERSION = 2;
6
+ export declare const XOPC_DB_SCHEMA_VERSION = 3;
7
7
  export declare function resolveMigrationsDir(override?: string): string;
8
8
  /**
9
9
  * Apply sequential SQL migrations from (currentVersion + 1) through targetVersion.
@@ -3,16 +3,16 @@ import { init_logger } from "../../../utils/logger.js";
3
3
  import { readSchemaVersion, setSchemaVersion } from "../schema-version.js";
4
4
  import { discoverSqlMigrations } from "./discover.js";
5
5
  import { DatabaseSchemaMigrationGapError, DatabaseSchemaTooNewError } from "./errors.js";
6
- import { dirname, join } from "node:path";
7
6
  import { existsSync } from "node:fs";
7
+ import { dirname, join } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
9
  //#region src/storage/sqlite/migrations/runner.ts
10
10
  init_logger();
11
11
  const log = createLogger("Sqlite:Migrations");
12
12
  /** Baseline schema version applied from schema.sql on first open. */
13
- const XOPC_DB_BASELINE_SCHEMA_VERSION = 2;
13
+ const XOPC_DB_BASELINE_SCHEMA_VERSION = 3;
14
14
  /** Latest schema version this release supports (increment when adding migrations). */
15
- const XOPC_DB_SCHEMA_VERSION = 2;
15
+ const XOPC_DB_SCHEMA_VERSION = 3;
16
16
  const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
17
17
  function resolveMigrationsDir(override) {
18
18
  if (override) return override;
@@ -46,7 +46,7 @@ function applySingleMigration(db, migration) {
46
46
  * Each step runs in its own transaction and updates schema_meta on success.
47
47
  */
48
48
  function applyPendingMigrations(db, options = {}) {
49
- const targetVersion = options.targetVersion ?? 2;
49
+ const targetVersion = options.targetVersion ?? 3;
50
50
  let currentVersion = readSchemaVersion(db);
51
51
  if (currentVersion > targetVersion) throw new DatabaseSchemaTooNewError(currentVersion, targetVersion);
52
52
  if (currentVersion >= targetVersion) return currentVersion;
@@ -62,7 +62,7 @@ function applyPendingMigrations(db, options = {}) {
62
62
  }
63
63
  /** Inspect schema version without mutating the database. */
64
64
  function inspectSchemaMigrationStatus(db, options = {}) {
65
- const appVersion = options.targetVersion ?? 2;
65
+ const appVersion = options.targetVersion ?? 3;
66
66
  const dbVersion = readSchemaVersion(db);
67
67
  if (dbVersion > appVersion) return {
68
68
  dbVersion,
@@ -1 +1 @@
1
- {"version":3,"file":"runner.js","names":[],"sources":["../../../../../src/storage/sqlite/migrations/runner.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { DatabaseSync } from 'node:sqlite';\n\nimport { createLogger } from '../../../utils/logger.js';\nimport { readSchemaVersion, setSchemaVersion } from '../schema-version.js';\nimport { discoverSqlMigrations } from './discover.js';\nimport {\n DatabaseSchemaMigrationGapError,\n DatabaseSchemaTooNewError,\n} from './errors.js';\nimport type { ApplyMigrationsOptions, SqlMigration } from './types.js';\n\nconst log = createLogger('Sqlite:Migrations');\n\n/** Baseline schema version applied from schema.sql on first open. */\nexport const XOPC_DB_BASELINE_SCHEMA_VERSION = 2;\n\n/** Latest schema version this release supports (increment when adding migrations). */\nexport const XOPC_DB_SCHEMA_VERSION = 2;\n\nconst MODULE_DIR = dirname(fileURLToPath(import.meta.url));\n\nexport function resolveMigrationsDir(override?: string): string {\n if (override) {\n return override;\n }\n // Packaged Electron gateway bundle: `out/server/index.js` + `out/server/migrations/`.\n const siblingDir = join(MODULE_DIR, 'migrations');\n if (existsSync(siblingDir)) {\n return siblingDir;\n }\n // Dev / dist: SQL files live next to `migrations/runner.js`.\n return MODULE_DIR;\n}\n\nfunction migrationByTarget(\n migrations: SqlMigration[],\n targetVersion: number,\n): SqlMigration | undefined {\n return migrations.find((migration) => migration.targetVersion === targetVersion);\n}\n\nfunction applySingleMigration(db: DatabaseSync, migration: SqlMigration): void {\n log.info({ targetVersion: migration.targetVersion, file: migration.filename }, 'Applying SQLite migration');\n db.exec('BEGIN IMMEDIATE');\n try {\n db.exec(migration.sql);\n setSchemaVersion(db, migration.targetVersion);\n db.exec('COMMIT');\n } catch (error) {\n try {\n db.exec('ROLLBACK');\n } catch {\n /* preserve original error */\n }\n const em = error instanceof Error ? error.message : String(error);\n throw new Error(\n `SQLite migration to v${migration.targetVersion} (${migration.filename}) failed: ${em}`,\n { cause: error },\n );\n }\n}\n\n/**\n * Apply sequential SQL migrations from (currentVersion + 1) through targetVersion.\n * Each step runs in its own transaction and updates schema_meta on success.\n */\nexport function applyPendingMigrations(\n db: DatabaseSync,\n options: ApplyMigrationsOptions = {},\n): number {\n const targetVersion = options.targetVersion ?? XOPC_DB_SCHEMA_VERSION;\n let currentVersion = readSchemaVersion(db);\n\n if (currentVersion > targetVersion) {\n throw new DatabaseSchemaTooNewError(currentVersion, targetVersion);\n }\n\n if (currentVersion >= targetVersion) {\n return currentVersion;\n }\n\n const migrations = discoverSqlMigrations(resolveMigrationsDir(options.migrationsDir));\n\n while (currentVersion < targetVersion) {\n const nextVersion = currentVersion + 1;\n const migration = migrationByTarget(migrations, nextVersion);\n if (!migration) {\n throw new DatabaseSchemaMigrationGapError(currentVersion, targetVersion, nextVersion);\n }\n applySingleMigration(db, migration);\n currentVersion = nextVersion;\n }\n\n return currentVersion;\n}\n\nexport type SchemaMigrationStatus = {\n dbVersion: number;\n appVersion: number;\n pendingVersions: number[];\n isTooNew: boolean;\n hasMigrationGap: boolean;\n missingVersion: number | null;\n};\n\n/** Inspect schema version without mutating the database. */\nexport function inspectSchemaMigrationStatus(\n db: DatabaseSync,\n options: Pick<ApplyMigrationsOptions, 'migrationsDir' | 'targetVersion'> = {},\n): SchemaMigrationStatus {\n const appVersion = options.targetVersion ?? XOPC_DB_SCHEMA_VERSION;\n const dbVersion = readSchemaVersion(db);\n const isTooNew = dbVersion > appVersion;\n\n if (isTooNew) {\n return {\n dbVersion,\n appVersion,\n pendingVersions: [],\n isTooNew: true,\n hasMigrationGap: false,\n missingVersion: null,\n };\n }\n\n const migrations = discoverSqlMigrations(resolveMigrationsDir(options.migrationsDir));\n const pendingVersions: number[] = [];\n let hasMigrationGap = false;\n let missingVersion: number | null = null;\n\n for (let version = dbVersion + 1; version <= appVersion; version++) {\n if (migrationByTarget(migrations, version)) {\n pendingVersions.push(version);\n } else {\n hasMigrationGap = true;\n missingVersion = version;\n break;\n }\n }\n\n return {\n dbVersion,\n appVersion,\n pendingVersions,\n isTooNew: false,\n hasMigrationGap,\n missingVersion,\n };\n}\n"],"mappings":";;;;;;;;;aAMwD;AASxD,MAAM,MAAM,aAAa,oBAAoB;;AAG7C,MAAa,kCAAkC;;AAG/C,MAAa,yBAAyB;AAEtC,MAAM,aAAa,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAE1D,SAAgB,qBAAqB,UAA2B;AAC9D,KAAI,SACF,QAAO;CAGT,MAAM,aAAa,KAAK,YAAY,aAAa;AACjD,KAAI,WAAW,WAAW,CACxB,QAAO;AAGT,QAAO;;AAGT,SAAS,kBACP,YACA,eAC0B;AAC1B,QAAO,WAAW,MAAM,cAAc,UAAU,kBAAkB,cAAc;;AAGlF,SAAS,qBAAqB,IAAkB,WAA+B;AAC7E,KAAI,KAAK;EAAE,eAAe,UAAU;EAAe,MAAM,UAAU;EAAU,EAAE,4BAA4B;AAC3G,IAAG,KAAK,kBAAkB;AAC1B,KAAI;AACF,KAAG,KAAK,UAAU,IAAI;AACtB,mBAAiB,IAAI,UAAU,cAAc;AAC7C,KAAG,KAAK,SAAS;UACV,OAAO;AACd,MAAI;AACF,MAAG,KAAK,WAAW;UACb;EAGR,MAAM,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACjE,QAAM,IAAI,MACR,wBAAwB,UAAU,cAAc,IAAI,UAAU,SAAS,YAAY,MACnF,EAAE,OAAO,OAAO,CACjB;;;;;;;AAQL,SAAgB,uBACd,IACA,UAAkC,EAAE,EAC5B;CACR,MAAM,gBAAgB,QAAQ,iBAAA;CAC9B,IAAI,iBAAiB,kBAAkB,GAAG;AAE1C,KAAI,iBAAiB,cACnB,OAAM,IAAI,0BAA0B,gBAAgB,cAAc;AAGpE,KAAI,kBAAkB,cACpB,QAAO;CAGT,MAAM,aAAa,sBAAsB,qBAAqB,QAAQ,cAAc,CAAC;AAErF,QAAO,iBAAiB,eAAe;EACrC,MAAM,cAAc,iBAAiB;EACrC,MAAM,YAAY,kBAAkB,YAAY,YAAY;AAC5D,MAAI,CAAC,UACH,OAAM,IAAI,gCAAgC,gBAAgB,eAAe,YAAY;AAEvF,uBAAqB,IAAI,UAAU;AACnC,mBAAiB;;AAGnB,QAAO;;;AAaT,SAAgB,6BACd,IACA,UAA2E,EAAE,EACtD;CACvB,MAAM,aAAa,QAAQ,iBAAA;CAC3B,MAAM,YAAY,kBAAkB,GAAG;AAGvC,KAFiB,YAAY,WAG3B,QAAO;EACL;EACA;EACA,iBAAiB,EAAE;EACnB,UAAU;EACV,iBAAiB;EACjB,gBAAgB;EACjB;CAGH,MAAM,aAAa,sBAAsB,qBAAqB,QAAQ,cAAc,CAAC;CACrF,MAAM,kBAA4B,EAAE;CACpC,IAAI,kBAAkB;CACtB,IAAI,iBAAgC;AAEpC,MAAK,IAAI,UAAU,YAAY,GAAG,WAAW,YAAY,UACvD,KAAI,kBAAkB,YAAY,QAAQ,CACxC,iBAAgB,KAAK,QAAQ;MACxB;AACL,oBAAkB;AAClB,mBAAiB;AACjB;;AAIJ,QAAO;EACL;EACA;EACA;EACA,UAAU;EACV;EACA;EACD"}
1
+ {"version":3,"file":"runner.js","names":[],"sources":["../../../../../src/storage/sqlite/migrations/runner.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { DatabaseSync } from 'node:sqlite';\n\nimport { createLogger } from '../../../utils/logger.js';\nimport { readSchemaVersion, setSchemaVersion } from '../schema-version.js';\nimport { discoverSqlMigrations } from './discover.js';\nimport {\n DatabaseSchemaMigrationGapError,\n DatabaseSchemaTooNewError,\n} from './errors.js';\nimport type { ApplyMigrationsOptions, SqlMigration } from './types.js';\n\nconst log = createLogger('Sqlite:Migrations');\n\n/** Baseline schema version applied from schema.sql on first open. */\nexport const XOPC_DB_BASELINE_SCHEMA_VERSION = 3;\n\n/** Latest schema version this release supports (increment when adding migrations). */\nexport const XOPC_DB_SCHEMA_VERSION = 3;\n\nconst MODULE_DIR = dirname(fileURLToPath(import.meta.url));\n\nexport function resolveMigrationsDir(override?: string): string {\n if (override) {\n return override;\n }\n // Packaged Electron gateway bundle: `out/server/index.js` + `out/server/migrations/`.\n const siblingDir = join(MODULE_DIR, 'migrations');\n if (existsSync(siblingDir)) {\n return siblingDir;\n }\n // Dev / dist: SQL files live next to `migrations/runner.js`.\n return MODULE_DIR;\n}\n\nfunction migrationByTarget(\n migrations: SqlMigration[],\n targetVersion: number,\n): SqlMigration | undefined {\n return migrations.find((migration) => migration.targetVersion === targetVersion);\n}\n\nfunction applySingleMigration(db: DatabaseSync, migration: SqlMigration): void {\n log.info({ targetVersion: migration.targetVersion, file: migration.filename }, 'Applying SQLite migration');\n db.exec('BEGIN IMMEDIATE');\n try {\n db.exec(migration.sql);\n setSchemaVersion(db, migration.targetVersion);\n db.exec('COMMIT');\n } catch (error) {\n try {\n db.exec('ROLLBACK');\n } catch {\n /* preserve original error */\n }\n const em = error instanceof Error ? error.message : String(error);\n throw new Error(\n `SQLite migration to v${migration.targetVersion} (${migration.filename}) failed: ${em}`,\n { cause: error },\n );\n }\n}\n\n/**\n * Apply sequential SQL migrations from (currentVersion + 1) through targetVersion.\n * Each step runs in its own transaction and updates schema_meta on success.\n */\nexport function applyPendingMigrations(\n db: DatabaseSync,\n options: ApplyMigrationsOptions = {},\n): number {\n const targetVersion = options.targetVersion ?? XOPC_DB_SCHEMA_VERSION;\n let currentVersion = readSchemaVersion(db);\n\n if (currentVersion > targetVersion) {\n throw new DatabaseSchemaTooNewError(currentVersion, targetVersion);\n }\n\n if (currentVersion >= targetVersion) {\n return currentVersion;\n }\n\n const migrations = discoverSqlMigrations(resolveMigrationsDir(options.migrationsDir));\n\n while (currentVersion < targetVersion) {\n const nextVersion = currentVersion + 1;\n const migration = migrationByTarget(migrations, nextVersion);\n if (!migration) {\n throw new DatabaseSchemaMigrationGapError(currentVersion, targetVersion, nextVersion);\n }\n applySingleMigration(db, migration);\n currentVersion = nextVersion;\n }\n\n return currentVersion;\n}\n\nexport type SchemaMigrationStatus = {\n dbVersion: number;\n appVersion: number;\n pendingVersions: number[];\n isTooNew: boolean;\n hasMigrationGap: boolean;\n missingVersion: number | null;\n};\n\n/** Inspect schema version without mutating the database. */\nexport function inspectSchemaMigrationStatus(\n db: DatabaseSync,\n options: Pick<ApplyMigrationsOptions, 'migrationsDir' | 'targetVersion'> = {},\n): SchemaMigrationStatus {\n const appVersion = options.targetVersion ?? XOPC_DB_SCHEMA_VERSION;\n const dbVersion = readSchemaVersion(db);\n const isTooNew = dbVersion > appVersion;\n\n if (isTooNew) {\n return {\n dbVersion,\n appVersion,\n pendingVersions: [],\n isTooNew: true,\n hasMigrationGap: false,\n missingVersion: null,\n };\n }\n\n const migrations = discoverSqlMigrations(resolveMigrationsDir(options.migrationsDir));\n const pendingVersions: number[] = [];\n let hasMigrationGap = false;\n let missingVersion: number | null = null;\n\n for (let version = dbVersion + 1; version <= appVersion; version++) {\n if (migrationByTarget(migrations, version)) {\n pendingVersions.push(version);\n } else {\n hasMigrationGap = true;\n missingVersion = version;\n break;\n }\n }\n\n return {\n dbVersion,\n appVersion,\n pendingVersions,\n isTooNew: false,\n hasMigrationGap,\n missingVersion,\n };\n}\n"],"mappings":";;;;;;;;;aAMwD;AASxD,MAAM,MAAM,aAAa,oBAAoB;;AAG7C,MAAa,kCAAkC;;AAG/C,MAAa,yBAAyB;AAEtC,MAAM,aAAa,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAE1D,SAAgB,qBAAqB,UAA2B;AAC9D,KAAI,SACF,QAAO;CAGT,MAAM,aAAa,KAAK,YAAY,aAAa;AACjD,KAAI,WAAW,WAAW,CACxB,QAAO;AAGT,QAAO;;AAGT,SAAS,kBACP,YACA,eAC0B;AAC1B,QAAO,WAAW,MAAM,cAAc,UAAU,kBAAkB,cAAc;;AAGlF,SAAS,qBAAqB,IAAkB,WAA+B;AAC7E,KAAI,KAAK;EAAE,eAAe,UAAU;EAAe,MAAM,UAAU;EAAU,EAAE,4BAA4B;AAC3G,IAAG,KAAK,kBAAkB;AAC1B,KAAI;AACF,KAAG,KAAK,UAAU,IAAI;AACtB,mBAAiB,IAAI,UAAU,cAAc;AAC7C,KAAG,KAAK,SAAS;UACV,OAAO;AACd,MAAI;AACF,MAAG,KAAK,WAAW;UACb;EAGR,MAAM,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACjE,QAAM,IAAI,MACR,wBAAwB,UAAU,cAAc,IAAI,UAAU,SAAS,YAAY,MACnF,EAAE,OAAO,OAAO,CACjB;;;;;;;AAQL,SAAgB,uBACd,IACA,UAAkC,EAAE,EAC5B;CACR,MAAM,gBAAgB,QAAQ,iBAAA;CAC9B,IAAI,iBAAiB,kBAAkB,GAAG;AAE1C,KAAI,iBAAiB,cACnB,OAAM,IAAI,0BAA0B,gBAAgB,cAAc;AAGpE,KAAI,kBAAkB,cACpB,QAAO;CAGT,MAAM,aAAa,sBAAsB,qBAAqB,QAAQ,cAAc,CAAC;AAErF,QAAO,iBAAiB,eAAe;EACrC,MAAM,cAAc,iBAAiB;EACrC,MAAM,YAAY,kBAAkB,YAAY,YAAY;AAC5D,MAAI,CAAC,UACH,OAAM,IAAI,gCAAgC,gBAAgB,eAAe,YAAY;AAEvF,uBAAqB,IAAI,UAAU;AACnC,mBAAiB;;AAGnB,QAAO;;;AAaT,SAAgB,6BACd,IACA,UAA2E,EAAE,EACtD;CACvB,MAAM,aAAa,QAAQ,iBAAA;CAC3B,MAAM,YAAY,kBAAkB,GAAG;AAGvC,KAFiB,YAAY,WAG3B,QAAO;EACL;EACA;EACA,iBAAiB,EAAE;EACnB,UAAU;EACV,iBAAiB;EACjB,gBAAgB;EACjB;CAGH,MAAM,aAAa,sBAAsB,qBAAqB,QAAQ,cAAc,CAAC;CACrF,MAAM,kBAA4B,EAAE;CACpC,IAAI,kBAAkB;CACtB,IAAI,iBAAgC;AAEpC,MAAK,IAAI,UAAU,YAAY,GAAG,WAAW,YAAY,UACvD,KAAI,kBAAkB,YAAY,QAAQ,CACxC,iBAAgB,KAAK,QAAQ;MACxB;AACL,oBAAkB;AAClB,mBAAiB;AACjB;;AAIJ,QAAO;EACL;EACA;EACA;EACA,UAAU;EACV;EACA;EACD"}
@@ -16,10 +16,10 @@ import type { ApplyMigrationsOptions, SqlMigration } from './types.js';
16
16
  const log = createLogger('Sqlite:Migrations');
17
17
 
18
18
  /** Baseline schema version applied from schema.sql on first open. */
19
- export const XOPC_DB_BASELINE_SCHEMA_VERSION = 2;
19
+ export const XOPC_DB_BASELINE_SCHEMA_VERSION = 3;
20
20
 
21
21
  /** Latest schema version this release supports (increment when adding migrations). */
22
- export const XOPC_DB_SCHEMA_VERSION = 2;
22
+ export const XOPC_DB_SCHEMA_VERSION = 3;
23
23
 
24
24
  const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
25
25
 
@@ -37,7 +37,11 @@ function rowToIndexEntry(row) {
37
37
  groupId: row.group_id ?? void 0,
38
38
  lastOpenedAt: row.last_opened_at ?? void 0,
39
39
  taskDone: row.task_done != null ? Boolean(row.task_done) : void 0,
40
- taskDueAt: row.task_due_at ?? void 0
40
+ taskDueAt: row.task_due_at ?? void 0,
41
+ headingCount: row.heading_count ?? void 0,
42
+ taskCount: row.task_count ?? void 0,
43
+ uncheckedTaskCount: row.unchecked_task_count ?? void 0,
44
+ linkCount: row.link_count ?? void 0
41
45
  };
42
46
  }
43
47
  function noteSearchContent(note) {
@@ -72,7 +76,11 @@ function noteToRow(note) {
72
76
  group_id: note.groupId ?? null,
73
77
  last_opened_at: note.lastOpenedAt ?? null,
74
78
  task_done: note.taskMeta?.done != null ? note.taskMeta.done ? 1 : 0 : null,
75
- task_due_at: note.taskMeta?.dueAt ?? null
79
+ task_due_at: note.taskMeta?.dueAt ?? null,
80
+ heading_count: meta.headingCount ?? null,
81
+ task_count: meta.taskCount ?? null,
82
+ unchecked_task_count: meta.uncheckedTaskCount ?? null,
83
+ link_count: meta.linkCount ?? null
76
84
  };
77
85
  }
78
86
  function upsertNoteFts(db, note) {
@@ -88,8 +96,8 @@ function upsertNoteRecord(note) {
88
96
  note_id, title, kind, status, payload_json, created_at, updated_at,
89
97
  pinned, tags_json, snippet, cover_attachment_id, voice_attachment_id,
90
98
  voice_duration_sec, attachment_names_json, group_id, last_opened_at,
91
- task_done, task_due_at
92
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
99
+ task_done, task_due_at, heading_count, task_count, unchecked_task_count, link_count
100
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
93
101
  ON CONFLICT(note_id) DO UPDATE SET
94
102
  title = excluded.title,
95
103
  kind = excluded.kind,
@@ -106,7 +114,11 @@ function upsertNoteRecord(note) {
106
114
  group_id = excluded.group_id,
107
115
  last_opened_at = excluded.last_opened_at,
108
116
  task_done = excluded.task_done,
109
- task_due_at = excluded.task_due_at`).run(row.note_id, row.title, row.kind, row.status, row.payload_json, row.created_at, row.updated_at, row.pinned, row.tags_json, row.snippet, row.cover_attachment_id, row.voice_attachment_id, row.voice_duration_sec, row.attachment_names_json, row.group_id, row.last_opened_at, row.task_done, row.task_due_at);
117
+ task_due_at = excluded.task_due_at,
118
+ heading_count = excluded.heading_count,
119
+ task_count = excluded.task_count,
120
+ unchecked_task_count = excluded.unchecked_task_count,
121
+ link_count = excluded.link_count`).run(row.note_id, row.title, row.kind, row.status, row.payload_json, row.created_at, row.updated_at, row.pinned, row.tags_json, row.snippet, row.cover_attachment_id, row.voice_attachment_id, row.voice_duration_sec, row.attachment_names_json, row.group_id, row.last_opened_at, row.task_done, row.task_due_at, row.heading_count, row.task_count, row.unchecked_task_count, row.link_count);
110
122
  upsertNoteFts(db, note);
111
123
  });
112
124
  }
@@ -156,7 +168,7 @@ function listNoteRecords(query = {}) {
156
168
  let entries = db.prepare(`SELECT note_id, title, kind, status, payload_json, created_at, updated_at,
157
169
  pinned, tags_json, snippet, cover_attachment_id, voice_attachment_id,
158
170
  voice_duration_sec, attachment_names_json, group_id, last_opened_at,
159
- task_done, task_due_at
171
+ task_done, task_due_at, heading_count, task_count, unchecked_task_count, link_count
160
172
  FROM notes ${where}`).all(...params).map(rowToIndexEntry);
161
173
  if (query.tag) entries = entries.filter((entry) => entry.tags?.includes(query.tag));
162
174
  if (query.search) {
@@ -1 +1 @@
1
- {"version":3,"file":"notes-repository.js","names":[],"sources":["../../../../src/storage/sqlite/notes-repository.ts"],"sourcesContent":["import type { Note, NoteIndexEntry, NotesListQuery } from '../../notes/types.js';\nimport { buildNoteIndexMeta, extractAttachmentFileNames, notePlainText } from '../../notes/note-index-meta.js';\nimport { escapeFts5Query } from './fts.js';\nimport { getSqliteDatabase, runSqliteWriteTransaction } from './transaction.js';\n\ntype NoteRow = {\n note_id: string;\n title: string | null;\n kind: string;\n status: string;\n payload_json: string;\n created_at: number;\n updated_at: number;\n pinned: number;\n tags_json: string;\n snippet: string | null;\n cover_attachment_id: string | null;\n voice_attachment_id: string | null;\n voice_duration_sec: number | null;\n attachment_names_json: string | null;\n group_id: string | null;\n last_opened_at: number | null;\n task_done: number | null;\n task_due_at: number | null;\n};\n\nfunction parseTags(json: string): string[] {\n try {\n const parsed = JSON.parse(json) as unknown;\n return Array.isArray(parsed) ? parsed.filter((t): t is string => typeof t === 'string') : [];\n } catch {\n return [];\n }\n}\n\nfunction parseAttachmentNames(json: string | null): string[] | undefined {\n if (!json) return undefined;\n try {\n const parsed = JSON.parse(json) as unknown;\n return Array.isArray(parsed) ? parsed.filter((n): n is string => typeof n === 'string') : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction rowToIndexEntry(row: NoteRow): NoteIndexEntry {\n return {\n id: row.note_id,\n title: row.title ?? undefined,\n kind: row.kind as NoteIndexEntry['kind'],\n status: row.status as NoteIndexEntry['status'],\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n pinned: row.pinned ? true : undefined,\n tags: parseTags(row.tags_json),\n snippet: row.snippet ?? undefined,\n coverAttachmentId: row.cover_attachment_id ?? undefined,\n voiceAttachmentId: row.voice_attachment_id ?? undefined,\n voiceDurationSec: row.voice_duration_sec ?? undefined,\n attachmentNames: parseAttachmentNames(row.attachment_names_json),\n groupId: row.group_id ?? undefined,\n lastOpenedAt: row.last_opened_at ?? undefined,\n taskDone: row.task_done != null ? Boolean(row.task_done) : undefined,\n taskDueAt: row.task_due_at ?? undefined,\n };\n}\n\nfunction noteSearchContent(note: Note): string {\n const plain = notePlainText(note);\n const attachmentText = note.attachments?.map((a) => a.transcript).filter(Boolean).join(' ') ?? '';\n const attachmentNames = extractAttachmentFileNames(note)?.join(' ') ?? '';\n return [note.title, plain, attachmentText, attachmentNames, ...(note.tags ?? [])].filter(Boolean).join('\\n');\n}\n\nfunction noteToRow(note: Note): Omit<NoteRow, 'payload_json'> & { payload_json: string } {\n const meta = buildNoteIndexMeta(note);\n return {\n note_id: note.id,\n title: note.title ?? null,\n kind: note.kind,\n status: note.status,\n payload_json: JSON.stringify(note),\n created_at: note.createdAt,\n updated_at: note.updatedAt,\n pinned: note.pinned ? 1 : 0,\n tags_json: JSON.stringify(note.tags ?? []),\n snippet: meta.snippet ?? null,\n cover_attachment_id: meta.coverAttachmentId ?? null,\n voice_attachment_id: meta.voiceAttachmentId ?? null,\n voice_duration_sec: meta.voiceDurationSec ?? null,\n attachment_names_json: meta.attachmentNames ? JSON.stringify(meta.attachmentNames) : null,\n group_id: note.groupId ?? null,\n last_opened_at: note.lastOpenedAt ?? null,\n task_done: note.taskMeta?.done != null ? (note.taskMeta.done ? 1 : 0) : null,\n task_due_at: note.taskMeta?.dueAt ?? null,\n };\n}\n\nfunction upsertNoteFts(db: ReturnType<typeof getSqliteDatabase>, note: Note): void {\n db.prepare(`DELETE FROM notes_fts WHERE note_id = ?`).run(note.id);\n const content = noteSearchContent(note);\n if (!content.trim()) {\n return;\n }\n db.prepare(`INSERT INTO notes_fts (content, note_id) VALUES (?, ?)`).run(content, note.id);\n}\n\nexport function upsertNoteRecord(note: Note): void {\n const row = noteToRow(note);\n runSqliteWriteTransaction((db) => {\n db.prepare(\n `INSERT INTO notes (\n note_id, title, kind, status, payload_json, created_at, updated_at,\n pinned, tags_json, snippet, cover_attachment_id, voice_attachment_id,\n voice_duration_sec, attachment_names_json, group_id, last_opened_at,\n task_done, task_due_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(note_id) DO UPDATE SET\n title = excluded.title,\n kind = excluded.kind,\n status = excluded.status,\n payload_json = excluded.payload_json,\n updated_at = excluded.updated_at,\n pinned = excluded.pinned,\n tags_json = excluded.tags_json,\n snippet = excluded.snippet,\n cover_attachment_id = excluded.cover_attachment_id,\n voice_attachment_id = excluded.voice_attachment_id,\n voice_duration_sec = excluded.voice_duration_sec,\n attachment_names_json = excluded.attachment_names_json,\n group_id = excluded.group_id,\n last_opened_at = excluded.last_opened_at,\n task_done = excluded.task_done,\n task_due_at = excluded.task_due_at`,\n ).run(\n row.note_id,\n row.title,\n row.kind,\n row.status,\n row.payload_json,\n row.created_at,\n row.updated_at,\n row.pinned,\n row.tags_json,\n row.snippet,\n row.cover_attachment_id,\n row.voice_attachment_id,\n row.voice_duration_sec,\n row.attachment_names_json,\n row.group_id,\n row.last_opened_at,\n row.task_done,\n row.task_due_at,\n );\n upsertNoteFts(db, note);\n });\n}\n\nexport function getNoteRecord(noteId: string): Note | null {\n const db = getSqliteDatabase();\n const row = db.prepare(`SELECT payload_json FROM notes WHERE note_id = ?`).get(noteId) as\n | { payload_json: string }\n | undefined;\n if (!row?.payload_json) {\n return null;\n }\n try {\n return JSON.parse(row.payload_json) as Note;\n } catch {\n return null;\n }\n}\n\nexport function deleteNoteRecord(noteId: string): boolean {\n return runSqliteWriteTransaction((db) => {\n const existing = db.prepare(`SELECT note_id FROM notes WHERE note_id = ?`).get(noteId);\n if (!existing) {\n return false;\n }\n db.prepare(`DELETE FROM notes_fts WHERE note_id = ?`).run(noteId);\n db.prepare(`DELETE FROM notes WHERE note_id = ?`).run(noteId);\n return true;\n });\n}\n\nexport function listNoteRecords(\n query: NotesListQuery = {},\n): { items: NoteIndexEntry[]; total: number; limit: number; offset: number; hasMore: boolean } {\n const db = getSqliteDatabase();\n const conditions: string[] = [];\n const params: Array<string | number> = [];\n\n if (query.status) {\n conditions.push(`status = ?`);\n params.push(query.status);\n } else {\n conditions.push(`status != ?`);\n params.push('trashed');\n }\n if (query.kind) {\n conditions.push(`kind = ?`);\n params.push(query.kind);\n }\n if (query.pinned !== undefined) {\n conditions.push(`pinned = ?`);\n params.push(query.pinned ? 1 : 0);\n }\n if (query.groupId !== undefined) {\n if (query.groupId === 'ungrouped') {\n conditions.push(`(group_id IS NULL OR group_id = '')`);\n } else {\n conditions.push(`group_id = ?`);\n params.push(query.groupId);\n }\n }\n if (query.pendingTasksOnly) {\n conditions.push(`kind = 'task' AND (task_done IS NULL OR task_done = 0)`);\n }\n\n const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';\n const rows = db\n .prepare(\n `SELECT note_id, title, kind, status, payload_json, created_at, updated_at,\n pinned, tags_json, snippet, cover_attachment_id, voice_attachment_id,\n voice_duration_sec, attachment_names_json, group_id, last_opened_at,\n task_done, task_due_at\n FROM notes ${where}`,\n )\n .all(...params) as NoteRow[];\n\n let entries = rows.map(rowToIndexEntry);\n\n if (query.tag) {\n entries = entries.filter((entry) => entry.tags?.includes(query.tag!));\n }\n\n if (query.search) {\n const ftsQuery = escapeFts5Query(query.search);\n const ftsIds = new Set<string>();\n if (ftsQuery) {\n const ftsRows = db\n .prepare(\n `SELECT note_id FROM notes_fts WHERE notes_fts MATCH ? ORDER BY rank`,\n )\n .all(ftsQuery) as Array<{ note_id: string }>;\n for (const row of ftsRows) {\n ftsIds.add(row.note_id);\n }\n }\n entries = entries.filter((entry) => ftsIds.has(entry.id));\n }\n\n const sortField = query.sortBy || 'createdAt';\n const sortDir = query.sortOrder === 'asc' ? 1 : -1;\n entries = [...entries].sort((a, b) => {\n const aVal = (a[sortField] as number | undefined) ?? 0;\n const bVal = (b[sortField] as number | undefined) ?? 0;\n return (aVal - bVal) * sortDir;\n });\n\n const total = entries.length;\n const offset = query.offset || 0;\n const limit = Math.min(query.limit || 50, 200);\n const items = entries.slice(offset, offset + limit);\n const hasMore = offset + items.length < total;\n\n return { items, total, limit, offset, hasMore };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"notes-repository.js","names":[],"sources":["../../../../src/storage/sqlite/notes-repository.ts"],"sourcesContent":["import type { Note, NoteIndexEntry, NotesListQuery } from '../../notes/types.js';\nimport { buildNoteIndexMeta, extractAttachmentFileNames, notePlainText } from '../../notes/note-index-meta.js';\nimport { escapeFts5Query } from './fts.js';\nimport { getSqliteDatabase, runSqliteWriteTransaction } from './transaction.js';\n\ntype NoteRow = {\n note_id: string;\n title: string | null;\n kind: string;\n status: string;\n payload_json: string;\n created_at: number;\n updated_at: number;\n pinned: number;\n tags_json: string;\n snippet: string | null;\n cover_attachment_id: string | null;\n voice_attachment_id: string | null;\n voice_duration_sec: number | null;\n attachment_names_json: string | null;\n group_id: string | null;\n last_opened_at: number | null;\n task_done: number | null;\n task_due_at: number | null;\n heading_count: number | null;\n task_count: number | null;\n unchecked_task_count: number | null;\n link_count: number | null;\n};\n\nfunction parseTags(json: string): string[] {\n try {\n const parsed = JSON.parse(json) as unknown;\n return Array.isArray(parsed) ? parsed.filter((t): t is string => typeof t === 'string') : [];\n } catch {\n return [];\n }\n}\n\nfunction parseAttachmentNames(json: string | null): string[] | undefined {\n if (!json) return undefined;\n try {\n const parsed = JSON.parse(json) as unknown;\n return Array.isArray(parsed) ? parsed.filter((n): n is string => typeof n === 'string') : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction rowToIndexEntry(row: NoteRow): NoteIndexEntry {\n return {\n id: row.note_id,\n title: row.title ?? undefined,\n kind: row.kind as NoteIndexEntry['kind'],\n status: row.status as NoteIndexEntry['status'],\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n pinned: row.pinned ? true : undefined,\n tags: parseTags(row.tags_json),\n snippet: row.snippet ?? undefined,\n coverAttachmentId: row.cover_attachment_id ?? undefined,\n voiceAttachmentId: row.voice_attachment_id ?? undefined,\n voiceDurationSec: row.voice_duration_sec ?? undefined,\n attachmentNames: parseAttachmentNames(row.attachment_names_json),\n groupId: row.group_id ?? undefined,\n lastOpenedAt: row.last_opened_at ?? undefined,\n taskDone: row.task_done != null ? Boolean(row.task_done) : undefined,\n taskDueAt: row.task_due_at ?? undefined,\n headingCount: row.heading_count ?? undefined,\n taskCount: row.task_count ?? undefined,\n uncheckedTaskCount: row.unchecked_task_count ?? undefined,\n linkCount: row.link_count ?? undefined,\n };\n}\n\nfunction noteSearchContent(note: Note): string {\n const plain = notePlainText(note);\n const attachmentText = note.attachments?.map((a) => a.transcript).filter(Boolean).join(' ') ?? '';\n const attachmentNames = extractAttachmentFileNames(note)?.join(' ') ?? '';\n return [note.title, plain, attachmentText, attachmentNames, ...(note.tags ?? [])].filter(Boolean).join('\\n');\n}\n\nfunction noteToRow(note: Note): Omit<NoteRow, 'payload_json'> & { payload_json: string } {\n const meta = buildNoteIndexMeta(note);\n return {\n note_id: note.id,\n title: note.title ?? null,\n kind: note.kind,\n status: note.status,\n payload_json: JSON.stringify(note),\n created_at: note.createdAt,\n updated_at: note.updatedAt,\n pinned: note.pinned ? 1 : 0,\n tags_json: JSON.stringify(note.tags ?? []),\n snippet: meta.snippet ?? null,\n cover_attachment_id: meta.coverAttachmentId ?? null,\n voice_attachment_id: meta.voiceAttachmentId ?? null,\n voice_duration_sec: meta.voiceDurationSec ?? null,\n attachment_names_json: meta.attachmentNames ? JSON.stringify(meta.attachmentNames) : null,\n group_id: note.groupId ?? null,\n last_opened_at: note.lastOpenedAt ?? null,\n task_done: note.taskMeta?.done != null ? (note.taskMeta.done ? 1 : 0) : null,\n task_due_at: note.taskMeta?.dueAt ?? null,\n heading_count: meta.headingCount ?? null,\n task_count: meta.taskCount ?? null,\n unchecked_task_count: meta.uncheckedTaskCount ?? null,\n link_count: meta.linkCount ?? null,\n };\n}\n\nfunction upsertNoteFts(db: ReturnType<typeof getSqliteDatabase>, note: Note): void {\n db.prepare(`DELETE FROM notes_fts WHERE note_id = ?`).run(note.id);\n const content = noteSearchContent(note);\n if (!content.trim()) {\n return;\n }\n db.prepare(`INSERT INTO notes_fts (content, note_id) VALUES (?, ?)`).run(content, note.id);\n}\n\nexport function upsertNoteRecord(note: Note): void {\n const row = noteToRow(note);\n runSqliteWriteTransaction((db) => {\n db.prepare(\n `INSERT INTO notes (\n note_id, title, kind, status, payload_json, created_at, updated_at,\n pinned, tags_json, snippet, cover_attachment_id, voice_attachment_id,\n voice_duration_sec, attachment_names_json, group_id, last_opened_at,\n task_done, task_due_at, heading_count, task_count, unchecked_task_count, link_count\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(note_id) DO UPDATE SET\n title = excluded.title,\n kind = excluded.kind,\n status = excluded.status,\n payload_json = excluded.payload_json,\n updated_at = excluded.updated_at,\n pinned = excluded.pinned,\n tags_json = excluded.tags_json,\n snippet = excluded.snippet,\n cover_attachment_id = excluded.cover_attachment_id,\n voice_attachment_id = excluded.voice_attachment_id,\n voice_duration_sec = excluded.voice_duration_sec,\n attachment_names_json = excluded.attachment_names_json,\n group_id = excluded.group_id,\n last_opened_at = excluded.last_opened_at,\n task_done = excluded.task_done,\n task_due_at = excluded.task_due_at,\n heading_count = excluded.heading_count,\n task_count = excluded.task_count,\n unchecked_task_count = excluded.unchecked_task_count,\n link_count = excluded.link_count`,\n ).run(\n row.note_id,\n row.title,\n row.kind,\n row.status,\n row.payload_json,\n row.created_at,\n row.updated_at,\n row.pinned,\n row.tags_json,\n row.snippet,\n row.cover_attachment_id,\n row.voice_attachment_id,\n row.voice_duration_sec,\n row.attachment_names_json,\n row.group_id,\n row.last_opened_at,\n row.task_done,\n row.task_due_at,\n row.heading_count,\n row.task_count,\n row.unchecked_task_count,\n row.link_count,\n );\n upsertNoteFts(db, note);\n });\n}\n\nexport function getNoteRecord(noteId: string): Note | null {\n const db = getSqliteDatabase();\n const row = db.prepare(`SELECT payload_json FROM notes WHERE note_id = ?`).get(noteId) as\n | { payload_json: string }\n | undefined;\n if (!row?.payload_json) {\n return null;\n }\n try {\n return JSON.parse(row.payload_json) as Note;\n } catch {\n return null;\n }\n}\n\nexport function deleteNoteRecord(noteId: string): boolean {\n return runSqliteWriteTransaction((db) => {\n const existing = db.prepare(`SELECT note_id FROM notes WHERE note_id = ?`).get(noteId);\n if (!existing) {\n return false;\n }\n db.prepare(`DELETE FROM notes_fts WHERE note_id = ?`).run(noteId);\n db.prepare(`DELETE FROM notes WHERE note_id = ?`).run(noteId);\n return true;\n });\n}\n\nexport function listNoteRecords(\n query: NotesListQuery = {},\n): { items: NoteIndexEntry[]; total: number; limit: number; offset: number; hasMore: boolean } {\n const db = getSqliteDatabase();\n const conditions: string[] = [];\n const params: Array<string | number> = [];\n\n if (query.status) {\n conditions.push(`status = ?`);\n params.push(query.status);\n } else {\n conditions.push(`status != ?`);\n params.push('trashed');\n }\n if (query.kind) {\n conditions.push(`kind = ?`);\n params.push(query.kind);\n }\n if (query.pinned !== undefined) {\n conditions.push(`pinned = ?`);\n params.push(query.pinned ? 1 : 0);\n }\n if (query.groupId !== undefined) {\n if (query.groupId === 'ungrouped') {\n conditions.push(`(group_id IS NULL OR group_id = '')`);\n } else {\n conditions.push(`group_id = ?`);\n params.push(query.groupId);\n }\n }\n if (query.pendingTasksOnly) {\n conditions.push(`kind = 'task' AND (task_done IS NULL OR task_done = 0)`);\n }\n\n const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';\n const rows = db\n .prepare(\n `SELECT note_id, title, kind, status, payload_json, created_at, updated_at,\n pinned, tags_json, snippet, cover_attachment_id, voice_attachment_id,\n voice_duration_sec, attachment_names_json, group_id, last_opened_at,\n task_done, task_due_at, heading_count, task_count, unchecked_task_count, link_count\n FROM notes ${where}`,\n )\n .all(...params) as NoteRow[];\n\n let entries = rows.map(rowToIndexEntry);\n\n if (query.tag) {\n entries = entries.filter((entry) => entry.tags?.includes(query.tag!));\n }\n\n if (query.search) {\n const ftsQuery = escapeFts5Query(query.search);\n const ftsIds = new Set<string>();\n if (ftsQuery) {\n const ftsRows = db\n .prepare(\n `SELECT note_id FROM notes_fts WHERE notes_fts MATCH ? ORDER BY rank`,\n )\n .all(ftsQuery) as Array<{ note_id: string }>;\n for (const row of ftsRows) {\n ftsIds.add(row.note_id);\n }\n }\n entries = entries.filter((entry) => ftsIds.has(entry.id));\n }\n\n const sortField = query.sortBy || 'createdAt';\n const sortDir = query.sortOrder === 'asc' ? 1 : -1;\n entries = [...entries].sort((a, b) => {\n const aVal = (a[sortField] as number | undefined) ?? 0;\n const bVal = (b[sortField] as number | undefined) ?? 0;\n return (aVal - bVal) * sortDir;\n });\n\n const total = entries.length;\n const offset = query.offset || 0;\n const limit = Math.min(query.limit || 50, 200);\n const items = entries.slice(offset, offset + limit);\n const hasMore = offset + items.length < total;\n\n return { items, total, limit, offset, hasMore };\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { SCHEMA_META_SCHEMA_VERSION_KEY, ensureSchemaMetaTable, readSchemaVersion, readSchemaVersionForTest, setSchemaVersion } from "./schema-version.js";
2
2
  import { XOPC_DB_BASELINE_SCHEMA_VERSION, XOPC_DB_SCHEMA_VERSION, applyPendingMigrations } from "./migrations/runner.js";
3
- import { dirname, join } from "node:path";
4
3
  import { readFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  //#region src/storage/sqlite/schema.ts
7
7
  const SCHEMA_DIR = dirname(fileURLToPath(import.meta.url));
@@ -12,7 +12,7 @@ function bootstrapFreshDatabase(db) {
12
12
  db.exec("BEGIN IMMEDIATE");
13
13
  try {
14
14
  db.exec(readSchemaSql());
15
- setSchemaVersion(db, 2);
15
+ setSchemaVersion(db, 3);
16
16
  db.exec("COMMIT");
17
17
  } catch (error) {
18
18
  try {
@@ -29,9 +29,9 @@ function ensureXopcDatabaseSchema(db) {
29
29
  let currentVersion = readSchemaVersion(db);
30
30
  if (currentVersion === 0) {
31
31
  bootstrapFreshDatabase(db);
32
- currentVersion = 2;
32
+ currentVersion = 3;
33
33
  }
34
- applyPendingMigrations(db, { targetVersion: 2 });
34
+ applyPendingMigrations(db, { targetVersion: 3 });
35
35
  }
36
36
  //#endregion
37
37
  export { SCHEMA_META_SCHEMA_VERSION_KEY, XOPC_DB_BASELINE_SCHEMA_VERSION, XOPC_DB_SCHEMA_VERSION, ensureSchemaMetaTable, ensureXopcDatabaseSchema, readSchemaVersion, readSchemaVersionForTest, setSchemaVersion };
@@ -149,7 +149,11 @@ CREATE TABLE IF NOT EXISTS notes (
149
149
  group_id TEXT,
150
150
  last_opened_at INTEGER,
151
151
  task_done INTEGER,
152
- task_due_at INTEGER
152
+ task_due_at INTEGER,
153
+ heading_count INTEGER,
154
+ task_count INTEGER,
155
+ unchecked_task_count INTEGER,
156
+ link_count INTEGER
153
157
  );
154
158
 
155
159
  CREATE INDEX IF NOT EXISTS idx_notes_status_updated
@@ -1,7 +1,7 @@
1
- import { tmpdir } from "node:os";
2
- import { join } from "node:path";
3
- import { readFileSync, unlinkSync, writeFileSync } from "node:fs";
4
1
  import { randomUUID } from "node:crypto";
2
+ import { readFileSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
5
  import { spawnSync } from "node:child_process";
6
6
  //#region src/tui/clipboard-image.ts
7
7
  const SUPPORTED_IMAGE_MIME_TYPES = [
@@ -2,8 +2,8 @@ import { resolveStateDir } from "../config/paths-state.js";
2
2
  import { init_paths } from "../config/paths.js";
3
3
  import { palette } from "./theme/dark.js";
4
4
  import { palette as palette$1 } from "./theme/light.js";
5
- import { join } from "node:path";
6
5
  import { existsSync, readFileSync, readdirSync } from "node:fs";
6
+ import { join } from "node:path";
7
7
  import chalk from "chalk";
8
8
  //#region src/tui/theme-manager.ts
9
9
  init_paths();
@@ -1,8 +1,8 @@
1
1
  import { resolveStateDir } from "../config/paths-state.js";
2
2
  import { init_paths } from "../config/paths.js";
3
3
  import { XOPC_TUI_KEYBINDINGS } from "./xopc-tui-keybindings.js";
4
- import { join } from "node:path";
5
4
  import { existsSync, readFileSync } from "node:fs";
5
+ import { join } from "node:path";
6
6
  import { KeybindingsManager } from "@earendil-works/pi-tui";
7
7
  //#region src/tui/tui-keybindings-file.ts
8
8
  init_paths();
@@ -1,6 +1,6 @@
1
- import { homedir } from "node:os";
2
- import { join } from "node:path";
3
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
4
  //#region src/tui/tui-scoped-models.ts
5
5
  const STORE_PATH = join(homedir(), ".xopc", "tui-scoped-models.json");
6
6
  function normalizeWorkspaceKey(cwd) {
@@ -1,7 +1,7 @@
1
1
  import { resolveStateDir } from "../config/paths-state.js";
2
2
  import { init_paths } from "../config/paths.js";
3
- import { join } from "node:path";
4
3
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
5
  //#region src/tui/tui-settings.ts
6
6
  init_paths();
7
7
  const DEFAULT_TUI_SETTINGS = {
@@ -1,7 +1,7 @@
1
1
  import { version } from "../../package.js";
2
+ import { loadConfig } from "../config/loader.js";
2
3
  import { parseAgentSessionKey } from "../routing/session-key-utils.js";
3
4
  import { init_agent_session_key } from "../routing/agent-session-key.js";
4
- import { loadConfig } from "../config/loader.js";
5
5
  import "../config/index.js";
6
6
  import { resolveTuiSessionKey, resolveTuiStartupSessionKey } from "../routing/resolve-tui-session-key.js";
7
7
  import { saveClipboardImageToTempFile } from "./clipboard-image.js";
@@ -31,9 +31,9 @@ import { TuiBottomBar } from "./components/tui-bottom-bar.js";
31
31
  import { TuiHeader } from "./components/tui-header.js";
32
32
  import { loadExtensionsForTuiLocalMode } from "./extension-host/load-extensions.js";
33
33
  import { createTuiExtensionRuntime } from "./extension-host/runtime.js";
34
- import { tmpdir } from "node:os";
35
- import { join } from "node:path";
36
34
  import { mkdtempSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
35
+ import { join } from "node:path";
36
+ import { tmpdir } from "node:os";
37
37
  import { spawnSync } from "node:child_process";
38
38
  import { Container, Loader, ProcessTerminal, TUI, setKeybindings } from "@earendil-works/pi-tui";
39
39
  //#region src/tui/tui.ts
@@ -2,10 +2,10 @@ import { createLogger } from "../utils/logger/index.js";
2
2
  import { init_logger } from "../utils/logger.js";
3
3
  import { init_paths, resolveBinDir } from "../config/paths.js";
4
4
  import { extractFrpcFromReleaseArchive, frpcReleaseArchiveExtension, nodePlatformForFrpTarget } from "./frpc-extract.js";
5
- import { tmpdir } from "node:os";
6
- import { join } from "node:path";
7
- import { chmodSync, createWriteStream, existsSync, mkdirSync, rmSync } from "node:fs";
8
5
  import { randomBytes } from "node:crypto";
6
+ import { chmodSync, createWriteStream, existsSync, mkdirSync, rmSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { tmpdir } from "node:os";
9
9
  import { Readable } from "node:stream";
10
10
  import { pipeline } from "node:stream/promises";
11
11
  //#region src/tunnel/frpc-binary.ts
@@ -1,7 +1,7 @@
1
1
  import { resolveStateDir } from "../config/paths-state.js";
2
2
  import { init_paths } from "../config/paths.js";
3
- import { join } from "node:path";
4
3
  import { mkdirSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
5
  //#region src/tunnel/frpc-config.ts
6
6
  init_paths();
7
7
  function resolveFrpcConfigPath(tunnelId) {
@@ -1,7 +1,7 @@
1
1
  import { createLogger } from "../utils/logger/index.js";
2
2
  import { init_logger } from "../utils/logger.js";
3
- import { dirname, join } from "node:path";
4
3
  import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
5
  import { spawn } from "node:child_process";
6
6
  import AdmZip from "adm-zip";
7
7
  import { gunzipSync } from "node:zlib";
@@ -1,7 +1,7 @@
1
1
  import { resolveStateDir } from "../config/paths-state.js";
2
2
  import { init_paths } from "../config/paths.js";
3
- import { join } from "node:path";
4
3
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
5
  //#region src/tunnel/tunnel-state.ts
6
6
  init_paths();
7
7
  const TUNNEL_STATE_FILE = "tunnel.json";
@@ -1,8 +1,8 @@
1
1
  import { __esmMin } from "../../../_virtual/_rolldown/runtime.js";
2
2
  import { getLogDir, init_config } from "./config.js";
3
+ import { appendFile } from "fs/promises";
3
4
  import path from "path";
4
5
  import { existsSync, mkdirSync } from "fs";
5
- import { appendFile } from "fs/promises";
6
6
  //#region src/utils/logger/audit.ts
7
7
  /**
8
8
  * Audit Log
@@ -1,8 +1,8 @@
1
1
  import { init_pino_record, logEntrySearchText, pinoRecordToLogEntry } from "./pino-record.js";
2
+ import { readFile } from "fs/promises";
2
3
  import { basename, join } from "path";
3
4
  import { createReadStream, existsSync, mkdirSync, readdirSync, statSync } from "fs";
4
5
  import { Readable } from "stream";
5
- import { readFile } from "fs/promises";
6
6
  import { gunzip } from "zlib";
7
7
  import { promisify } from "util";
8
8
  import { createInterface } from "readline";
@@ -1,8 +1,8 @@
1
1
  import { __esmMin } from "../../../_virtual/_rolldown/runtime.js";
2
2
  import { config, getLogDir, init_config } from "./config.js";
3
+ import { writeFile } from "fs/promises";
3
4
  import { join } from "path";
4
5
  import { readFileSync, readdirSync, statSync, unlinkSync } from "fs";
5
- import { writeFile } from "fs/promises";
6
6
  import { gzip } from "zlib";
7
7
  import { promisify } from "util";
8
8
  //#region src/utils/logger/rotation.ts
@@ -1,7 +1,7 @@
1
1
  import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
- import { join } from "path";
4
3
  import { unlink, writeFile } from "fs/promises";
4
+ import { join } from "path";
5
5
  import { spawn } from "child_process";
6
6
  import { tmpdir } from "os";
7
7
  //#region src/voice/tts/audio.ts
@@ -1,9 +1,9 @@
1
1
  import { createLogger } from "../../../utils/logger/index.js";
2
2
  import { init_logger } from "../../../utils/logger.js";
3
3
  import { registerSpeechProvider } from "../speech-registry.js";
4
- import { tmpdir } from "node:os";
5
- import path from "node:path";
6
4
  import { mkdtempSync, readFileSync, rmSync } from "node:fs";
5
+ import path from "node:path";
6
+ import { tmpdir } from "node:os";
7
7
  //#region src/voice/tts/providers/edge-speech.ts
8
8
  /**
9
9
  * Edge TTS provider — wraps the `node-edge-tts` package. Requires no API key
@@ -1,9 +1,9 @@
1
1
  import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
3
  import { resolveWorkflowRunEventsPath, resolveWorkflowRunsDir } from "./paths.js";
4
- import { dirname } from "node:path";
5
4
  import { randomUUID } from "node:crypto";
6
5
  import { appendFile, mkdir, readFile, readdir } from "node:fs/promises";
6
+ import { dirname } from "node:path";
7
7
  //#region src/workflows/store/event-store.ts
8
8
  init_logger();
9
9
  const log = createLogger("WorkflowEventStore");
@@ -1,6 +1,6 @@
1
+ import { init_write_file_atomic, writeTextAtomic } from "../../infra/write-file-atomic.js";
1
2
  import { createLogger } from "../../utils/logger/index.js";
2
3
  import { init_logger } from "../../utils/logger.js";
3
- import { init_write_file_atomic, writeTextAtomic } from "../../infra/write-file-atomic.js";
4
4
  import { projectWorkflowRunView } from "../engine/projector.js";
5
5
  import { resolveWorkflowRunViewPath } from "./paths.js";
6
6
  import { WorkflowEventStore } from "./event-store.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xopcai/xopc",
3
- "version": "0.0.101",
3
+ "version": "0.0.102",
4
4
  "description": "The OPC workstation that grows with you: AI assistant for One Person Companies — CLI, gateway, multi-channel (Telegram/WeChat), 20+ LLM providers via pi-ai, extensions and skills.",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",