studiograph 1.3.48-next.13 → 1.3.48-next.131

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 (1008) hide show
  1. package/README.md +7 -7
  2. package/dist/agent/agent-pool.d.ts +27 -1
  3. package/dist/agent/agent-pool.js +55 -12
  4. package/dist/agent/agent-pool.js.map +1 -1
  5. package/dist/agent/followups.d.ts +34 -0
  6. package/dist/agent/followups.js +52 -0
  7. package/dist/agent/followups.js.map +1 -0
  8. package/dist/agent/orchestrator.d.ts +29 -2
  9. package/dist/agent/orchestrator.js +251 -38
  10. package/dist/agent/orchestrator.js.map +1 -1
  11. package/dist/agent/prompts/entity-types-section.d.ts +30 -0
  12. package/dist/agent/prompts/entity-types-section.js +83 -0
  13. package/dist/agent/prompts/entity-types-section.js.map +1 -0
  14. package/dist/agent/prompts/system.md +106 -47
  15. package/dist/agent/skill-loader.d.ts +30 -17
  16. package/dist/agent/skill-loader.js +63 -30
  17. package/dist/agent/skill-loader.js.map +1 -1
  18. package/dist/agent/skills/entity-schema.md +79 -343
  19. package/dist/agent/skills/interview/entity-templates.md +38 -37
  20. package/dist/agent/skills/interview/question-domains.md +54 -49
  21. package/dist/agent/skills/interview/skill.md +89 -14
  22. package/dist/agent/tools/capture-tools.d.ts +31 -0
  23. package/dist/agent/tools/capture-tools.js +40 -0
  24. package/dist/agent/tools/capture-tools.js.map +1 -0
  25. package/dist/agent/tools/folder-tools.d.ts +47 -0
  26. package/dist/agent/tools/folder-tools.js +249 -0
  27. package/dist/agent/tools/folder-tools.js.map +1 -0
  28. package/dist/agent/tools/fs-tools.d.ts +6 -5
  29. package/dist/agent/tools/fs-tools.js +5 -5
  30. package/dist/agent/tools/fs-tools.js.map +1 -1
  31. package/dist/agent/tools/graph-tools.d.ts +25 -72
  32. package/dist/agent/tools/graph-tools.js +362 -366
  33. package/dist/agent/tools/graph-tools.js.map +1 -1
  34. package/dist/agent/tools/history-tools.d.ts +95 -0
  35. package/dist/agent/tools/history-tools.js +461 -0
  36. package/dist/agent/tools/history-tools.js.map +1 -0
  37. package/dist/agent/tools/load-skill.d.ts +6 -5
  38. package/dist/agent/tools/load-skill.js +3 -3
  39. package/dist/agent/tools/load-skill.js.map +1 -1
  40. package/dist/agent/tools/message-tools.d.ts +13 -11
  41. package/dist/agent/tools/message-tools.js +31 -39
  42. package/dist/agent/tools/message-tools.js.map +1 -1
  43. package/dist/agent/tools/ops-tools.d.ts +5 -4
  44. package/dist/agent/tools/ops-tools.js +99 -212
  45. package/dist/agent/tools/ops-tools.js.map +1 -1
  46. package/dist/agent/tools/permission-tools.d.ts +39 -16
  47. package/dist/agent/tools/permission-tools.js +66 -41
  48. package/dist/agent/tools/permission-tools.js.map +1 -1
  49. package/dist/agent/tools/tool-loader.js +5 -4
  50. package/dist/agent/tools/tool-loader.js.map +1 -1
  51. package/dist/agent/tools/web-tools.js +58 -9
  52. package/dist/agent/tools/web-tools.js.map +1 -1
  53. package/dist/cli/commands/about.d.ts +1 -0
  54. package/dist/cli/commands/about.js +55 -3
  55. package/dist/cli/commands/about.js.map +1 -1
  56. package/dist/cli/commands/audit.d.ts +21 -0
  57. package/dist/cli/commands/audit.js +174 -0
  58. package/dist/cli/commands/audit.js.map +1 -0
  59. package/dist/cli/commands/clear.d.ts +5 -0
  60. package/dist/cli/commands/clear.js +144 -1
  61. package/dist/cli/commands/clear.js.map +1 -1
  62. package/dist/cli/commands/clone.d.ts +1 -1
  63. package/dist/cli/commands/clone.js +82 -23
  64. package/dist/cli/commands/clone.js.map +1 -1
  65. package/dist/cli/commands/deploy.js +79 -18
  66. package/dist/cli/commands/deploy.js.map +1 -1
  67. package/dist/cli/commands/folder.d.ts +11 -0
  68. package/dist/cli/commands/folder.js +186 -0
  69. package/dist/cli/commands/folder.js.map +1 -0
  70. package/dist/cli/commands/index.js +2 -2
  71. package/dist/cli/commands/index.js.map +1 -1
  72. package/dist/cli/commands/init.js +16 -14
  73. package/dist/cli/commands/init.js.map +1 -1
  74. package/dist/cli/commands/join.d.ts +1 -1
  75. package/dist/cli/commands/join.js +26 -14
  76. package/dist/cli/commands/join.js.map +1 -1
  77. package/dist/cli/commands/mcp.js +12 -4
  78. package/dist/cli/commands/mcp.js.map +1 -1
  79. package/dist/cli/commands/r2.d.ts +3 -0
  80. package/dist/cli/commands/r2.js +196 -1
  81. package/dist/cli/commands/r2.js.map +1 -1
  82. package/dist/cli/commands/redeploy.js +41 -1
  83. package/dist/cli/commands/redeploy.js.map +1 -1
  84. package/dist/cli/commands/secrets.d.ts +23 -0
  85. package/dist/cli/commands/secrets.js +256 -0
  86. package/dist/cli/commands/secrets.js.map +1 -0
  87. package/dist/cli/commands/serve.js +143 -24
  88. package/dist/cli/commands/serve.js.map +1 -1
  89. package/dist/cli/commands/start.js +140 -71
  90. package/dist/cli/commands/start.js.map +1 -1
  91. package/dist/cli/commands/sync.d.ts +1 -1
  92. package/dist/cli/commands/sync.js +509 -163
  93. package/dist/cli/commands/sync.js.map +1 -1
  94. package/dist/cli/commands/user.js +8 -24
  95. package/dist/cli/commands/user.js.map +1 -1
  96. package/dist/cli/index.js +8 -7
  97. package/dist/cli/index.js.map +1 -1
  98. package/dist/cli/railway-provisioning.d.ts +53 -0
  99. package/dist/cli/railway-provisioning.js +85 -0
  100. package/dist/cli/railway-provisioning.js.map +1 -0
  101. package/dist/cli/scaffolding.d.ts +4 -2
  102. package/dist/cli/scaffolding.js +42 -47
  103. package/dist/cli/scaffolding.js.map +1 -1
  104. package/dist/cli/setup-wizard.d.ts +30 -49
  105. package/dist/cli/setup-wizard.js +35 -38
  106. package/dist/cli/setup-wizard.js.map +1 -1
  107. package/dist/core/accent-palette.d.ts +22 -0
  108. package/dist/core/accent-palette.js +47 -0
  109. package/dist/core/accent-palette.js.map +1 -0
  110. package/dist/core/entity-body-templates.d.ts +21 -0
  111. package/dist/core/entity-body-templates.js +48 -0
  112. package/dist/core/entity-body-templates.js.map +1 -0
  113. package/dist/core/entity-types-catalog.d.ts +65 -0
  114. package/dist/core/entity-types-catalog.js +136 -0
  115. package/dist/core/entity-types-catalog.js.map +1 -0
  116. package/dist/core/feature-flags.d.ts +22 -0
  117. package/dist/core/feature-flags.js +15 -0
  118. package/dist/core/feature-flags.js.map +1 -0
  119. package/dist/core/features.d.ts +16 -0
  120. package/dist/core/features.js +48 -0
  121. package/dist/core/features.js.map +1 -0
  122. package/dist/core/folder-paths.d.ts +41 -0
  123. package/dist/core/folder-paths.js +95 -0
  124. package/dist/core/folder-paths.js.map +1 -0
  125. package/dist/core/folder-sidecar.d.ts +16 -0
  126. package/dist/core/folder-sidecar.js +45 -0
  127. package/dist/core/folder-sidecar.js.map +1 -0
  128. package/dist/core/graph.d.ts +471 -19
  129. package/dist/core/graph.js +1411 -284
  130. package/dist/core/graph.js.map +1 -1
  131. package/dist/core/schema-registry.js +1 -19
  132. package/dist/core/schema-registry.js.map +1 -1
  133. package/dist/core/schemas/connector.d.ts +67 -0
  134. package/dist/core/schemas/connector.js +100 -0
  135. package/dist/core/schemas/connector.js.map +1 -0
  136. package/dist/core/schemas/workspace.d.ts +122 -0
  137. package/dist/core/schemas/workspace.js +211 -0
  138. package/dist/core/schemas/workspace.js.map +1 -0
  139. package/dist/core/secrets/SecretStore.d.ts +77 -0
  140. package/dist/core/secrets/SecretStore.js +13 -0
  141. package/dist/core/secrets/SecretStore.js.map +1 -0
  142. package/dist/core/secrets/SettingsResolver.d.ts +54 -0
  143. package/dist/core/secrets/SettingsResolver.js +80 -0
  144. package/dist/core/secrets/SettingsResolver.js.map +1 -0
  145. package/dist/core/secrets/SettingsStore.d.ts +30 -0
  146. package/dist/core/secrets/SettingsStore.js +9 -0
  147. package/dist/core/secrets/SettingsStore.js.map +1 -0
  148. package/dist/core/secrets/SqliteEncryptedStore.d.ts +78 -0
  149. package/dist/core/secrets/SqliteEncryptedStore.js +581 -0
  150. package/dist/core/secrets/SqliteEncryptedStore.js.map +1 -0
  151. package/dist/core/secrets/audit.d.ts +36 -0
  152. package/dist/core/secrets/audit.js +60 -0
  153. package/dist/core/secrets/audit.js.map +1 -0
  154. package/dist/core/secrets/auth-db-migration.d.ts +129 -0
  155. package/dist/core/secrets/auth-db-migration.js +653 -0
  156. package/dist/core/secrets/auth-db-migration.js.map +1 -0
  157. package/dist/core/secrets/bundle.d.ts +141 -0
  158. package/dist/core/secrets/bundle.js +435 -0
  159. package/dist/core/secrets/bundle.js.map +1 -0
  160. package/dist/core/secrets/dual-write.d.ts +76 -0
  161. package/dist/core/secrets/dual-write.js +236 -0
  162. package/dist/core/secrets/dual-write.js.map +1 -0
  163. package/dist/core/secrets/encryption.d.ts +44 -0
  164. package/dist/core/secrets/encryption.js +97 -0
  165. package/dist/core/secrets/encryption.js.map +1 -0
  166. package/dist/core/secrets/importer.d.ts +94 -0
  167. package/dist/core/secrets/importer.js +319 -0
  168. package/dist/core/secrets/importer.js.map +1 -0
  169. package/dist/core/secrets/index.d.ts +49 -0
  170. package/dist/core/secrets/index.js +74 -0
  171. package/dist/core/secrets/index.js.map +1 -0
  172. package/dist/core/secrets/master-key.d.ts +38 -0
  173. package/dist/core/secrets/master-key.js +122 -0
  174. package/dist/core/secrets/master-key.js.map +1 -0
  175. package/dist/core/secrets/probes/anthropic.d.ts +98 -0
  176. package/dist/core/secrets/probes/anthropic.js +300 -0
  177. package/dist/core/secrets/probes/anthropic.js.map +1 -0
  178. package/dist/core/secrets/probes/brave.d.ts +9 -0
  179. package/dist/core/secrets/probes/brave.js +15 -0
  180. package/dist/core/secrets/probes/brave.js.map +1 -0
  181. package/dist/core/secrets/probes/r2.d.ts +15 -0
  182. package/dist/core/secrets/probes/r2.js +14 -0
  183. package/dist/core/secrets/probes/r2.js.map +1 -0
  184. package/dist/core/secrets/probes/voyage.d.ts +13 -0
  185. package/dist/core/secrets/probes/voyage.js +52 -0
  186. package/dist/core/secrets/probes/voyage.js.map +1 -0
  187. package/dist/core/secrets/read-flip.d.ts +59 -0
  188. package/dist/core/secrets/read-flip.js +87 -0
  189. package/dist/core/secrets/read-flip.js.map +1 -0
  190. package/dist/core/secrets/registry.d.ts +188 -0
  191. package/dist/core/secrets/registry.js +531 -0
  192. package/dist/core/secrets/registry.js.map +1 -0
  193. package/dist/core/types.d.ts +70 -483
  194. package/dist/core/types.js +6 -3
  195. package/dist/core/types.js.map +1 -1
  196. package/dist/core/user-config.d.ts +52 -12
  197. package/dist/core/user-config.js +119 -14
  198. package/dist/core/user-config.js.map +1 -1
  199. package/dist/core/validation.d.ts +603 -1409
  200. package/dist/core/validation.js +248 -146
  201. package/dist/core/validation.js.map +1 -1
  202. package/dist/core/workspace-manager.d.ts +35 -14
  203. package/dist/core/workspace-manager.js +105 -56
  204. package/dist/core/workspace-manager.js.map +1 -1
  205. package/dist/core/workspace.d.ts +19 -5
  206. package/dist/core/workspace.js +66 -39
  207. package/dist/core/workspace.js.map +1 -1
  208. package/dist/mcp/connector-manager.d.ts +16 -1
  209. package/dist/mcp/connector-manager.js +44 -5
  210. package/dist/mcp/connector-manager.js.map +1 -1
  211. package/dist/mcp/server.d.ts +11 -3
  212. package/dist/mcp/server.js +30 -5
  213. package/dist/mcp/server.js.map +1 -1
  214. package/dist/mcp/tools.d.ts +27 -2
  215. package/dist/mcp/tools.js +544 -113
  216. package/dist/mcp/tools.js.map +1 -1
  217. package/dist/server/__tests__/helpers/graph-api.d.ts +18 -0
  218. package/dist/server/__tests__/helpers/graph-api.js +16 -0
  219. package/dist/server/__tests__/helpers/graph-api.js.map +1 -0
  220. package/dist/server/commit-audit.d.ts +26 -0
  221. package/dist/server/commit-audit.js +30 -0
  222. package/dist/server/commit-audit.js.map +1 -0
  223. package/dist/server/index.d.ts +14 -3
  224. package/dist/server/index.js +385 -177
  225. package/dist/server/index.js.map +1 -1
  226. package/dist/server/middleware/sanitize.d.ts +46 -0
  227. package/dist/server/middleware/sanitize.js +171 -0
  228. package/dist/server/middleware/sanitize.js.map +1 -0
  229. package/dist/server/routes/asset-api.js +217 -65
  230. package/dist/server/routes/asset-api.js.map +1 -1
  231. package/dist/server/routes/audit-api.d.ts +24 -0
  232. package/dist/server/routes/audit-api.js +117 -0
  233. package/dist/server/routes/audit-api.js.map +1 -0
  234. package/dist/server/routes/auth-api.js +220 -44
  235. package/dist/server/routes/auth-api.js.map +1 -1
  236. package/dist/server/routes/capture-api.d.ts +10 -3
  237. package/dist/server/routes/capture-api.js +180 -24
  238. package/dist/server/routes/capture-api.js.map +1 -1
  239. package/dist/server/routes/chat.d.ts +4 -1
  240. package/dist/server/routes/chat.js +178 -46
  241. package/dist/server/routes/chat.js.map +1 -1
  242. package/dist/server/routes/config-api.d.ts +21 -0
  243. package/dist/server/routes/config-api.js +256 -0
  244. package/dist/server/routes/config-api.js.map +1 -0
  245. package/dist/server/routes/connectors-api.js +24 -18
  246. package/dist/server/routes/connectors-api.js.map +1 -1
  247. package/dist/server/routes/event-ingest-api.d.ts +15 -0
  248. package/dist/server/routes/event-ingest-api.js +125 -0
  249. package/dist/server/routes/event-ingest-api.js.map +1 -0
  250. package/dist/server/routes/features-api.d.ts +19 -0
  251. package/dist/server/routes/features-api.js +28 -0
  252. package/dist/server/routes/features-api.js.map +1 -0
  253. package/dist/server/routes/git-http.d.ts +2 -2
  254. package/dist/server/routes/git-http.js +69 -34
  255. package/dist/server/routes/git-http.js.map +1 -1
  256. package/dist/server/routes/graph-api-access.d.ts +35 -0
  257. package/dist/server/routes/graph-api-access.js +93 -0
  258. package/dist/server/routes/graph-api-access.js.map +1 -0
  259. package/dist/server/routes/graph-api.d.ts +3 -2
  260. package/dist/server/routes/graph-api.js +1014 -315
  261. package/dist/server/routes/graph-api.js.map +1 -1
  262. package/dist/server/routes/health.d.ts +18 -0
  263. package/dist/server/routes/health.js +74 -0
  264. package/dist/server/routes/health.js.map +1 -0
  265. package/dist/server/routes/insights-api.js +156 -33
  266. package/dist/server/routes/insights-api.js.map +1 -1
  267. package/dist/server/routes/mcp.d.ts +6 -3
  268. package/dist/server/routes/mcp.js +22 -6
  269. package/dist/server/routes/mcp.js.map +1 -1
  270. package/dist/server/routes/messages-api.d.ts +3 -3
  271. package/dist/server/routes/messages-api.js +308 -120
  272. package/dist/server/routes/messages-api.js.map +1 -1
  273. package/dist/server/routes/permissions-api.d.ts +9 -2
  274. package/dist/server/routes/permissions-api.js +87 -31
  275. package/dist/server/routes/permissions-api.js.map +1 -1
  276. package/dist/server/routes/secrets-api.d.ts +36 -0
  277. package/dist/server/routes/secrets-api.js +308 -0
  278. package/dist/server/routes/secrets-api.js.map +1 -0
  279. package/dist/server/routes/settings-api.d.ts +29 -0
  280. package/dist/server/routes/settings-api.js +180 -0
  281. package/dist/server/routes/settings-api.js.map +1 -0
  282. package/dist/server/routes/workspace-api.d.ts +2 -1
  283. package/dist/server/routes/workspace-api.js +131 -20
  284. package/dist/server/routes/workspace-api.js.map +1 -1
  285. package/dist/server/routes/ws.d.ts +3 -2
  286. package/dist/server/routes/ws.js +68 -18
  287. package/dist/server/routes/ws.js.map +1 -1
  288. package/dist/server/session-manager.d.ts +48 -5
  289. package/dist/server/session-manager.js +175 -11
  290. package/dist/server/session-manager.js.map +1 -1
  291. package/dist/server/ws-hub.d.ts +134 -22
  292. package/dist/server/ws-hub.js +0 -0
  293. package/dist/server/ws-hub.js.map +1 -1
  294. package/dist/server/yjs-manager.d.ts +107 -7
  295. package/dist/server/yjs-manager.js +444 -38
  296. package/dist/server/yjs-manager.js.map +1 -1
  297. package/dist/server/yjs-persistence.d.ts +143 -0
  298. package/dist/server/yjs-persistence.js +543 -0
  299. package/dist/server/yjs-persistence.js.map +1 -0
  300. package/dist/server/yjs-text-diff.d.ts +32 -0
  301. package/dist/server/yjs-text-diff.js +61 -0
  302. package/dist/server/yjs-text-diff.js.map +1 -0
  303. package/dist/services/access-control.d.ts +53 -0
  304. package/dist/services/access-control.js +132 -0
  305. package/dist/services/access-control.js.map +1 -0
  306. package/dist/services/asset-upload-service.d.ts +53 -0
  307. package/dist/services/asset-upload-service.js +200 -0
  308. package/dist/services/asset-upload-service.js.map +1 -0
  309. package/dist/services/assets/base.d.ts +38 -0
  310. package/dist/services/assets/base.js +55 -0
  311. package/dist/services/assets/base.js.map +1 -1
  312. package/dist/services/assets/index.d.ts +41 -1
  313. package/dist/services/assets/index.js +65 -0
  314. package/dist/services/assets/index.js.map +1 -1
  315. package/dist/services/assets/local.d.ts +7 -1
  316. package/dist/services/assets/local.js +83 -0
  317. package/dist/services/assets/local.js.map +1 -1
  318. package/dist/services/assets/r2.d.ts +28 -1
  319. package/dist/services/assets/r2.js +75 -1
  320. package/dist/services/assets/r2.js.map +1 -1
  321. package/dist/services/auth-service.d.ts +184 -36
  322. package/dist/services/auth-service.js +763 -138
  323. package/dist/services/auth-service.js.map +1 -1
  324. package/dist/services/briefing.d.ts +1 -1
  325. package/dist/services/briefing.js +12 -35
  326. package/dist/services/briefing.js.map +1 -1
  327. package/dist/services/event-processor.d.ts +104 -0
  328. package/dist/services/event-processor.js +443 -0
  329. package/dist/services/event-processor.js.map +1 -0
  330. package/dist/services/folder-creation.d.ts +33 -0
  331. package/dist/services/folder-creation.js +103 -0
  332. package/dist/services/folder-creation.js.map +1 -0
  333. package/dist/services/git.d.ts +44 -0
  334. package/dist/services/git.js +162 -55
  335. package/dist/services/git.js.map +1 -1
  336. package/dist/services/heartbeat.d.ts +19 -12
  337. package/dist/services/heartbeat.js +162 -107
  338. package/dist/services/heartbeat.js.map +1 -1
  339. package/dist/services/import-service.d.ts +37 -2
  340. package/dist/services/import-service.js +222 -123
  341. package/dist/services/import-service.js.map +1 -1
  342. package/dist/services/lint-service.js +7 -14
  343. package/dist/services/lint-service.js.map +1 -1
  344. package/dist/services/markdown.js +6 -2
  345. package/dist/services/markdown.js.map +1 -1
  346. package/dist/services/message-service.d.ts +62 -21
  347. package/dist/services/message-service.js +160 -43
  348. package/dist/services/message-service.js.map +1 -1
  349. package/dist/services/personal-folder.d.ts +40 -0
  350. package/dist/services/personal-folder.js +101 -0
  351. package/dist/services/personal-folder.js.map +1 -0
  352. package/dist/services/vector-service.d.ts +73 -3
  353. package/dist/services/vector-service.js +106 -7
  354. package/dist/services/vector-service.js.map +1 -1
  355. package/dist/services/workspace-access.d.ts +106 -0
  356. package/dist/services/workspace-access.js +149 -0
  357. package/dist/services/workspace-access.js.map +1 -0
  358. package/dist/utils/git.js +3 -3
  359. package/dist/utils/git.js.map +1 -1
  360. package/dist/utils/log.d.ts +42 -0
  361. package/dist/utils/log.js +137 -0
  362. package/dist/utils/log.js.map +1 -0
  363. package/dist/utils/preflight.js +2 -2
  364. package/dist/utils/preflight.js.map +1 -1
  365. package/dist/utils/version-checker.d.ts +4 -1
  366. package/dist/utils/version-checker.js +8 -8
  367. package/dist/utils/version-checker.js.map +1 -1
  368. package/dist/web/_app/immutable/assets/0.Bp6xXZUi.css +2 -0
  369. package/dist/web/_app/immutable/assets/11.DlIf1mKh.css +1 -0
  370. package/dist/web/_app/immutable/assets/12.Dmlt28l2.css +1 -0
  371. package/dist/web/_app/immutable/assets/13.CmLrbzZp.css +1 -0
  372. package/dist/web/_app/immutable/assets/2.oo1ac0x_.css +1 -0
  373. package/dist/web/_app/immutable/assets/3.DJJsQzDb.css +1 -0
  374. package/dist/web/_app/immutable/assets/4.CZ-fNJ91.css +1 -0
  375. package/dist/web/_app/immutable/assets/AgentOverlay.BfWESlQU.css +1 -0
  376. package/dist/web/_app/immutable/assets/AssetManagerModal.ZfasE2L3.css +1 -0
  377. package/dist/web/_app/immutable/assets/CalendarView.CeNagvyK.css +1 -0
  378. package/dist/web/_app/immutable/assets/ChatPanel.po0gYtVH.css +1 -0
  379. package/dist/web/_app/immutable/assets/FolderMembersPanel.Bepw-r0F.css +1 -0
  380. package/dist/web/_app/immutable/assets/FolderMutationDialogs.BMRF6Z3z.css +1 -0
  381. package/dist/web/_app/immutable/assets/FolderPicker.B5KdvRYl.css +1 -0
  382. package/dist/web/_app/immutable/assets/GalleryView.Dy-qQjEc.css +1 -0
  383. package/dist/web/_app/immutable/assets/GraphView.BHnGIltS.css +1 -0
  384. package/dist/web/_app/immutable/assets/KanbanView.G3NZ0uHb.css +1 -0
  385. package/dist/web/_app/immutable/assets/SettingsDialog.tBn8y9DD.css +1 -0
  386. package/dist/web/_app/immutable/assets/Spinner.UBfl0pab.css +1 -0
  387. package/dist/web/_app/immutable/assets/TagFilter.DNipjifq.css +1 -0
  388. package/dist/web/_app/immutable/assets/TagPickerSheet.DJPuqnTg.css +1 -0
  389. package/dist/web/_app/immutable/assets/WorkspaceView.B9zA92UB.css +1 -0
  390. package/dist/web/_app/immutable/assets/button.BlLm4Dg1.css +1 -0
  391. package/dist/web/_app/immutable/assets/dialog.BxVal_19.css +1 -0
  392. package/dist/web/_app/immutable/assets/dist.B5hGjnfj.css +1 -0
  393. package/dist/web/_app/immutable/assets/dist.DDCWxn3B.css +1 -0
  394. package/dist/web/_app/immutable/assets/dropdown-menu.BSsUUQ3o.css +1 -0
  395. package/dist/web/_app/immutable/assets/entity-row.Bl5FyNCt.css +1 -0
  396. package/dist/web/_app/immutable/assets/greeting.Bm7lTneV.css +1 -0
  397. package/dist/web/_app/immutable/assets/ibm-plex-mono-latin-400-normal.CvHOgSBP.woff +0 -0
  398. package/dist/web/_app/immutable/assets/ibm-plex-mono-latin-400-normal.DMJ8VG8y.woff2 +0 -0
  399. package/dist/web/_app/immutable/assets/ibm-plex-mono-latin-500-normal.CB9ihrfo.woff +0 -0
  400. package/dist/web/_app/immutable/assets/ibm-plex-mono-latin-500-normal.DSY6xOcd.woff2 +0 -0
  401. package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-400-italic.CZTNEAuW.woff2 +0 -0
  402. package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-400-italic.CsGl1sm0.woff +0 -0
  403. package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-400-normal.CDDApCn2.woff2 +0 -0
  404. package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-400-normal.CYLoc0-x.woff +0 -0
  405. package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-500-italic.BNK2_mGO.woff2 +0 -0
  406. package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-500-italic.DpEwFAQM.woff +0 -0
  407. package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-500-normal.6ng42L7E.woff2 +0 -0
  408. package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-500-normal.BgVn5rGT.woff +0 -0
  409. package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-600-normal.Cu4Hd6ag.woff +0 -0
  410. package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-600-normal.CuJfVYMP.woff2 +0 -0
  411. package/dist/web/_app/immutable/assets/inline-list.DORLo4xF.css +1 -0
  412. package/dist/web/_app/immutable/assets/notifications.clodJdp5.css +1 -0
  413. package/dist/web/_app/immutable/assets/radio-group.GJJRVPUJ.css +1 -0
  414. package/dist/web/_app/immutable/assets/select.D_AV992j.css +1 -0
  415. package/dist/web/_app/immutable/chunks/-KAQXGuI2.js +1 -0
  416. package/dist/web/_app/immutable/chunks/-wzwcH00.js +1 -0
  417. package/dist/web/_app/immutable/chunks/07l_1Zpm2.js +1 -0
  418. package/dist/web/_app/immutable/chunks/38oWKNzM2.js +6 -0
  419. package/dist/web/_app/immutable/chunks/54VmiJfH.js +1 -0
  420. package/dist/web/_app/immutable/chunks/6LRpFvjo.js +185 -0
  421. package/dist/web/_app/immutable/chunks/7R0Rp3Mz.js +1 -0
  422. package/dist/web/_app/immutable/chunks/9bCTHkeS.js +1 -0
  423. package/dist/web/_app/immutable/chunks/B-822WnD2.js +22 -0
  424. package/dist/web/_app/immutable/chunks/B0HUvliX2.js +2 -0
  425. package/dist/web/_app/immutable/chunks/B12eXGpE.js +1 -0
  426. package/dist/web/_app/immutable/chunks/B5af0X0p2.js +1 -0
  427. package/dist/web/_app/immutable/chunks/B6iYNwo72.js +1 -0
  428. package/dist/web/_app/immutable/chunks/B7EMtoAB.js +1 -0
  429. package/dist/web/_app/immutable/chunks/B7L4gmPR.js +2 -0
  430. package/dist/web/_app/immutable/chunks/B9qBGxh62.js +3 -0
  431. package/dist/web/_app/immutable/chunks/BDJYj9yi.js +2 -0
  432. package/dist/web/_app/immutable/chunks/BEpMEjHO.js +1 -0
  433. package/dist/web/_app/immutable/chunks/BErNpj-S2.js +1 -0
  434. package/dist/web/_app/immutable/chunks/BHBk4zfV.js +1 -0
  435. package/dist/web/_app/immutable/chunks/BHH-JGNF.js +1 -0
  436. package/dist/web/_app/immutable/chunks/BKe5wPiN.js +1 -0
  437. package/dist/web/_app/immutable/chunks/BNTlsKDJ.js +1 -0
  438. package/dist/web/_app/immutable/chunks/BOgcXgis2.js +1 -0
  439. package/dist/web/_app/immutable/chunks/BOyyDrjn2.js +1 -0
  440. package/dist/web/_app/immutable/chunks/BPR_ufQ0.js +41 -0
  441. package/dist/web/_app/immutable/chunks/BRcFsc6v.js +5 -0
  442. package/dist/web/_app/immutable/chunks/BT3KAag_.js +3 -0
  443. package/dist/web/_app/immutable/chunks/BX2T6xiZ.js +224 -0
  444. package/dist/web/_app/immutable/chunks/BZnJ8tbJ.js +1 -0
  445. package/dist/web/_app/immutable/chunks/Ba1Yzb7u.js +1 -0
  446. package/dist/web/_app/immutable/chunks/BcsvgA3B2.js +1 -0
  447. package/dist/web/_app/immutable/chunks/BfbS7rGT.js +1 -0
  448. package/dist/web/_app/immutable/chunks/BgagL41Z.js +1 -0
  449. package/dist/web/_app/immutable/chunks/BiUh0Sik.js +1 -0
  450. package/dist/web/_app/immutable/chunks/Biajc_oU.js +1 -0
  451. package/dist/web/_app/immutable/chunks/Bl9Pzopy.js +1 -0
  452. package/dist/web/_app/immutable/chunks/BmcU05mO.js +1 -0
  453. package/dist/web/_app/immutable/chunks/Bmgp0jlT.js +1 -0
  454. package/dist/web/_app/immutable/chunks/BmgyOoEp.js +1 -0
  455. package/dist/web/_app/immutable/chunks/Bq0-jBGC2.js +1 -0
  456. package/dist/web/_app/immutable/chunks/BqGj0_GS.js +1 -0
  457. package/dist/web/_app/immutable/chunks/Bsc9K3Pz.js +1 -0
  458. package/dist/web/_app/immutable/chunks/Bssb691Q.js +1 -0
  459. package/dist/web/_app/immutable/chunks/BuzQceOn.js +1 -0
  460. package/dist/web/_app/immutable/chunks/BzeVNab0.js +1 -0
  461. package/dist/web/_app/immutable/chunks/C-OaypFN.js +1 -0
  462. package/dist/web/_app/immutable/chunks/C0fjDjvE2.js +1 -0
  463. package/dist/web/_app/immutable/chunks/C0pTXwEX.js +2 -0
  464. package/dist/web/_app/immutable/chunks/C3Enww9L2.js +1 -0
  465. package/dist/web/_app/immutable/chunks/C3oa0Cks.js +1 -0
  466. package/dist/web/_app/immutable/chunks/CC-YWp-f.js +1 -0
  467. package/dist/web/_app/immutable/chunks/CGdKvXV92.js +1 -0
  468. package/dist/web/_app/immutable/chunks/CH0siYFL.js +1 -0
  469. package/dist/web/_app/immutable/chunks/CIchr8Tk2.js +1 -0
  470. package/dist/web/_app/immutable/chunks/CK_yXDQI.js +1 -0
  471. package/dist/web/_app/immutable/chunks/CLPtMig0.js +1 -0
  472. package/dist/web/_app/immutable/chunks/CLSxJmTj.js +2 -0
  473. package/dist/web/_app/immutable/chunks/CPQNcHIB.js +3 -0
  474. package/dist/web/_app/immutable/chunks/CPt2D5aU.js +1 -0
  475. package/dist/web/_app/immutable/chunks/CQ-2yccd.js +7 -0
  476. package/dist/web/_app/immutable/chunks/CS6VEwn7.js +1 -0
  477. package/dist/web/_app/immutable/chunks/CSGIGrrK.js +1 -0
  478. package/dist/web/_app/immutable/chunks/CSsDOIrk.js +1 -0
  479. package/dist/web/_app/immutable/chunks/CVXvEWhv2.js +1 -0
  480. package/dist/web/_app/immutable/chunks/CaChtuYU2.js +1 -0
  481. package/dist/web/_app/immutable/chunks/CbiwvM3R2.js +1 -0
  482. package/dist/web/_app/immutable/chunks/CdCuBMLm.js +156 -0
  483. package/dist/web/_app/immutable/chunks/CfqjDDMu.js +1 -0
  484. package/dist/web/_app/immutable/chunks/CjGzUnEF2.js +1 -0
  485. package/dist/web/_app/immutable/chunks/Cjgbiel7.js +1 -0
  486. package/dist/web/_app/immutable/chunks/ClNEskRf.js +1 -0
  487. package/dist/web/_app/immutable/chunks/CnqSKqA62.js +1 -0
  488. package/dist/web/_app/immutable/chunks/CqyqKPuh2.js +1 -0
  489. package/dist/web/_app/immutable/chunks/CrnX-oFV.js +1 -0
  490. package/dist/web/_app/immutable/chunks/CsGahE3a.js +1 -0
  491. package/dist/web/_app/immutable/chunks/CsXIliss.js +1 -0
  492. package/dist/web/_app/immutable/chunks/Ct3quP0F.js +1 -0
  493. package/dist/web/_app/immutable/chunks/CvZaxjRC.js +1 -0
  494. package/dist/web/_app/immutable/chunks/CwD4G56R.js +5 -0
  495. package/dist/web/_app/immutable/chunks/CxalqrMB.js +1 -0
  496. package/dist/web/_app/immutable/chunks/D1Ap3evT2.js +1 -0
  497. package/dist/web/_app/immutable/chunks/D7qgUfnr.js +2487 -0
  498. package/dist/web/_app/immutable/chunks/D9-_Euza2.js +1 -0
  499. package/dist/web/_app/immutable/chunks/DCCaoKt42.js +1 -0
  500. package/dist/web/_app/immutable/chunks/DDKweOZr2.js +23 -0
  501. package/dist/web/_app/immutable/chunks/DHeU-hHI2.js +2 -0
  502. package/dist/web/_app/immutable/chunks/DHepUItL.js +1 -0
  503. package/dist/web/_app/immutable/chunks/DL_KbCKq.js +8 -0
  504. package/dist/web/_app/immutable/chunks/DNtqqgv32.js +1 -0
  505. package/dist/web/_app/immutable/chunks/DRl7vwNC2.js +184 -0
  506. package/dist/web/_app/immutable/chunks/DTc5W_V6.js +1 -0
  507. package/dist/web/_app/immutable/chunks/DZetJu9k2.js +1 -0
  508. package/dist/web/_app/immutable/chunks/DaiJs3St.js +1 -0
  509. package/dist/web/_app/immutable/chunks/DhuJRrEX2.js +1 -0
  510. package/dist/web/_app/immutable/chunks/DiP2EplP.js +3 -0
  511. package/dist/web/_app/immutable/chunks/Dm25ot5x2.js +1 -0
  512. package/dist/web/_app/immutable/chunks/DqOcb9RX2.js +83 -0
  513. package/dist/web/_app/immutable/chunks/DrbUJCAE.js +1 -0
  514. package/dist/web/_app/immutable/chunks/Dt4HY2cT2.js +2 -0
  515. package/dist/web/_app/immutable/chunks/DtGUCMv8.js +6 -0
  516. package/dist/web/_app/immutable/chunks/Du2-B3c8.js +1 -0
  517. package/dist/web/_app/immutable/chunks/DwdkE4UD2.js +1 -0
  518. package/dist/web/_app/immutable/chunks/DxWesPbn.js +1 -0
  519. package/dist/web/_app/immutable/chunks/DyYZqUV72.js +1 -0
  520. package/dist/web/_app/immutable/chunks/HfUnhvrv2.js +1 -0
  521. package/dist/web/_app/immutable/chunks/Je8-BtCk2.js +1 -0
  522. package/dist/web/_app/immutable/chunks/LAN53VbP2.js +5 -0
  523. package/dist/web/_app/immutable/chunks/MSB7eA4_.js +1 -0
  524. package/dist/web/_app/immutable/chunks/NPe9WyW1.js +1 -0
  525. package/dist/web/_app/immutable/chunks/O6IzcxXJ.js +2 -0
  526. package/dist/web/_app/immutable/chunks/SXhc9rJW.js +1 -0
  527. package/dist/web/_app/immutable/chunks/TuBvdkhV2.js +1 -0
  528. package/dist/web/_app/immutable/chunks/U40J2SnU.js +1 -0
  529. package/dist/web/_app/immutable/chunks/VS3l21Gu.js +1 -0
  530. package/dist/web/_app/immutable/chunks/YEPEvlbU2.js +1 -0
  531. package/dist/web/_app/immutable/chunks/Z7TNRcgK.js +11 -0
  532. package/dist/web/_app/immutable/chunks/ZCekGjo6.js +5 -0
  533. package/dist/web/_app/immutable/chunks/_rnxQ655.js +4 -0
  534. package/dist/web/_app/immutable/chunks/dTXrX5Bw.js +2 -0
  535. package/dist/web/_app/immutable/chunks/gNXLhsdy.js +1 -0
  536. package/dist/web/_app/immutable/chunks/hKSTEN36.js +1 -0
  537. package/dist/web/_app/immutable/chunks/jZZTbzL5.js +1 -0
  538. package/dist/web/_app/immutable/chunks/l4FIBKss.js +1 -0
  539. package/dist/web/_app/immutable/chunks/l_4snZnh2.js +1 -0
  540. package/dist/web/_app/immutable/chunks/q6R6T-lK.js +1 -0
  541. package/dist/web/_app/immutable/chunks/qgprkMcL.js +1 -0
  542. package/dist/web/_app/immutable/chunks/rOisBLC62.js +14 -0
  543. package/dist/web/_app/immutable/chunks/t08TznXc2.js +1 -0
  544. package/dist/web/_app/immutable/chunks/t62M88qj.js +1 -0
  545. package/dist/web/_app/immutable/chunks/uCohGWC42.js +1 -0
  546. package/dist/web/_app/immutable/chunks/uqxmB78b.js +1 -0
  547. package/dist/web/_app/immutable/chunks/vwTMEIJQ.js +1 -0
  548. package/dist/web/_app/immutable/chunks/w1FCELam.js +64 -0
  549. package/dist/web/_app/immutable/chunks/zmjgin7A.js +1 -0
  550. package/dist/web/_app/immutable/chunks/zumDjcgJ.js +1 -0
  551. package/dist/web/_app/immutable/entry/app.B3JEO5sy.js +2 -0
  552. package/dist/web/_app/immutable/entry/start.Dk84vnrL.js +1 -0
  553. package/dist/web/_app/immutable/nodes/0.BKLFgYTV.js +2 -0
  554. package/dist/web/_app/immutable/nodes/1.Djf_1kN1.js +1 -0
  555. package/dist/web/_app/immutable/nodes/10.De6eGWLx.js +1 -0
  556. package/dist/web/_app/immutable/nodes/11.CDsfSRTw.js +1 -0
  557. package/dist/web/_app/immutable/nodes/12.BeKkKpkY.js +1 -0
  558. package/dist/web/_app/immutable/nodes/13.BcI8xwSy.js +1 -0
  559. package/dist/web/_app/immutable/nodes/2.ZMZ-QAZ7.js +106 -0
  560. package/dist/web/_app/immutable/nodes/3.C1ZiAfle.js +2 -0
  561. package/dist/web/_app/immutable/nodes/4.Chmqy9KF.js +2 -0
  562. package/dist/web/_app/immutable/nodes/5.Bpvr_Vcq.js +1 -0
  563. package/dist/web/_app/immutable/nodes/6.BHa_gOs3.js +1 -0
  564. package/dist/web/_app/immutable/nodes/7.D0hg4Eap.js +1 -0
  565. package/dist/web/_app/immutable/nodes/8.3ZUUxJqR.js +1 -0
  566. package/dist/web/_app/immutable/nodes/9.Ducy-oqR.js +1 -0
  567. package/dist/web/_app/version.json +1 -1
  568. package/dist/web/favicon-16.png +0 -0
  569. package/dist/web/favicon-180.png +0 -0
  570. package/dist/web/favicon-32.png +0 -0
  571. package/dist/web/favicon-48.png +0 -0
  572. package/dist/web/favicon-512.png +0 -0
  573. package/dist/web/favicon.ico +0 -0
  574. package/dist/web/favicon.svg +8 -0
  575. package/dist/web/index.html +64 -21
  576. package/dist/web/studiograph-logomark.svg +29 -0
  577. package/package.json +28 -14
  578. package/dist/agent/role-loader.d.ts +0 -24
  579. package/dist/agent/role-loader.js +0 -67
  580. package/dist/agent/role-loader.js.map +0 -1
  581. package/dist/agent/skills/enrich-entities.md +0 -136
  582. package/dist/agent/skills/obsidian-source-setup.md +0 -246
  583. package/dist/agent/skills/skill-loader.d.ts +0 -48
  584. package/dist/agent/skills/skill-loader.js +0 -166
  585. package/dist/agent/skills/skill-loader.js.map +0 -1
  586. package/dist/agent/skills/sync-configuration.md +0 -119
  587. package/dist/agent/skills/sync-setup.md +0 -82
  588. package/dist/agent/tools/sync-tools.d.ts +0 -35
  589. package/dist/agent/tools/sync-tools.js +0 -992
  590. package/dist/agent/tools/sync-tools.js.map +0 -1
  591. package/dist/auth/github.d.ts +0 -56
  592. package/dist/auth/github.js +0 -169
  593. package/dist/auth/github.js.map +0 -1
  594. package/dist/cli/commands/access.d.ts +0 -11
  595. package/dist/cli/commands/access.js +0 -156
  596. package/dist/cli/commands/access.js.map +0 -1
  597. package/dist/cli/commands/app.d.ts +0 -7
  598. package/dist/cli/commands/app.js +0 -268
  599. package/dist/cli/commands/app.js.map +0 -1
  600. package/dist/cli/commands/auth.d.ts +0 -10
  601. package/dist/cli/commands/auth.js +0 -79
  602. package/dist/cli/commands/auth.js.map +0 -1
  603. package/dist/cli/commands/collection.d.ts +0 -10
  604. package/dist/cli/commands/collection.js +0 -185
  605. package/dist/cli/commands/collection.js.map +0 -1
  606. package/dist/cli/commands/enrich.d.ts +0 -11
  607. package/dist/cli/commands/enrich.js +0 -135
  608. package/dist/cli/commands/enrich.js.map +0 -1
  609. package/dist/cli/commands/graphrag.d.ts +0 -12
  610. package/dist/cli/commands/graphrag.js +0 -122
  611. package/dist/cli/commands/graphrag.js.map +0 -1
  612. package/dist/cli/commands/members.d.ts +0 -11
  613. package/dist/cli/commands/members.js +0 -230
  614. package/dist/cli/commands/members.js.map +0 -1
  615. package/dist/cli/commands/org.d.ts +0 -10
  616. package/dist/cli/commands/org.js +0 -132
  617. package/dist/cli/commands/org.js.map +0 -1
  618. package/dist/cli/commands/provision.d.ts +0 -8
  619. package/dist/cli/commands/provision.js +0 -116
  620. package/dist/cli/commands/provision.js.map +0 -1
  621. package/dist/cli/commands/resolve.d.ts +0 -8
  622. package/dist/cli/commands/resolve.js +0 -85
  623. package/dist/cli/commands/resolve.js.map +0 -1
  624. package/dist/cli/commands/review.d.ts +0 -19
  625. package/dist/cli/commands/review.js +0 -128
  626. package/dist/cli/commands/review.js.map +0 -1
  627. package/dist/cli/commands/source.d.ts +0 -16
  628. package/dist/cli/commands/source.js +0 -159
  629. package/dist/cli/commands/source.js.map +0 -1
  630. package/dist/cli/commands/sync-collection.d.ts +0 -14
  631. package/dist/cli/commands/sync-collection.js +0 -355
  632. package/dist/cli/commands/sync-collection.js.map +0 -1
  633. package/dist/cli/commands/sync-entities.d.ts +0 -13
  634. package/dist/cli/commands/sync-entities.js +0 -242
  635. package/dist/cli/commands/sync-entities.js.map +0 -1
  636. package/dist/cli/commands/team.d.ts +0 -12
  637. package/dist/cli/commands/team.js +0 -185
  638. package/dist/cli/commands/team.js.map +0 -1
  639. package/dist/cli/sync-review-interactive.d.ts +0 -31
  640. package/dist/cli/sync-review-interactive.js +0 -393
  641. package/dist/cli/sync-review-interactive.js.map +0 -1
  642. package/dist/core/migration-runner.d.ts +0 -42
  643. package/dist/core/migration-runner.js +0 -232
  644. package/dist/core/migration-runner.js.map +0 -1
  645. package/dist/core/migration-types.d.ts +0 -101
  646. package/dist/core/migration-types.js +0 -21
  647. package/dist/core/migration-types.js.map +0 -1
  648. package/dist/core/migrations/20260219-formalize-memory-location.d.ts +0 -2
  649. package/dist/core/migrations/20260219-formalize-memory-location.js +0 -35
  650. package/dist/core/migrations/20260219-formalize-memory-location.js.map +0 -1
  651. package/dist/core/migrations/20260220-add-workspace-metadata.d.ts +0 -12
  652. package/dist/core/migrations/20260220-add-workspace-metadata.js +0 -65
  653. package/dist/core/migrations/20260220-add-workspace-metadata.js.map +0 -1
  654. package/dist/core/migrations/20260220-add-workspace-readme.d.ts +0 -11
  655. package/dist/core/migrations/20260220-add-workspace-readme.js +0 -82
  656. package/dist/core/migrations/20260220-add-workspace-readme.js.map +0 -1
  657. package/dist/core/migrations/20260220-migrate-yaml-to-json.d.ts +0 -9
  658. package/dist/core/migrations/20260220-migrate-yaml-to-json.js +0 -64
  659. package/dist/core/migrations/20260220-migrate-yaml-to-json.js.map +0 -1
  660. package/dist/core/migrations/index.d.ts +0 -11
  661. package/dist/core/migrations/index.js +0 -23
  662. package/dist/core/migrations/index.js.map +0 -1
  663. package/dist/integrations/asana.d.ts +0 -26
  664. package/dist/integrations/asana.js +0 -78
  665. package/dist/integrations/asana.js.map +0 -1
  666. package/dist/integrations/figma-local.d.ts +0 -16
  667. package/dist/integrations/figma-local.js +0 -10
  668. package/dist/integrations/figma-local.js.map +0 -1
  669. package/dist/integrations/figma.d.ts +0 -17
  670. package/dist/integrations/figma.js +0 -17
  671. package/dist/integrations/figma.js.map +0 -1
  672. package/dist/integrations/granola.d.ts +0 -24
  673. package/dist/integrations/granola.js +0 -60
  674. package/dist/integrations/granola.js.map +0 -1
  675. package/dist/integrations/linear.d.ts +0 -14
  676. package/dist/integrations/linear.js +0 -80
  677. package/dist/integrations/linear.js.map +0 -1
  678. package/dist/integrations/paper-local.d.ts +0 -14
  679. package/dist/integrations/paper-local.js +0 -10
  680. package/dist/integrations/paper-local.js.map +0 -1
  681. package/dist/integrations/paper.d.ts +0 -2
  682. package/dist/integrations/paper.js +0 -10
  683. package/dist/integrations/paper.js.map +0 -1
  684. package/dist/integrations/pipedrive.d.ts +0 -26
  685. package/dist/integrations/pipedrive.js +0 -97
  686. package/dist/integrations/pipedrive.js.map +0 -1
  687. package/dist/integrations/registry.d.ts +0 -8
  688. package/dist/integrations/registry.js +0 -7
  689. package/dist/integrations/registry.js.map +0 -1
  690. package/dist/integrations/types.d.ts +0 -43
  691. package/dist/integrations/types.js +0 -5
  692. package/dist/integrations/types.js.map +0 -1
  693. package/dist/lib/lib/utils.d.ts +0 -2
  694. package/dist/lib/lib/utils.js +0 -6
  695. package/dist/lib/lib/utils.js.map +0 -1
  696. package/dist/mcp/connectors/asana.d.ts +0 -2
  697. package/dist/mcp/connectors/asana.js +0 -20
  698. package/dist/mcp/connectors/asana.js.map +0 -1
  699. package/dist/mcp/connectors/definitions.d.ts +0 -45
  700. package/dist/mcp/connectors/definitions.js +0 -32
  701. package/dist/mcp/connectors/definitions.js.map +0 -1
  702. package/dist/mcp/connectors/figma.d.ts +0 -5
  703. package/dist/mcp/connectors/figma.js +0 -21
  704. package/dist/mcp/connectors/figma.js.map +0 -1
  705. package/dist/mcp/connectors/gdrive.d.ts +0 -2
  706. package/dist/mcp/connectors/gdrive.js +0 -20
  707. package/dist/mcp/connectors/gdrive.js.map +0 -1
  708. package/dist/mcp/connectors/granola.d.ts +0 -2
  709. package/dist/mcp/connectors/granola.js +0 -12
  710. package/dist/mcp/connectors/granola.js.map +0 -1
  711. package/dist/mcp/connectors/linear.d.ts +0 -2
  712. package/dist/mcp/connectors/linear.js +0 -19
  713. package/dist/mcp/connectors/linear.js.map +0 -1
  714. package/dist/mcp/connectors/obsidian.d.ts +0 -2
  715. package/dist/mcp/connectors/obsidian.js +0 -19
  716. package/dist/mcp/connectors/obsidian.js.map +0 -1
  717. package/dist/mcp/connectors/pipedrive.d.ts +0 -2
  718. package/dist/mcp/connectors/pipedrive.js +0 -20
  719. package/dist/mcp/connectors/pipedrive.js.map +0 -1
  720. package/dist/mcp/connectors/slack.d.ts +0 -2
  721. package/dist/mcp/connectors/slack.js +0 -21
  722. package/dist/mcp/connectors/slack.js.map +0 -1
  723. package/dist/mcp/server-oauth-provider.d.ts +0 -56
  724. package/dist/mcp/server-oauth-provider.js +0 -142
  725. package/dist/mcp/server-oauth-provider.js.map +0 -1
  726. package/dist/server/agents/agent-loader.d.ts +0 -16
  727. package/dist/server/agents/agent-loader.js +0 -175
  728. package/dist/server/agents/agent-loader.js.map +0 -1
  729. package/dist/server/agents/agent-runner.d.ts +0 -33
  730. package/dist/server/agents/agent-runner.js +0 -187
  731. package/dist/server/agents/agent-runner.js.map +0 -1
  732. package/dist/server/agents/agent-runtime.d.ts +0 -39
  733. package/dist/server/agents/agent-runtime.js +0 -214
  734. package/dist/server/agents/agent-runtime.js.map +0 -1
  735. package/dist/server/agents/base-agent.d.ts +0 -30
  736. package/dist/server/agents/base-agent.js +0 -88
  737. package/dist/server/agents/base-agent.js.map +0 -1
  738. package/dist/server/agents/executive-digest.d.ts +0 -16
  739. package/dist/server/agents/executive-digest.js +0 -115
  740. package/dist/server/agents/executive-digest.js.map +0 -1
  741. package/dist/server/agents/graph-steward.d.ts +0 -16
  742. package/dist/server/agents/graph-steward.js +0 -59
  743. package/dist/server/agents/graph-steward.js.map +0 -1
  744. package/dist/server/agents/meeting-followup.d.ts +0 -16
  745. package/dist/server/agents/meeting-followup.js +0 -88
  746. package/dist/server/agents/meeting-followup.js.map +0 -1
  747. package/dist/server/agents/pipeline-monitor.d.ts +0 -15
  748. package/dist/server/agents/pipeline-monitor.js +0 -86
  749. package/dist/server/agents/pipeline-monitor.js.map +0 -1
  750. package/dist/server/agents/types.d.ts +0 -81
  751. package/dist/server/agents/types.js +0 -41
  752. package/dist/server/agents/types.js.map +0 -1
  753. package/dist/server/chrome/chrome.css +0 -691
  754. package/dist/server/chrome/chrome.js +0 -374
  755. package/dist/server/collab-authority.d.ts +0 -70
  756. package/dist/server/collab-authority.js +0 -218
  757. package/dist/server/collab-authority.js.map +0 -1
  758. package/dist/server/collab.d.ts +0 -29
  759. package/dist/server/collab.js +0 -195
  760. package/dist/server/collab.js.map +0 -1
  761. package/dist/server/commit-scheduler.d.ts +0 -39
  762. package/dist/server/commit-scheduler.js +0 -113
  763. package/dist/server/commit-scheduler.js.map +0 -1
  764. package/dist/server/plugin-loader.d.ts +0 -53
  765. package/dist/server/plugin-loader.js +0 -155
  766. package/dist/server/plugin-loader.js.map +0 -1
  767. package/dist/server/routes/agents-api.d.ts +0 -10
  768. package/dist/server/routes/agents-api.js +0 -25
  769. package/dist/server/routes/agents-api.js.map +0 -1
  770. package/dist/server/routes/collab.d.ts +0 -6
  771. package/dist/server/routes/collab.js +0 -10
  772. package/dist/server/routes/collab.js.map +0 -1
  773. package/dist/server/routes/git-api.d.ts +0 -9
  774. package/dist/server/routes/git-api.js +0 -82
  775. package/dist/server/routes/git-api.js.map +0 -1
  776. package/dist/server/routes/meeting-ingest-api.d.ts +0 -14
  777. package/dist/server/routes/meeting-ingest-api.js +0 -98
  778. package/dist/server/routes/meeting-ingest-api.js.map +0 -1
  779. package/dist/server/routes/sync-api.d.ts +0 -26
  780. package/dist/server/routes/sync-api.js +0 -848
  781. package/dist/server/routes/sync-api.js.map +0 -1
  782. package/dist/server/routes/webhook.d.ts +0 -14
  783. package/dist/server/routes/webhook.js +0 -102
  784. package/dist/server/routes/webhook.js.map +0 -1
  785. package/dist/services/batch-processor.d.ts +0 -49
  786. package/dist/services/batch-processor.js +0 -166
  787. package/dist/services/batch-processor.js.map +0 -1
  788. package/dist/services/csv-service.d.ts +0 -36
  789. package/dist/services/csv-service.js +0 -143
  790. package/dist/services/csv-service.js.map +0 -1
  791. package/dist/services/github-provisioner.d.ts +0 -36
  792. package/dist/services/github-provisioner.js +0 -126
  793. package/dist/services/github-provisioner.js.map +0 -1
  794. package/dist/services/github-service.d.ts +0 -99
  795. package/dist/services/github-service.js +0 -310
  796. package/dist/services/github-service.js.map +0 -1
  797. package/dist/services/image-import-service.d.ts +0 -24
  798. package/dist/services/image-import-service.js +0 -108
  799. package/dist/services/image-import-service.js.map +0 -1
  800. package/dist/services/insights.d.ts +0 -38
  801. package/dist/services/insights.js +0 -269
  802. package/dist/services/insights.js.map +0 -1
  803. package/dist/services/meeting-processor.d.ts +0 -91
  804. package/dist/services/meeting-processor.js +0 -331
  805. package/dist/services/meeting-processor.js.map +0 -1
  806. package/dist/services/sync/collection-sync.d.ts +0 -60
  807. package/dist/services/sync/collection-sync.js +0 -509
  808. package/dist/services/sync/collection-sync.js.map +0 -1
  809. package/dist/services/sync/commit.d.ts +0 -60
  810. package/dist/services/sync/commit.js +0 -354
  811. package/dist/services/sync/commit.js.map +0 -1
  812. package/dist/services/sync/context-index.d.ts +0 -69
  813. package/dist/services/sync/context-index.js +0 -280
  814. package/dist/services/sync/context-index.js.map +0 -1
  815. package/dist/services/sync/data-fetcher.d.ts +0 -31
  816. package/dist/services/sync/data-fetcher.js +0 -12
  817. package/dist/services/sync/data-fetcher.js.map +0 -1
  818. package/dist/services/sync/derive.d.ts +0 -34
  819. package/dist/services/sync/derive.js +0 -164
  820. package/dist/services/sync/derive.js.map +0 -1
  821. package/dist/services/sync/enrichment-state.d.ts +0 -31
  822. package/dist/services/sync/enrichment-state.js +0 -63
  823. package/dist/services/sync/enrichment-state.js.map +0 -1
  824. package/dist/services/sync/enrichment.d.ts +0 -25
  825. package/dist/services/sync/enrichment.js +0 -121
  826. package/dist/services/sync/enrichment.js.map +0 -1
  827. package/dist/services/sync/entity-refresh.d.ts +0 -30
  828. package/dist/services/sync/entity-refresh.js +0 -275
  829. package/dist/services/sync/entity-refresh.js.map +0 -1
  830. package/dist/services/sync/frontmatter-extractor.d.ts +0 -40
  831. package/dist/services/sync/frontmatter-extractor.js +0 -279
  832. package/dist/services/sync/frontmatter-extractor.js.map +0 -1
  833. package/dist/services/sync/graph-match-state.d.ts +0 -33
  834. package/dist/services/sync/graph-match-state.js +0 -61
  835. package/dist/services/sync/graph-match-state.js.map +0 -1
  836. package/dist/services/sync/graph-match.d.ts +0 -53
  837. package/dist/services/sync/graph-match.js +0 -316
  838. package/dist/services/sync/graph-match.js.map +0 -1
  839. package/dist/services/sync/graphrag-client.d.ts +0 -43
  840. package/dist/services/sync/graphrag-client.js +0 -94
  841. package/dist/services/sync/graphrag-client.js.map +0 -1
  842. package/dist/services/sync/graphrag-config.d.ts +0 -16
  843. package/dist/services/sync/graphrag-config.js +0 -39
  844. package/dist/services/sync/graphrag-config.js.map +0 -1
  845. package/dist/services/sync/graphrag-context.d.ts +0 -14
  846. package/dist/services/sync/graphrag-context.js +0 -109
  847. package/dist/services/sync/graphrag-context.js.map +0 -1
  848. package/dist/services/sync/graphrag-indexer.d.ts +0 -30
  849. package/dist/services/sync/graphrag-indexer.js +0 -358
  850. package/dist/services/sync/graphrag-indexer.js.map +0 -1
  851. package/dist/services/sync/llm.d.ts +0 -32
  852. package/dist/services/sync/llm.js +0 -115
  853. package/dist/services/sync/llm.js.map +0 -1
  854. package/dist/services/sync/mcp-client.d.ts +0 -71
  855. package/dist/services/sync/mcp-client.js +0 -299
  856. package/dist/services/sync/mcp-client.js.map +0 -1
  857. package/dist/services/sync/model-factory.d.ts +0 -13
  858. package/dist/services/sync/model-factory.js +0 -38
  859. package/dist/services/sync/model-factory.js.map +0 -1
  860. package/dist/services/sync/name-quality.d.ts +0 -31
  861. package/dist/services/sync/name-quality.js +0 -60
  862. package/dist/services/sync/name-quality.js.map +0 -1
  863. package/dist/services/sync/output-schemas.d.ts +0 -92
  864. package/dist/services/sync/output-schemas.js +0 -43
  865. package/dist/services/sync/output-schemas.js.map +0 -1
  866. package/dist/services/sync/prompts.d.ts +0 -19
  867. package/dist/services/sync/prompts.js +0 -128
  868. package/dist/services/sync/prompts.js.map +0 -1
  869. package/dist/services/sync/reconciler.d.ts +0 -48
  870. package/dist/services/sync/reconciler.js +0 -294
  871. package/dist/services/sync/reconciler.js.map +0 -1
  872. package/dist/services/sync/rest-client.d.ts +0 -40
  873. package/dist/services/sync/rest-client.js +0 -100
  874. package/dist/services/sync/rest-client.js.map +0 -1
  875. package/dist/services/sync/source-config.d.ts +0 -67
  876. package/dist/services/sync/source-config.js +0 -304
  877. package/dist/services/sync/source-config.js.map +0 -1
  878. package/dist/services/sync/source-definitions/asana.d.ts +0 -14
  879. package/dist/services/sync/source-definitions/asana.js +0 -42
  880. package/dist/services/sync/source-definitions/asana.js.map +0 -1
  881. package/dist/services/sync/source-definitions/definitions.d.ts +0 -8
  882. package/dist/services/sync/source-definitions/definitions.js +0 -8
  883. package/dist/services/sync/source-definitions/definitions.js.map +0 -1
  884. package/dist/services/sync/source-definitions/gdrive.d.ts +0 -16
  885. package/dist/services/sync/source-definitions/gdrive.js +0 -68
  886. package/dist/services/sync/source-definitions/gdrive.js.map +0 -1
  887. package/dist/services/sync/source-definitions/granola.d.ts +0 -2
  888. package/dist/services/sync/source-definitions/granola.js +0 -21
  889. package/dist/services/sync/source-definitions/granola.js.map +0 -1
  890. package/dist/services/sync/source-definitions/linear.d.ts +0 -2
  891. package/dist/services/sync/source-definitions/linear.js +0 -62
  892. package/dist/services/sync/source-definitions/linear.js.map +0 -1
  893. package/dist/services/sync/source-definitions/obsidian.d.ts +0 -2
  894. package/dist/services/sync/source-definitions/obsidian.js +0 -55
  895. package/dist/services/sync/source-definitions/obsidian.js.map +0 -1
  896. package/dist/services/sync/source-definitions/pipedrive.d.ts +0 -2
  897. package/dist/services/sync/source-definitions/pipedrive.js +0 -43
  898. package/dist/services/sync/source-definitions/pipedrive.js.map +0 -1
  899. package/dist/services/sync/staging.d.ts +0 -53
  900. package/dist/services/sync/staging.js +0 -130
  901. package/dist/services/sync/staging.js.map +0 -1
  902. package/dist/services/sync/structured-extractor.d.ts +0 -57
  903. package/dist/services/sync/structured-extractor.js +0 -584
  904. package/dist/services/sync/structured-extractor.js.map +0 -1
  905. package/dist/services/sync/sync-runner.d.ts +0 -32
  906. package/dist/services/sync/sync-runner.js +0 -195
  907. package/dist/services/sync/sync-runner.js.map +0 -1
  908. package/dist/services/sync/sync-state.d.ts +0 -43
  909. package/dist/services/sync/sync-state.js +0 -154
  910. package/dist/services/sync/sync-state.js.map +0 -1
  911. package/dist/services/sync/types.d.ts +0 -381
  912. package/dist/services/sync/types.js +0 -8
  913. package/dist/services/sync/types.js.map +0 -1
  914. package/dist/services/sync/unstructured-extractor.d.ts +0 -27
  915. package/dist/services/sync/unstructured-extractor.js +0 -185
  916. package/dist/services/sync/unstructured-extractor.js.map +0 -1
  917. package/dist/utils/merge-resolver.d.ts +0 -59
  918. package/dist/utils/merge-resolver.js +0 -303
  919. package/dist/utils/merge-resolver.js.map +0 -1
  920. package/dist/utils/workspace-config.d.ts +0 -8
  921. package/dist/utils/workspace-config.js +0 -22
  922. package/dist/utils/workspace-config.js.map +0 -1
  923. package/dist/web/_app/immutable/assets/0.QUqBcyuu.css +0 -1
  924. package/dist/web/_app/immutable/assets/11.2jzI3cZY.css +0 -1
  925. package/dist/web/_app/immutable/assets/12.CT4xL4K3.css +0 -1
  926. package/dist/web/_app/immutable/assets/13.BUrHkYry.css +0 -1
  927. package/dist/web/_app/immutable/assets/2.5jNm7Pm1.css +0 -1
  928. package/dist/web/_app/immutable/assets/3.BxdqT7zi.css +0 -1
  929. package/dist/web/_app/immutable/assets/4.BjPwtpNd.css +0 -1
  930. package/dist/web/_app/immutable/assets/5.DFeGxLYf.css +0 -1
  931. package/dist/web/_app/immutable/assets/6.DXZr_rI_.css +0 -1
  932. package/dist/web/_app/immutable/assets/GraphView.D9ePpZez.css +0 -1
  933. package/dist/web/_app/immutable/assets/Toaster.rN8r_Hzv.css +0 -1
  934. package/dist/web/_app/immutable/assets/ViewToolbar.CZjsdjy4.css +0 -1
  935. package/dist/web/_app/immutable/assets/WorkspaceView.B-1DbivC.css +0 -1
  936. package/dist/web/_app/immutable/assets/wikilinks.CtSydmQl.css +0 -1
  937. package/dist/web/_app/immutable/chunks/3--t-YNA.js +0 -1
  938. package/dist/web/_app/immutable/chunks/5OU-4caN.js +0 -1
  939. package/dist/web/_app/immutable/chunks/7S2LGqoP.js +0 -2
  940. package/dist/web/_app/immutable/chunks/B6FUcAv5.js +0 -1
  941. package/dist/web/_app/immutable/chunks/B9gDgSlp.js +0 -1
  942. package/dist/web/_app/immutable/chunks/BB_5th5W.js +0 -3383
  943. package/dist/web/_app/immutable/chunks/BFtIWl5z.js +0 -1
  944. package/dist/web/_app/immutable/chunks/BHKpz6_7.js +0 -1
  945. package/dist/web/_app/immutable/chunks/BNfrhBeA.js +0 -1
  946. package/dist/web/_app/immutable/chunks/BgO5HlCg.js +0 -2
  947. package/dist/web/_app/immutable/chunks/BqnODQDb.js +0 -1
  948. package/dist/web/_app/immutable/chunks/BrebtJNG.js +0 -18
  949. package/dist/web/_app/immutable/chunks/BvIFhEn7.js +0 -2
  950. package/dist/web/_app/immutable/chunks/BvfdTHv8.js +0 -1
  951. package/dist/web/_app/immutable/chunks/BxGm65PH.js +0 -1
  952. package/dist/web/_app/immutable/chunks/BxyxnEKw.js +0 -1
  953. package/dist/web/_app/immutable/chunks/BygFIGq4.js +0 -1
  954. package/dist/web/_app/immutable/chunks/Bz0ZjVQE.js +0 -2
  955. package/dist/web/_app/immutable/chunks/C0LVZhvn.js +0 -1
  956. package/dist/web/_app/immutable/chunks/CIb_hHMy.js +0 -1
  957. package/dist/web/_app/immutable/chunks/CNKK6nZb.js +0 -1
  958. package/dist/web/_app/immutable/chunks/CQ5FjHZK.js +0 -1
  959. package/dist/web/_app/immutable/chunks/CSVbGg_b.js +0 -5
  960. package/dist/web/_app/immutable/chunks/CX_61fY8.js +0 -5
  961. package/dist/web/_app/immutable/chunks/CYrVHOHA.js +0 -1
  962. package/dist/web/_app/immutable/chunks/CaEaeeKb.js +0 -1
  963. package/dist/web/_app/immutable/chunks/Ch6e_wpP.js +0 -1
  964. package/dist/web/_app/immutable/chunks/Ci4zzCVB.js +0 -2
  965. package/dist/web/_app/immutable/chunks/CknqBVQE.js +0 -1
  966. package/dist/web/_app/immutable/chunks/Cmfiirqp.js +0 -4
  967. package/dist/web/_app/immutable/chunks/CqkleIqs.js +0 -1
  968. package/dist/web/_app/immutable/chunks/CrFpCC6p.js +0 -1
  969. package/dist/web/_app/immutable/chunks/CxX8hcT6.js +0 -1
  970. package/dist/web/_app/immutable/chunks/D0T1ps1G.js +0 -1
  971. package/dist/web/_app/immutable/chunks/D2ZEj9xD.js +0 -1
  972. package/dist/web/_app/immutable/chunks/D3sy0IkA.js +0 -2
  973. package/dist/web/_app/immutable/chunks/DFYOFE3o.js +0 -1
  974. package/dist/web/_app/immutable/chunks/DHWoAC4y.js +0 -1
  975. package/dist/web/_app/immutable/chunks/DYYBfXU3.js +0 -1
  976. package/dist/web/_app/immutable/chunks/DiNPRA8T.js +0 -1
  977. package/dist/web/_app/immutable/chunks/DkjsqHkM.js +0 -1
  978. package/dist/web/_app/immutable/chunks/DnL9f0lc.js +0 -7
  979. package/dist/web/_app/immutable/chunks/DpMCynC3.js +0 -1
  980. package/dist/web/_app/immutable/chunks/DqrybUuA.js +0 -1
  981. package/dist/web/_app/immutable/chunks/DsnmJJEf.js +0 -1
  982. package/dist/web/_app/immutable/chunks/KyjzlWRN.js +0 -2
  983. package/dist/web/_app/immutable/chunks/O9MAeAgV.js +0 -5
  984. package/dist/web/_app/immutable/chunks/PPVm8Dsz.js +0 -1
  985. package/dist/web/_app/immutable/chunks/SKSQ4m85.js +0 -2
  986. package/dist/web/_app/immutable/chunks/WEBTyD-H.js +0 -1
  987. package/dist/web/_app/immutable/chunks/WSUKABI_.js +0 -1
  988. package/dist/web/_app/immutable/chunks/bBXT5iJP.js +0 -1
  989. package/dist/web/_app/immutable/chunks/vxEtkL8z.js +0 -1
  990. package/dist/web/_app/immutable/chunks/wDIGUaoU.js +0 -3
  991. package/dist/web/_app/immutable/chunks/xkNhbnff.js +0 -1
  992. package/dist/web/_app/immutable/chunks/zoVX1Zab.js +0 -1
  993. package/dist/web/_app/immutable/entry/app.GfE4z2GO.js +0 -2
  994. package/dist/web/_app/immutable/entry/start.H-LFJK9u.js +0 -1
  995. package/dist/web/_app/immutable/nodes/0.Bwi7tTNC.js +0 -2
  996. package/dist/web/_app/immutable/nodes/1.DgNJPEWk.js +0 -1
  997. package/dist/web/_app/immutable/nodes/10.BW_XCD2O.js +0 -1
  998. package/dist/web/_app/immutable/nodes/11.DM7RX3Ms.js +0 -1
  999. package/dist/web/_app/immutable/nodes/12.DKgssZo8.js +0 -1
  1000. package/dist/web/_app/immutable/nodes/13.DCdmTre-.js +0 -1
  1001. package/dist/web/_app/immutable/nodes/2.DOA5QBVG.js +0 -266
  1002. package/dist/web/_app/immutable/nodes/3.bpoHQwAI.js +0 -5
  1003. package/dist/web/_app/immutable/nodes/4.ye0Vy7A1.js +0 -11
  1004. package/dist/web/_app/immutable/nodes/5.rxpnYrx_.js +0 -4
  1005. package/dist/web/_app/immutable/nodes/6.DP8HSxtc.js +0 -1
  1006. package/dist/web/_app/immutable/nodes/7.C1h4Bf-E.js +0 -1
  1007. package/dist/web/_app/immutable/nodes/8.BzmQ7S7Y.js +0 -1
  1008. package/dist/web/_app/immutable/nodes/9.CEakpXpF.js +0 -1
@@ -5,42 +5,211 @@
5
5
  * Database lives at .studiograph/auth.db inside the workspace.
6
6
  *
7
7
  * Auth state is portable via .studiograph/auth-seed.json — a JSON export of
8
- * all users (with bcrypt hashes) and the JWT secret. The seed file is
9
- * committed to the config repo so it syncs between local and Railway:
8
+ * all users (with bcrypt hashes) and the JWT signing secret. The seed file
9
+ * is committed to the config repo so it syncs between local and Railway:
10
10
  * local setup → commit → push → Railway redeploy → users restored from seed
11
+ *
12
+ * ─────────────────────────────────────────────────────────────────────────
13
+ * SECURITY — config repo is a secret store
14
+ * ─────────────────────────────────────────────────────────────────────────
15
+ * `auth-seed.json` carries:
16
+ * • the JWT signing secret (anyone holding this can mint sessions for any
17
+ * user in the workspace, bypassing password auth entirely)
18
+ * • bcrypt password hashes for every user (offline-crackable; bcrypt cost
19
+ * 12 buys time but not invulnerability against weak passwords)
20
+ *
21
+ * Treat the .studiograph/ config repo with the same operational care as any
22
+ * other secret store: private remote, restricted contributors, never made
23
+ * public. The audit (2026-05) flagged this surface specifically — `getSeedData`
24
+ * returns these fields by design (they're how the seed-restore flow works on
25
+ * Railway redeploys) but every caller needs to understand they are exporting
26
+ * raw secrets.
27
+ *
28
+ * To override the file-based JWT secret in production (e.g. Railway), set
29
+ * the STUDIOGRAPH_JWT_SECRET environment variable. The env var wins over the
30
+ * `auth-secret` file and the seed file: a leaked seed file alone won't let
31
+ * an attacker mint tokens for an environment with a different in-process
32
+ * secret. Rotation: change the env var and restart — all extant tokens
33
+ * invalidate together.
11
34
  */
12
35
  import Database from 'better-sqlite3';
13
36
  import bcrypt from 'bcryptjs';
14
37
  import jwt from 'jsonwebtoken';
15
- import { randomBytes } from 'crypto';
38
+ import { randomBytes, createHash } from 'crypto';
16
39
  import { spawnSync } from 'child_process';
17
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
40
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync, chmodSync } from 'fs';
18
41
  import { join } from 'path';
19
- /** Check if a role has admin-level privileges (owner or admin). */
20
- export function isAdminOrOwner(role) {
21
- return role === 'owner' || role === 'admin';
42
+ import { pickDefaultAccentColor } from '../core/accent-palette.js';
43
+ import { sanitizeAuthSeed } from '../core/secrets/bundle.js';
44
+ import { assertAuthDbConsistent, decryptOAuthTokenData, decryptUserPreferencesSettings, encryptOAuthTokenData, encryptUserPreferencesSettings, isAuthDbPhase8Migrated, markAuthDbPhase8Migrated, } from '../core/secrets/auth-db-migration.js';
45
+ import { sanitize } from '../server/middleware/sanitize.js';
46
+ import { log } from '../utils/log.js';
47
+ /**
48
+ * Resolve the JWT signing secret. Priority order:
49
+ * 1. STUDIOGRAPH_JWT_SECRET env var — production override; not committed,
50
+ * not on disk, can rotate by restarting the process.
51
+ * 2. .studiograph/auth-secret file — local-dev / single-host stable secret.
52
+ * 3. auth-seed.json's `jwtSecret` field — restored from a portable seed.
53
+ * 4. Freshly generated 32-byte random — first-run fallback; written to
54
+ * auth-secret so subsequent restarts pick path 2.
55
+ *
56
+ * Phase 4: production (RAILWAY_ENVIRONMENT set) MUST hit path 1. The
57
+ * file/seed/generated fallbacks are local-dev only — a misconfigured
58
+ * Railway service should fail loud rather than silently mint a fresh
59
+ * secret per restart (which would invalidate every active session and
60
+ * mask the misconfiguration).
61
+ *
62
+ * Returns the secret AND the source so the caller can log/operator-visible
63
+ * which path won (without leaking the secret itself).
64
+ */
65
+ function resolveJwtSecret(sgDir) {
66
+ // 1. Env var wins. Trim — accidental newline at the end of `secret > file`
67
+ // happens often enough that we shouldn't punish operators for it.
68
+ const envSecret = process.env.STUDIOGRAPH_JWT_SECRET?.trim();
69
+ if (envSecret) {
70
+ if (envSecret.length < 16) {
71
+ // Refuse comically weak secrets so an operator who set
72
+ // STUDIOGRAPH_JWT_SECRET=hunter2 doesn't accidentally weaken the
73
+ // workspace versus the auto-generated 64-hex-char fallback.
74
+ throw new Error('STUDIOGRAPH_JWT_SECRET is set but too short (need ≥16 chars). ' +
75
+ 'Generate one with: openssl rand -hex 32');
76
+ }
77
+ return { secret: envSecret, source: 'env' };
78
+ }
79
+ // Phase 4 production gate. File/seed/generated paths below are local-dev
80
+ // only; a Railway deploy reaching them indicates the backfill missed this
81
+ // service (or someone unset the var). Hard-fail so an operator notices
82
+ // immediately instead of silently rotating the JWT on every restart.
83
+ if (process.env.RAILWAY_ENVIRONMENT) {
84
+ throw new Error('auth: STUDIOGRAPH_JWT_SECRET is required in production (RAILWAY_ENVIRONMENT is set). ' +
85
+ 'Generate one with: openssl rand -hex 32 — then set on the Railway service. ' +
86
+ 'Use scripts/backfill-railway-secrets.ts to provision across all services.');
87
+ }
88
+ // 2. On-disk secret file (local-dev path).
89
+ const secretPath = join(sgDir, 'auth-secret');
90
+ if (existsSync(secretPath)) {
91
+ return { secret: readFileSync(secretPath, 'utf-8').trim(), source: 'file' };
92
+ }
93
+ // 3. Seed file may have been imported just before us (applySeed path).
94
+ const seedPath = join(sgDir, 'auth-seed.json');
95
+ if (existsSync(seedPath)) {
96
+ try {
97
+ const seed = JSON.parse(readFileSync(seedPath, 'utf-8'));
98
+ if (typeof seed.jwtSecret === 'string' && seed.jwtSecret.length >= 16) {
99
+ // Persist to auth-secret so subsequent boots pick the file path
100
+ // without re-parsing the seed JSON.
101
+ writeFileSync(secretPath, seed.jwtSecret, { mode: 0o600 });
102
+ return { secret: seed.jwtSecret, source: 'seed' };
103
+ }
104
+ }
105
+ catch { /* fall through to generation */ }
106
+ }
107
+ // 4. First-run: generate and persist. 32 bytes of entropy = 64-char hex.
108
+ const generated = randomBytes(32).toString('hex');
109
+ writeFileSync(secretPath, generated, { mode: 0o600 });
110
+ return { secret: generated, source: 'generated' };
111
+ }
112
+ /**
113
+ * Warn loudly if `auth-seed.json` is world-readable. Anyone with read access
114
+ * to the file holds the workspace's JWT secret + bcrypt hashes; on a shared
115
+ * host that's an instant credential-store leak. We can't `chmod` it ourselves
116
+ * because the user may want their checkout group-readable for collaborators —
117
+ * but we WILL tell them the risk.
118
+ *
119
+ * Idempotent: the warning prints at boot once. Posix only — Windows mode bits
120
+ * don't carry the same meaning, so we skip there.
121
+ */
122
+ function warnIfSeedWorldReadable(sgDir) {
123
+ if (process.platform === 'win32')
124
+ return;
125
+ const seedPath = join(sgDir, 'auth-seed.json');
126
+ if (!existsSync(seedPath))
127
+ return;
128
+ try {
129
+ const mode = statSync(seedPath).mode;
130
+ // Other-readable bit (S_IROTH = 0o004).
131
+ if ((mode & 0o004) !== 0) {
132
+ log.warn(`[auth] WARNING: ${seedPath} is world-readable (mode ${(mode & 0o777).toString(8)}). ` +
133
+ 'It contains the JWT signing secret and bcrypt password hashes — anyone with ' +
134
+ 'read access to this file can impersonate workspace users. Attempting chmod 600...');
135
+ // Best-effort tightening. The seed lives inside the .studiograph/ git
136
+ // repo; a fresh `git checkout` restores files at 0644 by default. Trying
137
+ // to fix it on every boot is harmless (already-0600 is a no-op) and
138
+ // defends against the most common leak path. NEVER throw — this is a
139
+ // boot-time hygiene step, not a hard gate.
140
+ try {
141
+ chmodSync(seedPath, 0o600);
142
+ }
143
+ catch { /* non-fatal */ }
144
+ }
145
+ }
146
+ catch { /* stat failure is non-fatal */ }
147
+ }
148
+ /** Check if a user has workspace-owner privileges. */
149
+ export function isWorkspaceOwner(role) {
150
+ return role === 'owner';
22
151
  }
23
152
  const BCRYPT_ROUNDS = 12;
24
153
  const JWT_EXPIRY = '7d';
154
+ function hashToken(token) {
155
+ return createHash('sha256').update(token).digest('hex');
156
+ }
157
+ /**
158
+ * Slug-ify a name into a handle candidate. Lowercases, strips combining
159
+ * diacritics, replaces non-alphanumerics with hyphens, trims edge hyphens.
160
+ * Falls back to 'user' if the input has nothing usable (emoji-only, etc.) —
161
+ * the caller is responsible for ensuring uniqueness via collision suffix.
162
+ */
163
+ function slugifyHandle(name) {
164
+ const cleaned = name
165
+ .normalize('NFKD')
166
+ .replace(/[̀-ͯ]/g, '')
167
+ .toLowerCase()
168
+ .replace(/[^a-z0-9]+/g, '-')
169
+ .replace(/^-+|-+$/g, '');
170
+ return cleaned || 'user';
171
+ }
25
172
  export class AuthService {
26
173
  db;
27
174
  jwtSecret;
28
- apiKey;
29
175
  sgDir;
30
- constructor(workspacePath) {
176
+ crypto;
177
+ /**
178
+ * True once we've confirmed `data_ciphertext` exists on `oauth_tokens`
179
+ * (post-migration shape). The encrypted code path keys off this rather
180
+ * than re-running PRAGMA on every call.
181
+ */
182
+ oauthTokensEncrypted = false;
183
+ /** True once `settings_ciphertext` is available on `user_preferences`. */
184
+ userPrefsEncrypted = false;
185
+ constructor(workspacePath, cryptoOpts) {
31
186
  this.sgDir = join(workspacePath, '.studiograph');
187
+ this.crypto = cryptoOpts;
32
188
  if (!existsSync(this.sgDir)) {
33
189
  mkdirSync(this.sgDir, { recursive: true });
34
190
  }
35
191
  // Open/create SQLite database
36
192
  const dbPath = join(this.sgDir, 'auth.db');
37
193
  this.db = new Database(dbPath);
194
+ // Tighten file mode immediately after open. The DB embeds the JWT signing
195
+ // secret-equivalent (bcrypt hashes + per-user OAuth tokens via
196
+ // oauth_tokens.data_json) — leaving it 0644 on a shared host is an
197
+ // instant-credential-leak vector. Posix only; Windows ACLs differ.
198
+ if (process.platform !== 'win32') {
199
+ try {
200
+ chmodSync(dbPath, 0o600);
201
+ }
202
+ catch { /* non-fatal */ }
203
+ }
38
204
  this.db.pragma('journal_mode = WAL');
39
- // Create users table
205
+ // Create users table. CHECK tolerates legacy 'admin' rows so existing
206
+ // databases don't error on load; app code only ever writes 'owner' or
207
+ // 'member' going forward.
40
208
  this.db.exec(`
41
209
  CREATE TABLE IF NOT EXISTS users (
42
210
  id INTEGER PRIMARY KEY AUTOINCREMENT,
43
211
  email TEXT UNIQUE NOT NULL,
212
+ handle TEXT UNIQUE NOT NULL,
44
213
  password_hash TEXT NOT NULL,
45
214
  display_name TEXT NOT NULL,
46
215
  role TEXT NOT NULL DEFAULT 'member' CHECK(role IN ('owner', 'admin', 'member')),
@@ -48,24 +217,102 @@ export class AuthService {
48
217
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
49
218
  )
50
219
  `);
51
- // Create collection_access table for per-collection permissions
220
+ // Migration: backfill `handle` on databases created before commit 26d3d6f9
221
+ // (Apr 25 2026). The original commit said "wipe manually" — fine for dev,
222
+ // but Railway tenants can't be wiped without losing data. Idempotent:
223
+ // ALTER fails harmlessly when the column already exists, and the
224
+ // backfill loop only touches NULL rows.
225
+ const userCols = this.db.prepare("PRAGMA table_info(users)").all();
226
+ if (!userCols.some(c => c.name === 'handle')) {
227
+ this.db.exec("ALTER TABLE users ADD COLUMN handle TEXT");
228
+ const needHandle = this.db.prepare("SELECT id, display_name, email FROM users WHERE handle IS NULL").all();
229
+ const update = this.db.prepare("UPDATE users SET handle = ? WHERE id = ?");
230
+ const seen = new Set();
231
+ for (const row of needHandle) {
232
+ const baseSource = row.display_name?.trim() || row.email.split('@')[0];
233
+ let candidate = slugifyHandle(baseSource);
234
+ if (seen.has(candidate)) {
235
+ for (let n = 2; n < 1000; n++) {
236
+ const next = `${candidate}-${n}`;
237
+ if (!seen.has(next)) {
238
+ candidate = next;
239
+ break;
240
+ }
241
+ }
242
+ }
243
+ seen.add(candidate);
244
+ update.run(candidate, row.id);
245
+ }
246
+ // App code expects UNIQUE; a unique index gives us that without
247
+ // recreating the table.
248
+ try {
249
+ this.db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_users_handle ON users(handle)");
250
+ }
251
+ catch { /* ignore */ }
252
+ }
253
+ // Create folder_access table for per-folder membership + role (admin | editor).
254
+ // `folder_name` stores the repo slug — the underlying git-backed container
255
+ // is still called a repo internally; the column value equals the repo name.
52
256
  this.db.exec(`
53
- CREATE TABLE IF NOT EXISTS collection_access (
257
+ CREATE TABLE IF NOT EXISTS folder_access (
54
258
  user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
55
- collection_name TEXT NOT NULL,
259
+ folder_name TEXT NOT NULL,
260
+ role TEXT NOT NULL DEFAULT 'editor',
56
261
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
57
- PRIMARY KEY (user_id, collection_name)
262
+ PRIMARY KEY (user_id, folder_name)
58
263
  )
59
264
  `);
60
- // Per-user OAuth tokens for MCP connectors
265
+ // Per-user OAuth tokens for MCP connectors.
266
+ //
267
+ // Phase 8: `data_json` is nullable on freshly-provisioned DBs because
268
+ // encrypted values land in `data_ciphertext` instead. Pre-existing
269
+ // databases were created with `data_json TEXT NOT NULL`; the Phase 8
270
+ // migration (`auth-db-migration.ts`) rebuilds the table to relax that
271
+ // constraint. CREATE TABLE IF NOT EXISTS is a no-op on those — the
272
+ // migrate path handles them.
61
273
  this.db.exec(`
62
274
  CREATE TABLE IF NOT EXISTS oauth_tokens (
63
275
  user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
64
276
  connector_name TEXT NOT NULL,
65
- data_json TEXT NOT NULL,
277
+ data_json TEXT,
278
+ data_ciphertext TEXT,
279
+ key_version TEXT,
66
280
  updated_at TEXT NOT NULL DEFAULT (datetime('now')),
67
281
  PRIMARY KEY (user_id, connector_name)
68
282
  )
283
+ `);
284
+ // Defensive: on a pre-migration DB the table was created without the
285
+ // ciphertext columns, but the migration may not yet have run (tests
286
+ // construct AuthService directly without the boot path). Add them
287
+ // idempotently so the read/write paths can branch on column presence.
288
+ {
289
+ const oauthCols = this.db.prepare("PRAGMA table_info(oauth_tokens)").all();
290
+ if (!oauthCols.some(c => c.name === 'data_ciphertext')) {
291
+ try {
292
+ this.db.exec("ALTER TABLE oauth_tokens ADD COLUMN data_ciphertext TEXT");
293
+ }
294
+ catch { /* idempotent */ }
295
+ }
296
+ if (!oauthCols.some(c => c.name === 'key_version')) {
297
+ try {
298
+ this.db.exec("ALTER TABLE oauth_tokens ADD COLUMN key_version TEXT");
299
+ }
300
+ catch { /* idempotent */ }
301
+ }
302
+ this.oauthTokensEncrypted = true;
303
+ }
304
+ // Per-user API tokens. Used by MCP clients (HTTP and stdio) so every
305
+ // call carries a real user identity and access control can be enforced.
306
+ // Only the sha256 hash is stored — the raw token is shown once at mint.
307
+ this.db.exec(`
308
+ CREATE TABLE IF NOT EXISTS api_tokens (
309
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
310
+ user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
311
+ token_hash TEXT NOT NULL UNIQUE,
312
+ name TEXT NOT NULL,
313
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
314
+ last_used_at TEXT
315
+ )
69
316
  `);
70
317
  // Per-user saved views
71
318
  this.db.exec(`
@@ -116,31 +363,74 @@ export class AuthService {
116
363
  if (!cols.some(c => c.name === 'previous_briefing_text')) {
117
364
  this.db.exec("ALTER TABLE user_preferences ADD COLUMN previous_briefing_text TEXT");
118
365
  }
366
+ // Meeting capture: per-user settings stored as JSON (deepgram key, calendar tokens, default folder)
367
+ if (!cols.some(c => c.name === 'settings_json')) {
368
+ this.db.exec("ALTER TABLE user_preferences ADD COLUMN settings_json TEXT NOT NULL DEFAULT '{}'");
369
+ }
370
+ // Phase 8: split user_preferences.settings_json by classification —
371
+ // `user-secret` keys land in the encrypted column. Migration handles
372
+ // pre-existing rows; here we just make sure the columns exist so
373
+ // fresh DBs can use the new code path immediately.
374
+ if (!cols.some(c => c.name === 'settings_ciphertext')) {
375
+ try {
376
+ this.db.exec("ALTER TABLE user_preferences ADD COLUMN settings_ciphertext TEXT");
377
+ }
378
+ catch { /* idempotent */ }
379
+ }
380
+ if (!cols.some(c => c.name === 'key_version')) {
381
+ try {
382
+ this.db.exec("ALTER TABLE user_preferences ADD COLUMN key_version TEXT");
383
+ }
384
+ catch { /* idempotent */ }
385
+ }
386
+ this.userPrefsEncrypted = true;
119
387
  this.db.pragma('foreign_keys = ON');
388
+ // Half-state guard. If a previous run committed partial migration
389
+ // state (rows with both data_json AND data_ciphertext populated),
390
+ // refuse to start. Better to fail-fast than silently serve corrupt
391
+ // OAuth data. See auth-db-migration.ts for the migration's own
392
+ // transactional safety net — this is the last-resort detector.
393
+ assertAuthDbConsistent(this.db);
394
+ // On a fresh DB (no pre-existing rows in either secret-carrying
395
+ // table) we can mark Phase 8 as already migrated, so a later
396
+ // `migrateAuthDbPhase8` invocation short-circuits. This matters for
397
+ // the test path and for first-run installs where no legacy data
398
+ // ever existed.
399
+ if (this.crypto && !isAuthDbPhase8Migrated(this.db)) {
400
+ const hasOauthRows = this.db.prepare('SELECT COUNT(*) AS n FROM oauth_tokens').get().n > 0;
401
+ const hasSecretPrefRows = this.db.prepare(`SELECT COUNT(*) AS n FROM user_preferences WHERE settings_json IS NOT NULL AND settings_json != '' AND settings_json != '{}'`).get().n > 0;
402
+ if (!hasOauthRows && !hasSecretPrefRows) {
403
+ markAuthDbPhase8Migrated(this.db);
404
+ }
405
+ }
120
406
  // Ensure auth.db and auth-secret are gitignored (only seed file should be committed)
121
407
  this.ensureGitignore();
122
- // If DB is empty, try restoring from seed file (synced via config repo)
408
+ // If DB is empty, try restoring from seed file (synced via config repo).
409
+ // Best-effort during boot — a corrupt or partial seed shouldn't prevent
410
+ // the server from starting. Explicit pull/import callers still get the
411
+ // throw and can surface the error to the user.
123
412
  if (!this.hasUsers()) {
124
- this.applySeed();
125
- }
126
- // Load or generate JWT secret (seed import may have set it)
127
- const secretPath = join(this.sgDir, 'auth-secret');
128
- if (existsSync(secretPath)) {
129
- this.jwtSecret = readFileSync(secretPath, 'utf-8').trim();
130
- }
131
- else {
132
- this.jwtSecret = randomBytes(32).toString('hex');
133
- writeFileSync(secretPath, this.jwtSecret, { mode: 0o600 });
413
+ try {
414
+ this.applySeed();
415
+ }
416
+ catch { /* already logged inside applySeed */ }
134
417
  }
135
- // Load or generate API key (seed import may have set it)
136
- const apiKeyPath = join(this.sgDir, 'api-keys');
137
- if (existsSync(apiKeyPath)) {
138
- this.apiKey = readFileSync(apiKeyPath, 'utf-8').trim();
418
+ // Load JWT secret. Priority: env var > auth-secret file > seed file >
419
+ // freshly generated. The env-var path lets production deployments
420
+ // (Railway, etc.) hold the signing secret outside the config repo, so a
421
+ // leaked auth-seed.json is not by itself a session-forgery risk for
422
+ // that environment. See resolveJwtSecret for the full ordering.
423
+ const resolved = resolveJwtSecret(this.sgDir);
424
+ this.jwtSecret = resolved.secret;
425
+ if (resolved.source === 'env') {
426
+ log.info('[auth] JWT secret loaded from STUDIOGRAPH_JWT_SECRET env var');
139
427
  }
140
- else {
141
- this.apiKey = randomBytes(32).toString('hex');
142
- writeFileSync(apiKeyPath, this.apiKey, { mode: 0o600 });
428
+ else if (resolved.source === 'generated') {
429
+ log.info('[auth] JWT secret generated (first run)');
143
430
  }
431
+ // One-shot operator warning if the seed file is world-readable. Loud
432
+ // because the file embeds the signing secret + bcrypt hashes.
433
+ warnIfSeedWorldReadable(this.sgDir);
144
434
  }
145
435
  createUser(email, password, displayName, role = 'member') {
146
436
  const normalized = email.toLowerCase().trim();
@@ -151,17 +441,45 @@ export class AuthService {
151
441
  throw new Error('Password must be at least 8 characters');
152
442
  }
153
443
  const hash = bcrypt.hashSync(password, BCRYPT_ROUNDS);
154
- const stmt = this.db.prepare('INSERT INTO users (email, password_hash, display_name, role) VALUES (?, ?, ?, ?)');
155
- const result = stmt.run(normalized, hash, displayName.trim(), role);
444
+ const handle = this.allocateHandle(displayName);
445
+ const stmt = this.db.prepare('INSERT INTO users (email, handle, password_hash, display_name, role) VALUES (?, ?, ?, ?, ?)');
446
+ const result = stmt.run(normalized, handle, hash, displayName.trim(), role);
447
+ const userId = result.lastInsertRowid;
448
+ // Assign a default avatar accent color so the user shows up distinct
449
+ // across presence / member lists immediately, without having to open
450
+ // their profile.
451
+ this.setAccentColor(userId, pickDefaultAccentColor(userId));
156
452
  this.exportSeed();
157
453
  return {
158
- id: result.lastInsertRowid,
454
+ id: userId,
159
455
  email: normalized,
456
+ handle,
160
457
  displayName: displayName.trim(),
161
458
  role,
162
459
  createdAt: new Date().toISOString(),
163
460
  };
164
461
  }
462
+ /**
463
+ * Pick a unique handle by slugifying the display name (or email local-part
464
+ * fallback) and appending `-2`, `-3`, … on collision. Handles are immutable
465
+ * once assigned, so we don't have to worry about two parallel inserts
466
+ * racing — SQLite's UNIQUE constraint will reject the loser and the caller
467
+ * can retry.
468
+ */
469
+ allocateHandle(displayName) {
470
+ const base = slugifyHandle(displayName.trim());
471
+ const exists = this.db.prepare('SELECT 1 FROM users WHERE handle = ? LIMIT 1');
472
+ if (!exists.get(base))
473
+ return base;
474
+ for (let suffix = 2; suffix < 1000; suffix++) {
475
+ const candidate = `${base}-${suffix}`;
476
+ if (!exists.get(candidate))
477
+ return candidate;
478
+ }
479
+ // Pathological: 1000 collisions on the same slug. Fall back to a random
480
+ // tail so we don't block account creation.
481
+ return `${base}-${randomBytes(3).toString('hex')}`;
482
+ }
165
483
  authenticate(email, password) {
166
484
  const normalized = email.toLowerCase().trim();
167
485
  const row = this.db.prepare('SELECT * FROM users WHERE email = ?').get(normalized);
@@ -197,6 +515,39 @@ export class AuthService {
197
515
  this.exportSeed();
198
516
  return result.changes > 0;
199
517
  }
518
+ /**
519
+ * Before deleting `targetUserId`, transfer folder-admin rights to
520
+ * `actorUserId` for every folder where the target is the sole admin.
521
+ * Inserts an admin folder_access row for the actor (or upgrades an
522
+ * existing editor row). Returns the list of folder names that were
523
+ * transferred so the caller can surface them in UX.
524
+ *
525
+ * This prevents folders from becoming orphaned (no admins) after the
526
+ * target user's folder_access rows cascade-delete. Folders with other
527
+ * admins are left alone — no redundant membership added.
528
+ */
529
+ transferFoldersOnDelete(targetUserId, actorUserId) {
530
+ if (targetUserId === actorUserId)
531
+ return [];
532
+ const targetAdminFolders = this.db.prepare(`SELECT folder_name FROM folder_access WHERE user_id = ? AND role = 'admin'`).all(targetUserId);
533
+ const transferred = [];
534
+ const countAdminsStmt = this.db.prepare(`SELECT COUNT(*) as count FROM folder_access WHERE folder_name = ? AND role = 'admin'`);
535
+ const upsertStmt = this.db.prepare(`
536
+ INSERT INTO folder_access (user_id, folder_name, role)
537
+ VALUES (?, ?, 'admin')
538
+ ON CONFLICT(user_id, folder_name) DO UPDATE SET role = 'admin'
539
+ `);
540
+ for (const { folder_name } of targetAdminFolders) {
541
+ const { count } = countAdminsStmt.get(folder_name);
542
+ if (count > 1)
543
+ continue; // Other admins remain — nothing to transfer.
544
+ upsertStmt.run(actorUserId, folder_name);
545
+ transferred.push(folder_name);
546
+ }
547
+ if (transferred.length > 0)
548
+ this.exportSeed();
549
+ return transferred;
550
+ }
200
551
  updateProfile(userId, fields) {
201
552
  const updates = [];
202
553
  const params = [];
@@ -236,57 +587,127 @@ export class AuthService {
236
587
  return row ? this.toAuthUser(row) : null;
237
588
  }
238
589
  /**
239
- * Transfer ownership to another user. The current owner becomes an admin.
590
+ * Transfer ownership to another user. The current owner becomes a regular member.
240
591
  * Both changes happen in a single transaction.
241
592
  */
242
593
  transferOwnership(currentOwnerId, newOwnerId) {
243
594
  const target = this.db.prepare('SELECT * FROM users WHERE id = ?').get(newOwnerId);
244
595
  if (!target)
245
596
  throw new Error('Target user not found');
246
- if (target.role === 'member')
247
- throw new Error('Target must be an admin before ownership can be transferred');
248
597
  const current = this.db.prepare('SELECT * FROM users WHERE id = ?').get(currentOwnerId);
249
598
  if (!current || current.role !== 'owner')
250
599
  throw new Error('Current user is not the owner');
251
600
  const transfer = this.db.transaction(() => {
252
- this.db.prepare("UPDATE users SET role = 'admin', updated_at = datetime('now') WHERE id = ?").run(currentOwnerId);
601
+ this.db.prepare("UPDATE users SET role = 'member', updated_at = datetime('now') WHERE id = ?").run(currentOwnerId);
253
602
  this.db.prepare("UPDATE users SET role = 'owner', updated_at = datetime('now') WHERE id = ?").run(newOwnerId);
254
603
  });
255
604
  transfer();
256
605
  this.exportSeed();
257
606
  }
258
- // ── Collection access management ──
259
- grantCollectionAccess(userId, collectionName) {
260
- this.db.prepare('INSERT OR IGNORE INTO collection_access (user_id, collection_name) VALUES (?, ?)').run(userId, collectionName);
607
+ // ── Folder access management ──
608
+ /** Grant folder membership. `role` defaults to 'editor'. Upserts the role if the row exists. */
609
+ grantFolderAccess(userId, folder, role = 'editor') {
610
+ this.db.prepare(`
611
+ INSERT INTO folder_access (user_id, folder_name, role) VALUES (?, ?, ?)
612
+ ON CONFLICT (user_id, folder_name) DO UPDATE SET role = excluded.role
613
+ `).run(userId, folder, role);
261
614
  this.exportSeed();
262
615
  }
263
- revokeCollectionAccess(userId, collectionName) {
264
- this.db.prepare('DELETE FROM collection_access WHERE user_id = ? AND collection_name = ?').run(userId, collectionName);
616
+ revokeFolderAccess(userId, folder) {
617
+ this.db.prepare('DELETE FROM folder_access WHERE user_id = ? AND folder_name = ?').run(userId, folder);
265
618
  this.exportSeed();
266
619
  }
267
- getUserCollections(userId) {
268
- const rows = this.db.prepare('SELECT collection_name FROM collection_access WHERE user_id = ? ORDER BY collection_name').all(userId);
269
- return rows.map(r => r.collection_name);
620
+ /** Folder names the user is a member of. */
621
+ getUserFolders(userId) {
622
+ const rows = this.db.prepare('SELECT folder_name FROM folder_access WHERE user_id = ? ORDER BY folder_name').all(userId);
623
+ return rows.map(r => r.folder_name);
624
+ }
625
+ /** All users with any role on a folder. */
626
+ getFolderUsers(folder) {
627
+ const rows = this.db.prepare(`
628
+ SELECT u.* FROM users u
629
+ JOIN folder_access fa ON fa.user_id = u.id
630
+ WHERE fa.folder_name = ?
631
+ ORDER BY u.display_name
632
+ `).all(folder);
633
+ return rows.map(r => this.toAuthUser(r));
634
+ }
635
+ /** (user, role) pairs for every member of a folder. */
636
+ getFolderMembers(folder) {
637
+ const rows = this.db.prepare(`
638
+ SELECT u.*, fa.role AS folder_role FROM users u
639
+ JOIN folder_access fa ON fa.user_id = u.id
640
+ WHERE fa.folder_name = ?
641
+ ORDER BY u.display_name
642
+ `).all(folder);
643
+ return rows.map(r => ({ user: this.toAuthUser(r), role: r.folder_role }));
644
+ }
645
+ /** Folder role for a specific user, or null if they aren't a member. */
646
+ getFolderRole(userId, folder) {
647
+ const row = this.db.prepare('SELECT role FROM folder_access WHERE user_id = ? AND folder_name = ?').get(userId, folder);
648
+ return row?.role ?? null;
649
+ }
650
+ /** True if the user is a folder admin on that folder. */
651
+ isFolderAdmin(userId, folder) {
652
+ return this.getFolderRole(userId, folder) === 'admin';
270
653
  }
271
- getCollectionUsers(collectionName) {
654
+ /** All users with folder-admin role on a folder. */
655
+ getFolderAdmins(folder) {
272
656
  const rows = this.db.prepare(`
273
657
  SELECT u.* FROM users u
274
- JOIN collection_access ca ON ca.user_id = u.id
275
- WHERE ca.collection_name = ?
658
+ JOIN folder_access fa ON fa.user_id = u.id
659
+ WHERE fa.folder_name = ? AND fa.role = 'admin'
276
660
  ORDER BY u.display_name
277
- `).all(collectionName);
661
+ `).all(folder);
278
662
  return rows.map(r => this.toAuthUser(r));
279
663
  }
280
- renameCollection(oldName, newName) {
281
- this.db.prepare('UPDATE collection_access SET collection_name = ? WHERE collection_name = ?').run(newName, oldName);
664
+ /**
665
+ * Transfer folder admin role from one user to another. Used during member
666
+ * removal to pass orphaned folders to the workspace owner.
667
+ */
668
+ transferFolderAdmin(folder, fromUserId, toUserId) {
669
+ const transfer = this.db.transaction(() => {
670
+ this.db.prepare('DELETE FROM folder_access WHERE user_id = ? AND folder_name = ?')
671
+ .run(fromUserId, folder);
672
+ this.db.prepare(`
673
+ INSERT INTO folder_access (user_id, folder_name, role) VALUES (?, ?, 'admin')
674
+ ON CONFLICT (user_id, folder_name) DO UPDATE SET role = 'admin'
675
+ `).run(toUserId, folder);
676
+ });
677
+ transfer();
678
+ this.exportSeed();
679
+ }
680
+ /** Rename the folder slug across all membership rows. */
681
+ renameFolder(oldName, newName) {
682
+ this.db.prepare('UPDATE folder_access SET folder_name = ? WHERE folder_name = ?').run(newName, oldName);
282
683
  this.exportSeed();
283
684
  }
284
685
  // ── OAuth token management ──
285
686
  /** Get stored OAuth data (tokens, clientInfo, codeVerifier) for a user+connector. */
286
687
  getOAuthData(userId, connectorName) {
287
- const row = this.db.prepare('SELECT data_json FROM oauth_tokens WHERE user_id = ? AND connector_name = ?').get(userId, connectorName);
688
+ const row = this.db.prepare('SELECT data_json, data_ciphertext, key_version FROM oauth_tokens WHERE user_id = ? AND connector_name = ?').get(userId, connectorName);
288
689
  if (!row)
289
690
  return null;
691
+ // Encrypted-path: ciphertext present. Decryption errors throw — a row
692
+ // bound to user A cannot be decrypted with user B's AAD, and tampering
693
+ // is detected. Callers should let the error propagate.
694
+ if (row.data_ciphertext) {
695
+ if (!this.crypto) {
696
+ throw new Error(`oauth_tokens row for user_id=${userId} connector=${connectorName} is encrypted but ` +
697
+ `AuthService was constructed without a master key. Wire the crypto options in serve.ts.`);
698
+ }
699
+ try {
700
+ const plaintext = decryptOAuthTokenData(this.crypto.masterKey, userId, row.data_ciphertext, row.key_version);
701
+ return JSON.parse(plaintext);
702
+ }
703
+ catch (err) {
704
+ log.error(`[auth] OAuth token decrypt failed for user_id=${userId} connector=${connectorName}: ${err instanceof Error ? err.message : String(err)}`);
705
+ throw err;
706
+ }
707
+ }
708
+ // Pre-migration fallback: legacy plaintext column.
709
+ if (!row.data_json)
710
+ return null;
290
711
  try {
291
712
  return JSON.parse(row.data_json);
292
713
  }
@@ -296,12 +717,30 @@ export class AuthService {
296
717
  }
297
718
  /** Save OAuth data (upsert). */
298
719
  saveOAuthData(userId, connectorName, data) {
720
+ const payload = JSON.stringify(data);
721
+ // When crypto is wired up, every new write goes to data_ciphertext —
722
+ // data_json stays NULL on insert and is explicitly nulled on update
723
+ // so a row never carries both plaintext and ciphertext (the
724
+ // half-state guard would refuse to boot if it did).
725
+ if (this.crypto && this.oauthTokensEncrypted) {
726
+ const enc = encryptOAuthTokenData(this.crypto.masterKey, userId, payload, this.crypto.keyVersion);
727
+ this.db.prepare(`
728
+ INSERT INTO oauth_tokens (user_id, connector_name, data_json, data_ciphertext, key_version, updated_at)
729
+ VALUES (?, ?, NULL, ?, ?, datetime('now'))
730
+ ON CONFLICT (user_id, connector_name)
731
+ DO UPDATE SET data_json = NULL, data_ciphertext = excluded.data_ciphertext,
732
+ key_version = excluded.key_version, updated_at = excluded.updated_at
733
+ `).run(userId, connectorName, enc.ciphertext, enc.keyVersion);
734
+ return;
735
+ }
736
+ // Legacy path (tests / flag-off boots / pre-migration). Writes
737
+ // plaintext to data_json the way the v1 code always did.
299
738
  this.db.prepare(`
300
739
  INSERT INTO oauth_tokens (user_id, connector_name, data_json, updated_at)
301
740
  VALUES (?, ?, ?, datetime('now'))
302
741
  ON CONFLICT (user_id, connector_name)
303
742
  DO UPDATE SET data_json = excluded.data_json, updated_at = excluded.updated_at
304
- `).run(userId, connectorName, JSON.stringify(data));
743
+ `).run(userId, connectorName, payload);
305
744
  }
306
745
  /** List all connected connectors for a user. */
307
746
  getUserConnectedConnectors(userId) {
@@ -324,41 +763,56 @@ export class AuthService {
324
763
  const row = this.db.prepare('SELECT COUNT(*) as count FROM users').get();
325
764
  return row.count;
326
765
  }
327
- /** Get the workspace API key (for MCP and programmatic access). */
328
- getApiKey() {
329
- return this.apiKey;
766
+ // ── Per-user API tokens (MCP auth, CLI, git HTTP) ──
767
+ /**
768
+ * Mint a new per-user API token. Returns the raw token once — only its
769
+ * sha256 hash is stored in the database. The raw value cannot be recovered
770
+ * later, matching how GitHub personal access tokens work.
771
+ */
772
+ createApiToken(userId, name) {
773
+ const cleanName = (name || '').trim();
774
+ if (!cleanName)
775
+ throw new Error('Token name is required');
776
+ const token = `sg_${randomBytes(24).toString('hex')}`;
777
+ const hash = hashToken(token);
778
+ const result = this.db.prepare('INSERT INTO api_tokens (user_id, token_hash, name) VALUES (?, ?, ?)').run(userId, hash, cleanName);
779
+ return { id: result.lastInsertRowid, token, name: cleanName };
330
780
  }
331
- /** Check whether the given string matches the workspace API key. */
332
- validateApiKey(key) {
333
- return !!key && key === this.apiKey;
781
+ /**
782
+ * Resolve a raw API token to its owning user, updating `last_used_at`
783
+ * on success. Returns null if the token is unknown or the owning user
784
+ * has been deleted.
785
+ */
786
+ verifyApiToken(token) {
787
+ return this.verifyApiTokenWithMeta(token)?.user ?? null;
334
788
  }
335
789
  /**
336
- * Ensure the current API key is present in auth-seed.json and pushed to the
337
- * config repo. Called on server startup so the key is always reachable by CLI.
338
- * Non-fatalnever throws.
790
+ * Same as verifyApiToken but also returns the token's user-set name (e.g.
791
+ * "Cursor", "Claude Desktop"). Used by surfaces that need to attribute an
792
+ * action to a specific named integration most importantly the
793
+ * agent-activity feature, which renders "<owner>'s <token name>" chips.
339
794
  */
340
- syncApiKeyToSeed() {
341
- try {
342
- const seedPath = join(this.sgDir, 'auth-seed.json');
343
- if (!existsSync(seedPath)) {
344
- this.exportSeed();
345
- return;
346
- }
347
- const seed = JSON.parse(readFileSync(seedPath, 'utf-8'));
348
- if (seed.apiKey !== this.apiKey) {
349
- this.exportSeed();
350
- }
351
- }
352
- catch (err) {
353
- console.warn(`Auth: syncApiKeyToSeed failed — ${err.message}`);
354
- }
795
+ verifyApiTokenWithMeta(token) {
796
+ if (!token)
797
+ return null;
798
+ const hash = hashToken(token);
799
+ const row = this.db.prepare('SELECT user_id, name FROM api_tokens WHERE token_hash = ?').get(hash);
800
+ if (!row)
801
+ return null;
802
+ this.db.prepare('UPDATE api_tokens SET last_used_at = datetime(\'now\') WHERE token_hash = ?').run(hash);
803
+ const user = this.findUserById(row.user_id);
804
+ if (!user)
805
+ return null;
806
+ return { user, tokenName: row.name };
355
807
  }
356
- /** Regenerate the workspace API key. Returns the new key. */
357
- regenerateApiKey() {
358
- this.apiKey = randomBytes(32).toString('hex');
359
- writeFileSync(join(this.sgDir, 'api-keys'), this.apiKey, { mode: 0o600 });
360
- this.exportSeed();
361
- return this.apiKey;
808
+ /** List a user's API tokens. Never returns the raw token — only metadata. */
809
+ listApiTokens(userId) {
810
+ return this.db.prepare('SELECT id, name, created_at, last_used_at FROM api_tokens WHERE user_id = ? ORDER BY created_at DESC').all(userId);
811
+ }
812
+ /** Revoke (delete) an API token owned by the given user. No-op if not found. */
813
+ revokeApiToken(tokenId, userId) {
814
+ const result = this.db.prepare('DELETE FROM api_tokens WHERE id = ? AND user_id = ?').run(tokenId, userId);
815
+ return result.changes > 0;
362
816
  }
363
817
  /**
364
818
  * Ensure auth.db and auth-secret are in .studiograph/.gitignore.
@@ -380,21 +834,44 @@ export class AuthService {
380
834
  }
381
835
  }
382
836
  /**
383
- * Build and return the current auth seed data (users + JWT secret + collection access).
837
+ * Build and return the current auth seed data (users + JWT secret + folder access).
384
838
  * Does not write to disk — use exportSeed() for that.
839
+ *
840
+ * SECURITY: the returned object embeds raw secrets:
841
+ * • `jwtSecret` — signing key for ALL session tokens. Anyone holding this
842
+ * can mint a valid session for any user. Rotate by setting
843
+ * STUDIOGRAPH_JWT_SECRET in production and restarting.
844
+ * • `users[].password_hash` — bcrypt hashes (cost 12). Offline-crackable
845
+ * against weak passwords; treat as compromise of the listed accounts
846
+ * if leaked.
847
+ *
848
+ * Only call this from paths that write to a trusted location (auth-seed.json
849
+ * inside the .studiograph/ config repo). Never expose over HTTP, never log,
850
+ * never include in error messages or telemetry.
385
851
  */
386
852
  getSeedData() {
387
- const rows = this.db.prepare('SELECT email, password_hash, display_name, role, created_at FROM users ORDER BY id ASC').all();
853
+ const rows = this.db.prepare('SELECT email, handle, password_hash, display_name, role, created_at FROM users ORDER BY id ASC').all();
388
854
  const accessRows = this.db.prepare(`
389
- SELECT u.email AS user_email, ca.collection_name
390
- FROM collection_access ca JOIN users u ON u.id = ca.user_id
391
- ORDER BY u.email, ca.collection_name
855
+ SELECT u.email AS user_email, fa.folder_name AS folder_name, fa.role
856
+ FROM folder_access fa JOIN users u ON u.id = fa.user_id
857
+ ORDER BY u.email, fa.folder_name
392
858
  `).all();
859
+ // Phase 3: in production (Railway), drop the JWT secret from the
860
+ // returned seed entirely. Hosted instances read the JWT secret from
861
+ // STUDIOGRAPH_JWT_SECRET env-var and never need it embedded in the
862
+ // committed seed file. In dev / single-host, callers (applySeed,
863
+ // first-run import) still need the value to keep the
864
+ // env > file > seed > generated chain working.
865
+ //
866
+ // The exportSeed() / pushSeed() paths run the result through the
867
+ // bundle-sanitizer regardless, so even a dev seed never reaches the
868
+ // committed file with `jwtSecret`. This split keeps in-process API
869
+ // callers (which never write to disk) backwards-compatible.
870
+ const inProduction = Boolean(process.env.RAILWAY_ENVIRONMENT);
393
871
  return {
394
- jwtSecret: this.jwtSecret,
395
- apiKey: this.apiKey,
872
+ jwtSecret: inProduction ? '' : this.jwtSecret,
396
873
  users: rows,
397
- collection_access: accessRows,
874
+ folder_access: accessRows,
398
875
  };
399
876
  }
400
877
  /**
@@ -403,9 +880,24 @@ export class AuthService {
403
880
  * the seed file so it syncs between environments.
404
881
  */
405
882
  exportSeed() {
406
- const seed = this.getSeedData();
883
+ // Phase 3: route the seed through sanitizeAuthSeed() so the
884
+ // committed seed file NEVER carries jwtSecret or the deprecated
885
+ // top-level apiKey. Bcrypt hashes (users[].password_hash) are
886
+ // retained — that's the workspace-replication mechanism, see
887
+ // docs/SECURITY.md for the product decision.
888
+ const seed = sanitizeAuthSeed(this.getSeedData());
407
889
  const seedPath = join(this.sgDir, 'auth-seed.json');
408
- writeFileSync(seedPath, JSON.stringify(seed, null, 2), 'utf-8');
890
+ // Restrictive mode by default. Even with jwtSecret stripped, the
891
+ // file still carries every user's bcrypt hash — world-readable mode
892
+ // would enable offline cracking by any local user.
893
+ writeFileSync(seedPath, JSON.stringify(seed, null, 2), { mode: 0o600 });
894
+ if (process.platform !== 'win32') {
895
+ // Force-tighten when the file pre-existed with looser mode bits.
896
+ try {
897
+ chmodSync(seedPath, 0o600);
898
+ }
899
+ catch { /* non-fatal */ }
900
+ }
409
901
  this.pushSeed();
410
902
  }
411
903
  /**
@@ -445,7 +937,10 @@ export class AuthService {
445
937
  cwd: this.sgDir, encoding: 'utf-8', stdio: 'pipe',
446
938
  });
447
939
  if (commit.status !== 0) {
448
- console.warn(`Auth: failed to commit seed ${(commit.stderr || '').trim()}`);
940
+ // Phase 11: git's stderr can carry the remote URL with embedded
941
+ // credentials when push fails (https://x-access-token:<jwt>@host).
942
+ // Route stderr through sanitize() before logging.
943
+ log.warn(`Auth: failed to commit seed — ${sanitize((commit.stderr || '').trim())}`);
449
944
  return;
450
945
  }
451
946
  // Push
@@ -453,50 +948,120 @@ export class AuthService {
453
948
  cwd: this.sgDir, encoding: 'utf-8', stdio: 'pipe', timeout: 30_000,
454
949
  });
455
950
  if (push.status !== 0) {
456
- console.warn(`Auth: failed to push seed — ${(push.stderr || '').trim()}`);
951
+ log.warn(`Auth: failed to push seed — ${sanitize((push.stderr || '').trim())}`);
457
952
  }
458
953
  }
459
954
  catch (err) {
460
- console.warn(`Auth: seed sync failed — ${err.message}`);
955
+ log.warn(`Auth: seed sync failed — ${sanitize(err.message)}`);
461
956
  }
462
957
  }
463
958
  /**
464
959
  * Import users and JWT secret from .studiograph/auth-seed.json.
465
- * Uses INSERT OR IGNORE so it's safe to call on non-empty databases
466
- * (e.g. after `studiograph pull` brings a new seed file).
960
+ * Two modes:
961
+ * `merge` (default) INSERT OR IGNORE on email. Existing local rows win
962
+ * on conflict. Safe for boot-time recovery when the local DB may carry
963
+ * legitimate state the seed shouldn't clobber.
964
+ * • `overwrite` — UPSERT on email. Seed values win on conflict. Correct
965
+ * for explicit "remote is authoritative" callers (`studiograph pull`,
966
+ * `studiograph clone`, admin bundle import) where any local divergence
967
+ * was almost certainly the post-clone setup-wizard stamping fresh
968
+ * credentials over the real ones. `merge` mode silently shadowed
969
+ * production credentials in that exact scenario — see 2026-05 incident.
467
970
  */
468
- applySeed() {
971
+ applySeed(options = {}) {
972
+ const mode = options.mode ?? 'merge';
469
973
  const seedPath = join(this.sgDir, 'auth-seed.json');
470
974
  if (!existsSync(seedPath))
471
975
  return;
472
976
  try {
473
977
  const seed = JSON.parse(readFileSync(seedPath, 'utf-8'));
474
- // Restore JWT secret so existing tokens remain valid
978
+ // Restore JWT secret if the seed carried one. With our uniform sanitizer
979
+ // this is empty on production-server pulls; the local server then generates
980
+ // its own (sovereign-instance model).
475
981
  if (seed.jwtSecret) {
476
982
  const secretPath = join(this.sgDir, 'auth-secret');
477
983
  writeFileSync(secretPath, seed.jwtSecret, { mode: 0o600 });
478
984
  }
479
- // Restore API key
480
- if (seed.apiKey) {
481
- const apiKeyPath = join(this.sgDir, 'api-keys');
482
- writeFileSync(apiKeyPath, seed.apiKey, { mode: 0o600 });
985
+ // Normalize WAL state before writing. We've seen apply-seed writes
986
+ // silently disappear when the WAL was bloated by a prior crashed server
987
+ // checkpointing first guarantees the page cache reflects the on-disk
988
+ // committed state. RESTART (vs TRUNCATE) is the gentler form: it waits
989
+ // for readers but doesn't unlink the WAL file.
990
+ try {
991
+ this.db.pragma('wal_checkpoint(RESTART)');
483
992
  }
484
- // Restore users with their original password hashes (no re-hashing needed)
485
- const stmt = this.db.prepare('INSERT OR IGNORE INTO users (email, password_hash, display_name, role, created_at) VALUES (?, ?, ?, ?, ?)');
486
- for (const u of seed.users) {
487
- stmt.run(u.email, u.password_hash, u.display_name, u.role, u.created_at);
993
+ catch { /* non-fatal */ }
994
+ // Apply users + folder_access atomically. Explicit transaction means
995
+ // any failure aborts the whole import (no half-applied state) and
996
+ // commit semantics are unambiguous — there's no "implicit autocommit"
997
+ // path that can be silently skipped under WAL weirdness.
998
+ //
999
+ // UPSERT keyed on email preserves the autoincrement id so foreign keys
1000
+ // (folder_access, oauth_tokens, api_tokens) stay valid across an
1001
+ // overwrite. handle gets updated too — if the seed renames a handle,
1002
+ // we want the rename to land.
1003
+ const userStmt = mode === 'overwrite'
1004
+ ? this.db.prepare(`
1005
+ INSERT INTO users (email, handle, password_hash, display_name, role, created_at)
1006
+ VALUES (?, ?, ?, ?, ?, ?)
1007
+ ON CONFLICT(email) DO UPDATE SET
1008
+ handle = excluded.handle,
1009
+ password_hash = excluded.password_hash,
1010
+ display_name = excluded.display_name,
1011
+ role = excluded.role,
1012
+ created_at = excluded.created_at
1013
+ `)
1014
+ : this.db.prepare('INSERT OR IGNORE INTO users (email, handle, password_hash, display_name, role, created_at) VALUES (?, ?, ?, ?, ?, ?)');
1015
+ const accessStmt = this.db.prepare(`
1016
+ INSERT INTO folder_access (user_id, folder_name, role)
1017
+ SELECT id, ?, ? FROM users WHERE email = ?
1018
+ ON CONFLICT (user_id, folder_name) DO UPDATE SET role = excluded.role
1019
+ `);
1020
+ const accessRows = seed.folder_access ?? [];
1021
+ const apply = this.db.transaction(() => {
1022
+ for (const u of seed.users) {
1023
+ userStmt.run(u.email, u.handle, u.password_hash, u.display_name, u.role, u.created_at);
1024
+ }
1025
+ for (const fa of accessRows) {
1026
+ accessStmt.run(fa.folder_name, fa.role ?? 'editor', fa.user_email);
1027
+ }
1028
+ });
1029
+ apply();
1030
+ // Verify the writes survived. If the post-commit count doesn't reflect
1031
+ // at least the seed's user set, something is rolling our writes back —
1032
+ // throw loudly rather than letting the caller think the import succeeded.
1033
+ // In overwrite mode we additionally verify password_hash equality:
1034
+ // the 2026-05 incident was specifically about a stale local hash
1035
+ // surviving import, so "row exists" wasn't enough to catch it.
1036
+ const seedEmails = new Set(seed.users.map(u => u.email));
1037
+ const presentRows = this.db.prepare(`SELECT email, password_hash FROM users WHERE email IN (${seed.users.map(() => '?').join(',')})`).all(...seed.users.map(u => u.email));
1038
+ const missing = [...seedEmails].filter(e => !presentRows.some(r => r.email === e));
1039
+ if (missing.length > 0) {
1040
+ throw new Error(`seed apply verification failed — ${missing.length} user(s) not present after commit: ${missing.slice(0, 3).join(', ')}${missing.length > 3 ? '…' : ''}`);
488
1041
  }
489
- // Restore collection access grants (uses email as portable key)
490
- if (seed.collection_access?.length) {
491
- const accessStmt = this.db.prepare('INSERT OR IGNORE INTO collection_access (user_id, collection_name) SELECT id, ? FROM users WHERE email = ?');
492
- for (const ca of seed.collection_access) {
493
- accessStmt.run(ca.collection_name, ca.user_email);
1042
+ if (mode === 'overwrite') {
1043
+ const drift = seed.users
1044
+ .filter(u => {
1045
+ const row = presentRows.find(r => r.email === u.email);
1046
+ return row && row.password_hash !== u.password_hash;
1047
+ })
1048
+ .map(u => u.email);
1049
+ if (drift.length > 0) {
1050
+ throw new Error(`seed apply verification failed — ${drift.length} user(s) have stale password_hash after overwrite: ${drift.slice(0, 3).join(', ')}${drift.length > 3 ? '…' : ''}`);
494
1051
  }
495
1052
  }
496
- console.log(`Auth: restored ${seed.users.length} user(s) from seed`);
1053
+ // Flush WAL to main so a subsequent reader (sqlite3 CLI, another process)
1054
+ // sees the same state we just wrote. Non-fatal — pages are committed
1055
+ // either way; this just shortens the window where the WAL is the truth.
1056
+ try {
1057
+ this.db.pragma('wal_checkpoint(TRUNCATE)');
1058
+ }
1059
+ catch { /* non-fatal */ }
1060
+ log.info(`Auth: restored ${seed.users.length} user(s) from seed`);
497
1061
  }
498
1062
  catch (err) {
499
- console.warn(`Auth: failed to import seed — ${err.message}`);
1063
+ log.warn(`Auth: failed to import seed — ${sanitize(err.message)}`);
1064
+ throw err;
500
1065
  }
501
1066
  }
502
1067
  // ── Views ──
@@ -579,14 +1144,14 @@ export class AuthService {
579
1144
  ];
580
1145
  // Display name + sort config per type
581
1146
  const TYPE_LABELS = {
582
- meeting: 'Meetings', task: 'Tasks', person: 'People', decision: 'Decisions',
1147
+ event: 'Events', task: 'Tasks', person: 'People',
583
1148
  project: 'Projects', organization: 'Organizations', deal: 'Deals',
584
1149
  document: 'Documents', note: 'Notes', deliverable: 'Deliverables',
585
- reference: 'References', publication: 'Publications', process: 'Processes',
586
- standard: 'Standards', 'case-study': 'Case Studies', role: 'Roles',
587
- 'practice-area': 'Practice Areas', dataset: 'Datasets',
1150
+ reference: 'References', publication: 'Publications', service: 'Services',
1151
+ 'case-study': 'Case Studies', position: 'Positions', skill: 'Skills',
1152
+ deck: 'Decks',
588
1153
  };
589
- const DATE_SORTED = new Set(['meeting', 'decision', 'note', 'deal', 'task', 'proposal', 'brief']);
1154
+ const DATE_SORTED = new Set(['event', 'note', 'deal', 'task']);
590
1155
  // Sort types by count descending, take top 5 with at least 1 entity
591
1156
  const topTypes = Object.entries(typeCounts)
592
1157
  .filter(([, count]) => count > 0)
@@ -648,6 +1213,31 @@ export class AuthService {
648
1213
  this.db.prepare(`INSERT INTO user_preferences (user_id, onboarded, updated_at) VALUES (?, ?, datetime('now'))
649
1214
  ON CONFLICT(user_id) DO UPDATE SET onboarded = excluded.onboarded, updated_at = excluded.updated_at`).run(userId, value ? 1 : 0);
650
1215
  }
1216
+ /**
1217
+ * Reset per-user state used by the agent: onboarding flag, cached briefing,
1218
+ * home brief, and meeting-capture settings (Deepgram key, calendar, default
1219
+ * folder). Preserves the user account, accent color, and minted API tokens.
1220
+ * Used by `POST /api/reset` to put a tester back to a clean slate without
1221
+ * a redeploy or DB surgery.
1222
+ */
1223
+ resetUserState(userId) {
1224
+ // Phase 8: also wipe settings_ciphertext + key_version so the reset
1225
+ // matches the conceptual contract ("user state is gone"). Otherwise
1226
+ // a previously-stored Deepgram key would survive a reset.
1227
+ this.db.prepare(`INSERT INTO user_preferences
1228
+ (user_id, onboarded, briefing_text, previous_briefing_text, insights_generated_at, home_brief, settings_json, settings_ciphertext, key_version, updated_at)
1229
+ VALUES (?, 0, NULL, NULL, NULL, '', '{}', NULL, NULL, datetime('now'))
1230
+ ON CONFLICT(user_id) DO UPDATE SET
1231
+ onboarded = 0,
1232
+ briefing_text = NULL,
1233
+ previous_briefing_text = NULL,
1234
+ insights_generated_at = NULL,
1235
+ home_brief = '',
1236
+ settings_json = '{}',
1237
+ settings_ciphertext = NULL,
1238
+ key_version = NULL,
1239
+ updated_at = datetime('now')`).run(userId);
1240
+ }
651
1241
  getHomeBrief(userId) {
652
1242
  const row = this.db.prepare('SELECT home_brief FROM user_preferences WHERE user_id = ?').get(userId);
653
1243
  return row?.home_brief ?? '';
@@ -656,27 +1246,61 @@ export class AuthService {
656
1246
  this.db.prepare(`INSERT INTO user_preferences (user_id, home_brief, updated_at) VALUES (?, ?, datetime('now'))
657
1247
  ON CONFLICT(user_id) DO UPDATE SET home_brief = excluded.home_brief, updated_at = excluded.updated_at`).run(userId, brief);
658
1248
  }
659
- getCachedInsights(userId) {
660
- const row = this.db.prepare('SELECT insights_json, insights_generated_at FROM user_preferences WHERE user_id = ?').get(userId);
1249
+ // ── Meeting capture: per-user settings (Deepgram key, calendar, default folder) ──
1250
+ getUserSettings(userId) {
1251
+ const row = this.db.prepare('SELECT settings_json, settings_ciphertext, key_version FROM user_preferences WHERE user_id = ?').get(userId);
661
1252
  if (!row)
662
- return { insights: [], generatedAt: null };
1253
+ return {};
1254
+ // When the ciphertext column is populated we MUST have a master key
1255
+ // available — otherwise we'd silently drop the secret half of the
1256
+ // user's settings (e.g. lose their Deepgram key) and the meeting-
1257
+ // capture UI would re-prompt them. Refuse to read.
1258
+ if (row.settings_ciphertext) {
1259
+ if (!this.crypto) {
1260
+ throw new Error(`user_preferences row for user_id=${userId} has encrypted secrets but AuthService was ` +
1261
+ `constructed without a master key. Wire crypto options in serve.ts.`);
1262
+ }
1263
+ try {
1264
+ return decryptUserPreferencesSettings(this.crypto.masterKey, userId, row.settings_json, row.settings_ciphertext, row.key_version);
1265
+ }
1266
+ catch (err) {
1267
+ log.error(`[auth] user_preferences decrypt failed for user_id=${userId}: ${err instanceof Error ? err.message : String(err)}`);
1268
+ throw err;
1269
+ }
1270
+ }
1271
+ if (!row.settings_json)
1272
+ return {};
663
1273
  try {
664
- return { insights: JSON.parse(row.insights_json), generatedAt: row.insights_generated_at };
1274
+ return JSON.parse(row.settings_json);
665
1275
  }
666
1276
  catch {
667
- return { insights: [], generatedAt: null };
1277
+ return {};
668
1278
  }
669
1279
  }
670
- setCachedInsights(userId, insights) {
671
- this.db.prepare(`INSERT INTO user_preferences (user_id, insights_json, insights_generated_at, updated_at) VALUES (?, ?, datetime('now'), datetime('now'))
672
- ON CONFLICT(user_id) DO UPDATE SET insights_json = excluded.insights_json, insights_generated_at = excluded.insights_generated_at, updated_at = excluded.updated_at`).run(userId, JSON.stringify(insights));
1280
+ setUserSettings(userId, settings) {
1281
+ const current = this.getUserSettings(userId);
1282
+ const merged = { ...current, ...settings };
1283
+ // Crypto on: partition by classification, encrypt the secret subset,
1284
+ // write both halves atomically. Crypto off: behave exactly like v1.
1285
+ if (this.crypto && this.userPrefsEncrypted) {
1286
+ const split = encryptUserPreferencesSettings(this.crypto.masterKey, userId, merged, this.crypto.keyVersion ?? 'mk_v1');
1287
+ this.db.prepare(`INSERT INTO user_preferences (user_id, settings_json, settings_ciphertext, key_version, updated_at)
1288
+ VALUES (?, ?, ?, ?, datetime('now'))
1289
+ ON CONFLICT(user_id) DO UPDATE SET
1290
+ settings_json = excluded.settings_json,
1291
+ settings_ciphertext = excluded.settings_ciphertext,
1292
+ key_version = excluded.key_version,
1293
+ updated_at = excluded.updated_at`).run(userId, split.settingsJson, split.settingsCiphertext, split.keyVersion);
1294
+ return;
1295
+ }
1296
+ this.db.prepare(`INSERT INTO user_preferences (user_id, settings_json, updated_at) VALUES (?, ?, datetime('now'))
1297
+ ON CONFLICT(user_id) DO UPDATE SET settings_json = excluded.settings_json, updated_at = excluded.updated_at`).run(userId, JSON.stringify(merged));
673
1298
  }
674
- /** Returns ms since insights were last generated, or null if never generated. */
675
- getCachedInsightsAge(userId) {
676
- const row = this.db.prepare('SELECT insights_generated_at FROM user_preferences WHERE user_id = ?').get(userId);
677
- if (!row?.insights_generated_at)
678
- return null;
679
- return Date.now() - new Date(row.insights_generated_at).getTime();
1299
+ getUserSetting(userId, key) {
1300
+ return this.getUserSettings(userId)[key] ?? null;
1301
+ }
1302
+ setUserSetting(userId, key, value) {
1303
+ this.setUserSettings(userId, { [key]: value });
680
1304
  }
681
1305
  // ── Briefing memo (replaces structured insights) ──
682
1306
  getCachedBriefing(userId) {
@@ -707,6 +1331,7 @@ export class AuthService {
707
1331
  return {
708
1332
  id: row.id,
709
1333
  email: row.email,
1334
+ handle: row.handle,
710
1335
  displayName: row.display_name,
711
1336
  role: row.role,
712
1337
  createdAt: row.created_at,