@runcore-sh/runcore 0.1.2

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 (1112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +353 -0
  3. package/dist/activity/log.d.ts +37 -0
  4. package/dist/activity/log.d.ts.map +1 -0
  5. package/dist/activity/log.js +259 -0
  6. package/dist/activity/log.js.map +1 -0
  7. package/dist/adapters/storage/gdrive-backup.d.ts +20 -0
  8. package/dist/adapters/storage/gdrive-backup.d.ts.map +1 -0
  9. package/dist/adapters/storage/gdrive-backup.js +244 -0
  10. package/dist/adapters/storage/gdrive-backup.js.map +1 -0
  11. package/dist/adapters/storage/local.d.ts +19 -0
  12. package/dist/adapters/storage/local.d.ts.map +1 -0
  13. package/dist/adapters/storage/local.js +101 -0
  14. package/dist/adapters/storage/local.js.map +1 -0
  15. package/dist/adapters/storage/types.d.ts +44 -0
  16. package/dist/adapters/storage/types.d.ts.map +1 -0
  17. package/dist/adapters/storage/types.js +6 -0
  18. package/dist/adapters/storage/types.js.map +1 -0
  19. package/dist/agents/autonomous.d.ts +67 -0
  20. package/dist/agents/autonomous.d.ts.map +1 -0
  21. package/dist/agents/autonomous.js +710 -0
  22. package/dist/agents/autonomous.js.map +1 -0
  23. package/dist/agents/commit.d.ts +22 -0
  24. package/dist/agents/commit.d.ts.map +1 -0
  25. package/dist/agents/commit.js +120 -0
  26. package/dist/agents/commit.js.map +1 -0
  27. package/dist/agents/continue.d.ts +19 -0
  28. package/dist/agents/continue.d.ts.map +1 -0
  29. package/dist/agents/continue.js +158 -0
  30. package/dist/agents/continue.js.map +1 -0
  31. package/dist/agents/cooldown.d.ts +127 -0
  32. package/dist/agents/cooldown.d.ts.map +1 -0
  33. package/dist/agents/cooldown.js +396 -0
  34. package/dist/agents/cooldown.js.map +1 -0
  35. package/dist/agents/dedup-guard.d.ts +15 -0
  36. package/dist/agents/dedup-guard.d.ts.map +1 -0
  37. package/dist/agents/dedup-guard.js +128 -0
  38. package/dist/agents/dedup-guard.js.map +1 -0
  39. package/dist/agents/index.d.ts +34 -0
  40. package/dist/agents/index.d.ts.map +1 -0
  41. package/dist/agents/index.js +51 -0
  42. package/dist/agents/index.js.map +1 -0
  43. package/dist/agents/instance-manager.d.ts +262 -0
  44. package/dist/agents/instance-manager.d.ts.map +1 -0
  45. package/dist/agents/instance-manager.js +850 -0
  46. package/dist/agents/instance-manager.js.map +1 -0
  47. package/dist/agents/locks.d.ts +81 -0
  48. package/dist/agents/locks.d.ts.map +1 -0
  49. package/dist/agents/locks.js +234 -0
  50. package/dist/agents/locks.js.map +1 -0
  51. package/dist/agents/memory.d.ts +37 -0
  52. package/dist/agents/memory.d.ts.map +1 -0
  53. package/dist/agents/memory.js +92 -0
  54. package/dist/agents/memory.js.map +1 -0
  55. package/dist/agents/monitor.d.ts +16 -0
  56. package/dist/agents/monitor.d.ts.map +1 -0
  57. package/dist/agents/monitor.js +235 -0
  58. package/dist/agents/monitor.js.map +1 -0
  59. package/dist/agents/orchestration.d.ts +218 -0
  60. package/dist/agents/orchestration.d.ts.map +1 -0
  61. package/dist/agents/orchestration.js +715 -0
  62. package/dist/agents/orchestration.js.map +1 -0
  63. package/dist/agents/recover.d.ts +30 -0
  64. package/dist/agents/recover.d.ts.map +1 -0
  65. package/dist/agents/recover.js +166 -0
  66. package/dist/agents/recover.js.map +1 -0
  67. package/dist/agents/reflection.d.ts +36 -0
  68. package/dist/agents/reflection.d.ts.map +1 -0
  69. package/dist/agents/reflection.js +198 -0
  70. package/dist/agents/reflection.js.map +1 -0
  71. package/dist/agents/runtime/bus.d.ts +46 -0
  72. package/dist/agents/runtime/bus.d.ts.map +1 -0
  73. package/dist/agents/runtime/bus.js +174 -0
  74. package/dist/agents/runtime/bus.js.map +1 -0
  75. package/dist/agents/runtime/config.d.ts +14 -0
  76. package/dist/agents/runtime/config.d.ts.map +1 -0
  77. package/dist/agents/runtime/config.js +100 -0
  78. package/dist/agents/runtime/config.js.map +1 -0
  79. package/dist/agents/runtime/driver.d.ts +25 -0
  80. package/dist/agents/runtime/driver.d.ts.map +1 -0
  81. package/dist/agents/runtime/driver.js +215 -0
  82. package/dist/agents/runtime/driver.js.map +1 -0
  83. package/dist/agents/runtime/errors.d.ts +30 -0
  84. package/dist/agents/runtime/errors.d.ts.map +1 -0
  85. package/dist/agents/runtime/errors.js +40 -0
  86. package/dist/agents/runtime/errors.js.map +1 -0
  87. package/dist/agents/runtime/index.d.ts +29 -0
  88. package/dist/agents/runtime/index.d.ts.map +1 -0
  89. package/dist/agents/runtime/index.js +54 -0
  90. package/dist/agents/runtime/index.js.map +1 -0
  91. package/dist/agents/runtime/lifecycle.d.ts +46 -0
  92. package/dist/agents/runtime/lifecycle.d.ts.map +1 -0
  93. package/dist/agents/runtime/lifecycle.js +116 -0
  94. package/dist/agents/runtime/lifecycle.js.map +1 -0
  95. package/dist/agents/runtime/manager.d.ts +129 -0
  96. package/dist/agents/runtime/manager.d.ts.map +1 -0
  97. package/dist/agents/runtime/manager.js +947 -0
  98. package/dist/agents/runtime/manager.js.map +1 -0
  99. package/dist/agents/runtime/registry.d.ts +66 -0
  100. package/dist/agents/runtime/registry.d.ts.map +1 -0
  101. package/dist/agents/runtime/registry.js +195 -0
  102. package/dist/agents/runtime/registry.js.map +1 -0
  103. package/dist/agents/runtime/resources.d.ts +49 -0
  104. package/dist/agents/runtime/resources.d.ts.map +1 -0
  105. package/dist/agents/runtime/resources.js +146 -0
  106. package/dist/agents/runtime/resources.js.map +1 -0
  107. package/dist/agents/runtime/types.d.ts +168 -0
  108. package/dist/agents/runtime/types.d.ts.map +1 -0
  109. package/dist/agents/runtime/types.js +24 -0
  110. package/dist/agents/runtime/types.js.map +1 -0
  111. package/dist/agents/runtime.d.ts +240 -0
  112. package/dist/agents/runtime.d.ts.map +1 -0
  113. package/dist/agents/runtime.js +577 -0
  114. package/dist/agents/runtime.js.map +1 -0
  115. package/dist/agents/spawn.d.ts +49 -0
  116. package/dist/agents/spawn.d.ts.map +1 -0
  117. package/dist/agents/spawn.js +975 -0
  118. package/dist/agents/spawn.js.map +1 -0
  119. package/dist/agents/store.d.ts +29 -0
  120. package/dist/agents/store.d.ts.map +1 -0
  121. package/dist/agents/store.js +174 -0
  122. package/dist/agents/store.js.map +1 -0
  123. package/dist/agents/triage.d.ts +23 -0
  124. package/dist/agents/triage.d.ts.map +1 -0
  125. package/dist/agents/triage.js +81 -0
  126. package/dist/agents/triage.js.map +1 -0
  127. package/dist/agents/types.d.ts +37 -0
  128. package/dist/agents/types.d.ts.map +1 -0
  129. package/dist/agents/types.js +2 -0
  130. package/dist/agents/types.js.map +1 -0
  131. package/dist/agents/workflow.d.ts +137 -0
  132. package/dist/agents/workflow.d.ts.map +1 -0
  133. package/dist/agents/workflow.js +542 -0
  134. package/dist/agents/workflow.js.map +1 -0
  135. package/dist/auth/crypto.d.ts +22 -0
  136. package/dist/auth/crypto.d.ts.map +1 -0
  137. package/dist/auth/crypto.js +42 -0
  138. package/dist/auth/crypto.js.map +1 -0
  139. package/dist/auth/identity.d.ts +89 -0
  140. package/dist/auth/identity.d.ts.map +1 -0
  141. package/dist/auth/identity.js +264 -0
  142. package/dist/auth/identity.js.map +1 -0
  143. package/dist/avatar/client.d.ts +34 -0
  144. package/dist/avatar/client.d.ts.map +1 -0
  145. package/dist/avatar/client.js +172 -0
  146. package/dist/avatar/client.js.map +1 -0
  147. package/dist/avatar/sidecar.d.ts +16 -0
  148. package/dist/avatar/sidecar.d.ts.map +1 -0
  149. package/dist/avatar/sidecar.js +125 -0
  150. package/dist/avatar/sidecar.js.map +1 -0
  151. package/dist/board/provider.d.ts +13 -0
  152. package/dist/board/provider.d.ts.map +1 -0
  153. package/dist/board/provider.js +19 -0
  154. package/dist/board/provider.js.map +1 -0
  155. package/dist/board/types.d.ts +76 -0
  156. package/dist/board/types.d.ts.map +1 -0
  157. package/dist/board/types.js +7 -0
  158. package/dist/board/types.js.map +1 -0
  159. package/dist/brain/skills.d.ts +177 -0
  160. package/dist/brain/skills.d.ts.map +1 -0
  161. package/dist/brain/skills.js +452 -0
  162. package/dist/brain/skills.js.map +1 -0
  163. package/dist/brain.d.ts +42 -0
  164. package/dist/brain.d.ts.map +1 -0
  165. package/dist/brain.js +98 -0
  166. package/dist/brain.js.map +1 -0
  167. package/dist/browser/sessions.d.ts +23 -0
  168. package/dist/browser/sessions.d.ts.map +1 -0
  169. package/dist/browser/sessions.js +121 -0
  170. package/dist/browser/sessions.js.map +1 -0
  171. package/dist/cache/file.d.ts +56 -0
  172. package/dist/cache/file.d.ts.map +1 -0
  173. package/dist/cache/file.js +176 -0
  174. package/dist/cache/file.js.map +1 -0
  175. package/dist/cache/index.d.ts +64 -0
  176. package/dist/cache/index.d.ts.map +1 -0
  177. package/dist/cache/index.js +108 -0
  178. package/dist/cache/index.js.map +1 -0
  179. package/dist/cache/keys.d.ts +29 -0
  180. package/dist/cache/keys.d.ts.map +1 -0
  181. package/dist/cache/keys.js +52 -0
  182. package/dist/cache/keys.js.map +1 -0
  183. package/dist/cache/llm-cache.d.ts +70 -0
  184. package/dist/cache/llm-cache.d.ts.map +1 -0
  185. package/dist/cache/llm-cache.js +165 -0
  186. package/dist/cache/llm-cache.js.map +1 -0
  187. package/dist/cache/memory.d.ts +53 -0
  188. package/dist/cache/memory.d.ts.map +1 -0
  189. package/dist/cache/memory.js +114 -0
  190. package/dist/cache/memory.js.map +1 -0
  191. package/dist/calendar/google-adapter.d.ts +16 -0
  192. package/dist/calendar/google-adapter.d.ts.map +1 -0
  193. package/dist/calendar/google-adapter.js +163 -0
  194. package/dist/calendar/google-adapter.js.map +1 -0
  195. package/dist/calendar/index.d.ts +8 -0
  196. package/dist/calendar/index.d.ts.map +1 -0
  197. package/dist/calendar/index.js +7 -0
  198. package/dist/calendar/index.js.map +1 -0
  199. package/dist/calendar/routes.d.ts +7 -0
  200. package/dist/calendar/routes.d.ts.map +1 -0
  201. package/dist/calendar/routes.js +199 -0
  202. package/dist/calendar/routes.js.map +1 -0
  203. package/dist/calendar/store.d.ts +73 -0
  204. package/dist/calendar/store.d.ts.map +1 -0
  205. package/dist/calendar/store.js +373 -0
  206. package/dist/calendar/store.js.map +1 -0
  207. package/dist/calendar/types.d.ts +99 -0
  208. package/dist/calendar/types.d.ts.map +1 -0
  209. package/dist/calendar/types.js +7 -0
  210. package/dist/calendar/types.js.map +1 -0
  211. package/dist/capabilities/definitions/board.d.ts +7 -0
  212. package/dist/capabilities/definitions/board.d.ts.map +1 -0
  213. package/dist/capabilities/definitions/board.js +232 -0
  214. package/dist/capabilities/definitions/board.js.map +1 -0
  215. package/dist/capabilities/definitions/browser.d.ts +18 -0
  216. package/dist/capabilities/definitions/browser.d.ts.map +1 -0
  217. package/dist/capabilities/definitions/browser.js +242 -0
  218. package/dist/capabilities/definitions/browser.js.map +1 -0
  219. package/dist/capabilities/definitions/calendar-context.d.ts +8 -0
  220. package/dist/capabilities/definitions/calendar-context.d.ts.map +1 -0
  221. package/dist/capabilities/definitions/calendar-context.js +41 -0
  222. package/dist/capabilities/definitions/calendar-context.js.map +1 -0
  223. package/dist/capabilities/definitions/calendar.d.ts +7 -0
  224. package/dist/capabilities/definitions/calendar.d.ts.map +1 -0
  225. package/dist/capabilities/definitions/calendar.js +173 -0
  226. package/dist/capabilities/definitions/calendar.js.map +1 -0
  227. package/dist/capabilities/definitions/docs.d.ts +6 -0
  228. package/dist/capabilities/definitions/docs.d.ts.map +1 -0
  229. package/dist/capabilities/definitions/docs.js +62 -0
  230. package/dist/capabilities/definitions/docs.js.map +1 -0
  231. package/dist/capabilities/definitions/email-context.d.ts +7 -0
  232. package/dist/capabilities/definitions/email-context.d.ts.map +1 -0
  233. package/dist/capabilities/definitions/email-context.js +55 -0
  234. package/dist/capabilities/definitions/email-context.js.map +1 -0
  235. package/dist/capabilities/definitions/email.d.ts +7 -0
  236. package/dist/capabilities/definitions/email.d.ts.map +1 -0
  237. package/dist/capabilities/definitions/email.js +94 -0
  238. package/dist/capabilities/definitions/email.js.map +1 -0
  239. package/dist/capabilities/definitions/task-done.d.ts +10 -0
  240. package/dist/capabilities/definitions/task-done.d.ts.map +1 -0
  241. package/dist/capabilities/definitions/task-done.js +83 -0
  242. package/dist/capabilities/definitions/task-done.js.map +1 -0
  243. package/dist/capabilities/definitions/vault-context.d.ts +12 -0
  244. package/dist/capabilities/definitions/vault-context.d.ts.map +1 -0
  245. package/dist/capabilities/definitions/vault-context.js +62 -0
  246. package/dist/capabilities/definitions/vault-context.js.map +1 -0
  247. package/dist/capabilities/definitions/web-search-context.d.ts +22 -0
  248. package/dist/capabilities/definitions/web-search-context.d.ts.map +1 -0
  249. package/dist/capabilities/definitions/web-search-context.js +60 -0
  250. package/dist/capabilities/definitions/web-search-context.js.map +1 -0
  251. package/dist/capabilities/index.d.ts +18 -0
  252. package/dist/capabilities/index.d.ts.map +1 -0
  253. package/dist/capabilities/index.js +21 -0
  254. package/dist/capabilities/index.js.map +1 -0
  255. package/dist/capabilities/registry.d.ts +84 -0
  256. package/dist/capabilities/registry.d.ts.map +1 -0
  257. package/dist/capabilities/registry.js +248 -0
  258. package/dist/capabilities/registry.js.map +1 -0
  259. package/dist/capabilities/types.d.ts +157 -0
  260. package/dist/capabilities/types.d.ts.map +1 -0
  261. package/dist/capabilities/types.js +35 -0
  262. package/dist/capabilities/types.js.map +1 -0
  263. package/dist/channels/whatsapp.d.ts +88 -0
  264. package/dist/channels/whatsapp.d.ts.map +1 -0
  265. package/dist/channels/whatsapp.js +200 -0
  266. package/dist/channels/whatsapp.js.map +1 -0
  267. package/dist/cli/backup.d.ts +13 -0
  268. package/dist/cli/backup.d.ts.map +1 -0
  269. package/dist/cli/backup.js +176 -0
  270. package/dist/cli/backup.js.map +1 -0
  271. package/dist/cli.d.ts +12 -0
  272. package/dist/cli.d.ts.map +1 -0
  273. package/dist/cli.js +231 -0
  274. package/dist/cli.js.map +1 -0
  275. package/dist/config/defaults.d.ts +45 -0
  276. package/dist/config/defaults.d.ts.map +1 -0
  277. package/dist/config/defaults.js +54 -0
  278. package/dist/config/defaults.js.map +1 -0
  279. package/dist/contacts/index.d.ts +3 -0
  280. package/dist/contacts/index.d.ts.map +1 -0
  281. package/dist/contacts/index.js +2 -0
  282. package/dist/contacts/index.js.map +1 -0
  283. package/dist/contacts/store.d.ts +58 -0
  284. package/dist/contacts/store.d.ts.map +1 -0
  285. package/dist/contacts/store.js +278 -0
  286. package/dist/contacts/store.js.map +1 -0
  287. package/dist/contacts/types.d.ts +47 -0
  288. package/dist/contacts/types.d.ts.map +1 -0
  289. package/dist/contacts/types.js +5 -0
  290. package/dist/contacts/types.js.map +1 -0
  291. package/dist/context/assembler.d.ts +26 -0
  292. package/dist/context/assembler.d.ts.map +1 -0
  293. package/dist/context/assembler.js +65 -0
  294. package/dist/context/assembler.js.map +1 -0
  295. package/dist/context/compaction.d.ts +34 -0
  296. package/dist/context/compaction.d.ts.map +1 -0
  297. package/dist/context/compaction.js +84 -0
  298. package/dist/context/compaction.js.map +1 -0
  299. package/dist/context/index.d.ts +3 -0
  300. package/dist/context/index.d.ts.map +1 -0
  301. package/dist/context/index.js +2 -0
  302. package/dist/context/index.js.map +1 -0
  303. package/dist/core/registry/index.d.ts +12 -0
  304. package/dist/core/registry/index.d.ts.map +1 -0
  305. package/dist/core/registry/index.js +14 -0
  306. package/dist/core/registry/index.js.map +1 -0
  307. package/dist/core/registry/publisher.d.ts +22 -0
  308. package/dist/core/registry/publisher.d.ts.map +1 -0
  309. package/dist/core/registry/publisher.js +195 -0
  310. package/dist/core/registry/publisher.js.map +1 -0
  311. package/dist/core/registry/registry.d.ts +92 -0
  312. package/dist/core/registry/registry.d.ts.map +1 -0
  313. package/dist/core/registry/registry.js +254 -0
  314. package/dist/core/registry/registry.js.map +1 -0
  315. package/dist/core/registry/search.d.ts +12 -0
  316. package/dist/core/registry/search.d.ts.map +1 -0
  317. package/dist/core/registry/search.js +132 -0
  318. package/dist/core/registry/search.js.map +1 -0
  319. package/dist/core/registry/store.d.ts +55 -0
  320. package/dist/core/registry/store.d.ts.map +1 -0
  321. package/dist/core/registry/store.js +185 -0
  322. package/dist/core/registry/store.js.map +1 -0
  323. package/dist/core/registry/types.d.ts +141 -0
  324. package/dist/core/registry/types.d.ts.map +1 -0
  325. package/dist/core/registry/types.js +30 -0
  326. package/dist/core/registry/types.js.map +1 -0
  327. package/dist/core/registry/versions.d.ts +56 -0
  328. package/dist/core/registry/versions.d.ts.map +1 -0
  329. package/dist/core/registry/versions.js +101 -0
  330. package/dist/core/registry/versions.js.map +1 -0
  331. package/dist/credentials/store.d.ts +59 -0
  332. package/dist/credentials/store.d.ts.map +1 -0
  333. package/dist/credentials/store.js +178 -0
  334. package/dist/credentials/store.js.map +1 -0
  335. package/dist/files/agent-api.d.ts +50 -0
  336. package/dist/files/agent-api.d.ts.map +1 -0
  337. package/dist/files/agent-api.js +126 -0
  338. package/dist/files/agent-api.js.map +1 -0
  339. package/dist/files/compress.d.ts +20 -0
  340. package/dist/files/compress.d.ts.map +1 -0
  341. package/dist/files/compress.js +83 -0
  342. package/dist/files/compress.js.map +1 -0
  343. package/dist/files/extract.d.ts +11 -0
  344. package/dist/files/extract.d.ts.map +1 -0
  345. package/dist/files/extract.js +33 -0
  346. package/dist/files/extract.js.map +1 -0
  347. package/dist/files/gdrive.d.ts +56 -0
  348. package/dist/files/gdrive.d.ts.map +1 -0
  349. package/dist/files/gdrive.js +246 -0
  350. package/dist/files/gdrive.js.map +1 -0
  351. package/dist/files/ingest-folder.d.ts +22 -0
  352. package/dist/files/ingest-folder.d.ts.map +1 -0
  353. package/dist/files/ingest-folder.js +71 -0
  354. package/dist/files/ingest-folder.js.map +1 -0
  355. package/dist/files/ingest.d.ts +13 -0
  356. package/dist/files/ingest.d.ts.map +1 -0
  357. package/dist/files/ingest.js +127 -0
  358. package/dist/files/ingest.js.map +1 -0
  359. package/dist/files/manager.d.ts +117 -0
  360. package/dist/files/manager.d.ts.map +1 -0
  361. package/dist/files/manager.js +306 -0
  362. package/dist/files/manager.js.map +1 -0
  363. package/dist/files/store.d.ts +41 -0
  364. package/dist/files/store.d.ts.map +1 -0
  365. package/dist/files/store.js +271 -0
  366. package/dist/files/store.js.map +1 -0
  367. package/dist/files/templates.d.ts +45 -0
  368. package/dist/files/templates.d.ts.map +1 -0
  369. package/dist/files/templates.js +179 -0
  370. package/dist/files/templates.js.map +1 -0
  371. package/dist/files/types.d.ts +115 -0
  372. package/dist/files/types.d.ts.map +1 -0
  373. package/dist/files/types.js +20 -0
  374. package/dist/files/types.js.map +1 -0
  375. package/dist/files/validate.d.ts +15 -0
  376. package/dist/files/validate.d.ts.map +1 -0
  377. package/dist/files/validate.js +213 -0
  378. package/dist/files/validate.js.map +1 -0
  379. package/dist/files/version.d.ts +31 -0
  380. package/dist/files/version.d.ts.map +1 -0
  381. package/dist/files/version.js +129 -0
  382. package/dist/files/version.js.map +1 -0
  383. package/dist/github/client.d.ts +83 -0
  384. package/dist/github/client.d.ts.map +1 -0
  385. package/dist/github/client.js +408 -0
  386. package/dist/github/client.js.map +1 -0
  387. package/dist/github/commit-analysis.d.ts +30 -0
  388. package/dist/github/commit-analysis.d.ts.map +1 -0
  389. package/dist/github/commit-analysis.js +276 -0
  390. package/dist/github/commit-analysis.js.map +1 -0
  391. package/dist/github/contributor-stats.d.ts +18 -0
  392. package/dist/github/contributor-stats.d.ts.map +1 -0
  393. package/dist/github/contributor-stats.js +119 -0
  394. package/dist/github/contributor-stats.js.map +1 -0
  395. package/dist/github/issue-sla.d.ts +25 -0
  396. package/dist/github/issue-sla.d.ts.map +1 -0
  397. package/dist/github/issue-sla.js +220 -0
  398. package/dist/github/issue-sla.js.map +1 -0
  399. package/dist/github/issue-triage.d.ts +49 -0
  400. package/dist/github/issue-triage.d.ts.map +1 -0
  401. package/dist/github/issue-triage.js +286 -0
  402. package/dist/github/issue-triage.js.map +1 -0
  403. package/dist/github/pr-readiness.d.ts +18 -0
  404. package/dist/github/pr-readiness.d.ts.map +1 -0
  405. package/dist/github/pr-readiness.js +197 -0
  406. package/dist/github/pr-readiness.js.map +1 -0
  407. package/dist/github/pr-review.d.ts +17 -0
  408. package/dist/github/pr-review.d.ts.map +1 -0
  409. package/dist/github/pr-review.js +410 -0
  410. package/dist/github/pr-review.js.map +1 -0
  411. package/dist/github/release-notes.d.ts +32 -0
  412. package/dist/github/release-notes.d.ts.map +1 -0
  413. package/dist/github/release-notes.js +227 -0
  414. package/dist/github/release-notes.js.map +1 -0
  415. package/dist/github/repo-health.d.ts +17 -0
  416. package/dist/github/repo-health.d.ts.map +1 -0
  417. package/dist/github/repo-health.js +303 -0
  418. package/dist/github/repo-health.js.map +1 -0
  419. package/dist/github/retry.d.ts +39 -0
  420. package/dist/github/retry.d.ts.map +1 -0
  421. package/dist/github/retry.js +117 -0
  422. package/dist/github/retry.js.map +1 -0
  423. package/dist/github/types.d.ts +527 -0
  424. package/dist/github/types.d.ts.map +1 -0
  425. package/dist/github/types.js +8 -0
  426. package/dist/github/types.js.map +1 -0
  427. package/dist/github/webhooks.d.ts +36 -0
  428. package/dist/github/webhooks.d.ts.map +1 -0
  429. package/dist/github/webhooks.js +153 -0
  430. package/dist/github/webhooks.js.map +1 -0
  431. package/dist/goals/loop.d.ts +27 -0
  432. package/dist/goals/loop.d.ts.map +1 -0
  433. package/dist/goals/loop.js +239 -0
  434. package/dist/goals/loop.js.map +1 -0
  435. package/dist/goals/notifications.d.ts +20 -0
  436. package/dist/goals/notifications.d.ts.map +1 -0
  437. package/dist/goals/notifications.js +101 -0
  438. package/dist/goals/notifications.js.map +1 -0
  439. package/dist/goals/timer.d.ts +21 -0
  440. package/dist/goals/timer.d.ts.map +1 -0
  441. package/dist/goals/timer.js +60 -0
  442. package/dist/goals/timer.js.map +1 -0
  443. package/dist/google/auth.d.ts +84 -0
  444. package/dist/google/auth.d.ts.map +1 -0
  445. package/dist/google/auth.js +323 -0
  446. package/dist/google/auth.js.map +1 -0
  447. package/dist/google/calendar-timer.d.ts +20 -0
  448. package/dist/google/calendar-timer.d.ts.map +1 -0
  449. package/dist/google/calendar-timer.js +91 -0
  450. package/dist/google/calendar-timer.js.map +1 -0
  451. package/dist/google/calendar.d.ts +126 -0
  452. package/dist/google/calendar.d.ts.map +1 -0
  453. package/dist/google/calendar.js +270 -0
  454. package/dist/google/calendar.js.map +1 -0
  455. package/dist/google/docs.d.ts +87 -0
  456. package/dist/google/docs.d.ts.map +1 -0
  457. package/dist/google/docs.js +309 -0
  458. package/dist/google/docs.js.map +1 -0
  459. package/dist/google/gmail-send.d.ts +58 -0
  460. package/dist/google/gmail-send.d.ts.map +1 -0
  461. package/dist/google/gmail-send.js +219 -0
  462. package/dist/google/gmail-send.js.map +1 -0
  463. package/dist/google/gmail-timer.d.ts +34 -0
  464. package/dist/google/gmail-timer.d.ts.map +1 -0
  465. package/dist/google/gmail-timer.js +223 -0
  466. package/dist/google/gmail-timer.js.map +1 -0
  467. package/dist/google/gmail.d.ts +172 -0
  468. package/dist/google/gmail.d.ts.map +1 -0
  469. package/dist/google/gmail.js +470 -0
  470. package/dist/google/gmail.js.map +1 -0
  471. package/dist/google/tasks-timer.d.ts +20 -0
  472. package/dist/google/tasks-timer.d.ts.map +1 -0
  473. package/dist/google/tasks-timer.js +107 -0
  474. package/dist/google/tasks-timer.js.map +1 -0
  475. package/dist/google/tasks.d.ts +167 -0
  476. package/dist/google/tasks.d.ts.map +1 -0
  477. package/dist/google/tasks.js +331 -0
  478. package/dist/google/tasks.js.map +1 -0
  479. package/dist/google/temporal.d.ts +76 -0
  480. package/dist/google/temporal.d.ts.map +1 -0
  481. package/dist/google/temporal.js +176 -0
  482. package/dist/google/temporal.js.map +1 -0
  483. package/dist/health/alert-defaults.d.ts +12 -0
  484. package/dist/health/alert-defaults.d.ts.map +1 -0
  485. package/dist/health/alert-defaults.js +88 -0
  486. package/dist/health/alert-defaults.js.map +1 -0
  487. package/dist/health/alert-types.d.ts +97 -0
  488. package/dist/health/alert-types.d.ts.map +1 -0
  489. package/dist/health/alert-types.js +8 -0
  490. package/dist/health/alert-types.js.map +1 -0
  491. package/dist/health/alerting.d.ts +66 -0
  492. package/dist/health/alerting.d.ts.map +1 -0
  493. package/dist/health/alerting.js +373 -0
  494. package/dist/health/alerting.js.map +1 -0
  495. package/dist/health/checker.d.ts +32 -0
  496. package/dist/health/checker.d.ts.map +1 -0
  497. package/dist/health/checker.js +138 -0
  498. package/dist/health/checker.js.map +1 -0
  499. package/dist/health/checks/openrouter.d.ts +29 -0
  500. package/dist/health/checks/openrouter.d.ts.map +1 -0
  501. package/dist/health/checks/openrouter.js +75 -0
  502. package/dist/health/checks/openrouter.js.map +1 -0
  503. package/dist/health/checks.d.ts +26 -0
  504. package/dist/health/checks.d.ts.map +1 -0
  505. package/dist/health/checks.js +122 -0
  506. package/dist/health/checks.js.map +1 -0
  507. package/dist/health/components.d.ts +38 -0
  508. package/dist/health/components.d.ts.map +1 -0
  509. package/dist/health/components.js +112 -0
  510. package/dist/health/components.js.map +1 -0
  511. package/dist/health/index.d.ts +19 -0
  512. package/dist/health/index.d.ts.map +1 -0
  513. package/dist/health/index.js +23 -0
  514. package/dist/health/index.js.map +1 -0
  515. package/dist/health/recovery.d.ts +42 -0
  516. package/dist/health/recovery.d.ts.map +1 -0
  517. package/dist/health/recovery.js +138 -0
  518. package/dist/health/recovery.js.map +1 -0
  519. package/dist/health/types.d.ts +68 -0
  520. package/dist/health/types.d.ts.map +1 -0
  521. package/dist/health/types.js +5 -0
  522. package/dist/health/types.js.map +1 -0
  523. package/dist/index.d.ts +25 -0
  524. package/dist/index.d.ts.map +1 -0
  525. package/dist/index.js +22 -0
  526. package/dist/index.js.map +1 -0
  527. package/dist/instance.d.ts +24 -0
  528. package/dist/instance.d.ts.map +1 -0
  529. package/dist/instance.js +48 -0
  530. package/dist/instance.js.map +1 -0
  531. package/dist/integrations/github.d.ts +83 -0
  532. package/dist/integrations/github.d.ts.map +1 -0
  533. package/dist/integrations/github.js +331 -0
  534. package/dist/integrations/github.js.map +1 -0
  535. package/dist/integrations/google-tasks.d.ts +232 -0
  536. package/dist/integrations/google-tasks.d.ts.map +1 -0
  537. package/dist/integrations/google-tasks.js +432 -0
  538. package/dist/integrations/google-tasks.js.map +1 -0
  539. package/dist/learning/extractor.d.ts +28 -0
  540. package/dist/learning/extractor.d.ts.map +1 -0
  541. package/dist/learning/extractor.js +135 -0
  542. package/dist/learning/extractor.js.map +1 -0
  543. package/dist/lib/BasePlugin.d.ts +58 -0
  544. package/dist/lib/BasePlugin.d.ts.map +1 -0
  545. package/dist/lib/BasePlugin.js +181 -0
  546. package/dist/lib/BasePlugin.js.map +1 -0
  547. package/dist/lib/PluginRegistry.d.ts +56 -0
  548. package/dist/lib/PluginRegistry.d.ts.map +1 -0
  549. package/dist/lib/PluginRegistry.js +172 -0
  550. package/dist/lib/PluginRegistry.js.map +1 -0
  551. package/dist/lib/brain-io.d.ts +60 -0
  552. package/dist/lib/brain-io.d.ts.map +1 -0
  553. package/dist/lib/brain-io.js +180 -0
  554. package/dist/lib/brain-io.js.map +1 -0
  555. package/dist/lib/encryption-config.d.ts +16 -0
  556. package/dist/lib/encryption-config.d.ts.map +1 -0
  557. package/dist/lib/encryption-config.js +40 -0
  558. package/dist/lib/encryption-config.js.map +1 -0
  559. package/dist/lib/encryption.d.ts +19 -0
  560. package/dist/lib/encryption.d.ts.map +1 -0
  561. package/dist/lib/encryption.js +65 -0
  562. package/dist/lib/encryption.js.map +1 -0
  563. package/dist/lib/key-store.d.ts +24 -0
  564. package/dist/lib/key-store.d.ts.map +1 -0
  565. package/dist/lib/key-store.js +38 -0
  566. package/dist/lib/key-store.js.map +1 -0
  567. package/dist/lib/schema-migration.d.ts +34 -0
  568. package/dist/lib/schema-migration.d.ts.map +1 -0
  569. package/dist/lib/schema-migration.js +77 -0
  570. package/dist/lib/schema-migration.js.map +1 -0
  571. package/dist/library/brain-shadow.d.ts +10 -0
  572. package/dist/library/brain-shadow.d.ts.map +1 -0
  573. package/dist/library/brain-shadow.js +158 -0
  574. package/dist/library/brain-shadow.js.map +1 -0
  575. package/dist/library/index.d.ts +7 -0
  576. package/dist/library/index.d.ts.map +1 -0
  577. package/dist/library/index.js +6 -0
  578. package/dist/library/index.js.map +1 -0
  579. package/dist/library/routes.d.ts +7 -0
  580. package/dist/library/routes.d.ts.map +1 -0
  581. package/dist/library/routes.js +473 -0
  582. package/dist/library/routes.js.map +1 -0
  583. package/dist/library/store.d.ts +54 -0
  584. package/dist/library/store.d.ts.map +1 -0
  585. package/dist/library/store.js +403 -0
  586. package/dist/library/store.js.map +1 -0
  587. package/dist/library/types.d.ts +56 -0
  588. package/dist/library/types.d.ts.map +1 -0
  589. package/dist/library/types.js +12 -0
  590. package/dist/library/types.js.map +1 -0
  591. package/dist/llm/cache.d.ts +42 -0
  592. package/dist/llm/cache.d.ts.map +1 -0
  593. package/dist/llm/cache.js +104 -0
  594. package/dist/llm/cache.js.map +1 -0
  595. package/dist/llm/complete.d.ts +24 -0
  596. package/dist/llm/complete.d.ts.map +1 -0
  597. package/dist/llm/complete.js +56 -0
  598. package/dist/llm/complete.js.map +1 -0
  599. package/dist/llm/errors.d.ts +28 -0
  600. package/dist/llm/errors.d.ts.map +1 -0
  601. package/dist/llm/errors.js +82 -0
  602. package/dist/llm/errors.js.map +1 -0
  603. package/dist/llm/ollama.d.ts +21 -0
  604. package/dist/llm/ollama.d.ts.map +1 -0
  605. package/dist/llm/ollama.js +116 -0
  606. package/dist/llm/ollama.js.map +1 -0
  607. package/dist/llm/openrouter.d.ts +13 -0
  608. package/dist/llm/openrouter.d.ts.map +1 -0
  609. package/dist/llm/openrouter.js +105 -0
  610. package/dist/llm/openrouter.js.map +1 -0
  611. package/dist/llm/providers/anthropic.d.ts +8 -0
  612. package/dist/llm/providers/anthropic.d.ts.map +1 -0
  613. package/dist/llm/providers/anthropic.js +189 -0
  614. package/dist/llm/providers/anthropic.js.map +1 -0
  615. package/dist/llm/providers/index.d.ts +20 -0
  616. package/dist/llm/providers/index.d.ts.map +1 -0
  617. package/dist/llm/providers/index.js +47 -0
  618. package/dist/llm/providers/index.js.map +1 -0
  619. package/dist/llm/providers/ollama.d.ts +13 -0
  620. package/dist/llm/providers/ollama.d.ts.map +1 -0
  621. package/dist/llm/providers/ollama.js +188 -0
  622. package/dist/llm/providers/ollama.js.map +1 -0
  623. package/dist/llm/providers/openai.d.ts +8 -0
  624. package/dist/llm/providers/openai.d.ts.map +1 -0
  625. package/dist/llm/providers/openai.js +144 -0
  626. package/dist/llm/providers/openai.js.map +1 -0
  627. package/dist/llm/providers/openrouter.d.ts +7 -0
  628. package/dist/llm/providers/openrouter.d.ts.map +1 -0
  629. package/dist/llm/providers/openrouter.js +158 -0
  630. package/dist/llm/providers/openrouter.js.map +1 -0
  631. package/dist/llm/providers/types.d.ts +29 -0
  632. package/dist/llm/providers/types.d.ts.map +1 -0
  633. package/dist/llm/providers/types.js +6 -0
  634. package/dist/llm/providers/types.js.map +1 -0
  635. package/dist/llm/retry.d.ts +29 -0
  636. package/dist/llm/retry.d.ts.map +1 -0
  637. package/dist/llm/retry.js +139 -0
  638. package/dist/llm/retry.js.map +1 -0
  639. package/dist/memory/file-backed.d.ts +36 -0
  640. package/dist/memory/file-backed.d.ts.map +1 -0
  641. package/dist/memory/file-backed.js +178 -0
  642. package/dist/memory/file-backed.js.map +1 -0
  643. package/dist/memory/index.d.ts +7 -0
  644. package/dist/memory/index.d.ts.map +1 -0
  645. package/dist/memory/index.js +6 -0
  646. package/dist/memory/index.js.map +1 -0
  647. package/dist/memory/long-term.d.ts +38 -0
  648. package/dist/memory/long-term.d.ts.map +1 -0
  649. package/dist/memory/long-term.js +58 -0
  650. package/dist/memory/long-term.js.map +1 -0
  651. package/dist/memory/vector-index.d.ts +55 -0
  652. package/dist/memory/vector-index.d.ts.map +1 -0
  653. package/dist/memory/vector-index.js +207 -0
  654. package/dist/memory/vector-index.js.map +1 -0
  655. package/dist/memory/visual.d.ts +53 -0
  656. package/dist/memory/visual.d.ts.map +1 -0
  657. package/dist/memory/visual.js +218 -0
  658. package/dist/memory/visual.js.map +1 -0
  659. package/dist/memory/working.d.ts +12 -0
  660. package/dist/memory/working.d.ts.map +1 -0
  661. package/dist/memory/working.js +34 -0
  662. package/dist/memory/working.js.map +1 -0
  663. package/dist/metrics/aggregator.d.ts +58 -0
  664. package/dist/metrics/aggregator.d.ts.map +1 -0
  665. package/dist/metrics/aggregator.js +253 -0
  666. package/dist/metrics/aggregator.js.map +1 -0
  667. package/dist/metrics/collector.d.ts +26 -0
  668. package/dist/metrics/collector.d.ts.map +1 -0
  669. package/dist/metrics/collector.js +346 -0
  670. package/dist/metrics/collector.js.map +1 -0
  671. package/dist/metrics/firewall-metrics.d.ts +95 -0
  672. package/dist/metrics/firewall-metrics.d.ts.map +1 -0
  673. package/dist/metrics/firewall-metrics.js +261 -0
  674. package/dist/metrics/firewall-metrics.js.map +1 -0
  675. package/dist/metrics/index.d.ts +20 -0
  676. package/dist/metrics/index.d.ts.map +1 -0
  677. package/dist/metrics/index.js +23 -0
  678. package/dist/metrics/index.js.map +1 -0
  679. package/dist/metrics/instruments.d.ts +89 -0
  680. package/dist/metrics/instruments.d.ts.map +1 -0
  681. package/dist/metrics/instruments.js +172 -0
  682. package/dist/metrics/instruments.js.map +1 -0
  683. package/dist/metrics/middleware.d.ts +12 -0
  684. package/dist/metrics/middleware.d.ts.map +1 -0
  685. package/dist/metrics/middleware.js +47 -0
  686. package/dist/metrics/middleware.js.map +1 -0
  687. package/dist/metrics/prometheus.d.ts +39 -0
  688. package/dist/metrics/prometheus.d.ts.map +1 -0
  689. package/dist/metrics/prometheus.js +115 -0
  690. package/dist/metrics/prometheus.js.map +1 -0
  691. package/dist/metrics/registry.d.ts +58 -0
  692. package/dist/metrics/registry.d.ts.map +1 -0
  693. package/dist/metrics/registry.js +145 -0
  694. package/dist/metrics/registry.js.map +1 -0
  695. package/dist/metrics/reporter.d.ts +21 -0
  696. package/dist/metrics/reporter.d.ts.map +1 -0
  697. package/dist/metrics/reporter.js +207 -0
  698. package/dist/metrics/reporter.js.map +1 -0
  699. package/dist/metrics/store.d.ts +47 -0
  700. package/dist/metrics/store.d.ts.map +1 -0
  701. package/dist/metrics/store.js +209 -0
  702. package/dist/metrics/store.js.map +1 -0
  703. package/dist/metrics/system.d.ts +20 -0
  704. package/dist/metrics/system.d.ts.map +1 -0
  705. package/dist/metrics/system.js +109 -0
  706. package/dist/metrics/system.js.map +1 -0
  707. package/dist/metrics/types.d.ts +101 -0
  708. package/dist/metrics/types.d.ts.map +1 -0
  709. package/dist/metrics/types.js +6 -0
  710. package/dist/metrics/types.js.map +1 -0
  711. package/dist/modules/index.d.ts +6 -0
  712. package/dist/modules/index.d.ts.map +1 -0
  713. package/dist/modules/index.js +6 -0
  714. package/dist/modules/index.js.map +1 -0
  715. package/dist/modules/registry.d.ts +36 -0
  716. package/dist/modules/registry.d.ts.map +1 -0
  717. package/dist/modules/registry.js +155 -0
  718. package/dist/modules/registry.js.map +1 -0
  719. package/dist/modules/types.d.ts +37 -0
  720. package/dist/modules/types.d.ts.map +1 -0
  721. package/dist/modules/types.js +9 -0
  722. package/dist/modules/types.js.map +1 -0
  723. package/dist/notifications/channel.d.ts +25 -0
  724. package/dist/notifications/channel.d.ts.map +1 -0
  725. package/dist/notifications/channel.js +83 -0
  726. package/dist/notifications/channel.js.map +1 -0
  727. package/dist/notifications/email.d.ts +27 -0
  728. package/dist/notifications/email.d.ts.map +1 -0
  729. package/dist/notifications/email.js +72 -0
  730. package/dist/notifications/email.js.map +1 -0
  731. package/dist/notifications/index.d.ts +16 -0
  732. package/dist/notifications/index.d.ts.map +1 -0
  733. package/dist/notifications/index.js +12 -0
  734. package/dist/notifications/index.js.map +1 -0
  735. package/dist/notifications/phone.d.ts +16 -0
  736. package/dist/notifications/phone.d.ts.map +1 -0
  737. package/dist/notifications/phone.js +48 -0
  738. package/dist/notifications/phone.js.map +1 -0
  739. package/dist/notifications/sms.d.ts +26 -0
  740. package/dist/notifications/sms.d.ts.map +1 -0
  741. package/dist/notifications/sms.js +65 -0
  742. package/dist/notifications/sms.js.map +1 -0
  743. package/dist/notifications/webhook.d.ts +28 -0
  744. package/dist/notifications/webhook.d.ts.map +1 -0
  745. package/dist/notifications/webhook.js +65 -0
  746. package/dist/notifications/webhook.js.map +1 -0
  747. package/dist/openloop/foldback.d.ts +24 -0
  748. package/dist/openloop/foldback.d.ts.map +1 -0
  749. package/dist/openloop/foldback.js +127 -0
  750. package/dist/openloop/foldback.js.map +1 -0
  751. package/dist/openloop/index.d.ts +11 -0
  752. package/dist/openloop/index.d.ts.map +1 -0
  753. package/dist/openloop/index.js +9 -0
  754. package/dist/openloop/index.js.map +1 -0
  755. package/dist/openloop/lifecycle.d.ts +16 -0
  756. package/dist/openloop/lifecycle.d.ts.map +1 -0
  757. package/dist/openloop/lifecycle.js +304 -0
  758. package/dist/openloop/lifecycle.js.map +1 -0
  759. package/dist/openloop/resolution-scanner.d.ts +17 -0
  760. package/dist/openloop/resolution-scanner.d.ts.map +1 -0
  761. package/dist/openloop/resolution-scanner.js +551 -0
  762. package/dist/openloop/resolution-scanner.js.map +1 -0
  763. package/dist/openloop/scanner.d.ts +28 -0
  764. package/dist/openloop/scanner.d.ts.map +1 -0
  765. package/dist/openloop/scanner.js +587 -0
  766. package/dist/openloop/scanner.js.map +1 -0
  767. package/dist/openloop/store.d.ts +41 -0
  768. package/dist/openloop/store.d.ts.map +1 -0
  769. package/dist/openloop/store.js +154 -0
  770. package/dist/openloop/store.js.map +1 -0
  771. package/dist/openloop/types.d.ts +94 -0
  772. package/dist/openloop/types.d.ts.map +1 -0
  773. package/dist/openloop/types.js +6 -0
  774. package/dist/openloop/types.js.map +1 -0
  775. package/dist/plugins/google-tasks/index.d.ts +55 -0
  776. package/dist/plugins/google-tasks/index.d.ts.map +1 -0
  777. package/dist/plugins/google-tasks/index.js +135 -0
  778. package/dist/plugins/google-tasks/index.js.map +1 -0
  779. package/dist/pulse/activation-event.d.ts +66 -0
  780. package/dist/pulse/activation-event.d.ts.map +1 -0
  781. package/dist/pulse/activation-event.js +139 -0
  782. package/dist/pulse/activation-event.js.map +1 -0
  783. package/dist/pulse/activation-log.d.ts +37 -0
  784. package/dist/pulse/activation-log.d.ts.map +1 -0
  785. package/dist/pulse/activation-log.js +101 -0
  786. package/dist/pulse/activation-log.js.map +1 -0
  787. package/dist/pulse/index.d.ts +11 -0
  788. package/dist/pulse/index.d.ts.map +1 -0
  789. package/dist/pulse/index.js +13 -0
  790. package/dist/pulse/index.js.map +1 -0
  791. package/dist/pulse/pressure.d.ts +69 -0
  792. package/dist/pulse/pressure.d.ts.map +1 -0
  793. package/dist/pulse/pressure.js +304 -0
  794. package/dist/pulse/pressure.js.map +1 -0
  795. package/dist/pulse/types.d.ts +89 -0
  796. package/dist/pulse/types.d.ts.map +1 -0
  797. package/dist/pulse/types.js +6 -0
  798. package/dist/pulse/types.js.map +1 -0
  799. package/dist/queue/grooming.d.ts +16 -0
  800. package/dist/queue/grooming.d.ts.map +1 -0
  801. package/dist/queue/grooming.js +269 -0
  802. package/dist/queue/grooming.js.map +1 -0
  803. package/dist/queue/provider.d.ts +50 -0
  804. package/dist/queue/provider.d.ts.map +1 -0
  805. package/dist/queue/provider.js +131 -0
  806. package/dist/queue/provider.js.map +1 -0
  807. package/dist/queue/store.d.ts +97 -0
  808. package/dist/queue/store.d.ts.map +1 -0
  809. package/dist/queue/store.js +448 -0
  810. package/dist/queue/store.js.map +1 -0
  811. package/dist/queue/types.d.ts +47 -0
  812. package/dist/queue/types.d.ts.map +1 -0
  813. package/dist/queue/types.js +22 -0
  814. package/dist/queue/types.js.map +1 -0
  815. package/dist/rate-limit.d.ts +26 -0
  816. package/dist/rate-limit.d.ts.map +1 -0
  817. package/dist/rate-limit.js +74 -0
  818. package/dist/rate-limit.js.map +1 -0
  819. package/dist/registry/discovery.d.ts +30 -0
  820. package/dist/registry/discovery.d.ts.map +1 -0
  821. package/dist/registry/discovery.js +171 -0
  822. package/dist/registry/discovery.js.map +1 -0
  823. package/dist/registry/index.d.ts +14 -0
  824. package/dist/registry/index.d.ts.map +1 -0
  825. package/dist/registry/index.js +18 -0
  826. package/dist/registry/index.js.map +1 -0
  827. package/dist/registry/installer.d.ts +45 -0
  828. package/dist/registry/installer.d.ts.map +1 -0
  829. package/dist/registry/installer.js +175 -0
  830. package/dist/registry/installer.js.map +1 -0
  831. package/dist/registry/registry.d.ts +70 -0
  832. package/dist/registry/registry.d.ts.map +1 -0
  833. package/dist/registry/registry.js +153 -0
  834. package/dist/registry/registry.js.map +1 -0
  835. package/dist/registry/store.d.ts +69 -0
  836. package/dist/registry/store.d.ts.map +1 -0
  837. package/dist/registry/store.js +242 -0
  838. package/dist/registry/store.js.map +1 -0
  839. package/dist/registry/types.d.ts +116 -0
  840. package/dist/registry/types.d.ts.map +1 -0
  841. package/dist/registry/types.js +9 -0
  842. package/dist/registry/types.js.map +1 -0
  843. package/dist/registry/validator.d.ts +24 -0
  844. package/dist/registry/validator.d.ts.map +1 -0
  845. package/dist/registry/validator.js +203 -0
  846. package/dist/registry/validator.js.map +1 -0
  847. package/dist/scheduling/index.d.ts +4 -0
  848. package/dist/scheduling/index.d.ts.map +1 -0
  849. package/dist/scheduling/index.js +3 -0
  850. package/dist/scheduling/index.js.map +1 -0
  851. package/dist/scheduling/store.d.ts +41 -0
  852. package/dist/scheduling/store.d.ts.map +1 -0
  853. package/dist/scheduling/store.js +237 -0
  854. package/dist/scheduling/store.js.map +1 -0
  855. package/dist/scheduling/timer.d.ts +25 -0
  856. package/dist/scheduling/timer.d.ts.map +1 -0
  857. package/dist/scheduling/timer.js +118 -0
  858. package/dist/scheduling/timer.js.map +1 -0
  859. package/dist/scheduling/types.d.ts +43 -0
  860. package/dist/scheduling/types.d.ts.map +1 -0
  861. package/dist/scheduling/types.js +5 -0
  862. package/dist/scheduling/types.js.map +1 -0
  863. package/dist/search/brain-docs.d.ts +20 -0
  864. package/dist/search/brain-docs.d.ts.map +1 -0
  865. package/dist/search/brain-docs.js +103 -0
  866. package/dist/search/brain-docs.js.map +1 -0
  867. package/dist/search/browse.d.ts +45 -0
  868. package/dist/search/browse.d.ts.map +1 -0
  869. package/dist/search/browse.js +225 -0
  870. package/dist/search/browse.js.map +1 -0
  871. package/dist/search/classify.d.ts +23 -0
  872. package/dist/search/classify.d.ts.map +1 -0
  873. package/dist/search/classify.js +132 -0
  874. package/dist/search/classify.js.map +1 -0
  875. package/dist/search/client.d.ts +32 -0
  876. package/dist/search/client.d.ts.map +1 -0
  877. package/dist/search/client.js +72 -0
  878. package/dist/search/client.js.map +1 -0
  879. package/dist/search/perplexity.d.ts +13 -0
  880. package/dist/search/perplexity.d.ts.map +1 -0
  881. package/dist/search/perplexity.js +41 -0
  882. package/dist/search/perplexity.js.map +1 -0
  883. package/dist/search/sidecar.d.ts +20 -0
  884. package/dist/search/sidecar.d.ts.map +1 -0
  885. package/dist/search/sidecar.js +103 -0
  886. package/dist/search/sidecar.js.map +1 -0
  887. package/dist/server.d.ts +8 -0
  888. package/dist/server.d.ts.map +1 -0
  889. package/dist/server.js +4851 -0
  890. package/dist/server.js.map +1 -0
  891. package/dist/services/backlogReview.d.ts +66 -0
  892. package/dist/services/backlogReview.d.ts.map +1 -0
  893. package/dist/services/backlogReview.js +285 -0
  894. package/dist/services/backlogReview.js.map +1 -0
  895. package/dist/services/backup.d.ts +43 -0
  896. package/dist/services/backup.d.ts.map +1 -0
  897. package/dist/services/backup.js +334 -0
  898. package/dist/services/backup.js.map +1 -0
  899. package/dist/services/credit-monitor.d.ts +40 -0
  900. package/dist/services/credit-monitor.d.ts.map +1 -0
  901. package/dist/services/credit-monitor.js +147 -0
  902. package/dist/services/credit-monitor.js.map +1 -0
  903. package/dist/services/morningBriefing.d.ts +125 -0
  904. package/dist/services/morningBriefing.d.ts.map +1 -0
  905. package/dist/services/morningBriefing.js +660 -0
  906. package/dist/services/morningBriefing.js.map +1 -0
  907. package/dist/services/routine-patterns.d.ts +21 -0
  908. package/dist/services/routine-patterns.d.ts.map +1 -0
  909. package/dist/services/routine-patterns.js +46 -0
  910. package/dist/services/routine-patterns.js.map +1 -0
  911. package/dist/services/traceInsights.d.ts +53 -0
  912. package/dist/services/traceInsights.d.ts.map +1 -0
  913. package/dist/services/traceInsights.js +762 -0
  914. package/dist/services/traceInsights.js.map +1 -0
  915. package/dist/services/training.d.ts +63 -0
  916. package/dist/services/training.d.ts.map +1 -0
  917. package/dist/services/training.js +697 -0
  918. package/dist/services/training.js.map +1 -0
  919. package/dist/services/whatsapp.d.ts +45 -0
  920. package/dist/services/whatsapp.d.ts.map +1 -0
  921. package/dist/services/whatsapp.js +194 -0
  922. package/dist/services/whatsapp.js.map +1 -0
  923. package/dist/sessions/store.d.ts +21 -0
  924. package/dist/sessions/store.d.ts.map +1 -0
  925. package/dist/sessions/store.js +47 -0
  926. package/dist/sessions/store.js.map +1 -0
  927. package/dist/settings.d.ts +111 -0
  928. package/dist/settings.d.ts.map +1 -0
  929. package/dist/settings.js +256 -0
  930. package/dist/settings.js.map +1 -0
  931. package/dist/skills/index.d.ts +10 -0
  932. package/dist/skills/index.d.ts.map +1 -0
  933. package/dist/skills/index.js +11 -0
  934. package/dist/skills/index.js.map +1 -0
  935. package/dist/skills/loader.d.ts +22 -0
  936. package/dist/skills/loader.d.ts.map +1 -0
  937. package/dist/skills/loader.js +65 -0
  938. package/dist/skills/loader.js.map +1 -0
  939. package/dist/skills/registry.d.ts +161 -0
  940. package/dist/skills/registry.d.ts.map +1 -0
  941. package/dist/skills/registry.js +664 -0
  942. package/dist/skills/registry.js.map +1 -0
  943. package/dist/skills/types.d.ts +83 -0
  944. package/dist/skills/types.d.ts.map +1 -0
  945. package/dist/skills/types.js +31 -0
  946. package/dist/skills/types.js.map +1 -0
  947. package/dist/skills/validator.d.ts +36 -0
  948. package/dist/skills/validator.d.ts.map +1 -0
  949. package/dist/skills/validator.js +114 -0
  950. package/dist/skills/validator.js.map +1 -0
  951. package/dist/slack/channels.d.ts +102 -0
  952. package/dist/slack/channels.d.ts.map +1 -0
  953. package/dist/slack/channels.js +277 -0
  954. package/dist/slack/channels.js.map +1 -0
  955. package/dist/slack/client.d.ts +151 -0
  956. package/dist/slack/client.d.ts.map +1 -0
  957. package/dist/slack/client.js +468 -0
  958. package/dist/slack/client.js.map +1 -0
  959. package/dist/slack/retry.d.ts +36 -0
  960. package/dist/slack/retry.d.ts.map +1 -0
  961. package/dist/slack/retry.js +100 -0
  962. package/dist/slack/retry.js.map +1 -0
  963. package/dist/slack/types.d.ts +271 -0
  964. package/dist/slack/types.d.ts.map +1 -0
  965. package/dist/slack/types.js +52 -0
  966. package/dist/slack/types.js.map +1 -0
  967. package/dist/slack/webhooks.d.ts +55 -0
  968. package/dist/slack/webhooks.d.ts.map +1 -0
  969. package/dist/slack/webhooks.js +285 -0
  970. package/dist/slack/webhooks.js.map +1 -0
  971. package/dist/stt/client.d.ts +18 -0
  972. package/dist/stt/client.d.ts.map +1 -0
  973. package/dist/stt/client.js +66 -0
  974. package/dist/stt/client.js.map +1 -0
  975. package/dist/stt/sidecar.d.ts +16 -0
  976. package/dist/stt/sidecar.d.ts.map +1 -0
  977. package/dist/stt/sidecar.js +115 -0
  978. package/dist/stt/sidecar.js.map +1 -0
  979. package/dist/tracing/bridge.d.ts +14 -0
  980. package/dist/tracing/bridge.d.ts.map +1 -0
  981. package/dist/tracing/bridge.js +70 -0
  982. package/dist/tracing/bridge.js.map +1 -0
  983. package/dist/tracing/correlation.d.ts +34 -0
  984. package/dist/tracing/correlation.d.ts.map +1 -0
  985. package/dist/tracing/correlation.js +49 -0
  986. package/dist/tracing/correlation.js.map +1 -0
  987. package/dist/tracing/index.d.ts +15 -0
  988. package/dist/tracing/index.d.ts.map +1 -0
  989. package/dist/tracing/index.js +18 -0
  990. package/dist/tracing/index.js.map +1 -0
  991. package/dist/tracing/init.d.ts +42 -0
  992. package/dist/tracing/init.d.ts.map +1 -0
  993. package/dist/tracing/init.js +81 -0
  994. package/dist/tracing/init.js.map +1 -0
  995. package/dist/tracing/instrument.d.ts +39 -0
  996. package/dist/tracing/instrument.d.ts.map +1 -0
  997. package/dist/tracing/instrument.js +145 -0
  998. package/dist/tracing/instrument.js.map +1 -0
  999. package/dist/tracing/middleware.d.ts +18 -0
  1000. package/dist/tracing/middleware.d.ts.map +1 -0
  1001. package/dist/tracing/middleware.js +69 -0
  1002. package/dist/tracing/middleware.js.map +1 -0
  1003. package/dist/tracing/tracer.d.ts +105 -0
  1004. package/dist/tracing/tracer.d.ts.map +1 -0
  1005. package/dist/tracing/tracer.js +327 -0
  1006. package/dist/tracing/tracer.js.map +1 -0
  1007. package/dist/tts/client.d.ts +18 -0
  1008. package/dist/tts/client.d.ts.map +1 -0
  1009. package/dist/tts/client.js +48 -0
  1010. package/dist/tts/client.js.map +1 -0
  1011. package/dist/tts/sidecar.d.ts +16 -0
  1012. package/dist/tts/sidecar.d.ts.map +1 -0
  1013. package/dist/tts/sidecar.js +148 -0
  1014. package/dist/tts/sidecar.js.map +1 -0
  1015. package/dist/twilio/call.d.ts +22 -0
  1016. package/dist/twilio/call.d.ts.map +1 -0
  1017. package/dist/twilio/call.js +79 -0
  1018. package/dist/twilio/call.js.map +1 -0
  1019. package/dist/types/plugin.d.ts +342 -0
  1020. package/dist/types/plugin.d.ts.map +1 -0
  1021. package/dist/types/plugin.js +10 -0
  1022. package/dist/types/plugin.js.map +1 -0
  1023. package/dist/types.d.ts +97 -0
  1024. package/dist/types.d.ts.map +1 -0
  1025. package/dist/types.js +5 -0
  1026. package/dist/types.js.map +1 -0
  1027. package/dist/utils/logger.d.ts +53 -0
  1028. package/dist/utils/logger.d.ts.map +1 -0
  1029. package/dist/utils/logger.js +169 -0
  1030. package/dist/utils/logger.js.map +1 -0
  1031. package/dist/vault/matcher.d.ts +39 -0
  1032. package/dist/vault/matcher.d.ts.map +1 -0
  1033. package/dist/vault/matcher.js +197 -0
  1034. package/dist/vault/matcher.js.map +1 -0
  1035. package/dist/vault/personal.d.ts +60 -0
  1036. package/dist/vault/personal.d.ts.map +1 -0
  1037. package/dist/vault/personal.js +162 -0
  1038. package/dist/vault/personal.js.map +1 -0
  1039. package/dist/vault/store.d.ts +39 -0
  1040. package/dist/vault/store.d.ts.map +1 -0
  1041. package/dist/vault/store.js +111 -0
  1042. package/dist/vault/store.js.map +1 -0
  1043. package/dist/webhooks/config.d.ts +64 -0
  1044. package/dist/webhooks/config.d.ts.map +1 -0
  1045. package/dist/webhooks/config.js +214 -0
  1046. package/dist/webhooks/config.js.map +1 -0
  1047. package/dist/webhooks/event-log.d.ts +90 -0
  1048. package/dist/webhooks/event-log.d.ts.map +1 -0
  1049. package/dist/webhooks/event-log.js +132 -0
  1050. package/dist/webhooks/event-log.js.map +1 -0
  1051. package/dist/webhooks/handler.d.ts +92 -0
  1052. package/dist/webhooks/handler.d.ts.map +1 -0
  1053. package/dist/webhooks/handler.js +103 -0
  1054. package/dist/webhooks/handler.js.map +1 -0
  1055. package/dist/webhooks/handlers.d.ts +100 -0
  1056. package/dist/webhooks/handlers.d.ts.map +1 -0
  1057. package/dist/webhooks/handlers.js +178 -0
  1058. package/dist/webhooks/handlers.js.map +1 -0
  1059. package/dist/webhooks/index.d.ts +29 -0
  1060. package/dist/webhooks/index.d.ts.map +1 -0
  1061. package/dist/webhooks/index.js +33 -0
  1062. package/dist/webhooks/index.js.map +1 -0
  1063. package/dist/webhooks/mount.d.ts +77 -0
  1064. package/dist/webhooks/mount.d.ts.map +1 -0
  1065. package/dist/webhooks/mount.js +400 -0
  1066. package/dist/webhooks/mount.js.map +1 -0
  1067. package/dist/webhooks/registry.d.ts +52 -0
  1068. package/dist/webhooks/registry.d.ts.map +1 -0
  1069. package/dist/webhooks/registry.js +143 -0
  1070. package/dist/webhooks/registry.js.map +1 -0
  1071. package/dist/webhooks/relay.d.ts +25 -0
  1072. package/dist/webhooks/relay.d.ts.map +1 -0
  1073. package/dist/webhooks/relay.js +53 -0
  1074. package/dist/webhooks/relay.js.map +1 -0
  1075. package/dist/webhooks/retry.d.ts +92 -0
  1076. package/dist/webhooks/retry.d.ts.map +1 -0
  1077. package/dist/webhooks/retry.js +270 -0
  1078. package/dist/webhooks/retry.js.map +1 -0
  1079. package/dist/webhooks/router.d.ts +94 -0
  1080. package/dist/webhooks/router.d.ts.map +1 -0
  1081. package/dist/webhooks/router.js +290 -0
  1082. package/dist/webhooks/router.js.map +1 -0
  1083. package/dist/webhooks/twilio.d.ts +63 -0
  1084. package/dist/webhooks/twilio.d.ts.map +1 -0
  1085. package/dist/webhooks/twilio.js +129 -0
  1086. package/dist/webhooks/twilio.js.map +1 -0
  1087. package/dist/webhooks/types.d.ts +142 -0
  1088. package/dist/webhooks/types.d.ts.map +1 -0
  1089. package/dist/webhooks/types.js +8 -0
  1090. package/dist/webhooks/types.js.map +1 -0
  1091. package/dist/webhooks/verify.d.ts +51 -0
  1092. package/dist/webhooks/verify.d.ts.map +1 -0
  1093. package/dist/webhooks/verify.js +98 -0
  1094. package/dist/webhooks/verify.js.map +1 -0
  1095. package/package.json +70 -0
  1096. package/public/board.html +1316 -0
  1097. package/public/browser.html +600 -0
  1098. package/public/help.html +655 -0
  1099. package/public/index.html +4689 -0
  1100. package/public/library.html +3642 -0
  1101. package/public/observatory.html +1693 -0
  1102. package/public/ops.html +1129 -0
  1103. package/public/share-modal.js +211 -0
  1104. package/skills/README.md +34 -0
  1105. package/skills/core-architecture.md +33 -0
  1106. package/skills/form-fill.md +98 -0
  1107. package/skills/form-review.md +110 -0
  1108. package/skills/form-scout.md +94 -0
  1109. package/skills/log-decision.md +27 -0
  1110. package/skills/onboard.md +98 -0
  1111. package/skills/voice-guide.md +22 -0
  1112. package/skills/write-blog.md +43 -0
@@ -0,0 +1,3642 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{INSTANCE_NAME}} — Library</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0e0e10;
10
+ --surface: #18181b;
11
+ --surface2: #222226;
12
+ --border: #2e2e33;
13
+ --text: #e4e4e7;
14
+ --text-dim: #8b8b94;
15
+ --accent: #6d5dfc;
16
+ --green: #22c55e;
17
+ --red: #ef4444;
18
+ --yellow: #eab308;
19
+ --orange: #f97316;
20
+ --blue: #3b82f6;
21
+ --purple: #a78bfa;
22
+ --mono: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
23
+ }
24
+
25
+ * { margin: 0; padding: 0; box-sizing: border-box; }
26
+
27
+ body {
28
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
29
+ background: var(--bg);
30
+ color: var(--text);
31
+ height: 100vh;
32
+ display: flex;
33
+ flex-direction: column;
34
+ overflow: hidden;
35
+ }
36
+
37
+ /* --- Header --- */
38
+ header {
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: space-between;
42
+ padding: 12px 20px;
43
+ border-bottom: 1px solid var(--border);
44
+ background: var(--surface);
45
+ flex-shrink: 0;
46
+ }
47
+
48
+ header h1 {
49
+ font-size: 16px;
50
+ font-weight: 600;
51
+ }
52
+
53
+ header h1 a {
54
+ color: var(--text);
55
+ text-decoration: none;
56
+ }
57
+
58
+ header h1 a:hover { opacity: 0.8; }
59
+ header h1 span { color: var(--text-dim); font-weight: 400; }
60
+
61
+ .header-nav {
62
+ display: flex;
63
+ gap: 4px;
64
+ margin-left: 20px;
65
+ }
66
+
67
+ .header-nav a {
68
+ color: var(--text-dim);
69
+ text-decoration: none;
70
+ font-size: 13px;
71
+ padding: 4px 10px;
72
+ border-radius: 6px;
73
+ border: 1px solid transparent;
74
+ transition: all 0.15s;
75
+ }
76
+
77
+ .header-nav a:hover { color: var(--text); border-color: var(--accent); background: rgba(109,93,252,0.1); }
78
+ .header-nav a.active { color: var(--text); border-color: var(--border); background: var(--surface2); }
79
+
80
+ .header-stats {
81
+ display: flex;
82
+ gap: 16px;
83
+ font-size: 12px;
84
+ color: var(--text-dim);
85
+ }
86
+
87
+ .header-stats .stat-val { color: var(--text); }
88
+
89
+ /* --- Toolbar --- */
90
+ .toolbar {
91
+ display: flex;
92
+ align-items: center;
93
+ gap: 10px;
94
+ padding: 10px 20px;
95
+ border-bottom: 1px solid var(--border);
96
+ background: var(--surface);
97
+ flex-shrink: 0;
98
+ }
99
+
100
+ .search-box {
101
+ flex: 1;
102
+ max-width: 400px;
103
+ position: relative;
104
+ }
105
+
106
+ .search-box input {
107
+ width: 100%;
108
+ padding: 7px 12px 7px 32px;
109
+ background: var(--surface2);
110
+ border: 1px solid var(--border);
111
+ border-radius: 6px;
112
+ color: var(--text);
113
+ font-size: 13px;
114
+ outline: none;
115
+ }
116
+
117
+ .search-box input:focus { border-color: var(--accent); }
118
+
119
+ .search-box::before {
120
+ content: '🔍';
121
+ position: absolute;
122
+ left: 10px;
123
+ top: 50%;
124
+ transform: translateY(-50%);
125
+ font-size: 12px;
126
+ }
127
+
128
+ .toolbar-btn {
129
+ padding: 6px 14px;
130
+ background: var(--surface2);
131
+ border: 1px solid var(--border);
132
+ border-radius: 6px;
133
+ color: var(--text);
134
+ font-size: 13px;
135
+ cursor: pointer;
136
+ transition: all 0.15s;
137
+ }
138
+
139
+ .toolbar-btn:hover { border-color: var(--accent); background: rgba(109,93,252,0.1); }
140
+ .toolbar-btn.active { border-color: var(--accent); color: var(--accent); }
141
+
142
+ .toolbar-btn.primary {
143
+ background: var(--accent);
144
+ border-color: var(--accent);
145
+ color: #fff;
146
+ }
147
+
148
+ .toolbar-btn.primary:hover { opacity: 0.9; }
149
+
150
+ .view-toggle {
151
+ display: flex;
152
+ border: 1px solid var(--border);
153
+ border-radius: 6px;
154
+ overflow: hidden;
155
+ }
156
+
157
+ .view-toggle button {
158
+ padding: 6px 10px;
159
+ background: var(--surface2);
160
+ border: none;
161
+ color: var(--text-dim);
162
+ cursor: pointer;
163
+ font-size: 13px;
164
+ }
165
+
166
+ .view-toggle button.active { background: var(--accent); color: #fff; }
167
+ .view-toggle button:hover:not(.active) { color: var(--text); }
168
+
169
+ /* --- Layout --- */
170
+ .layout {
171
+ display: flex;
172
+ flex: 1;
173
+ overflow: hidden;
174
+ }
175
+
176
+ /* --- Sidebar --- */
177
+ .sidebar {
178
+ width: 250px;
179
+ border-right: 1px solid var(--border);
180
+ background: var(--surface);
181
+ overflow-y: auto;
182
+ flex-shrink: 0;
183
+ display: flex;
184
+ flex-direction: column;
185
+ }
186
+
187
+ .sidebar-section {
188
+ padding: 12px 14px;
189
+ border-bottom: 1px solid var(--border);
190
+ }
191
+
192
+ .sidebar-section h3 {
193
+ font-size: 10px;
194
+ text-transform: uppercase;
195
+ letter-spacing: 1px;
196
+ color: var(--text-dim);
197
+ margin-bottom: 8px;
198
+ }
199
+
200
+ .sidebar-item {
201
+ display: flex;
202
+ align-items: center;
203
+ gap: 8px;
204
+ padding: 5px 8px;
205
+ border-radius: 5px;
206
+ cursor: pointer;
207
+ font-size: 13px;
208
+ color: var(--text-dim);
209
+ transition: all 0.12s;
210
+ }
211
+
212
+ .sidebar-item:hover { background: var(--surface2); color: var(--text); }
213
+ .sidebar-item.active { background: rgba(109,93,252,0.15); color: var(--accent); }
214
+
215
+ .sidebar-item .icon { width: 16px; text-align: center; font-size: 12px; }
216
+ .sidebar-item .count {
217
+ margin-left: auto;
218
+ font-size: 11px;
219
+ color: var(--text-dim);
220
+ font-family: var(--mono);
221
+ }
222
+
223
+ .tree-children { padding-left: 16px; }
224
+
225
+ .tree-toggle {
226
+ width: 14px;
227
+ height: 14px;
228
+ display: flex;
229
+ align-items: center;
230
+ justify-content: center;
231
+ font-size: 9px;
232
+ cursor: pointer;
233
+ color: var(--text-dim);
234
+ flex-shrink: 0;
235
+ }
236
+
237
+ .tree-toggle:hover { color: var(--text); }
238
+
239
+ /* --- Main content --- */
240
+ .main {
241
+ flex: 1;
242
+ overflow-y: auto;
243
+ display: flex;
244
+ flex-direction: column;
245
+ }
246
+
247
+ .breadcrumb {
248
+ display: flex;
249
+ align-items: center;
250
+ gap: 4px;
251
+ padding: 10px 20px;
252
+ font-size: 13px;
253
+ color: var(--text-dim);
254
+ border-bottom: 1px solid var(--border);
255
+ flex-shrink: 0;
256
+ }
257
+
258
+ .breadcrumb a {
259
+ color: var(--text-dim);
260
+ text-decoration: none;
261
+ cursor: pointer;
262
+ }
263
+
264
+ .breadcrumb a:hover { color: var(--accent); }
265
+ .breadcrumb .sep { margin: 0 2px; }
266
+ .breadcrumb .current { color: var(--text); }
267
+
268
+ .content-area {
269
+ flex: 1;
270
+ padding: 16px 20px;
271
+ overflow-y: auto;
272
+ }
273
+
274
+ /* Subfolder chips */
275
+ .subfolders {
276
+ display: flex;
277
+ flex-wrap: wrap;
278
+ gap: 8px;
279
+ margin-bottom: 16px;
280
+ }
281
+
282
+ .subfolder-card {
283
+ display: flex;
284
+ align-items: center;
285
+ gap: 8px;
286
+ padding: 10px 14px;
287
+ background: var(--surface);
288
+ border: 1px solid var(--border);
289
+ border-radius: 8px;
290
+ cursor: pointer;
291
+ transition: all 0.15s;
292
+ font-size: 13px;
293
+ }
294
+
295
+ .subfolder-card:hover { border-color: var(--accent); background: var(--surface2); }
296
+ .subfolder-card .folder-icon { font-size: 18px; }
297
+ .subfolder-card .folder-count { color: var(--text-dim); font-size: 11px; margin-left: 4px; }
298
+
299
+ /* File grid */
300
+ .file-grid {
301
+ display: grid;
302
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
303
+ gap: 10px;
304
+ }
305
+
306
+ .file-card {
307
+ background: var(--surface);
308
+ border: 1px solid var(--border);
309
+ border-radius: 8px;
310
+ padding: 14px;
311
+ cursor: pointer;
312
+ transition: all 0.15s;
313
+ display: flex;
314
+ flex-direction: column;
315
+ gap: 8px;
316
+ }
317
+
318
+ .file-card:hover { border-color: var(--accent); }
319
+ .file-card.selected { border-color: var(--accent); background: rgba(109,93,252,0.08); }
320
+
321
+ .file-card .file-icon { font-size: 28px; }
322
+ .file-card .file-name {
323
+ font-size: 13px;
324
+ font-weight: 500;
325
+ overflow: hidden;
326
+ text-overflow: ellipsis;
327
+ white-space: nowrap;
328
+ }
329
+
330
+ .file-card .file-meta {
331
+ font-size: 11px;
332
+ color: var(--text-dim);
333
+ display: flex;
334
+ gap: 8px;
335
+ }
336
+
337
+ /* File list view */
338
+ .file-list {
339
+ display: flex;
340
+ flex-direction: column;
341
+ gap: 1px;
342
+ }
343
+
344
+ .file-row {
345
+ display: grid;
346
+ grid-template-columns: 32px 1fr 100px 100px 80px;
347
+ align-items: center;
348
+ gap: 12px;
349
+ padding: 8px 12px;
350
+ background: var(--surface);
351
+ cursor: pointer;
352
+ font-size: 13px;
353
+ transition: background 0.1s;
354
+ }
355
+
356
+ .file-row:hover { background: var(--surface2); }
357
+ .file-row.selected { background: rgba(109,93,252,0.08); }
358
+ .file-row .file-icon { font-size: 18px; text-align: center; }
359
+ .file-row .file-meta-cell { color: var(--text-dim); font-size: 12px; }
360
+
361
+ /* --- Detail panel --- */
362
+ .detail-panel {
363
+ width: 320px;
364
+ border-left: 1px solid var(--border);
365
+ background: var(--surface);
366
+ overflow-y: auto;
367
+ flex-shrink: 0;
368
+ display: none;
369
+ flex-direction: column;
370
+ }
371
+
372
+ .detail-panel.open { display: flex; }
373
+
374
+ .detail-header {
375
+ padding: 16px;
376
+ border-bottom: 1px solid var(--border);
377
+ display: flex;
378
+ justify-content: space-between;
379
+ align-items: flex-start;
380
+ }
381
+
382
+ .detail-header .close-btn {
383
+ background: none;
384
+ border: none;
385
+ color: var(--text-dim);
386
+ cursor: pointer;
387
+ font-size: 18px;
388
+ padding: 2px;
389
+ }
390
+
391
+ .detail-header .close-btn:hover { color: var(--text); }
392
+
393
+ .detail-icon { font-size: 36px; margin-bottom: 8px; }
394
+
395
+ .detail-name {
396
+ font-size: 15px;
397
+ font-weight: 600;
398
+ word-break: break-word;
399
+ }
400
+
401
+ .detail-body { padding: 16px; flex: 1; }
402
+
403
+ .detail-field {
404
+ margin-bottom: 14px;
405
+ }
406
+
407
+ .detail-field label {
408
+ font-size: 10px;
409
+ text-transform: uppercase;
410
+ letter-spacing: 0.8px;
411
+ color: var(--text-dim);
412
+ display: block;
413
+ margin-bottom: 4px;
414
+ }
415
+
416
+ .detail-field .value {
417
+ font-size: 13px;
418
+ color: var(--text);
419
+ }
420
+
421
+ .detail-tags {
422
+ display: flex;
423
+ flex-wrap: wrap;
424
+ gap: 4px;
425
+ }
426
+
427
+ .detail-tag {
428
+ padding: 2px 8px;
429
+ background: var(--surface2);
430
+ border-radius: 10px;
431
+ font-size: 11px;
432
+ color: var(--text-dim);
433
+ }
434
+
435
+ .detail-actions {
436
+ padding: 16px;
437
+ border-top: 1px solid var(--border);
438
+ display: flex;
439
+ flex-direction: column;
440
+ gap: 6px;
441
+ }
442
+
443
+ .detail-action-btn {
444
+ padding: 8px 12px;
445
+ background: var(--surface2);
446
+ border: 1px solid var(--border);
447
+ border-radius: 6px;
448
+ color: var(--text);
449
+ cursor: pointer;
450
+ font-size: 13px;
451
+ text-align: left;
452
+ transition: all 0.15s;
453
+ }
454
+
455
+ .detail-action-btn:hover { border-color: var(--accent); }
456
+ .detail-action-btn.danger { color: var(--red); }
457
+ .detail-action-btn.danger:hover { border-color: var(--red); }
458
+
459
+ /* --- Drop zone --- */
460
+ .drop-zone {
461
+ border: 2px dashed var(--border);
462
+ border-radius: 10px;
463
+ padding: 30px;
464
+ text-align: center;
465
+ color: var(--text-dim);
466
+ font-size: 13px;
467
+ margin-top: 16px;
468
+ transition: all 0.2s;
469
+ }
470
+
471
+ .drop-zone.dragover {
472
+ border-color: var(--accent);
473
+ background: rgba(109,93,252,0.08);
474
+ color: var(--accent);
475
+ }
476
+
477
+ /* --- Empty state --- */
478
+ .empty-state {
479
+ text-align: center;
480
+ padding: 40px 20px;
481
+ color: var(--text-dim);
482
+ }
483
+
484
+ .empty-state .icon { font-size: 36px; margin-bottom: 12px; }
485
+ .empty-state p { font-size: 13px; }
486
+
487
+ /* --- Upload modal --- */
488
+ .modal-overlay {
489
+ position: fixed;
490
+ inset: 0;
491
+ background: rgba(0,0,0,0.6);
492
+ display: none;
493
+ align-items: center;
494
+ justify-content: center;
495
+ z-index: 100;
496
+ }
497
+
498
+ .modal-overlay.open { display: flex; }
499
+
500
+ .modal {
501
+ background: var(--surface);
502
+ border: 1px solid var(--border);
503
+ border-radius: 12px;
504
+ padding: 24px;
505
+ width: 420px;
506
+ max-width: 90vw;
507
+ }
508
+
509
+ .modal h2 {
510
+ font-size: 16px;
511
+ margin-bottom: 16px;
512
+ }
513
+
514
+ .modal-field {
515
+ margin-bottom: 14px;
516
+ }
517
+
518
+ .modal-field label {
519
+ display: block;
520
+ font-size: 12px;
521
+ color: var(--text-dim);
522
+ margin-bottom: 4px;
523
+ }
524
+
525
+ .modal-field input, .modal-field select {
526
+ width: 100%;
527
+ padding: 8px 12px;
528
+ background: var(--surface2);
529
+ border: 1px solid var(--border);
530
+ border-radius: 6px;
531
+ color: var(--text);
532
+ font-size: 13px;
533
+ outline: none;
534
+ }
535
+
536
+ .modal-field input:focus, .modal-field select:focus { border-color: var(--accent); }
537
+
538
+ .modal-actions {
539
+ display: flex;
540
+ gap: 8px;
541
+ justify-content: flex-end;
542
+ margin-top: 16px;
543
+ }
544
+
545
+ /* --- Scrollbar --- */
546
+ ::-webkit-scrollbar { width: 6px; }
547
+ ::-webkit-scrollbar-track { background: transparent; }
548
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
549
+ ::-webkit-scrollbar-thumb:hover { background: var(--text-dim); }
550
+
551
+ /* --- Feed view (Last Used, etc.) --- */
552
+ .feed-stream {
553
+ display: flex;
554
+ flex-direction: column;
555
+ gap: 16px;
556
+ max-width: 800px;
557
+ }
558
+
559
+ .feed-card {
560
+ background: var(--surface);
561
+ border: 1px solid var(--border);
562
+ border-radius: 10px;
563
+ overflow: hidden;
564
+ transition: border-color 0.15s;
565
+ }
566
+
567
+ .feed-card:hover { border-color: var(--accent); }
568
+
569
+ .feed-card-header {
570
+ display: flex;
571
+ align-items: center;
572
+ gap: 10px;
573
+ padding: 12px 16px;
574
+ border-bottom: 1px solid var(--border);
575
+ cursor: pointer;
576
+ }
577
+
578
+ .feed-card-header .feed-icon { font-size: 18px; flex-shrink: 0; }
579
+
580
+ .feed-card-header .feed-title {
581
+ font-size: 13px;
582
+ font-weight: 600;
583
+ flex: 1;
584
+ overflow: hidden;
585
+ text-overflow: ellipsis;
586
+ white-space: nowrap;
587
+ }
588
+
589
+ .feed-card-header .feed-meta {
590
+ font-size: 11px;
591
+ color: var(--text-dim);
592
+ flex-shrink: 0;
593
+ display: flex;
594
+ gap: 10px;
595
+ }
596
+
597
+ .feed-card-body {
598
+ padding: 16px;
599
+ font-size: 13px;
600
+ line-height: 1.65;
601
+ color: var(--text);
602
+ max-height: 400px;
603
+ overflow-y: auto;
604
+ white-space: pre-wrap;
605
+ font-family: var(--mono);
606
+ tab-size: 2;
607
+ }
608
+
609
+ .feed-card-body h1, .feed-card-body h2, .feed-card-body h3 {
610
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
611
+ margin: 12px 0 6px;
612
+ }
613
+
614
+ .feed-card-body h1 { font-size: 18px; }
615
+ .feed-card-body h2 { font-size: 15px; }
616
+ .feed-card-body h3 { font-size: 13px; color: var(--text-dim); }
617
+
618
+ .feed-card-body .truncated-note {
619
+ margin-top: 8px;
620
+ padding-top: 8px;
621
+ border-top: 1px solid var(--border);
622
+ color: var(--text-dim);
623
+ font-size: 11px;
624
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
625
+ }
626
+
627
+ .feed-card-body.loading {
628
+ color: var(--text-dim);
629
+ font-style: italic;
630
+ }
631
+
632
+ .feed-card-body.binary {
633
+ color: var(--text-dim);
634
+ text-align: center;
635
+ padding: 24px 16px;
636
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
637
+ }
638
+
639
+ /* --- Brain shadow files --- */
640
+ .shadow-badge {
641
+ display: inline-block;
642
+ padding: 2px 6px;
643
+ background: rgba(167, 139, 250, 0.15);
644
+ color: var(--purple);
645
+ font-size: 9px;
646
+ font-weight: 600;
647
+ text-transform: uppercase;
648
+ letter-spacing: 0.5px;
649
+ border-radius: 4px;
650
+ margin-left: 8px;
651
+ }
652
+
653
+ .sidebar-item.pinned {
654
+ color: var(--purple);
655
+ }
656
+
657
+ .sidebar-item.pinned .icon { opacity: 0.8; }
658
+
659
+ .brain-category-card {
660
+ background: var(--surface);
661
+ border: 1px solid var(--border);
662
+ border-radius: 8px;
663
+ padding: 16px;
664
+ cursor: pointer;
665
+ transition: all 0.15s;
666
+ display: flex;
667
+ flex-direction: column;
668
+ gap: 6px;
669
+ }
670
+
671
+ .brain-category-card:hover { border-color: var(--purple); background: var(--surface2); }
672
+ .brain-category-card .cat-icon { font-size: 24px; }
673
+ .brain-category-card .cat-label { font-size: 14px; font-weight: 500; }
674
+ .brain-category-card .cat-meta { font-size: 11px; color: var(--text-dim); }
675
+
676
+ .file-card.shadow .file-name::after,
677
+ .file-row.shadow .file-name::after {
678
+ content: ' \1F512';
679
+ font-size: 10px;
680
+ }
681
+
682
+ /* --- Responsive --- */
683
+ @media (max-width: 800px) {
684
+ .sidebar { display: none; }
685
+ .detail-panel { position: fixed; right: 0; top: 0; bottom: 0; z-index: 50; width: 300px; }
686
+ }
687
+
688
+ /* --- Section toolbars --- */
689
+ #filesToolbar, #contactsToolbar, #calendarToolbar {
690
+ display: flex;
691
+ align-items: center;
692
+ gap: 10px;
693
+ flex: 1;
694
+ }
695
+
696
+ /* --- Contact grid --- */
697
+ .contact-grid {
698
+ display: grid;
699
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
700
+ gap: 10px;
701
+ }
702
+
703
+ .contact-card {
704
+ background: var(--surface);
705
+ border: 1px solid var(--border);
706
+ border-radius: 8px;
707
+ padding: 14px;
708
+ cursor: pointer;
709
+ transition: all 0.15s;
710
+ display: flex;
711
+ flex-direction: column;
712
+ gap: 6px;
713
+ }
714
+
715
+ .contact-card:hover { border-color: var(--accent); }
716
+ .contact-card.selected { border-color: var(--accent); background: rgba(109,93,252,0.08); }
717
+
718
+ .contact-card-header {
719
+ display: flex;
720
+ align-items: center;
721
+ gap: 10px;
722
+ }
723
+
724
+ .contact-card-icon { font-size: 28px; }
725
+
726
+ .contact-card-name {
727
+ font-size: 14px;
728
+ font-weight: 500;
729
+ overflow: hidden;
730
+ text-overflow: ellipsis;
731
+ white-space: nowrap;
732
+ }
733
+
734
+ .contact-type-badge {
735
+ display: inline-block;
736
+ padding: 2px 8px;
737
+ border-radius: 10px;
738
+ font-size: 10px;
739
+ text-transform: uppercase;
740
+ letter-spacing: 0.5px;
741
+ font-weight: 600;
742
+ }
743
+
744
+ .contact-type-badge.human { background: rgba(59,130,246,0.15); color: var(--blue); }
745
+ .contact-type-badge.ai { background: rgba(167,139,250,0.15); color: var(--purple); }
746
+ .contact-type-badge.organization { background: rgba(34,197,94,0.15); color: var(--green); }
747
+ .contact-type-badge.service { background: rgba(249,115,22,0.15); color: var(--orange); }
748
+
749
+ .contact-card-channel {
750
+ font-size: 12px;
751
+ color: var(--text-dim);
752
+ overflow: hidden;
753
+ text-overflow: ellipsis;
754
+ white-space: nowrap;
755
+ }
756
+
757
+ .contact-card-tags {
758
+ display: flex;
759
+ flex-wrap: wrap;
760
+ gap: 4px;
761
+ margin-top: 2px;
762
+ }
763
+
764
+ /* --- Contact detail --- */
765
+ .channel-list {
766
+ display: flex;
767
+ flex-direction: column;
768
+ gap: 6px;
769
+ }
770
+
771
+ .channel-item {
772
+ display: flex;
773
+ align-items: center;
774
+ gap: 8px;
775
+ font-size: 13px;
776
+ padding: 4px 0;
777
+ }
778
+
779
+ .channel-item .channel-type {
780
+ font-size: 10px;
781
+ text-transform: uppercase;
782
+ color: var(--text-dim);
783
+ width: 44px;
784
+ flex-shrink: 0;
785
+ }
786
+
787
+ .channel-item .channel-value {
788
+ color: var(--text);
789
+ overflow: hidden;
790
+ text-overflow: ellipsis;
791
+ white-space: nowrap;
792
+ }
793
+
794
+ .relationship-list {
795
+ display: flex;
796
+ flex-direction: column;
797
+ gap: 6px;
798
+ }
799
+
800
+ .relationship-item {
801
+ display: flex;
802
+ align-items: center;
803
+ gap: 8px;
804
+ font-size: 13px;
805
+ padding: 6px 8px;
806
+ background: var(--surface2);
807
+ border-radius: 6px;
808
+ cursor: pointer;
809
+ }
810
+
811
+ .relationship-item:hover { background: var(--border); }
812
+ .relationship-item .rel-icon { font-size: 14px; }
813
+
814
+ .relationship-item .rel-type {
815
+ font-size: 10px;
816
+ text-transform: uppercase;
817
+ color: var(--text-dim);
818
+ }
819
+
820
+ .relationship-item .rel-name {
821
+ color: var(--text);
822
+ overflow: hidden;
823
+ text-overflow: ellipsis;
824
+ white-space: nowrap;
825
+ }
826
+
827
+ /* --- Credential type badges --- */
828
+ .credential-type-badge {
829
+ display: inline-block;
830
+ padding: 2px 8px;
831
+ border-radius: 10px;
832
+ font-size: 10px;
833
+ text-transform: uppercase;
834
+ letter-spacing: 0.5px;
835
+ font-weight: 600;
836
+ }
837
+ .credential-type-badge.api_key { background: rgba(59,130,246,0.15); color: var(--blue); }
838
+ .credential-type-badge.token { background: rgba(167,139,250,0.15); color: var(--purple); }
839
+ .credential-type-badge.oauth { background: rgba(34,197,94,0.15); color: var(--green); }
840
+ .credential-type-badge.password { background: rgba(249,115,22,0.15); color: var(--orange); }
841
+ .credential-type-badge.secret { background: rgba(239,68,68,0.15); color: var(--red); }
842
+
843
+ .credential-value {
844
+ font-family: var(--mono);
845
+ font-size: 12px;
846
+ color: var(--text-dim);
847
+ overflow: hidden;
848
+ text-overflow: ellipsis;
849
+ white-space: nowrap;
850
+ }
851
+
852
+ .credential-reveal-btn {
853
+ background: var(--surface2);
854
+ border: 1px solid var(--border);
855
+ color: var(--text-dim);
856
+ font-size: 11px;
857
+ padding: 2px 8px;
858
+ border-radius: 4px;
859
+ cursor: pointer;
860
+ margin-left: 6px;
861
+ }
862
+ .credential-reveal-btn:hover { color: var(--text); border-color: var(--accent); }
863
+
864
+ /* --- Modal textarea --- */
865
+ .modal-field textarea {
866
+ width: 100%;
867
+ padding: 8px 12px;
868
+ background: var(--surface2);
869
+ border: 1px solid var(--border);
870
+ border-radius: 6px;
871
+ color: var(--text);
872
+ font-size: 13px;
873
+ outline: none;
874
+ resize: vertical;
875
+ font-family: inherit;
876
+ }
877
+
878
+ .modal-field textarea:focus { border-color: var(--accent); }
879
+
880
+ /* --- Auth gate --- */
881
+ .auth-gate {
882
+ text-align: center;
883
+ padding: 60px 20px;
884
+ color: var(--text-dim);
885
+ }
886
+
887
+ .auth-gate .icon { font-size: 48px; margin-bottom: 16px; }
888
+ .auth-gate h3 { font-size: 16px; color: var(--text); margin-bottom: 8px; }
889
+ .auth-gate p { font-size: 13px; margin-bottom: 16px; }
890
+
891
+ /* --- Calendar --- */
892
+ .calendar-nav {
893
+ display: flex;
894
+ align-items: center;
895
+ gap: 8px;
896
+ }
897
+
898
+ .calendar-nav .date-label {
899
+ font-size: 14px;
900
+ font-weight: 500;
901
+ min-width: 180px;
902
+ text-align: center;
903
+ }
904
+
905
+ .calendar-view-toggle { margin-left: auto; }
906
+
907
+ /* Agenda view */
908
+ .calendar-agenda {
909
+ display: flex;
910
+ flex-direction: column;
911
+ gap: 16px;
912
+ }
913
+
914
+ .agenda-day {
915
+ background: var(--surface);
916
+ border: 1px solid var(--border);
917
+ border-radius: 8px;
918
+ overflow: hidden;
919
+ }
920
+
921
+ .agenda-day-header {
922
+ padding: 8px 14px;
923
+ background: var(--surface2);
924
+ font-size: 13px;
925
+ font-weight: 600;
926
+ color: var(--text);
927
+ border-bottom: 1px solid var(--border);
928
+ }
929
+
930
+ .agenda-day-header.today {
931
+ background: rgba(109,93,252,0.15);
932
+ color: var(--accent);
933
+ }
934
+
935
+ .agenda-event {
936
+ display: flex;
937
+ align-items: flex-start;
938
+ gap: 12px;
939
+ padding: 10px 14px;
940
+ cursor: pointer;
941
+ transition: background 0.1s;
942
+ border-bottom: 1px solid var(--border);
943
+ }
944
+
945
+ .agenda-event:last-child { border-bottom: none; }
946
+ .agenda-event:hover { background: var(--surface2); }
947
+ .agenda-event.selected { background: rgba(109,93,252,0.08); }
948
+
949
+ .agenda-event-time {
950
+ font-size: 12px;
951
+ color: var(--text-dim);
952
+ font-family: var(--mono);
953
+ min-width: 100px;
954
+ flex-shrink: 0;
955
+ }
956
+
957
+ .agenda-event-info { flex: 1; min-width: 0; }
958
+
959
+ .agenda-event-title {
960
+ font-size: 13px;
961
+ font-weight: 500;
962
+ color: var(--text);
963
+ }
964
+
965
+ .agenda-event-location {
966
+ font-size: 12px;
967
+ color: var(--text-dim);
968
+ margin-top: 2px;
969
+ }
970
+
971
+ .agenda-event-status {
972
+ width: 4px;
973
+ border-radius: 2px;
974
+ align-self: stretch;
975
+ flex-shrink: 0;
976
+ }
977
+
978
+ .agenda-event-status.confirmed { background: var(--green); }
979
+ .agenda-event-status.tentative { background: var(--yellow); }
980
+ .agenda-event-status.cancelled { background: var(--red); }
981
+
982
+ /* Day view */
983
+ .calendar-day-view { position: relative; min-height: 600px; }
984
+
985
+ .hour-slot {
986
+ display: flex;
987
+ border-bottom: 1px solid var(--border);
988
+ min-height: 48px;
989
+ }
990
+
991
+ .hour-label {
992
+ width: 60px;
993
+ padding: 4px 8px;
994
+ font-size: 11px;
995
+ color: var(--text-dim);
996
+ font-family: var(--mono);
997
+ flex-shrink: 0;
998
+ text-align: right;
999
+ }
1000
+
1001
+ .hour-content {
1002
+ flex: 1;
1003
+ position: relative;
1004
+ border-left: 1px solid var(--border);
1005
+ }
1006
+
1007
+ .day-event {
1008
+ position: absolute;
1009
+ left: 4px;
1010
+ right: 4px;
1011
+ background: rgba(109,93,252,0.2);
1012
+ border-left: 3px solid var(--accent);
1013
+ border-radius: 4px;
1014
+ padding: 4px 8px;
1015
+ font-size: 12px;
1016
+ cursor: pointer;
1017
+ overflow: hidden;
1018
+ z-index: 1;
1019
+ }
1020
+
1021
+ .day-event:hover { background: rgba(109,93,252,0.3); }
1022
+ .day-event .event-title { font-weight: 500; }
1023
+ .day-event .event-time { font-size: 10px; color: var(--text-dim); }
1024
+
1025
+ /* Week view */
1026
+ .calendar-week-view {
1027
+ display: grid;
1028
+ grid-template-columns: 60px repeat(7, 1fr);
1029
+ border: 1px solid var(--border);
1030
+ border-radius: 8px;
1031
+ overflow: hidden;
1032
+ }
1033
+
1034
+ .week-header {
1035
+ padding: 8px 4px;
1036
+ text-align: center;
1037
+ font-size: 12px;
1038
+ font-weight: 600;
1039
+ background: var(--surface2);
1040
+ border-bottom: 1px solid var(--border);
1041
+ border-right: 1px solid var(--border);
1042
+ }
1043
+
1044
+ .week-header:last-child { border-right: none; }
1045
+
1046
+ .week-header.today {
1047
+ background: rgba(109,93,252,0.15);
1048
+ color: var(--accent);
1049
+ }
1050
+
1051
+ .week-hour-label {
1052
+ padding: 4px 8px;
1053
+ font-size: 10px;
1054
+ color: var(--text-dim);
1055
+ font-family: var(--mono);
1056
+ text-align: right;
1057
+ border-bottom: 1px solid var(--border);
1058
+ border-right: 1px solid var(--border);
1059
+ min-height: 40px;
1060
+ }
1061
+
1062
+ .week-cell {
1063
+ position: relative;
1064
+ border-bottom: 1px solid var(--border);
1065
+ border-right: 1px solid var(--border);
1066
+ min-height: 40px;
1067
+ }
1068
+
1069
+ .week-cell:nth-child(8n) { border-right: none; }
1070
+
1071
+ .week-event {
1072
+ background: rgba(109,93,252,0.25);
1073
+ border-radius: 3px;
1074
+ padding: 2px 4px;
1075
+ font-size: 10px;
1076
+ margin: 1px 2px;
1077
+ cursor: pointer;
1078
+ overflow: hidden;
1079
+ white-space: nowrap;
1080
+ text-overflow: ellipsis;
1081
+ }
1082
+
1083
+ .week-event:hover { background: rgba(109,93,252,0.4); }
1084
+
1085
+ /* Upcoming events sidebar */
1086
+ .upcoming-event {
1087
+ display: flex;
1088
+ align-items: flex-start;
1089
+ gap: 8px;
1090
+ padding: 6px 8px;
1091
+ border-radius: 5px;
1092
+ cursor: pointer;
1093
+ font-size: 12px;
1094
+ transition: background 0.1s;
1095
+ }
1096
+
1097
+ .upcoming-event:hover { background: var(--surface2); }
1098
+
1099
+ .upcoming-event-dot {
1100
+ width: 6px;
1101
+ height: 6px;
1102
+ border-radius: 50%;
1103
+ background: var(--accent);
1104
+ margin-top: 5px;
1105
+ flex-shrink: 0;
1106
+ }
1107
+
1108
+ .upcoming-event-title {
1109
+ font-size: 12px;
1110
+ color: var(--text);
1111
+ overflow: hidden;
1112
+ text-overflow: ellipsis;
1113
+ white-space: nowrap;
1114
+ }
1115
+
1116
+ .upcoming-event-time {
1117
+ font-size: 11px;
1118
+ color: var(--text-dim);
1119
+ }
1120
+ </style>
1121
+ </head>
1122
+ <body>
1123
+
1124
+ <header>
1125
+ <div style="display:flex;align-items:center;">
1126
+ <h1><a href="/">{{INSTANCE_NAME}}</a></h1>
1127
+ <nav class="header-nav">
1128
+ <a href="/library" class="active">Library</a>
1129
+ <a href="/board">Board</a>
1130
+ <a href="/ops">Ops</a>
1131
+ <a href="/observatory">Observatory</a>
1132
+ <a href="/browser">Browser</a>
1133
+ </nav>
1134
+ </div>
1135
+ <div style="display:flex;align-items:center;gap:12px;">
1136
+ <div class="header-stats" id="stats"></div>
1137
+ <div class="header-actions-right">
1138
+ <button class="gear-btn" onclick="openShareModal()" title="Share Core">&#9993;</button>
1139
+ <a class="gear-btn" href="/?settings=open" title="Settings">&#9881;</a>
1140
+ </div>
1141
+ </div>
1142
+ </header>
1143
+
1144
+ <div class="toolbar">
1145
+ <div class="search-box">
1146
+ <input type="text" id="globalSearchInput" placeholder="Search..." />
1147
+ </div>
1148
+ <div id="filesToolbar" style="display:flex;align-items:center;gap:10px;">
1149
+ <div class="view-toggle">
1150
+ <button id="feedViewBtn" title="Feed view">⊟</button>
1151
+ <button id="gridViewBtn" class="active" title="Grid view">▦</button>
1152
+ <button id="listViewBtn" title="List view">☰</button>
1153
+ </div>
1154
+ <select id="feedSortSelect" style="display:none;background:var(--surface);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:4px 8px;font-size:12px;outline:none;">
1155
+ <option value="recent">Most Recent</option>
1156
+ <option value="name">Name A-Z</option>
1157
+ <option value="name-desc">Name Z-A</option>
1158
+ <option value="size">Largest First</option>
1159
+ <option value="size-asc">Smallest First</option>
1160
+ <option value="oldest">Oldest First</option>
1161
+ </select>
1162
+ <button class="toolbar-btn" id="newFolderBtn">+ Folder</button>
1163
+ <label class="toolbar-btn primary" style="cursor:pointer;">
1164
+ ↑ Upload
1165
+ <input type="file" id="uploadInput" multiple style="display:none;" />
1166
+ </label>
1167
+ </div>
1168
+ <div id="contactsToolbar" style="display:none;align-items:center;gap:10px;">
1169
+ <select class="toolbar-btn" id="contactTypeFilter" onchange="filterContactsByType(this.value)">
1170
+ <option value="">All Types</option>
1171
+ <option value="human">People</option>
1172
+ <option value="organization">Organizations</option>
1173
+ <option value="ai">AI</option>
1174
+ <option value="service">Services</option>
1175
+ </select>
1176
+ <button class="toolbar-btn primary" onclick="openNewContactModal()">+ Contact</button>
1177
+ </div>
1178
+ <div id="calendarToolbar" style="display:none;align-items:center;gap:10px;">
1179
+ <div class="calendar-nav">
1180
+ <button class="toolbar-btn" onclick="goToToday()">Today</button>
1181
+ <button class="toolbar-btn" onclick="navigateCalendarDate(-1)">&larr;</button>
1182
+ <button class="toolbar-btn" onclick="navigateCalendarDate(1)">&rarr;</button>
1183
+ <span class="date-label" id="calendarDateLabel"></span>
1184
+ </div>
1185
+ <div class="view-toggle calendar-view-toggle">
1186
+ <button id="agendaViewBtn" class="active" onclick="setCalendarView('agenda')">Agenda</button>
1187
+ <button id="dayViewBtn" onclick="setCalendarView('day')">Day</button>
1188
+ <button id="weekViewBtn" onclick="setCalendarView('week')">Week</button>
1189
+ </div>
1190
+ <button class="toolbar-btn primary" onclick="openNewEventModal()">+ Event</button>
1191
+ </div>
1192
+ <div id="credentialsToolbar" style="display:none;align-items:center;gap:10px;">
1193
+ <button class="toolbar-btn" onclick="migrateFromVault()">Import from Vault</button>
1194
+ <button class="toolbar-btn primary" onclick="openNewCredentialModal()">+ Credential</button>
1195
+ </div>
1196
+ </div>
1197
+
1198
+ <div class="layout">
1199
+ <!-- Sidebar -->
1200
+ <aside class="sidebar">
1201
+ <div class="sidebar-section">
1202
+ <h3>Browse</h3>
1203
+ <div class="sidebar-item active" data-section="files" onclick="switchSection('files')">
1204
+ <span class="icon">📁</span><span>Files</span>
1205
+ </div>
1206
+ <div class="sidebar-item" data-section="contacts" onclick="switchSection('contacts')">
1207
+ <span class="icon">👤</span><span>Contacts</span>
1208
+ </div>
1209
+ <div class="sidebar-item" data-section="calendar" onclick="switchSection('calendar')">
1210
+ <span class="icon">📅</span><span>Calendar</span>
1211
+ </div>
1212
+ <div class="sidebar-item" data-section="credentials" onclick="switchSection('credentials')">
1213
+ <span class="icon">🔑</span><span>Credentials</span>
1214
+ </div>
1215
+ </div>
1216
+ <div id="filesNav">
1217
+ <div class="sidebar-section" id="favoritesSection" style="display:none;">
1218
+ <h3>Favorites</h3>
1219
+ <div id="favoritesList"></div>
1220
+ </div>
1221
+ <div class="sidebar-section" id="recentsSection" style="display:none;">
1222
+ <h3>Recents</h3>
1223
+ <div id="recentsList"></div>
1224
+ </div>
1225
+ <div class="sidebar-section" style="flex:1;">
1226
+ <h3>Folders</h3>
1227
+ <div id="folderTree"></div>
1228
+ </div>
1229
+ <div class="sidebar-section" id="brainSection">
1230
+ <h3>Brain</h3>
1231
+ <div id="brainCategoryList"></div>
1232
+ </div>
1233
+ </div>
1234
+ <div id="contactsNav" style="display:none;">
1235
+ <div class="sidebar-section">
1236
+ <h3>Types</h3>
1237
+ <div class="sidebar-item active" data-contact-type="" onclick="filterContactsByType('')">
1238
+ <span class="icon">📋</span><span>All</span><span class="count" id="contactCountAll"></span>
1239
+ </div>
1240
+ <div class="sidebar-item" data-contact-type="human" onclick="filterContactsByType('human')">
1241
+ <span class="icon">👤</span><span>People</span><span class="count" id="contactCountHuman"></span>
1242
+ </div>
1243
+ <div class="sidebar-item" data-contact-type="organization" onclick="filterContactsByType('organization')">
1244
+ <span class="icon">🏢</span><span>Organizations</span><span class="count" id="contactCountOrg"></span>
1245
+ </div>
1246
+ <div class="sidebar-item" data-contact-type="ai" onclick="filterContactsByType('ai')">
1247
+ <span class="icon">🤖</span><span>AI</span><span class="count" id="contactCountAi"></span>
1248
+ </div>
1249
+ <div class="sidebar-item" data-contact-type="service" onclick="filterContactsByType('service')">
1250
+ <span class="icon">⚙️</span><span>Services</span><span class="count" id="contactCountService"></span>
1251
+ </div>
1252
+ </div>
1253
+ </div>
1254
+ <div id="calendarNav" style="display:none;">
1255
+ <div class="sidebar-section">
1256
+ <h3>Quick Nav</h3>
1257
+ <div class="sidebar-item" onclick="goToToday()">
1258
+ <span class="icon">📌</span><span>Today</span>
1259
+ </div>
1260
+ <div class="sidebar-item" onclick="goToThisWeek()">
1261
+ <span class="icon">📅</span><span>This Week</span>
1262
+ </div>
1263
+ </div>
1264
+ <div class="sidebar-section" style="flex:1;">
1265
+ <h3>Upcoming</h3>
1266
+ <div id="upcomingEvents"></div>
1267
+ </div>
1268
+ </div>
1269
+ <div id="credentialsNav" style="display:none;">
1270
+ <div class="sidebar-section">
1271
+ <h3>Types</h3>
1272
+ <div class="sidebar-item active" data-cred-type="" onclick="filterCredentialsByType('')">
1273
+ <span class="icon">📋</span><span>All</span><span class="count" id="credCountAll"></span>
1274
+ </div>
1275
+ <div class="sidebar-item" data-cred-type="api_key" onclick="filterCredentialsByType('api_key')">
1276
+ <span class="icon">🔑</span><span>API Keys</span><span class="count" id="credCountApiKey"></span>
1277
+ </div>
1278
+ <div class="sidebar-item" data-cred-type="token" onclick="filterCredentialsByType('token')">
1279
+ <span class="icon">🔐</span><span>Tokens</span><span class="count" id="credCountToken"></span>
1280
+ </div>
1281
+ <div class="sidebar-item" data-cred-type="oauth" onclick="filterCredentialsByType('oauth')">
1282
+ <span class="icon">🔓</span><span>OAuth</span><span class="count" id="credCountOauth"></span>
1283
+ </div>
1284
+ <div class="sidebar-item" data-cred-type="password" onclick="filterCredentialsByType('password')">
1285
+ <span class="icon">🔒</span><span>Passwords</span><span class="count" id="credCountPassword"></span>
1286
+ </div>
1287
+ <div class="sidebar-item" data-cred-type="secret" onclick="filterCredentialsByType('secret')">
1288
+ <span class="icon">🗝️</span><span>Secrets</span><span class="count" id="credCountSecret"></span>
1289
+ </div>
1290
+ </div>
1291
+ </div>
1292
+ </aside>
1293
+
1294
+ <!-- Main content -->
1295
+ <main class="main">
1296
+ <div id="filesMain">
1297
+ <div class="breadcrumb" id="breadcrumb">
1298
+ <a onclick="navigateToRoot()">Library</a>
1299
+ </div>
1300
+ <div class="content-area" id="contentArea">
1301
+ <div id="subfolders" class="subfolders"></div>
1302
+ <div id="fileContainer"></div>
1303
+ <div class="drop-zone" id="dropZone">
1304
+ Drop files here to upload
1305
+ </div>
1306
+ </div>
1307
+ </div>
1308
+ <div id="contactsMain" style="display:none;">
1309
+ <div class="breadcrumb">
1310
+ <span class="current">Contacts</span>
1311
+ </div>
1312
+ <div class="content-area">
1313
+ <div id="contactGrid" class="contact-grid"></div>
1314
+ </div>
1315
+ </div>
1316
+ <div id="calendarMain" style="display:none;">
1317
+ <div class="breadcrumb">
1318
+ <span class="current">Calendar</span>
1319
+ </div>
1320
+ <div class="content-area">
1321
+ <div id="calendarContent"></div>
1322
+ </div>
1323
+ </div>
1324
+ <div id="credentialsMain" style="display:none;">
1325
+ <div class="breadcrumb">
1326
+ <span class="current">Credentials</span>
1327
+ </div>
1328
+ <div class="content-area">
1329
+ <div id="credentialGrid" class="contact-grid"></div>
1330
+ </div>
1331
+ </div>
1332
+ </main>
1333
+
1334
+ <!-- Detail panel -->
1335
+ <aside class="detail-panel" id="detailPanel">
1336
+ <div class="detail-header">
1337
+ <div>
1338
+ <div class="detail-icon" id="detailIcon"></div>
1339
+ <div class="detail-name" id="detailName"></div>
1340
+ </div>
1341
+ <button class="close-btn" onclick="closeDetail()">×</button>
1342
+ </div>
1343
+ <div class="detail-body" id="detailBody"></div>
1344
+ <div class="detail-actions" id="detailActions"></div>
1345
+ </aside>
1346
+ </div>
1347
+
1348
+ <!-- New Folder Modal -->
1349
+ <div class="modal-overlay" id="newFolderModal">
1350
+ <div class="modal">
1351
+ <h2>New Folder</h2>
1352
+ <div class="modal-field">
1353
+ <label>Name</label>
1354
+ <input type="text" id="newFolderName" placeholder="Folder name" />
1355
+ </div>
1356
+ <div class="modal-actions">
1357
+ <button class="toolbar-btn" onclick="closeNewFolderModal()">Cancel</button>
1358
+ <button class="toolbar-btn primary" onclick="createFolder()">Create</button>
1359
+ </div>
1360
+ </div>
1361
+ </div>
1362
+
1363
+ <!-- New Contact Modal -->
1364
+ <div class="modal-overlay" id="newContactModal">
1365
+ <div class="modal">
1366
+ <h2>New Contact</h2>
1367
+ <div class="modal-field">
1368
+ <label>Name</label>
1369
+ <input type="text" id="newContactName" placeholder="Contact name" />
1370
+ </div>
1371
+ <div class="modal-field">
1372
+ <label>Type</label>
1373
+ <select id="newContactType">
1374
+ <option value="human">Person</option>
1375
+ <option value="organization">Organization</option>
1376
+ <option value="ai">AI</option>
1377
+ <option value="service">Service</option>
1378
+ </select>
1379
+ </div>
1380
+ <div class="modal-field">
1381
+ <label>Email</label>
1382
+ <input type="email" id="newContactEmail" placeholder="email@example.com" />
1383
+ </div>
1384
+ <div class="modal-field">
1385
+ <label>Phone</label>
1386
+ <input type="tel" id="newContactPhone" placeholder="+1 (555) 000-0000" />
1387
+ </div>
1388
+ <div class="modal-field">
1389
+ <label>Notes</label>
1390
+ <textarea id="newContactNotes" placeholder="Notes..." rows="3"></textarea>
1391
+ </div>
1392
+ <div class="modal-field">
1393
+ <label>Tags (comma-separated)</label>
1394
+ <input type="text" id="newContactTags" placeholder="client, developer, friend" />
1395
+ </div>
1396
+ <div class="modal-actions">
1397
+ <button class="toolbar-btn" onclick="closeModal('newContactModal')">Cancel</button>
1398
+ <button class="toolbar-btn primary" onclick="createContact()">Create</button>
1399
+ </div>
1400
+ </div>
1401
+ </div>
1402
+
1403
+ <!-- New Relationship Modal -->
1404
+ <div class="modal-overlay" id="newRelationshipModal">
1405
+ <div class="modal">
1406
+ <h2>Add Relationship</h2>
1407
+ <div class="modal-field">
1408
+ <label>Related Contact</label>
1409
+ <select id="relTargetEntity"></select>
1410
+ </div>
1411
+ <div class="modal-field">
1412
+ <label>Relationship Type</label>
1413
+ <select id="relType">
1414
+ <option value="collaborates_with">Collaborates with</option>
1415
+ <option value="works_at">Works at</option>
1416
+ <option value="owns">Owns</option>
1417
+ <option value="introduced_by">Introduced by</option>
1418
+ <option value="uses">Uses</option>
1419
+ <option value="built_by">Built by</option>
1420
+ <option value="reports_to">Reports to</option>
1421
+ </select>
1422
+ </div>
1423
+ <div class="modal-field">
1424
+ <label>Label (optional)</label>
1425
+ <input type="text" id="relLabel" placeholder="e.g. Founder, Lead Developer" />
1426
+ </div>
1427
+ <div class="modal-field">
1428
+ <label>Notes (optional)</label>
1429
+ <input type="text" id="relNotes" placeholder="Additional context" />
1430
+ </div>
1431
+ <div class="modal-actions">
1432
+ <button class="toolbar-btn" onclick="closeModal('newRelationshipModal')">Cancel</button>
1433
+ <button class="toolbar-btn primary" onclick="createRelationship()">Create</button>
1434
+ </div>
1435
+ </div>
1436
+ </div>
1437
+
1438
+ <!-- New Event Modal -->
1439
+ <div class="modal-overlay" id="newEventModal">
1440
+ <div class="modal">
1441
+ <h2>New Event</h2>
1442
+ <div class="modal-field">
1443
+ <label>Title</label>
1444
+ <input type="text" id="newEventTitle" placeholder="Event title" />
1445
+ </div>
1446
+ <div class="modal-field">
1447
+ <label>Date</label>
1448
+ <input type="date" id="newEventDate" />
1449
+ </div>
1450
+ <div class="modal-field" style="display:flex;gap:10px;">
1451
+ <div style="flex:1;">
1452
+ <label>Start Time</label>
1453
+ <input type="time" id="newEventStart" />
1454
+ </div>
1455
+ <div style="flex:1;">
1456
+ <label>End Time</label>
1457
+ <input type="time" id="newEventEnd" />
1458
+ </div>
1459
+ </div>
1460
+ <div class="modal-field">
1461
+ <label>Location</label>
1462
+ <input type="text" id="newEventLocation" placeholder="Location" />
1463
+ </div>
1464
+ <div class="modal-field">
1465
+ <label>Description</label>
1466
+ <textarea id="newEventDescription" placeholder="Description..." rows="3"></textarea>
1467
+ </div>
1468
+ <div class="modal-field">
1469
+ <label>Attendees (comma-separated emails)</label>
1470
+ <input type="text" id="newEventAttendees" placeholder="person@example.com, other@example.com" />
1471
+ </div>
1472
+ <div class="modal-actions">
1473
+ <button class="toolbar-btn" onclick="closeModal('newEventModal')">Cancel</button>
1474
+ <button class="toolbar-btn primary" onclick="createEvent()">Create</button>
1475
+ </div>
1476
+ </div>
1477
+ </div>
1478
+
1479
+ <!-- New Credential Modal -->
1480
+ <div class="modal-overlay" id="newCredentialModal">
1481
+ <div class="modal">
1482
+ <h2 id="credentialModalTitle">New Credential</h2>
1483
+ <div class="modal-field">
1484
+ <label>Name</label>
1485
+ <input type="text" id="credentialName" placeholder="e.g. OpenRouter API Key" />
1486
+ </div>
1487
+ <div class="modal-field">
1488
+ <label>Service</label>
1489
+ <input type="text" id="credentialService" placeholder="e.g. openrouter, linear, twilio" />
1490
+ </div>
1491
+ <div class="modal-field">
1492
+ <label>Type</label>
1493
+ <select id="credentialType">
1494
+ <option value="api_key">API Key</option>
1495
+ <option value="token">Token</option>
1496
+ <option value="oauth">OAuth</option>
1497
+ <option value="password">Password</option>
1498
+ <option value="secret">Secret</option>
1499
+ </select>
1500
+ </div>
1501
+ <div class="modal-field">
1502
+ <label>Value</label>
1503
+ <div style="position:relative;">
1504
+ <input type="password" id="credentialValue" placeholder="Secret value" />
1505
+ <button type="button" class="credential-reveal-btn" style="position:absolute;right:8px;top:50%;transform:translateY(-50%);" onclick="toggleCredentialValueVisibility()">Show</button>
1506
+ </div>
1507
+ </div>
1508
+ <div class="modal-field">
1509
+ <label>Env Variable (optional)</label>
1510
+ <input type="text" id="credentialEnvVar" placeholder="e.g. OPENROUTER_API_KEY" />
1511
+ </div>
1512
+ <div class="modal-field">
1513
+ <label>Notes</label>
1514
+ <textarea id="credentialNotes" placeholder="Notes..." rows="2"></textarea>
1515
+ </div>
1516
+ <div class="modal-field">
1517
+ <label>Tags (comma-separated)</label>
1518
+ <input type="text" id="credentialTags" placeholder="production, ai" />
1519
+ </div>
1520
+ <div class="modal-actions">
1521
+ <button class="toolbar-btn" onclick="closeModal('newCredentialModal')">Cancel</button>
1522
+ <button class="toolbar-btn primary" id="credentialModalSubmit" onclick="saveCredential()">Create</button>
1523
+ </div>
1524
+ </div>
1525
+ </div>
1526
+
1527
+ <script>
1528
+ // ── State ────────────────────────────────────────────────────────────────
1529
+ let currentFolderId = null; // null = root
1530
+ let currentView = 'grid';
1531
+ let selectedFileId = null;
1532
+ let treeData = [];
1533
+ let expandedFolders = new Set();
1534
+ let searchTimeout = null;
1535
+
1536
+ const API = '/api/library';
1537
+ const CONTACTS_API = '/api/contacts';
1538
+ const CREDENTIALS_API = '/api/credentials';
1539
+ let currentSection = 'files'; // 'files' | 'contacts' | 'calendar' | 'credentials'
1540
+ let allContacts = [];
1541
+ let selectedContactId = null;
1542
+ let contactTypeFilter = '';
1543
+ let contactSearchTimeout = null;
1544
+ let allCredentials = [];
1545
+ let selectedCredentialId = null;
1546
+ let credentialTypeFilter = '';
1547
+ let credentialSearchTimeout = null;
1548
+ let editingCredentialId = null;
1549
+
1550
+ // ── Helpers ──────────────────────────────────────────────────────────────
1551
+
1552
+ function formatBytes(bytes) {
1553
+ if (!bytes || bytes === 0) return '0 B';
1554
+ const k = 1024;
1555
+ const sizes = ['B', 'KB', 'MB', 'GB'];
1556
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
1557
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
1558
+ }
1559
+
1560
+ function formatDate(iso) {
1561
+ if (!iso) return '—';
1562
+ const d = new Date(iso);
1563
+ return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
1564
+ }
1565
+
1566
+ function fileIcon(mime, name) {
1567
+ if (!mime) return '📄';
1568
+ if (mime.startsWith('image/')) return '🖼️';
1569
+ if (mime.startsWith('video/')) return '🎬';
1570
+ if (mime.startsWith('audio/')) return '🎵';
1571
+ if (mime.includes('pdf')) return '📕';
1572
+ if (mime.includes('spreadsheet') || mime.includes('csv') || mime.includes('excel')) return '📊';
1573
+ if (mime.includes('presentation') || mime.includes('powerpoint')) return '📽️';
1574
+ if (mime.includes('word') || mime.includes('document')) return '📝';
1575
+ if (mime.includes('zip') || mime.includes('archive') || mime.includes('compressed')) return '📦';
1576
+ if (mime.includes('json')) return '🔧';
1577
+ if (mime.includes('javascript') || mime.includes('typescript')) return '⚡';
1578
+ if (mime.includes('html') || mime.includes('xml')) return '🌐';
1579
+ if (mime.includes('css')) return '🎨';
1580
+ if (mime.includes('markdown') || mime.includes('text/plain')) return '📝';
1581
+ return '📄';
1582
+ }
1583
+
1584
+ function folderIcon(icon) {
1585
+ const map = {
1586
+ 'file-text': '📄', 'image': '🖼️', 'film': '🎬', 'copy': '📋',
1587
+ 'bar-chart': '📊', 'inbox': '📥', 'folder': '📁', 'clock': '🕐',
1588
+ };
1589
+ return map[icon] || '📁';
1590
+ }
1591
+
1592
+ async function api(path, opts) {
1593
+ const res = await fetch(API + path, opts);
1594
+ return res.json();
1595
+ }
1596
+
1597
+ // ── Tree ──────────────────────────────────────────────────────────────────
1598
+
1599
+ async function loadTree() {
1600
+ const data = await api('/tree');
1601
+ treeData = data.tree || [];
1602
+ renderTree();
1603
+ updateStats(data.unfiled);
1604
+ }
1605
+
1606
+ function renderTree() {
1607
+ const el = document.getElementById('folderTree');
1608
+ el.innerHTML = renderTreeNodes(treeData, 0);
1609
+
1610
+ // All Unfiled item
1611
+ const unfiledItem = document.createElement('div');
1612
+ unfiledItem.className = 'sidebar-item' + (currentFolderId === '__unfiled' ? ' active' : '');
1613
+ unfiledItem.innerHTML = '<span class="icon">📥</span><span>Unfiled</span>';
1614
+ unfiledItem.onclick = () => navigateToUnfiled();
1615
+ el.appendChild(unfiledItem);
1616
+ }
1617
+
1618
+ function renderTreeNodes(nodes, depth) {
1619
+ return nodes.map(n => {
1620
+ const hasChildren = n.children && n.children.length > 0;
1621
+ const expanded = expandedFolders.has(n.folder.id);
1622
+ const isActive = currentFolderId === n.folder.id;
1623
+ const isPinned = n.folder.systemType ? ' pinned' : '';
1624
+
1625
+ let html = `<div class="sidebar-item${isActive ? ' active' : ''}${isPinned}" onclick="navigateToFolder('${n.folder.id}')">`;
1626
+ if (hasChildren) {
1627
+ html += `<span class="tree-toggle" onclick="event.stopPropagation(); toggleTreeNode('${n.folder.id}')">${expanded ? '▾' : '▸'}</span>`;
1628
+ } else {
1629
+ html += '<span class="tree-toggle"></span>';
1630
+ }
1631
+ html += `<span class="icon">${folderIcon(n.folder.icon)}</span>`;
1632
+ html += `<span>${n.folder.name}</span>`;
1633
+ if (n.fileCount > 0) html += `<span class="count">${n.fileCount}</span>`;
1634
+ html += '</div>';
1635
+
1636
+ if (hasChildren && expanded) {
1637
+ html += `<div class="tree-children">${renderTreeNodes(n.children, depth + 1)}</div>`;
1638
+ }
1639
+
1640
+ return html;
1641
+ }).join('');
1642
+ }
1643
+
1644
+ function toggleTreeNode(id) {
1645
+ if (expandedFolders.has(id)) expandedFolders.delete(id);
1646
+ else expandedFolders.add(id);
1647
+ renderTree();
1648
+ }
1649
+
1650
+ // ── Navigation ───────────────────────────────────────────────────────────
1651
+
1652
+ async function navigateToRoot() {
1653
+ currentFolderId = null;
1654
+ currentFolderSystemType = null;
1655
+ selectedFileId = null;
1656
+ closeDetail();
1657
+ setViewActive(currentView);
1658
+ await loadRootContent();
1659
+ renderTree();
1660
+ renderBreadcrumb([]);
1661
+ document.getElementById('dropZone').style.display = '';
1662
+ }
1663
+
1664
+ async function navigateToFolder(id) {
1665
+ currentFolderId = id;
1666
+ selectedFileId = null;
1667
+ closeDetail();
1668
+ expandedFolders.add(id);
1669
+
1670
+ const data = await api(`/folders/${id}`);
1671
+ renderBreadcrumb(data.breadcrumb || []);
1672
+
1673
+ const dz = document.getElementById('dropZone');
1674
+
1675
+ // System folders with systemType default to feed view
1676
+ if (data.folder && data.folder.systemType) {
1677
+ currentFolderSystemType = data.folder.systemType;
1678
+ currentView = 'feed';
1679
+ setViewActive('feed');
1680
+ dz.style.display = 'none';
1681
+ document.getElementById('subfolders').innerHTML = '';
1682
+ renderFeed(data.files || []);
1683
+ } else {
1684
+ currentFolderSystemType = null;
1685
+ setViewActive(currentView);
1686
+ dz.style.display = '';
1687
+ if (currentView === 'feed') {
1688
+ document.getElementById('subfolders').innerHTML = '';
1689
+ renderFeed(data.files || []);
1690
+ } else {
1691
+ renderFolderContent(data.subfolders || [], data.files || []);
1692
+ }
1693
+ }
1694
+
1695
+ renderTree();
1696
+ }
1697
+
1698
+ async function navigateToUnfiled() {
1699
+ currentFolderId = '__unfiled';
1700
+ currentFolderSystemType = null;
1701
+ selectedFileId = null;
1702
+ closeDetail();
1703
+ setViewActive(currentView);
1704
+
1705
+ const data = await api('/files?unfiled=true&limit=200');
1706
+ renderBreadcrumb([{ id: '__unfiled', name: 'Unfiled' }]);
1707
+ renderFolderContent([], data.files || []);
1708
+ renderTree();
1709
+ document.getElementById('dropZone').style.display = '';
1710
+ }
1711
+
1712
+ async function loadRootContent() {
1713
+ // Show all root folders and recent files
1714
+ const treeResp = await api('/tree');
1715
+ treeData = treeResp.tree || [];
1716
+ const rootFolders = treeData.map(n => ({
1717
+ ...n.folder,
1718
+ _fileCount: n.fileCount,
1719
+ _childCount: n.children ? n.children.length : 0,
1720
+ }));
1721
+
1722
+ const filesResp = await api('/files?limit=20');
1723
+ renderRootContent(rootFolders, filesResp.files || []);
1724
+ }
1725
+
1726
+ // ── Render content ───────────────────────────────────────────────────────
1727
+
1728
+ function renderRootContent(folders, recentFiles) {
1729
+ const subfoldersEl = document.getElementById('subfolders');
1730
+ subfoldersEl.innerHTML = folders.map(f =>
1731
+ `<div class="subfolder-card" onclick="navigateToFolder('${f.id}')">
1732
+ <span class="folder-icon">${folderIcon(f.icon)}</span>
1733
+ <span>${f.name}</span>
1734
+ <span class="folder-count">${f._fileCount || 0}</span>
1735
+ </div>`
1736
+ ).join('');
1737
+
1738
+ renderFiles(recentFiles);
1739
+ }
1740
+
1741
+ function renderFolderContent(subfolders, files) {
1742
+ const subfoldersEl = document.getElementById('subfolders');
1743
+ if (subfolders.length === 0) {
1744
+ subfoldersEl.innerHTML = '';
1745
+ } else {
1746
+ subfoldersEl.innerHTML = subfolders.map(f =>
1747
+ `<div class="subfolder-card" onclick="navigateToFolder('${f.id}')">
1748
+ <span class="folder-icon">${folderIcon(f.icon)}</span>
1749
+ <span>${f.name}</span>
1750
+ </div>`
1751
+ ).join('');
1752
+ }
1753
+ renderFiles(files);
1754
+ }
1755
+
1756
+ function renderFiles(files) {
1757
+ const container = document.getElementById('fileContainer');
1758
+ if (files.length === 0) {
1759
+ container.innerHTML = '<div class="empty-state"><div class="icon">📂</div><p>No files here yet</p></div>';
1760
+ return;
1761
+ }
1762
+
1763
+ if (currentView === 'feed') {
1764
+ renderFeed(sortFiles(files));
1765
+ return;
1766
+ }
1767
+
1768
+ if (currentView === 'grid') {
1769
+ container.className = 'file-grid';
1770
+ container.innerHTML = files.map(f =>
1771
+ `<div class="file-card${selectedFileId === f.id ? ' selected' : ''}" onclick="selectFile('${f.id}')">
1772
+ <div class="file-icon">${fileIcon(f.mimeType, f.name)}</div>
1773
+ <div class="file-name" title="${f.name}">${f.name}</div>
1774
+ <div class="file-meta">
1775
+ <span>${formatBytes(f.sizeBytes)}</span>
1776
+ <span>${formatDate(f.createdAt)}</span>
1777
+ </div>
1778
+ </div>`
1779
+ ).join('');
1780
+ } else {
1781
+ container.className = 'file-list';
1782
+ container.innerHTML = files.map(f =>
1783
+ `<div class="file-row${selectedFileId === f.id ? ' selected' : ''}" onclick="selectFile('${f.id}')">
1784
+ <div class="file-icon">${fileIcon(f.mimeType, f.name)}</div>
1785
+ <div class="file-name" title="${f.name}">${f.name}</div>
1786
+ <div class="file-meta-cell">${formatBytes(f.sizeBytes)}</div>
1787
+ <div class="file-meta-cell">${formatDate(f.createdAt)}</div>
1788
+ <div class="file-meta-cell">${f.category || '—'}</div>
1789
+ </div>`
1790
+ ).join('');
1791
+ }
1792
+ }
1793
+
1794
+ function renderBreadcrumb(trail) {
1795
+ const el = document.getElementById('breadcrumb');
1796
+ let html = '<a onclick="navigateToRoot()">Library</a>';
1797
+ for (const item of trail) {
1798
+ html += '<span class="sep">/</span>';
1799
+ if (item === trail[trail.length - 1]) {
1800
+ html += `<span class="current">${item.name}</span>`;
1801
+ } else {
1802
+ html += `<a onclick="navigateToFolder('${item.id}')">${item.name}</a>`;
1803
+ }
1804
+ }
1805
+ el.innerHTML = html;
1806
+ }
1807
+
1808
+ // ── File detail ──────────────────────────────────────────────────────────
1809
+
1810
+ async function selectFile(id) {
1811
+ selectedFileId = id;
1812
+ const data = await api(`/files/${id}`);
1813
+ if (!data.file) return;
1814
+
1815
+ const f = data.file;
1816
+ const panel = document.getElementById('detailPanel');
1817
+ panel.classList.add('open');
1818
+
1819
+ document.getElementById('detailIcon').textContent = fileIcon(f.mimeType, f.name);
1820
+ document.getElementById('detailName').textContent = f.name;
1821
+
1822
+ const body = document.getElementById('detailBody');
1823
+ body.innerHTML = `
1824
+ <div class="detail-field">
1825
+ <label>Type</label>
1826
+ <div class="value">${f.mimeType}</div>
1827
+ </div>
1828
+ <div class="detail-field">
1829
+ <label>Size</label>
1830
+ <div class="value">${formatBytes(f.sizeBytes)}</div>
1831
+ </div>
1832
+ <div class="detail-field">
1833
+ <label>Category</label>
1834
+ <div class="value">${f.category || '—'}</div>
1835
+ </div>
1836
+ <div class="detail-field">
1837
+ <label>Created</label>
1838
+ <div class="value">${formatDate(f.createdAt)}</div>
1839
+ </div>
1840
+ <div class="detail-field">
1841
+ <label>Updated</label>
1842
+ <div class="value">${formatDate(f.updatedAt)}</div>
1843
+ </div>
1844
+ <div class="detail-field">
1845
+ <label>Version</label>
1846
+ <div class="value">v${f.version || 1}</div>
1847
+ </div>
1848
+ ${f.tags && f.tags.length > 0 ? `
1849
+ <div class="detail-field">
1850
+ <label>Tags</label>
1851
+ <div class="detail-tags">${f.tags.map(t => `<span class="detail-tag">${t}</span>`).join('')}</div>
1852
+ </div>` : ''}
1853
+ `;
1854
+
1855
+ const actions = document.getElementById('detailActions');
1856
+ actions.innerHTML = `
1857
+ <button class="detail-action-btn" onclick="downloadFile('${f.id}')">⬇ Download</button>
1858
+ <button class="detail-action-btn" onclick="moveFile('${f.id}')">📁 Move to folder</button>
1859
+ <button class="detail-action-btn" onclick="toggleFav('${f.id}', 'file')">${data.favorited ? '★ Unfavorite' : '☆ Favorite'}</button>
1860
+ <button class="detail-action-btn danger" onclick="archiveFile('${f.id}')">🗑 Archive</button>
1861
+ `;
1862
+
1863
+ // Re-render file list to update selection highlight
1864
+ refreshCurrentView();
1865
+ }
1866
+
1867
+ function closeDetail() {
1868
+ selectedFileId = null;
1869
+ selectedContactId = null;
1870
+ selectedCalendarEvent = null;
1871
+ selectedCredentialId = null;
1872
+ document.getElementById('detailPanel').classList.remove('open');
1873
+ if (currentSection === 'files') refreshCurrentView();
1874
+ if (currentSection === 'contacts' && allContacts.length > 0) renderContactGrid(allContacts);
1875
+ if (currentSection === 'calendar' && calendarEvents.length > 0) renderCalendarView();
1876
+ if (currentSection === 'credentials' && allCredentials.length > 0) renderCredentialGrid(allCredentials);
1877
+ }
1878
+
1879
+ // ── Actions ──────────────────────────────────────────────────────────────
1880
+
1881
+ function downloadFile(id) {
1882
+ window.open(API + `/files/${id}/download`, '_blank');
1883
+ }
1884
+
1885
+ async function archiveFile(id) {
1886
+ if (!confirm('Archive this file?')) return;
1887
+ await api(`/files/${id}`, { method: 'DELETE' });
1888
+ closeDetail();
1889
+ if (currentFolderId) {
1890
+ await navigateToFolder(currentFolderId);
1891
+ } else {
1892
+ await navigateToRoot();
1893
+ }
1894
+ loadTree();
1895
+ }
1896
+
1897
+ async function moveFile(id) {
1898
+ const allFolders = await api('/tree');
1899
+ const flat = flattenTree(allFolders.tree || []);
1900
+ const options = flat.map(f => f.folder.path).join('\n');
1901
+ const choice = prompt('Move to folder (enter path):\n\n' + options);
1902
+ if (!choice) return;
1903
+ const target = flat.find(f => f.folder.path === choice);
1904
+ if (!target) { alert('Folder not found'); return; }
1905
+ await api(`/files/${id}`, {
1906
+ method: 'PATCH',
1907
+ headers: { 'Content-Type': 'application/json' },
1908
+ body: JSON.stringify({ folderId: target.folder.id }),
1909
+ });
1910
+ if (currentFolderId && currentFolderId !== '__unfiled') {
1911
+ await navigateToFolder(currentFolderId);
1912
+ } else if (currentFolderId === '__unfiled') {
1913
+ await navigateToUnfiled();
1914
+ } else {
1915
+ await navigateToRoot();
1916
+ }
1917
+ loadTree();
1918
+ }
1919
+
1920
+ function flattenTree(nodes) {
1921
+ const result = [];
1922
+ for (const n of nodes) {
1923
+ result.push(n);
1924
+ if (n.children) result.push(...flattenTree(n.children));
1925
+ }
1926
+ return result;
1927
+ }
1928
+
1929
+ async function toggleFav(id, type) {
1930
+ await api(`/favorites/${id}`, {
1931
+ method: 'POST',
1932
+ headers: { 'Content-Type': 'application/json' },
1933
+ body: JSON.stringify({ type }),
1934
+ });
1935
+ if (selectedFileId === id) selectFile(id);
1936
+ loadFavorites();
1937
+ }
1938
+
1939
+ // ── Upload ───────────────────────────────────────────────────────────────
1940
+
1941
+ async function uploadFiles(files) {
1942
+ for (const file of files) {
1943
+ const form = new FormData();
1944
+ form.append('file', file);
1945
+ if (currentFolderId && currentFolderId !== '__unfiled') {
1946
+ form.append('folderId', currentFolderId);
1947
+ }
1948
+ await fetch(API + '/files/upload', { method: 'POST', body: form });
1949
+ }
1950
+ // Refresh
1951
+ if (currentFolderId && currentFolderId !== '__unfiled') {
1952
+ await navigateToFolder(currentFolderId);
1953
+ } else if (currentFolderId === '__unfiled') {
1954
+ await navigateToUnfiled();
1955
+ } else {
1956
+ await navigateToRoot();
1957
+ }
1958
+ loadTree();
1959
+ }
1960
+
1961
+ document.getElementById('uploadInput').addEventListener('change', (e) => {
1962
+ if (e.target.files.length > 0) uploadFiles(e.target.files);
1963
+ e.target.value = '';
1964
+ });
1965
+
1966
+ // Drag and drop
1967
+ const dropZone = document.getElementById('dropZone');
1968
+ const contentArea = document.getElementById('contentArea');
1969
+
1970
+ contentArea.addEventListener('dragover', (e) => {
1971
+ e.preventDefault();
1972
+ dropZone.classList.add('dragover');
1973
+ });
1974
+
1975
+ contentArea.addEventListener('dragleave', () => {
1976
+ dropZone.classList.remove('dragover');
1977
+ });
1978
+
1979
+ contentArea.addEventListener('drop', (e) => {
1980
+ e.preventDefault();
1981
+ dropZone.classList.remove('dragover');
1982
+ if (e.dataTransfer.files.length > 0) uploadFiles(e.dataTransfer.files);
1983
+ });
1984
+
1985
+ // ── New folder ───────────────────────────────────────────────────────────
1986
+
1987
+ document.getElementById('newFolderBtn').onclick = () => {
1988
+ document.getElementById('newFolderModal').classList.add('open');
1989
+ document.getElementById('newFolderName').value = '';
1990
+ document.getElementById('newFolderName').focus();
1991
+ };
1992
+
1993
+ function closeNewFolderModal() {
1994
+ document.getElementById('newFolderModal').classList.remove('open');
1995
+ }
1996
+
1997
+ async function createFolder() {
1998
+ const name = document.getElementById('newFolderName').value.trim();
1999
+ if (!name) return;
2000
+ await api('/folders', {
2001
+ method: 'POST',
2002
+ headers: { 'Content-Type': 'application/json' },
2003
+ body: JSON.stringify({
2004
+ name,
2005
+ parentId: (currentFolderId && currentFolderId !== '__unfiled') ? currentFolderId : null,
2006
+ }),
2007
+ });
2008
+ closeNewFolderModal();
2009
+ await loadTree();
2010
+ if (currentFolderId && currentFolderId !== '__unfiled') {
2011
+ await navigateToFolder(currentFolderId);
2012
+ } else {
2013
+ await navigateToRoot();
2014
+ }
2015
+ }
2016
+
2017
+ document.getElementById('newFolderName').addEventListener('keydown', (e) => {
2018
+ if (e.key === 'Enter') createFolder();
2019
+ if (e.key === 'Escape') closeNewFolderModal();
2020
+ });
2021
+
2022
+ // ── Search ───────────────────────────────────────────────────────────────
2023
+
2024
+ function handleFileSearch(q) {
2025
+ clearTimeout(searchTimeout);
2026
+ if (!q) {
2027
+ if (currentFolderId && currentFolderId !== '__unfiled') {
2028
+ navigateToFolder(currentFolderId);
2029
+ } else {
2030
+ navigateToRoot();
2031
+ }
2032
+ return;
2033
+ }
2034
+ searchTimeout = setTimeout(async () => {
2035
+ const data = await api(`/search?q=${encodeURIComponent(q)}`);
2036
+ renderBreadcrumb([{ id: '__search', name: `Search: "${q}"` }]);
2037
+ const subfoldersEl = document.getElementById('subfolders');
2038
+ if (data.folders && data.folders.length > 0) {
2039
+ subfoldersEl.innerHTML = data.folders.map(f =>
2040
+ `<div class="subfolder-card" onclick="navigateToFolder('${f.id}')">
2041
+ <span class="folder-icon">${folderIcon(f.icon)}</span>
2042
+ <span>${f.name}</span>
2043
+ </div>`
2044
+ ).join('');
2045
+ } else {
2046
+ subfoldersEl.innerHTML = '';
2047
+ }
2048
+ renderFiles(data.files || []);
2049
+ }, 300);
2050
+ }
2051
+
2052
+ // Unified search — routes to current section's search handler
2053
+ document.getElementById('globalSearchInput').addEventListener('input', (e) => {
2054
+ const q = e.target.value.trim();
2055
+ if (currentSection === 'files') {
2056
+ handleFileSearch(q);
2057
+ } else if (currentSection === 'contacts') {
2058
+ handleContactSearch(q);
2059
+ } else if (currentSection === 'credentials') {
2060
+ handleCredentialSearch(q);
2061
+ }
2062
+ // calendar: no client-side search for now
2063
+ });
2064
+
2065
+ // ── View toggle ──────────────────────────────────────────────────────────
2066
+
2067
+ function setViewActive(view) {
2068
+ document.getElementById('feedViewBtn').classList.toggle('active', view === 'feed');
2069
+ document.getElementById('gridViewBtn').classList.toggle('active', view === 'grid');
2070
+ document.getElementById('listViewBtn').classList.toggle('active', view === 'list');
2071
+ document.getElementById('feedSortSelect').style.display = view === 'feed' ? '' : 'none';
2072
+ }
2073
+
2074
+ document.getElementById('feedViewBtn').onclick = () => {
2075
+ currentView = 'feed';
2076
+ setViewActive('feed');
2077
+ refreshCurrentView();
2078
+ };
2079
+
2080
+ document.getElementById('feedSortSelect').onchange = () => {
2081
+ if (currentView === 'feed' && lastFeedFiles.length > 0) {
2082
+ renderFeed(sortFiles(lastFeedFiles));
2083
+ }
2084
+ };
2085
+
2086
+ document.getElementById('gridViewBtn').onclick = () => {
2087
+ currentView = 'grid';
2088
+ setViewActive('grid');
2089
+ refreshCurrentView();
2090
+ };
2091
+
2092
+ document.getElementById('listViewBtn').onclick = () => {
2093
+ currentView = 'list';
2094
+ setViewActive('list');
2095
+ refreshCurrentView();
2096
+ };
2097
+
2098
+ async function refreshCurrentView() {
2099
+ let files = [];
2100
+ if (currentFolderId === '__unfiled') {
2101
+ const data = await api('/files?unfiled=true&limit=200');
2102
+ files = data.files || [];
2103
+ } else if (currentFolderId) {
2104
+ const data = await api(`/folders/${currentFolderId}`);
2105
+ files = data.files || [];
2106
+ if (currentView !== 'feed') {
2107
+ renderFolderContent(data.subfolders || [], files);
2108
+ return;
2109
+ }
2110
+ } else {
2111
+ // Root: fetch recent files
2112
+ const data = await api('/files?limit=50');
2113
+ files = data.files || [];
2114
+ }
2115
+
2116
+ if (currentView === 'feed') {
2117
+ document.getElementById('subfolders').innerHTML = '';
2118
+ renderFeed(sortFiles(files));
2119
+ } else {
2120
+ renderFiles(files);
2121
+ }
2122
+ }
2123
+
2124
+ // ── Recents & Favorites ─────────────────────────────────────────────────
2125
+
2126
+ async function loadRecents() {
2127
+ const data = await api('/recents?limit=10');
2128
+ const section = document.getElementById('recentsSection');
2129
+ const list = document.getElementById('recentsList');
2130
+ const recents = data.recents || [];
2131
+
2132
+ if (recents.length === 0) {
2133
+ section.style.display = 'none';
2134
+ return;
2135
+ }
2136
+
2137
+ section.style.display = 'block';
2138
+ list.innerHTML = recents.map(r => {
2139
+ const icon = r.targetType === 'folder' ? '📁' : '📄';
2140
+ const label = r.name || r.targetId;
2141
+ const onclick = r.targetType === 'folder'
2142
+ ? `navigateToFolder('${r.targetId}')`
2143
+ : `selectFile('${r.targetId}')`;
2144
+ return `<div class="sidebar-item" onclick="${onclick}">
2145
+ <span class="icon">${icon}</span>
2146
+ <span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${escapeHtml(label)}</span>
2147
+ </div>`;
2148
+ }).join('');
2149
+ }
2150
+
2151
+ async function loadFavorites() {
2152
+ const data = await api('/favorites');
2153
+ const section = document.getElementById('favoritesSection');
2154
+ const list = document.getElementById('favoritesList');
2155
+ const favs = data.favorites || [];
2156
+
2157
+ if (favs.length === 0) {
2158
+ section.style.display = 'none';
2159
+ return;
2160
+ }
2161
+
2162
+ section.style.display = 'block';
2163
+ list.innerHTML = favs.map(f => {
2164
+ const icon = f.targetType === 'folder' ? '📁' : '⭐';
2165
+ const onclick = f.targetType === 'folder'
2166
+ ? `navigateToFolder('${f.targetId}')`
2167
+ : `selectFile('${f.targetId}')`;
2168
+ return `<div class="sidebar-item" onclick="${onclick}">
2169
+ <span class="icon">${icon}</span>
2170
+ <span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${f.targetId.slice(0, 16)}…</span>
2171
+ </div>`;
2172
+ }).join('');
2173
+ }
2174
+
2175
+ function updateStats(unfiled) {
2176
+ const el = document.getElementById('stats');
2177
+ const totalFolders = flattenTree(treeData).length;
2178
+ el.innerHTML = `
2179
+ <span>Folders: <span class="stat-val">${totalFolders}</span></span>
2180
+ <span>Unfiled: <span class="stat-val">${unfiled ?? 0}</span></span>
2181
+ `;
2182
+ }
2183
+
2184
+ // ── Section Routing ──────────────────────────────────────────────────────
2185
+
2186
+ function switchSection(section) {
2187
+ if (section === currentSection) return;
2188
+ currentSection = section;
2189
+ history.replaceState(null, '', '#' + section);
2190
+
2191
+ // Update sidebar type nav
2192
+ document.querySelectorAll('[data-section]').forEach(el => {
2193
+ el.classList.toggle('active', el.dataset.section === section);
2194
+ });
2195
+
2196
+ // Show/hide section-specific toolbar actions
2197
+ document.getElementById('filesToolbar').style.display = section === 'files' ? 'flex' : 'none';
2198
+ document.getElementById('contactsToolbar').style.display = section === 'contacts' ? 'flex' : 'none';
2199
+ document.getElementById('calendarToolbar').style.display = section === 'calendar' ? 'flex' : 'none';
2200
+ document.getElementById('credentialsToolbar').style.display = section === 'credentials' ? 'flex' : 'none';
2201
+
2202
+ // Update unified search placeholder and clear value
2203
+ const searchInput = document.getElementById('globalSearchInput');
2204
+ const placeholders = { files: 'Search files and folders...', contacts: 'Search contacts...', calendar: 'Search events...', credentials: 'Search credentials...' };
2205
+ searchInput.placeholder = placeholders[section] || 'Search...';
2206
+ searchInput.value = '';
2207
+
2208
+ // Show/hide sidebar nav sections
2209
+ document.getElementById('filesNav').style.display = section === 'files' ? '' : 'none';
2210
+ document.getElementById('contactsNav').style.display = section === 'contacts' ? '' : 'none';
2211
+ document.getElementById('calendarNav').style.display = section === 'calendar' ? '' : 'none';
2212
+ document.getElementById('credentialsNav').style.display = section === 'credentials' ? '' : 'none';
2213
+
2214
+ // Show/hide main content
2215
+ document.getElementById('filesMain').style.display = section === 'files' ? '' : 'none';
2216
+ document.getElementById('contactsMain').style.display = section === 'contacts' ? '' : 'none';
2217
+ document.getElementById('calendarMain').style.display = section === 'calendar' ? '' : 'none';
2218
+ document.getElementById('credentialsMain').style.display = section === 'credentials' ? '' : 'none';
2219
+
2220
+ // Close detail panel when switching
2221
+ closeDetail();
2222
+
2223
+ // Load section data
2224
+ if (section === 'contacts') loadContacts();
2225
+ if (section === 'calendar') loadCalendar();
2226
+ if (section === 'credentials') loadCredentials();
2227
+ }
2228
+
2229
+ window.addEventListener('hashchange', () => {
2230
+ const hash = location.hash.replace('#', '') || 'files';
2231
+ if (['files', 'contacts', 'calendar', 'credentials'].includes(hash) && hash !== currentSection) {
2232
+ switchSection(hash);
2233
+ }
2234
+ });
2235
+
2236
+ // ── Contacts Module ──────────────────────────────────────────────────────
2237
+
2238
+ const CONTACT_ICONS = { human: '👤', ai: '🤖', organization: '🏢', service: '⚙️' };
2239
+
2240
+ function escapeHtml(str) {
2241
+ if (!str) return '';
2242
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
2243
+ }
2244
+
2245
+ async function loadContacts(typeFilter) {
2246
+ if (typeFilter !== undefined) contactTypeFilter = typeFilter;
2247
+ const url = contactTypeFilter
2248
+ ? `${CONTACTS_API}/entities?type=${contactTypeFilter}`
2249
+ : `${CONTACTS_API}/entities`;
2250
+
2251
+ try {
2252
+ const data = await fetch(url).then(r => r.json());
2253
+ allContacts = (data.entities || []).filter(e => e.status !== 'archived');
2254
+ renderContactGrid(allContacts);
2255
+ updateContactCounts();
2256
+ } catch (err) {
2257
+ console.error('Failed to load contacts:', err);
2258
+ document.getElementById('contactGrid').innerHTML =
2259
+ '<div class="empty-state"><div class="icon">⚠️</div><p>Failed to load contacts</p></div>';
2260
+ }
2261
+ }
2262
+
2263
+ function renderContactGrid(contacts) {
2264
+ const grid = document.getElementById('contactGrid');
2265
+ if (contacts.length === 0) {
2266
+ grid.innerHTML = '<div class="empty-state"><div class="icon">👤</div><p>No contacts yet</p></div>';
2267
+ return;
2268
+ }
2269
+
2270
+ grid.innerHTML = contacts.map(c => {
2271
+ const icon = CONTACT_ICONS[c.type] || '👤';
2272
+ const topChannel = c.channels && c.channels.length > 0 ? c.channels[0].value : '';
2273
+ const tags = (c.tags || []).slice(0, 3);
2274
+
2275
+ return `<div class="contact-card${selectedContactId === c.id ? ' selected' : ''}" onclick="selectContact('${c.id}')">
2276
+ <div class="contact-card-header">
2277
+ <span class="contact-card-icon">${icon}</span>
2278
+ <div>
2279
+ <div class="contact-card-name">${escapeHtml(c.name)}</div>
2280
+ <span class="contact-type-badge ${c.type}">${c.type}</span>
2281
+ </div>
2282
+ </div>
2283
+ ${topChannel ? `<div class="contact-card-channel">${escapeHtml(topChannel)}</div>` : ''}
2284
+ ${tags.length > 0 ? `<div class="contact-card-tags">${tags.map(t => `<span class="detail-tag">${escapeHtml(t)}</span>`).join('')}</div>` : ''}
2285
+ </div>`;
2286
+ }).join('');
2287
+ }
2288
+
2289
+ async function selectContact(id) {
2290
+ selectedContactId = id;
2291
+ const entity = allContacts.find(c => c.id === id);
2292
+ if (!entity) return;
2293
+
2294
+ let edges = [];
2295
+ try {
2296
+ const data = await fetch(`${CONTACTS_API}/entities/${id}/relationships`).then(r => r.json());
2297
+ edges = data.edges || [];
2298
+ } catch (err) {
2299
+ console.error('Failed to load relationships:', err);
2300
+ }
2301
+
2302
+ renderContactDetail(entity, edges);
2303
+ renderContactGrid(allContacts);
2304
+ }
2305
+
2306
+ function renderContactDetail(entity, edges) {
2307
+ const panel = document.getElementById('detailPanel');
2308
+ panel.classList.add('open');
2309
+
2310
+ const icon = CONTACT_ICONS[entity.type] || '👤';
2311
+ document.getElementById('detailIcon').textContent = icon;
2312
+ document.getElementById('detailName').textContent = entity.name;
2313
+
2314
+ let html = `
2315
+ <div class="detail-field">
2316
+ <label>Type</label>
2317
+ <div class="value"><span class="contact-type-badge ${entity.type}">${entity.type}</span></div>
2318
+ </div>`;
2319
+
2320
+ if (entity.aliases && entity.aliases.length > 0) {
2321
+ html += `<div class="detail-field">
2322
+ <label>Aliases</label>
2323
+ <div class="value">${entity.aliases.map(a => escapeHtml(a)).join(', ')}</div>
2324
+ </div>`;
2325
+ }
2326
+
2327
+ if (entity.channels && entity.channels.length > 0) {
2328
+ html += `<div class="detail-field">
2329
+ <label>Channels</label>
2330
+ <div class="channel-list">
2331
+ ${entity.channels.map(ch => `<div class="channel-item">
2332
+ <span class="channel-type">${ch.type}</span>
2333
+ <span class="channel-value">${escapeHtml(ch.value)}</span>
2334
+ </div>`).join('')}
2335
+ </div>
2336
+ </div>`;
2337
+ }
2338
+
2339
+ if (entity.notes) {
2340
+ html += `<div class="detail-field">
2341
+ <label>Notes</label>
2342
+ <div class="value" style="white-space:pre-wrap;">${escapeHtml(entity.notes)}</div>
2343
+ </div>`;
2344
+ }
2345
+
2346
+ if (entity.tags && entity.tags.length > 0) {
2347
+ html += `<div class="detail-field">
2348
+ <label>Tags</label>
2349
+ <div class="detail-tags">${entity.tags.map(t => `<span class="detail-tag">${escapeHtml(t)}</span>`).join('')}</div>
2350
+ </div>`;
2351
+ }
2352
+
2353
+ html += `<div class="detail-field">
2354
+ <label>Created</label>
2355
+ <div class="value">${formatDate(entity.createdAt)}</div>
2356
+ </div>`;
2357
+
2358
+ if (edges.length > 0) {
2359
+ html += `<div class="detail-field">
2360
+ <label>Relationships</label>
2361
+ <div class="relationship-list">
2362
+ ${edges.map(e => {
2363
+ const isFrom = e.from === entity.id;
2364
+ const otherId = isFrom ? e.to : e.from;
2365
+ const other = allContacts.find(c => c.id === otherId);
2366
+ const otherName = other ? other.name : otherId;
2367
+ const otherIcon = other ? (CONTACT_ICONS[other.type] || '👤') : '👤';
2368
+ const relLabel = e.label ? ` (${escapeHtml(e.label)})` : '';
2369
+ return `<div class="relationship-item" onclick="selectContact('${otherId}')">
2370
+ <span class="rel-icon">${otherIcon}</span>
2371
+ <span class="rel-name">${escapeHtml(otherName)}</span>
2372
+ <span class="rel-type">${e.type.replace(/_/g, ' ')}${relLabel}</span>
2373
+ </div>`;
2374
+ }).join('')}
2375
+ </div>
2376
+ </div>`;
2377
+ }
2378
+
2379
+ const body = document.getElementById('detailBody');
2380
+ body.innerHTML = html;
2381
+
2382
+ const actions = document.getElementById('detailActions');
2383
+ actions.innerHTML = `
2384
+ <button class="detail-action-btn" onclick="openNewRelationshipModal('${entity.id}')">🔗 Add Relationship</button>
2385
+ <button class="detail-action-btn danger" onclick="archiveContact('${entity.id}')">🗑 Archive</button>
2386
+ `;
2387
+ }
2388
+
2389
+ async function updateContactCounts() {
2390
+ try {
2391
+ const data = await fetch(`${CONTACTS_API}/entities`).then(r => r.json());
2392
+ const all = (data.entities || []).filter(e => e.status !== 'archived');
2393
+ const counts = { human: 0, organization: 0, ai: 0, service: 0 };
2394
+ all.forEach(e => { if (counts[e.type] !== undefined) counts[e.type]++; });
2395
+
2396
+ const el = (id) => document.getElementById(id);
2397
+ if (el('contactCountAll')) el('contactCountAll').textContent = all.length || '';
2398
+ if (el('contactCountHuman')) el('contactCountHuman').textContent = counts.human || '';
2399
+ if (el('contactCountOrg')) el('contactCountOrg').textContent = counts.organization || '';
2400
+ if (el('contactCountAi')) el('contactCountAi').textContent = counts.ai || '';
2401
+ if (el('contactCountService')) el('contactCountService').textContent = counts.service || '';
2402
+ } catch (err) { /* non-critical */ }
2403
+ }
2404
+
2405
+ function filterContactsByType(type) {
2406
+ contactTypeFilter = type;
2407
+ document.querySelectorAll('[data-contact-type]').forEach(el => {
2408
+ el.classList.toggle('active', el.dataset.contactType === type);
2409
+ });
2410
+ const dropdown = document.getElementById('contactTypeFilter');
2411
+ if (dropdown) dropdown.value = type;
2412
+ loadContacts(type);
2413
+ }
2414
+
2415
+ // ── Contact Modals ───────────────────────────────────────────────────────
2416
+
2417
+ function openNewContactModal() {
2418
+ document.getElementById('newContactModal').classList.add('open');
2419
+ document.getElementById('newContactName').value = '';
2420
+ document.getElementById('newContactType').value = 'human';
2421
+ document.getElementById('newContactEmail').value = '';
2422
+ document.getElementById('newContactPhone').value = '';
2423
+ document.getElementById('newContactNotes').value = '';
2424
+ document.getElementById('newContactTags').value = '';
2425
+ document.getElementById('newContactName').focus();
2426
+ }
2427
+
2428
+ async function createContact() {
2429
+ const name = document.getElementById('newContactName').value.trim();
2430
+ if (!name) return;
2431
+
2432
+ const type = document.getElementById('newContactType').value;
2433
+ const email = document.getElementById('newContactEmail').value.trim();
2434
+ const phone = document.getElementById('newContactPhone').value.trim();
2435
+ const notes = document.getElementById('newContactNotes').value.trim();
2436
+ const tagsStr = document.getElementById('newContactTags').value.trim();
2437
+
2438
+ const channels = [];
2439
+ if (email) channels.push({ type: 'email', value: email });
2440
+ if (phone) channels.push({ type: 'phone', value: phone });
2441
+ const tags = tagsStr ? tagsStr.split(',').map(t => t.trim()).filter(Boolean) : [];
2442
+
2443
+ try {
2444
+ await fetch(`${CONTACTS_API}/entities`, {
2445
+ method: 'POST',
2446
+ headers: { 'Content-Type': 'application/json' },
2447
+ body: JSON.stringify({ name, type, channels, notes: notes || undefined, tags }),
2448
+ });
2449
+ closeModal('newContactModal');
2450
+ await loadContacts();
2451
+ } catch (err) {
2452
+ alert('Failed to create contact: ' + err.message);
2453
+ }
2454
+ }
2455
+
2456
+ function openNewRelationshipModal(entityId) {
2457
+ document.getElementById('newRelationshipModal').classList.add('open');
2458
+ document.getElementById('newRelationshipModal').dataset.fromEntity = entityId;
2459
+ document.getElementById('relLabel').value = '';
2460
+ document.getElementById('relNotes').value = '';
2461
+ document.getElementById('relType').value = 'collaborates_with';
2462
+
2463
+ const select = document.getElementById('relTargetEntity');
2464
+ select.innerHTML = allContacts
2465
+ .filter(c => c.id !== entityId)
2466
+ .map(c => `<option value="${c.id}">${escapeHtml(c.name)} (${c.type})</option>`)
2467
+ .join('');
2468
+ }
2469
+
2470
+ async function createRelationship() {
2471
+ const modal = document.getElementById('newRelationshipModal');
2472
+ const from = modal.dataset.fromEntity;
2473
+ const to = document.getElementById('relTargetEntity').value;
2474
+ const type = document.getElementById('relType').value;
2475
+ const label = document.getElementById('relLabel').value.trim();
2476
+ const notes = document.getElementById('relNotes').value.trim();
2477
+
2478
+ if (!from || !to) return;
2479
+
2480
+ try {
2481
+ await fetch(`${CONTACTS_API}/edges`, {
2482
+ method: 'POST',
2483
+ headers: { 'Content-Type': 'application/json' },
2484
+ body: JSON.stringify({ from, to, type, label: label || undefined, notes: notes || undefined }),
2485
+ });
2486
+ closeModal('newRelationshipModal');
2487
+ await selectContact(from);
2488
+ } catch (err) {
2489
+ alert('Failed to create relationship: ' + err.message);
2490
+ }
2491
+ }
2492
+
2493
+ async function archiveContact(id) {
2494
+ if (!confirm('Archive this contact?')) return;
2495
+ try {
2496
+ await fetch(`${CONTACTS_API}/entities/${id}`, {
2497
+ method: 'PATCH',
2498
+ headers: { 'Content-Type': 'application/json' },
2499
+ body: JSON.stringify({ status: 'archived' }),
2500
+ });
2501
+ closeDetail();
2502
+ selectedContactId = null;
2503
+ await loadContacts();
2504
+ } catch (err) {
2505
+ alert('Failed to archive contact: ' + err.message);
2506
+ }
2507
+ }
2508
+
2509
+ function closeModal(id) {
2510
+ document.getElementById(id).classList.remove('open');
2511
+ }
2512
+
2513
+ // ── Contact Search ───────────────────────────────────────────────────────
2514
+
2515
+ function handleContactSearch(q) {
2516
+ clearTimeout(contactSearchTimeout);
2517
+ const lq = (q || '').toLowerCase();
2518
+ contactSearchTimeout = setTimeout(() => {
2519
+ if (!lq) {
2520
+ renderContactGrid(allContacts);
2521
+ return;
2522
+ }
2523
+ const filtered = allContacts.filter(c =>
2524
+ c.name.toLowerCase().includes(lq) ||
2525
+ (c.notes && c.notes.toLowerCase().includes(lq)) ||
2526
+ (c.tags && c.tags.some(t => t.toLowerCase().includes(lq))) ||
2527
+ (c.channels && c.channels.some(ch => ch.value.toLowerCase().includes(lq)))
2528
+ );
2529
+ renderContactGrid(filtered);
2530
+ }, 200);
2531
+ }
2532
+
2533
+ document.getElementById('newContactName').addEventListener('keydown', (e) => {
2534
+ if (e.key === 'Enter') createContact();
2535
+ if (e.key === 'Escape') closeModal('newContactModal');
2536
+ });
2537
+
2538
+ // ── Credentials Module ────────────────────────────────────────────────────
2539
+
2540
+ const CRED_ICONS = { api_key: '🔑', token: '🔐', oauth: '🔓', password: '🔒', secret: '🗝️' };
2541
+
2542
+ async function loadCredentials(typeFilter) {
2543
+ if (typeFilter !== undefined) credentialTypeFilter = typeFilter;
2544
+ const params = new URLSearchParams();
2545
+ if (credentialTypeFilter) params.set('type', credentialTypeFilter);
2546
+ const url = params.toString() ? `${CREDENTIALS_API}?${params}` : CREDENTIALS_API;
2547
+
2548
+ try {
2549
+ const data = await fetch(url).then(r => r.json());
2550
+ allCredentials = (data.credentials || []).filter(c => c.status !== 'archived');
2551
+ renderCredentialGrid(allCredentials);
2552
+ updateCredentialCounts();
2553
+ } catch (err) {
2554
+ console.error('Failed to load credentials:', err);
2555
+ document.getElementById('credentialGrid').innerHTML =
2556
+ '<div class="empty-state"><div class="icon">⚠️</div><p>Failed to load credentials</p></div>';
2557
+ }
2558
+ }
2559
+
2560
+ function renderCredentialGrid(creds) {
2561
+ const grid = document.getElementById('credentialGrid');
2562
+ if (creds.length === 0) {
2563
+ grid.innerHTML = '<div class="empty-state"><div class="icon">🔑</div><p>No credentials yet</p><p style="color:var(--text-dim);font-size:13px;">Click "+ Credential" to add one</p></div>';
2564
+ return;
2565
+ }
2566
+
2567
+ grid.innerHTML = creds.map(c => {
2568
+ const icon = CRED_ICONS[c.type] || '🔑';
2569
+ const tags = (c.tags || []).slice(0, 3);
2570
+
2571
+ return `<div class="contact-card${selectedCredentialId === c.id ? ' selected' : ''}" onclick="selectCredential('${c.id}')">
2572
+ <div class="contact-card-header">
2573
+ <span class="contact-card-icon">${icon}</span>
2574
+ <div>
2575
+ <div class="contact-card-name">${escapeHtml(c.service)}</div>
2576
+ <span class="credential-type-badge ${c.type}">${c.type.replace('_', ' ')}</span>
2577
+ </div>
2578
+ </div>
2579
+ <div style="font-size:13px;color:var(--text);">${escapeHtml(c.name)}</div>
2580
+ <div class="credential-value">${escapeHtml(c.value)}</div>
2581
+ ${tags.length > 0 ? `<div class="contact-card-tags">${tags.map(t => `<span class="detail-tag">${escapeHtml(t)}</span>`).join('')}</div>` : ''}
2582
+ </div>`;
2583
+ }).join('');
2584
+ }
2585
+
2586
+ async function selectCredential(id) {
2587
+ selectedCredentialId = id;
2588
+ // Fetch full credential (with unmasked value) for detail view
2589
+ try {
2590
+ const cred = await fetch(`${CREDENTIALS_API}/${id}`).then(r => r.json());
2591
+ renderCredentialDetail(cred);
2592
+ } catch (err) {
2593
+ console.error('Failed to load credential:', err);
2594
+ }
2595
+ renderCredentialGrid(allCredentials);
2596
+ }
2597
+
2598
+ function renderCredentialDetail(cred) {
2599
+ const panel = document.getElementById('detailPanel');
2600
+ panel.classList.add('open');
2601
+
2602
+ const icon = CRED_ICONS[cred.type] || '🔑';
2603
+ document.getElementById('detailIcon').textContent = icon;
2604
+ document.getElementById('detailName').textContent = cred.name;
2605
+
2606
+ let html = `
2607
+ <div class="detail-field">
2608
+ <label>Service</label>
2609
+ <div class="value">${escapeHtml(cred.service)}</div>
2610
+ </div>
2611
+ <div class="detail-field">
2612
+ <label>Type</label>
2613
+ <div class="value"><span class="credential-type-badge ${cred.type}">${cred.type.replace('_', ' ')}</span></div>
2614
+ </div>
2615
+ <div class="detail-field">
2616
+ <label>Value</label>
2617
+ <div class="value">
2618
+ <code class="credential-value" id="credDetailValue" style="display:inline;">••••••••</code>
2619
+ <button class="credential-reveal-btn" onclick="toggleRevealCredential('${cred.id}')">Reveal</button>
2620
+ <button class="credential-reveal-btn" onclick="copyCredentialValue('${cred.id}')">Copy</button>
2621
+ </div>
2622
+ </div>`;
2623
+
2624
+ if (cred.envVar) {
2625
+ html += `<div class="detail-field">
2626
+ <label>Env Variable</label>
2627
+ <div class="value" style="font-family:var(--mono);font-size:12px;">${escapeHtml(cred.envVar)}</div>
2628
+ </div>`;
2629
+ }
2630
+
2631
+ if (cred.notes) {
2632
+ html += `<div class="detail-field">
2633
+ <label>Notes</label>
2634
+ <div class="value" style="white-space:pre-wrap;">${escapeHtml(cred.notes)}</div>
2635
+ </div>`;
2636
+ }
2637
+
2638
+ if (cred.tags && cred.tags.length > 0) {
2639
+ html += `<div class="detail-field">
2640
+ <label>Tags</label>
2641
+ <div class="detail-tags">${cred.tags.map(t => `<span class="detail-tag">${escapeHtml(t)}</span>`).join('')}</div>
2642
+ </div>`;
2643
+ }
2644
+
2645
+ html += `<div class="detail-field">
2646
+ <label>Created</label>
2647
+ <div class="value">${formatDate(cred.createdAt)}</div>
2648
+ </div>
2649
+ <div class="detail-field">
2650
+ <label>Updated</label>
2651
+ <div class="value">${formatDate(cred.updatedAt)}</div>
2652
+ </div>`;
2653
+
2654
+ if (cred.lastRotatedAt) {
2655
+ html += `<div class="detail-field">
2656
+ <label>Last Rotated</label>
2657
+ <div class="value">${formatDate(cred.lastRotatedAt)}</div>
2658
+ </div>`;
2659
+ }
2660
+
2661
+ document.getElementById('detailBody').innerHTML = html;
2662
+
2663
+ // Store full value on the panel for reveal/copy
2664
+ panel.dataset.credValue = cred.value || '';
2665
+
2666
+ document.getElementById('detailActions').innerHTML = `
2667
+ <button class="detail-action-btn" onclick="openEditCredentialModal('${cred.id}')">✏️ Edit</button>
2668
+ <button class="detail-action-btn" onclick="rotateCredential('${cred.id}')">🔄 Rotate</button>
2669
+ <button class="detail-action-btn danger" onclick="archiveCredential('${cred.id}')">🗑 Archive</button>
2670
+ `;
2671
+ }
2672
+
2673
+ function toggleRevealCredential(id) {
2674
+ const el = document.getElementById('credDetailValue');
2675
+ const panel = document.getElementById('detailPanel');
2676
+ const val = panel.dataset.credValue || '';
2677
+ if (el.textContent === '••••••••') {
2678
+ el.textContent = val;
2679
+ el.parentElement.querySelector('.credential-reveal-btn').textContent = 'Hide';
2680
+ } else {
2681
+ el.textContent = '••••••••';
2682
+ el.parentElement.querySelector('.credential-reveal-btn').textContent = 'Reveal';
2683
+ }
2684
+ }
2685
+
2686
+ async function copyCredentialValue(id) {
2687
+ const panel = document.getElementById('detailPanel');
2688
+ const val = panel.dataset.credValue || '';
2689
+ try {
2690
+ await navigator.clipboard.writeText(val);
2691
+ const btn = document.querySelectorAll('.credential-reveal-btn')[1];
2692
+ if (btn) { btn.textContent = 'Copied!'; setTimeout(() => btn.textContent = 'Copy', 1500); }
2693
+ } catch { alert('Failed to copy — check clipboard permissions'); }
2694
+ }
2695
+
2696
+ async function updateCredentialCounts() {
2697
+ try {
2698
+ const data = await fetch(CREDENTIALS_API).then(r => r.json());
2699
+ const all = (data.credentials || []).filter(c => c.status !== 'archived');
2700
+ const counts = { api_key: 0, token: 0, oauth: 0, password: 0, secret: 0 };
2701
+ all.forEach(c => { if (counts[c.type] !== undefined) counts[c.type]++; });
2702
+
2703
+ const el = (id) => document.getElementById(id);
2704
+ if (el('credCountAll')) el('credCountAll').textContent = all.length || '';
2705
+ if (el('credCountApiKey')) el('credCountApiKey').textContent = counts.api_key || '';
2706
+ if (el('credCountToken')) el('credCountToken').textContent = counts.token || '';
2707
+ if (el('credCountOauth')) el('credCountOauth').textContent = counts.oauth || '';
2708
+ if (el('credCountPassword')) el('credCountPassword').textContent = counts.password || '';
2709
+ if (el('credCountSecret')) el('credCountSecret').textContent = counts.secret || '';
2710
+ } catch { /* non-critical */ }
2711
+ }
2712
+
2713
+ function filterCredentialsByType(type) {
2714
+ credentialTypeFilter = type;
2715
+ document.querySelectorAll('[data-cred-type]').forEach(el => {
2716
+ el.classList.toggle('active', el.dataset.credType === type);
2717
+ });
2718
+ loadCredentials(type);
2719
+ }
2720
+
2721
+ // ── Credential Modals ────────────────────────────────────────────────────
2722
+
2723
+ function openNewCredentialModal() {
2724
+ editingCredentialId = null;
2725
+ document.getElementById('credentialModalTitle').textContent = 'New Credential';
2726
+ document.getElementById('credentialModalSubmit').textContent = 'Create';
2727
+ document.getElementById('credentialName').value = '';
2728
+ document.getElementById('credentialService').value = '';
2729
+ document.getElementById('credentialType').value = 'api_key';
2730
+ document.getElementById('credentialValue').value = '';
2731
+ document.getElementById('credentialValue').type = 'password';
2732
+ document.getElementById('credentialEnvVar').value = '';
2733
+ document.getElementById('credentialNotes').value = '';
2734
+ document.getElementById('credentialTags').value = '';
2735
+ document.getElementById('newCredentialModal').classList.add('open');
2736
+ document.getElementById('credentialName').focus();
2737
+ }
2738
+
2739
+ async function openEditCredentialModal(id) {
2740
+ const cred = await fetch(`${CREDENTIALS_API}/${id}`).then(r => r.json());
2741
+ editingCredentialId = id;
2742
+ document.getElementById('credentialModalTitle').textContent = 'Edit Credential';
2743
+ document.getElementById('credentialModalSubmit').textContent = 'Save';
2744
+ document.getElementById('credentialName').value = cred.name || '';
2745
+ document.getElementById('credentialService').value = cred.service || '';
2746
+ document.getElementById('credentialType').value = cred.type || 'api_key';
2747
+ document.getElementById('credentialValue').value = cred.value || '';
2748
+ document.getElementById('credentialValue').type = 'password';
2749
+ document.getElementById('credentialEnvVar').value = cred.envVar || '';
2750
+ document.getElementById('credentialNotes').value = cred.notes || '';
2751
+ document.getElementById('credentialTags').value = (cred.tags || []).join(', ');
2752
+ document.getElementById('newCredentialModal').classList.add('open');
2753
+ document.getElementById('credentialName').focus();
2754
+ }
2755
+
2756
+ function toggleCredentialValueVisibility() {
2757
+ const input = document.getElementById('credentialValue');
2758
+ const btn = input.parentElement.querySelector('.credential-reveal-btn');
2759
+ if (input.type === 'password') {
2760
+ input.type = 'text';
2761
+ btn.textContent = 'Hide';
2762
+ } else {
2763
+ input.type = 'password';
2764
+ btn.textContent = 'Show';
2765
+ }
2766
+ }
2767
+
2768
+ async function saveCredential() {
2769
+ const name = document.getElementById('credentialName').value.trim();
2770
+ const service = document.getElementById('credentialService').value.trim();
2771
+ const type = document.getElementById('credentialType').value;
2772
+ const value = document.getElementById('credentialValue').value;
2773
+ const envVar = document.getElementById('credentialEnvVar').value.trim() || undefined;
2774
+ const notes = document.getElementById('credentialNotes').value.trim() || undefined;
2775
+ const tagsStr = document.getElementById('credentialTags').value.trim();
2776
+ const tags = tagsStr ? tagsStr.split(',').map(t => t.trim()).filter(Boolean) : [];
2777
+
2778
+ if (!name || !service || !value) {
2779
+ alert('Name, Service, and Value are required.');
2780
+ return;
2781
+ }
2782
+
2783
+ try {
2784
+ if (editingCredentialId) {
2785
+ await fetch(`${CREDENTIALS_API}/${editingCredentialId}`, {
2786
+ method: 'PATCH',
2787
+ headers: { 'Content-Type': 'application/json' },
2788
+ body: JSON.stringify({ name, service, type, value, envVar, notes, tags }),
2789
+ });
2790
+ } else {
2791
+ await fetch(CREDENTIALS_API, {
2792
+ method: 'POST',
2793
+ headers: { 'Content-Type': 'application/json' },
2794
+ body: JSON.stringify({ name, service, type, value, envVar, notes, tags }),
2795
+ });
2796
+ }
2797
+ closeModal('newCredentialModal');
2798
+ editingCredentialId = null;
2799
+ closeDetail();
2800
+ await loadCredentials();
2801
+ } catch (err) {
2802
+ alert('Failed to save credential: ' + err.message);
2803
+ }
2804
+ }
2805
+
2806
+ async function rotateCredential(id) {
2807
+ const newValue = prompt('Enter the new secret value:');
2808
+ if (!newValue) return;
2809
+ try {
2810
+ await fetch(`${CREDENTIALS_API}/${id}`, {
2811
+ method: 'PATCH',
2812
+ headers: { 'Content-Type': 'application/json' },
2813
+ body: JSON.stringify({ value: newValue, lastRotatedAt: new Date().toISOString() }),
2814
+ });
2815
+ await loadCredentials();
2816
+ await selectCredential(id);
2817
+ } catch (err) {
2818
+ alert('Failed to rotate credential: ' + err.message);
2819
+ }
2820
+ }
2821
+
2822
+ async function archiveCredential(id) {
2823
+ if (!confirm('Archive this credential?')) return;
2824
+ try {
2825
+ await fetch(`${CREDENTIALS_API}/${id}`, { method: 'DELETE' });
2826
+ closeDetail();
2827
+ selectedCredentialId = null;
2828
+ await loadCredentials();
2829
+ } catch (err) {
2830
+ alert('Failed to archive credential: ' + err.message);
2831
+ }
2832
+ }
2833
+
2834
+ async function migrateFromVault() {
2835
+ if (!confirm('Import all keys from Settings vault into Credentials?\n\nExisting credentials with matching env vars will be skipped.')) return;
2836
+ try {
2837
+ const res = await fetch(`${CREDENTIALS_API}/migrate-vault`, { method: 'POST' });
2838
+ const data = await res.json();
2839
+ if (data.error) { alert('Migration failed: ' + data.error); return; }
2840
+ let msg = `Imported ${data.migrated} key(s) from vault.`;
2841
+ if (data.skipped && data.skipped.length > 0) {
2842
+ msg += `\nSkipped ${data.skipped.length} (already exist): ${data.skipped.join(', ')}`;
2843
+ }
2844
+ alert(msg);
2845
+ await loadCredentials();
2846
+ } catch (err) {
2847
+ alert('Migration failed: ' + err.message);
2848
+ }
2849
+ }
2850
+
2851
+ function handleCredentialSearch(q) {
2852
+ clearTimeout(credentialSearchTimeout);
2853
+ const lq = (q || '').toLowerCase();
2854
+ credentialSearchTimeout = setTimeout(() => {
2855
+ if (!lq) {
2856
+ renderCredentialGrid(allCredentials);
2857
+ return;
2858
+ }
2859
+ const filtered = allCredentials.filter(c =>
2860
+ c.name.toLowerCase().includes(lq) ||
2861
+ c.service.toLowerCase().includes(lq) ||
2862
+ (c.tags && c.tags.some(t => t.toLowerCase().includes(lq)))
2863
+ );
2864
+ renderCredentialGrid(filtered);
2865
+ }, 200);
2866
+ }
2867
+
2868
+ document.getElementById('credentialName').addEventListener('keydown', (e) => {
2869
+ if (e.key === 'Enter') saveCredential();
2870
+ if (e.key === 'Escape') closeModal('newCredentialModal');
2871
+ });
2872
+
2873
+ // ── Calendar Module ──────────────────────────────────────────────────────
2874
+
2875
+ let calendarView = 'agenda'; // 'agenda' | 'day' | 'week'
2876
+ let calendarDate = new Date();
2877
+ let calendarEvents = [];
2878
+ let selectedCalendarEvent = null;
2879
+ let googleAuthenticated = false;
2880
+
2881
+ function formatEventTime(startStr, endStr) {
2882
+ if (!startStr) return '';
2883
+ if (startStr.length === 10) return 'All day';
2884
+ const start = new Date(startStr);
2885
+ const end = endStr ? new Date(endStr) : null;
2886
+ const opts = { hour: 'numeric', minute: '2-digit', hour12: true };
2887
+ let result = start.toLocaleTimeString('en-US', opts);
2888
+ if (end) result += ' – ' + end.toLocaleTimeString('en-US', opts);
2889
+ return result;
2890
+ }
2891
+
2892
+ function calDateKey(d) {
2893
+ return d.toISOString().split('T')[0];
2894
+ }
2895
+
2896
+ function startOfDay(d) {
2897
+ const r = new Date(d);
2898
+ r.setHours(0, 0, 0, 0);
2899
+ return r;
2900
+ }
2901
+
2902
+ function addDays(d, n) {
2903
+ const r = new Date(d);
2904
+ r.setDate(r.getDate() + n);
2905
+ return r;
2906
+ }
2907
+
2908
+ function startOfWeek(d) {
2909
+ const r = new Date(d);
2910
+ r.setDate(r.getDate() - r.getDay());
2911
+ r.setHours(0, 0, 0, 0);
2912
+ return r;
2913
+ }
2914
+
2915
+ function getCalendarDateRange() {
2916
+ const day = startOfDay(calendarDate);
2917
+ if (calendarView === 'day') return { start: day, end: addDays(day, 1) };
2918
+ if (calendarView === 'week') {
2919
+ const ws = startOfWeek(calendarDate);
2920
+ return { start: ws, end: addDays(ws, 7) };
2921
+ }
2922
+ return { start: day, end: addDays(day, 14) }; // agenda: 2 weeks
2923
+ }
2924
+
2925
+ async function loadCalendar() {
2926
+ // Check if Google is authenticated (no session needed)
2927
+ try {
2928
+ const status = await fetch('/api/google/status').then(r => r.json());
2929
+ googleAuthenticated = status.authenticated;
2930
+ } catch (e) {
2931
+ googleAuthenticated = false;
2932
+ }
2933
+
2934
+ if (!googleAuthenticated) {
2935
+ renderCalendarAuthGate();
2936
+ return;
2937
+ }
2938
+
2939
+ updateCalendarDateLabel();
2940
+ const { start, end } = getCalendarDateRange();
2941
+
2942
+ try {
2943
+ const params = new URLSearchParams({
2944
+ timeMin: start.toISOString(),
2945
+ timeMax: end.toISOString(),
2946
+ maxResults: '100',
2947
+ });
2948
+ const res = await fetch(`/api/google/calendar/events?${params}`);
2949
+ const data = await res.json();
2950
+
2951
+ if (!res.ok || data.error) {
2952
+ if (data.error && data.error.includes('not authenticated')) {
2953
+ renderCalendarAuthGate();
2954
+ return;
2955
+ }
2956
+ throw new Error(data.error || 'Failed to load events');
2957
+ }
2958
+
2959
+ calendarEvents = data.events || [];
2960
+ renderCalendarView();
2961
+ loadUpcomingEvents();
2962
+ } catch (err) {
2963
+ console.error('Calendar load error:', err);
2964
+ document.getElementById('calendarContent').innerHTML =
2965
+ `<div class="empty-state"><div class="icon">⚠️</div><p>Failed to load calendar: ${escapeHtml(err.message)}</p></div>`;
2966
+ }
2967
+ }
2968
+
2969
+ function renderCalendarAuthGate() {
2970
+ document.getElementById('calendarContent').innerHTML = `
2971
+ <div class="auth-gate">
2972
+ <div class="icon">📅</div>
2973
+ <h3>Connect Google Calendar</h3>
2974
+ <p>Connect your Google account to see your events here.</p>
2975
+ <button class="toolbar-btn primary" onclick="initiateGoogleAuth()">Connect Google Calendar</button>
2976
+ </div>`;
2977
+ }
2978
+
2979
+ function initiateGoogleAuth() {
2980
+ // Backend redirects directly to Google consent screen
2981
+ window.open('/api/google/auth', 'google-auth', 'width=600,height=700');
2982
+ // Listen for success message from callback page
2983
+ window.addEventListener('message', function onMsg(e) {
2984
+ if (e.data === 'google-connected') {
2985
+ window.removeEventListener('message', onMsg);
2986
+ loadCalendar();
2987
+ }
2988
+ });
2989
+ }
2990
+
2991
+ function renderCalendarView() {
2992
+ if (calendarView === 'agenda') renderAgendaView();
2993
+ else if (calendarView === 'day') renderDayView();
2994
+ else if (calendarView === 'week') renderWeekView();
2995
+ }
2996
+
2997
+ function updateCalendarDateLabel() {
2998
+ const label = document.getElementById('calendarDateLabel');
2999
+ if (!label) return;
3000
+ const opts = { month: 'long', day: 'numeric', year: 'numeric' };
3001
+ if (calendarView === 'week') {
3002
+ const ws = startOfWeek(calendarDate);
3003
+ const we = addDays(ws, 6);
3004
+ label.textContent = ws.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) +
3005
+ ' – ' + we.toLocaleDateString('en-US', opts);
3006
+ } else if (calendarView === 'agenda') {
3007
+ label.textContent = calendarDate.toLocaleDateString('en-US', opts) + ' (2 weeks)';
3008
+ } else {
3009
+ label.textContent = calendarDate.toLocaleDateString('en-US', opts);
3010
+ }
3011
+ }
3012
+
3013
+ // ── Agenda View ──
3014
+
3015
+ function renderAgendaView() {
3016
+ const content = document.getElementById('calendarContent');
3017
+ if (calendarEvents.length === 0) {
3018
+ content.innerHTML = '<div class="empty-state"><div class="icon">📅</div><p>No events in this period</p></div>';
3019
+ return;
3020
+ }
3021
+
3022
+ const groups = {};
3023
+ calendarEvents.forEach((evt, idx) => {
3024
+ const d = evt.start ? evt.start.substring(0, 10) : 'unknown';
3025
+ if (!groups[d]) groups[d] = [];
3026
+ groups[d].push({ ...evt, _idx: idx });
3027
+ });
3028
+
3029
+ const todayStr = calDateKey(new Date());
3030
+ const days = Object.keys(groups).sort();
3031
+
3032
+ content.innerHTML = `<div class="calendar-agenda">${days.map(day => {
3033
+ const isToday = day === todayStr;
3034
+ const dayDate = new Date(day + 'T12:00:00');
3035
+ const dayLabel = dayDate.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' });
3036
+ return `<div class="agenda-day">
3037
+ <div class="agenda-day-header${isToday ? ' today' : ''}">${isToday ? 'Today — ' : ''}${dayLabel}</div>
3038
+ ${groups[day].map(evt => `<div class="agenda-event${selectedCalendarEvent && selectedCalendarEvent.id === evt.id ? ' selected' : ''}" onclick="selectCalendarEvent(${evt._idx})">
3039
+ <div class="agenda-event-status ${evt.status || 'confirmed'}"></div>
3040
+ <div class="agenda-event-time">${formatEventTime(evt.start, evt.end)}</div>
3041
+ <div class="agenda-event-info">
3042
+ <div class="agenda-event-title">${escapeHtml(evt.summary || 'Untitled')}</div>
3043
+ ${evt.location ? `<div class="agenda-event-location">${escapeHtml(evt.location)}</div>` : ''}
3044
+ </div>
3045
+ </div>`).join('')}
3046
+ </div>`;
3047
+ }).join('')}</div>`;
3048
+ }
3049
+
3050
+ // ── Day View ──
3051
+
3052
+ function renderDayView() {
3053
+ const content = document.getElementById('calendarContent');
3054
+ const dayStart = 7, dayEnd = 22;
3055
+ const hours = [];
3056
+ for (let h = dayStart; h <= dayEnd; h++) hours.push(h);
3057
+
3058
+ const dayEvents = calendarEvents.filter(evt => evt.start && evt.start.length > 10).map((evt, idx) => {
3059
+ const start = new Date(evt.start);
3060
+ const end = evt.end ? new Date(evt.end) : new Date(start.getTime() + 3600000);
3061
+ return { ...evt, _idx: idx, startHour: start.getHours() + start.getMinutes() / 60, endHour: end.getHours() + end.getMinutes() / 60 };
3062
+ });
3063
+
3064
+ const allDayEvents = calendarEvents.filter(evt => evt.start && evt.start.length === 10);
3065
+
3066
+ let html = '<div class="calendar-day-view">';
3067
+
3068
+ if (allDayEvents.length > 0) {
3069
+ html += '<div style="padding:8px 14px;background:var(--surface2);border-bottom:1px solid var(--border);font-size:12px;">';
3070
+ allDayEvents.forEach((evt, idx) => {
3071
+ html += `<div style="padding:4px 8px;background:rgba(109,93,252,0.2);border-radius:4px;margin:2px 0;cursor:pointer;" onclick="selectCalendarEvent(${calendarEvents.indexOf(evt)})">${escapeHtml(evt.summary || 'Untitled')} — All day</div>`;
3072
+ });
3073
+ html += '</div>';
3074
+ }
3075
+
3076
+ hours.forEach(h => {
3077
+ const label = h === 0 ? '12 AM' : h < 12 ? h + ' AM' : h === 12 ? '12 PM' : (h - 12) + ' PM';
3078
+ html += `<div class="hour-slot"><div class="hour-label">${label}</div><div class="hour-content">`;
3079
+ dayEvents.forEach(evt => {
3080
+ if (evt.startHour >= h && evt.startHour < h + 1) {
3081
+ const top = ((evt.startHour - h) * 48) + 'px';
3082
+ const height = Math.max(20, (evt.endHour - evt.startHour) * 48) + 'px';
3083
+ html += `<div class="day-event" style="top:${top};height:${height};" onclick="selectCalendarEvent(${evt._idx})">
3084
+ <div class="event-title">${escapeHtml(evt.summary || 'Untitled')}</div>
3085
+ <div class="event-time">${formatEventTime(evt.start, evt.end)}</div>
3086
+ </div>`;
3087
+ }
3088
+ });
3089
+ html += '</div></div>';
3090
+ });
3091
+
3092
+ html += '</div>';
3093
+ content.innerHTML = html;
3094
+ }
3095
+
3096
+ // ── Week View ──
3097
+
3098
+ function renderWeekView() {
3099
+ const content = document.getElementById('calendarContent');
3100
+ const ws = startOfWeek(calendarDate);
3101
+ const todayStr = calDateKey(new Date());
3102
+ const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
3103
+ const dayStart = 8, dayEnd = 20;
3104
+
3105
+ const days = [];
3106
+ for (let i = 0; i < 7; i++) {
3107
+ const d = addDays(ws, i);
3108
+ days.push({ date: d, key: calDateKey(d), label: dayNames[i] + ' ' + (d.getMonth() + 1) + '/' + d.getDate(), isToday: calDateKey(d) === todayStr });
3109
+ }
3110
+
3111
+ const eventsByDay = {};
3112
+ calendarEvents.forEach((evt, idx) => {
3113
+ const dk = evt.start ? evt.start.substring(0, 10) : null;
3114
+ if (dk) {
3115
+ if (!eventsByDay[dk]) eventsByDay[dk] = [];
3116
+ eventsByDay[dk].push({ ...evt, _idx: idx });
3117
+ }
3118
+ });
3119
+
3120
+ let html = '<div class="calendar-week-view">';
3121
+ html += '<div class="week-header"></div>';
3122
+ days.forEach(d => { html += `<div class="week-header${d.isToday ? ' today' : ''}">${d.label}</div>`; });
3123
+
3124
+ for (let h = dayStart; h <= dayEnd; h++) {
3125
+ const label = h < 12 ? h + 'a' : h === 12 ? '12p' : (h - 12) + 'p';
3126
+ html += `<div class="week-hour-label">${label}</div>`;
3127
+ days.forEach(d => {
3128
+ const dayEvts = (eventsByDay[d.key] || []).filter(evt => {
3129
+ if (!evt.start || evt.start.length === 10) return h === dayStart;
3130
+ return new Date(evt.start).getHours() === h;
3131
+ });
3132
+ html += '<div class="week-cell">';
3133
+ dayEvts.forEach(evt => {
3134
+ html += `<div class="week-event" onclick="selectCalendarEvent(${evt._idx})" title="${escapeHtml(evt.summary || '')}">${escapeHtml(evt.summary || '')}</div>`;
3135
+ });
3136
+ html += '</div>';
3137
+ });
3138
+ }
3139
+
3140
+ html += '</div>';
3141
+ content.innerHTML = html;
3142
+ }
3143
+
3144
+ // ── Calendar Navigation ──
3145
+
3146
+ function setCalendarView(view) {
3147
+ calendarView = view;
3148
+ document.getElementById('agendaViewBtn').classList.toggle('active', view === 'agenda');
3149
+ document.getElementById('dayViewBtn').classList.toggle('active', view === 'day');
3150
+ document.getElementById('weekViewBtn').classList.toggle('active', view === 'week');
3151
+ loadCalendar();
3152
+ }
3153
+
3154
+ function navigateCalendarDate(offset) {
3155
+ if (calendarView === 'week') calendarDate = addDays(calendarDate, offset * 7);
3156
+ else if (calendarView === 'day') calendarDate = addDays(calendarDate, offset);
3157
+ else calendarDate = addDays(calendarDate, offset * 14);
3158
+ loadCalendar();
3159
+ }
3160
+
3161
+ function goToToday() {
3162
+ calendarDate = new Date();
3163
+ loadCalendar();
3164
+ }
3165
+
3166
+ function goToThisWeek() {
3167
+ calendarDate = new Date();
3168
+ setCalendarView('week');
3169
+ }
3170
+
3171
+ // ── Calendar Event Detail ──
3172
+
3173
+ function selectCalendarEvent(idx) {
3174
+ const evt = calendarEvents[idx];
3175
+ if (!evt) return;
3176
+ selectedCalendarEvent = evt;
3177
+
3178
+ const panel = document.getElementById('detailPanel');
3179
+ panel.classList.add('open');
3180
+ document.getElementById('detailIcon').textContent = '📅';
3181
+ document.getElementById('detailName').textContent = evt.summary || 'Untitled';
3182
+
3183
+ let html = `
3184
+ <div class="detail-field">
3185
+ <label>Date & Time</label>
3186
+ <div class="value">${formatEventTime(evt.start, evt.end)}</div>
3187
+ </div>
3188
+ <div class="detail-field">
3189
+ <label>Date</label>
3190
+ <div class="value">${evt.start ? new Date(evt.start.length === 10 ? evt.start + 'T12:00:00' : evt.start).toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' }) : '—'}</div>
3191
+ </div>`;
3192
+
3193
+ if (evt.location) {
3194
+ html += `<div class="detail-field"><label>Location</label><div class="value">${escapeHtml(evt.location)}</div></div>`;
3195
+ }
3196
+ if (evt.description) {
3197
+ html += `<div class="detail-field"><label>Description</label><div class="value" style="white-space:pre-wrap;">${escapeHtml(evt.description)}</div></div>`;
3198
+ }
3199
+ if (evt.attendees && evt.attendees.length > 0) {
3200
+ html += `<div class="detail-field"><label>Attendees</label><div class="value">${evt.attendees.map(a => escapeHtml(a)).join('<br>')}</div></div>`;
3201
+ }
3202
+ html += `<div class="detail-field"><label>Status</label><div class="value">${evt.status || 'confirmed'}</div></div>`;
3203
+
3204
+ document.getElementById('detailBody').innerHTML = html;
3205
+
3206
+ let actionsHtml = '';
3207
+ if (evt.htmlLink) {
3208
+ actionsHtml += `<button class="detail-action-btn" onclick="window.open('${evt.htmlLink}', '_blank')">🔗 Open in Google Calendar</button>`;
3209
+ }
3210
+ actionsHtml += `<button class="detail-action-btn danger" onclick="deleteCalendarEvent('${evt.id}')">🗑 Delete Event</button>`;
3211
+ document.getElementById('detailActions').innerHTML = actionsHtml;
3212
+
3213
+ renderCalendarView();
3214
+ }
3215
+
3216
+ async function deleteCalendarEvent(eventId) {
3217
+ if (!confirm('Delete this event?')) return;
3218
+ try {
3219
+ const res = await fetch(`/api/google/calendar/events/${eventId}`, { method: 'DELETE' });
3220
+ if (!res.ok) throw new Error('Delete failed');
3221
+ closeDetail();
3222
+ selectedCalendarEvent = null;
3223
+ await loadCalendar();
3224
+ } catch (err) {
3225
+ alert('Failed to delete event: ' + err.message);
3226
+ }
3227
+ }
3228
+
3229
+ async function loadUpcomingEvents() {
3230
+ const el = document.getElementById('upcomingEvents');
3231
+ if (!el) return;
3232
+ const now = new Date();
3233
+ const upcoming = calendarEvents.filter(e => new Date(e.start) >= now).slice(0, 5);
3234
+ if (upcoming.length === 0) {
3235
+ el.innerHTML = '<div style="font-size:12px;color:var(--text-dim);padding:4px 8px;">No upcoming events</div>';
3236
+ return;
3237
+ }
3238
+ el.innerHTML = upcoming.map(evt => {
3239
+ const evtIdx = calendarEvents.indexOf(evt);
3240
+ return `<div class="upcoming-event" onclick="selectCalendarEvent(${evtIdx})">
3241
+ <div class="upcoming-event-dot"></div>
3242
+ <div>
3243
+ <div class="upcoming-event-title">${escapeHtml(evt.summary || 'Untitled')}</div>
3244
+ <div class="upcoming-event-time">${formatEventTime(evt.start, evt.end)}</div>
3245
+ </div>
3246
+ </div>`;
3247
+ }).join('');
3248
+ }
3249
+
3250
+ // ── Calendar Event Modal ──
3251
+
3252
+ function openNewEventModal() {
3253
+ if (!googleAuthenticated) {
3254
+ alert('Please connect Google Calendar first');
3255
+ return;
3256
+ }
3257
+ document.getElementById('newEventModal').classList.add('open');
3258
+ document.getElementById('newEventTitle').value = '';
3259
+ document.getElementById('newEventDate').value = calDateKey(new Date());
3260
+ document.getElementById('newEventStart').value = '09:00';
3261
+ document.getElementById('newEventEnd').value = '10:00';
3262
+ document.getElementById('newEventLocation').value = '';
3263
+ document.getElementById('newEventDescription').value = '';
3264
+ document.getElementById('newEventAttendees').value = '';
3265
+ document.getElementById('newEventTitle').focus();
3266
+ }
3267
+
3268
+ async function createEvent() {
3269
+ const title = document.getElementById('newEventTitle').value.trim();
3270
+ if (!title) return;
3271
+
3272
+ const date = document.getElementById('newEventDate').value;
3273
+ const startTime = document.getElementById('newEventStart').value;
3274
+ const endTime = document.getElementById('newEventEnd').value;
3275
+ const location = document.getElementById('newEventLocation').value.trim();
3276
+ const description = document.getElementById('newEventDescription').value.trim();
3277
+ const attendeesStr = document.getElementById('newEventAttendees').value.trim();
3278
+
3279
+ const start = new Date(`${date}T${startTime}:00`).toISOString();
3280
+ const end = new Date(`${date}T${endTime}:00`).toISOString();
3281
+ const attendees = attendeesStr ? attendeesStr.split(',').map(e => e.trim()).filter(Boolean) : [];
3282
+
3283
+ try {
3284
+ const res = await fetch('/api/google/calendar/events', {
3285
+ method: 'POST',
3286
+ headers: { 'Content-Type': 'application/json' },
3287
+ body: JSON.stringify({
3288
+ title, start, end,
3289
+ location: location || undefined,
3290
+ description: description || undefined,
3291
+ attendees: attendees.length > 0 ? attendees : undefined,
3292
+ }),
3293
+ });
3294
+ if (!res.ok) {
3295
+ const data = await res.json();
3296
+ throw new Error(data.error || 'Failed to create event');
3297
+ }
3298
+ closeModal('newEventModal');
3299
+ await loadCalendar();
3300
+ } catch (err) {
3301
+ alert('Failed to create event: ' + err.message);
3302
+ }
3303
+ }
3304
+
3305
+ document.getElementById('newEventTitle').addEventListener('keydown', (e) => {
3306
+ if (e.key === 'Escape') closeModal('newEventModal');
3307
+ });
3308
+
3309
+ // ── Feed View ────────────────────────────────────────────────────────────
3310
+
3311
+ let currentFolderSystemType = null;
3312
+ let lastFeedFiles = []; // cache for re-sorting without refetch
3313
+
3314
+ function sortFiles(files) {
3315
+ const order = document.getElementById('feedSortSelect').value;
3316
+ const sorted = [...files];
3317
+ switch (order) {
3318
+ case 'name':
3319
+ sorted.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
3320
+ break;
3321
+ case 'name-desc':
3322
+ sorted.sort((a, b) => (b.name || '').localeCompare(a.name || ''));
3323
+ break;
3324
+ case 'size':
3325
+ sorted.sort((a, b) => (b.sizeBytes || 0) - (a.sizeBytes || 0));
3326
+ break;
3327
+ case 'size-asc':
3328
+ sorted.sort((a, b) => (a.sizeBytes || 0) - (b.sizeBytes || 0));
3329
+ break;
3330
+ case 'oldest':
3331
+ sorted.sort((a, b) => new Date(a.updatedAt || a.createdAt).getTime() - new Date(b.updatedAt || b.createdAt).getTime());
3332
+ break;
3333
+ case 'recent':
3334
+ default:
3335
+ sorted.sort((a, b) => new Date(b.updatedAt || b.createdAt).getTime() - new Date(a.updatedAt || a.createdAt).getTime());
3336
+ break;
3337
+ }
3338
+ return sorted;
3339
+ }
3340
+
3341
+ function renderFeed(files) {
3342
+ lastFeedFiles = files;
3343
+ const container = document.getElementById('fileContainer');
3344
+ container.className = 'feed-stream';
3345
+
3346
+ if (files.length === 0) {
3347
+ container.innerHTML = '<div class="empty-state"><div class="icon">📑</div><p>No files to show</p></div>';
3348
+ return;
3349
+ }
3350
+
3351
+ container.innerHTML = files.map(f => {
3352
+ const icon = fileIcon(f.mimeType, f.name);
3353
+ const accessed = f._lastAccessed ? formatDate(f._lastAccessed) : formatDate(f.updatedAt);
3354
+ return `<div class="feed-card" id="feed-${f.id}">
3355
+ <div class="feed-card-header" onclick="selectFile('${f.id}')">
3356
+ <span class="feed-icon">${icon}</span>
3357
+ <span class="feed-title">${escapeHtml(f.name)}</span>
3358
+ <span class="feed-meta">
3359
+ <span>${formatBytes(f.sizeBytes)}</span>
3360
+ <span>${accessed}</span>
3361
+ </span>
3362
+ </div>
3363
+ <div class="feed-card-body loading" id="feed-body-${f.id}">Loading...</div>
3364
+ </div>`;
3365
+ }).join('');
3366
+
3367
+ // Load content for each file
3368
+ files.forEach(f => loadFeedContent(f.id, f.mimeType, f.name, f.textPreview));
3369
+ }
3370
+
3371
+ async function loadFeedContent(fileId, mimeType, name, textPreview) {
3372
+ const bodyEl = document.getElementById(`feed-body-${fileId}`);
3373
+ if (!bodyEl) return;
3374
+
3375
+ const textMimes = ['text/markdown', 'text/plain', 'text/yaml', 'text/csv', 'application/json', 'application/x-yaml'];
3376
+ const isText = textMimes.some(m => (mimeType || '').includes(m))
3377
+ || /\.(md|txt|yaml|yml|json|csv|log)$/i.test(name || '');
3378
+
3379
+ if (!isText) {
3380
+ bodyEl.className = 'feed-card-body binary';
3381
+ const icon = fileIcon(mimeType, name);
3382
+ bodyEl.innerHTML = `<div style="font-size:32px;margin-bottom:8px;">${icon}</div>
3383
+ <div>${mimeType || 'Binary file'}</div>
3384
+ <div style="margin-top:6px;">${formatBytes(0)} — click header to view details</div>`;
3385
+ return;
3386
+ }
3387
+
3388
+ try {
3389
+ const res = await fetch(`${API}/files/${fileId}/content?max=10000`);
3390
+ const data = await res.json();
3391
+ if (data.content) {
3392
+ bodyEl.className = 'feed-card-body';
3393
+ let html = escapeHtml(data.content);
3394
+ // Light markdown: headers
3395
+ html = html.replace(/^(#{1,3}) (.+)$/gm, (_, hashes, text) => {
3396
+ const level = hashes.length;
3397
+ return `<h${level}>${text}</h${level}>`;
3398
+ });
3399
+ if (data.truncated) {
3400
+ html += `<div class="truncated-note">Showing ${data.content.length.toLocaleString()} of ${data.totalChars.toLocaleString()} characters</div>`;
3401
+ }
3402
+ bodyEl.innerHTML = html;
3403
+ } else {
3404
+ // Fallback to stored textPreview
3405
+ bodyEl.className = 'feed-card-body';
3406
+ bodyEl.textContent = textPreview || '(No preview available)';
3407
+ }
3408
+ } catch {
3409
+ bodyEl.className = 'feed-card-body';
3410
+ bodyEl.textContent = textPreview || '(Failed to load content)';
3411
+ }
3412
+ }
3413
+
3414
+ // ── Brain Shadow Files ───────────────────────────────────────────────────
3415
+
3416
+ let currentBrainCategory = null; // null or category key
3417
+ const BRAIN_API = '/api/library/brain';
3418
+
3419
+ async function loadBrainCategories() {
3420
+ try {
3421
+ const data = await fetch(BRAIN_API).then(r => r.json());
3422
+ const cats = data.categories || [];
3423
+ const list = document.getElementById('brainCategoryList');
3424
+ list.innerHTML = cats.map(cat =>
3425
+ `<div class="sidebar-item${currentBrainCategory === cat.key ? ' active' : ''}" onclick="navigateToBrainCategory('${cat.key}')">
3426
+ <span class="icon">${cat.icon}</span>
3427
+ <span>${cat.label}</span>
3428
+ <span class="count">${cat.fileCount}</span>
3429
+ </div>`
3430
+ ).join('');
3431
+ } catch (err) {
3432
+ console.error('Failed to load brain categories:', err);
3433
+ }
3434
+ }
3435
+
3436
+ async function navigateToBrainRoot() {
3437
+ currentFolderId = null;
3438
+ currentBrainCategory = null;
3439
+ selectedFileId = null;
3440
+ closeDetail();
3441
+
3442
+ const data = await fetch(BRAIN_API).then(r => r.json());
3443
+ const cats = data.categories || [];
3444
+
3445
+ renderBreadcrumb([{ id: '__brain', name: 'Brain' }]);
3446
+ const subfoldersEl = document.getElementById('subfolders');
3447
+ subfoldersEl.innerHTML = '';
3448
+ const container = document.getElementById('fileContainer');
3449
+ container.className = 'file-grid';
3450
+ container.innerHTML = cats.map(cat =>
3451
+ `<div class="brain-category-card" onclick="navigateToBrainCategory('${cat.key}')">
3452
+ <div class="cat-icon">${cat.icon}</div>
3453
+ <div class="cat-label">${cat.label}</div>
3454
+ <div class="cat-meta">${cat.fileCount} files &middot; ${formatBytes(cat.totalBytes)}</div>
3455
+ </div>`
3456
+ ).join('');
3457
+
3458
+ renderTree();
3459
+ loadBrainCategories();
3460
+ }
3461
+
3462
+ async function navigateToBrainCategory(key) {
3463
+ currentFolderId = null;
3464
+ currentBrainCategory = key;
3465
+ selectedFileId = null;
3466
+ closeDetail();
3467
+
3468
+ const data = await fetch(`${BRAIN_API}/${key}`).then(r => r.json());
3469
+ if (!data.category) return;
3470
+
3471
+ renderBreadcrumb([
3472
+ { id: '__brain', name: 'Brain' },
3473
+ { id: `__brain_${key}`, name: data.category.label },
3474
+ ]);
3475
+
3476
+ const subfoldersEl = document.getElementById('subfolders');
3477
+ subfoldersEl.innerHTML = '';
3478
+
3479
+ renderBrainFiles(data.files || [], key);
3480
+ renderTree();
3481
+ loadBrainCategories();
3482
+ }
3483
+
3484
+ function renderBrainFiles(files, category) {
3485
+ const container = document.getElementById('fileContainer');
3486
+ if (files.length === 0) {
3487
+ container.innerHTML = '<div class="empty-state"><div class="icon">📂</div><p>No files in this category</p></div>';
3488
+ return;
3489
+ }
3490
+
3491
+ if (currentView === 'grid') {
3492
+ container.className = 'file-grid';
3493
+ container.innerHTML = files.map(f =>
3494
+ `<div class="file-card shadow${selectedFileId === f.id ? ' selected' : ''}" onclick="selectBrainFile('${category}', '${f.name}')">
3495
+ <div class="file-icon">${fileIcon(f.mimeType, f.name)}</div>
3496
+ <div class="file-name" title="${f.name}">${f.name}</div>
3497
+ <div class="file-meta">
3498
+ <span>${formatBytes(f.sizeBytes)}</span>
3499
+ <span>${formatDate(f.modifiedAt)}</span>
3500
+ </div>
3501
+ </div>`
3502
+ ).join('');
3503
+ } else {
3504
+ container.className = 'file-list';
3505
+ container.innerHTML = files.map(f =>
3506
+ `<div class="file-row shadow${selectedFileId === f.id ? ' selected' : ''}" onclick="selectBrainFile('${category}', '${f.name}')">
3507
+ <div class="file-icon">${fileIcon(f.mimeType, f.name)}</div>
3508
+ <div class="file-name" title="${f.name}">${f.name}</div>
3509
+ <div class="file-meta-cell">${formatBytes(f.sizeBytes)}</div>
3510
+ <div class="file-meta-cell">${formatDate(f.modifiedAt)}</div>
3511
+ <div class="file-meta-cell">${f.mimeType.split('/')[1] || '—'}</div>
3512
+ </div>`
3513
+ ).join('');
3514
+ }
3515
+ }
3516
+
3517
+ async function selectBrainFile(category, filename) {
3518
+ selectedFileId = `shadow_${category}_${filename}`;
3519
+ const data = await fetch(`${BRAIN_API}/${category}`).then(r => r.json());
3520
+ const f = (data.files || []).find(f => f.name === filename);
3521
+ if (!f) return;
3522
+
3523
+ const panel = document.getElementById('detailPanel');
3524
+ panel.classList.add('open');
3525
+
3526
+ document.getElementById('detailIcon').textContent = fileIcon(f.mimeType, f.name);
3527
+ document.getElementById('detailName').innerHTML = f.name + '<span class="shadow-badge">Brain</span>';
3528
+
3529
+ const body = document.getElementById('detailBody');
3530
+ body.innerHTML = `
3531
+ <div class="detail-field">
3532
+ <label>Source</label>
3533
+ <div class="value" style="color:var(--purple);">🔒 Brain (read-only)</div>
3534
+ </div>
3535
+ <div class="detail-field">
3536
+ <label>Type</label>
3537
+ <div class="value">${f.mimeType}</div>
3538
+ </div>
3539
+ <div class="detail-field">
3540
+ <label>Size</label>
3541
+ <div class="value">${formatBytes(f.sizeBytes)}</div>
3542
+ </div>
3543
+ <div class="detail-field">
3544
+ <label>Modified</label>
3545
+ <div class="value">${formatDate(f.modifiedAt)}</div>
3546
+ </div>
3547
+ <div class="detail-field">
3548
+ <label>Category</label>
3549
+ <div class="value">${data.category.icon} ${data.category.label}</div>
3550
+ </div>
3551
+ ${f.textPreview ? `<div class="detail-field">
3552
+ <label>Preview</label>
3553
+ <div class="value" style="font-family:var(--mono);font-size:11px;white-space:pre-wrap;max-height:200px;overflow-y:auto;color:var(--text-dim);">${escapeHtml(f.textPreview)}</div>
3554
+ </div>` : ''}
3555
+ `;
3556
+
3557
+ const actions = document.getElementById('detailActions');
3558
+ actions.innerHTML = `
3559
+ <button class="detail-action-btn" onclick="downloadBrainFile('${category}', '${filename}')">⬇ Download</button>
3560
+ `;
3561
+
3562
+ // Re-render file list to update selection highlight
3563
+ if (currentBrainCategory) {
3564
+ const reData = await fetch(`${BRAIN_API}/${currentBrainCategory}`).then(r => r.json());
3565
+ renderBrainFiles(reData.files || [], currentBrainCategory);
3566
+ }
3567
+ }
3568
+
3569
+ function downloadBrainFile(category, filename) {
3570
+ window.open(`${BRAIN_API}/${category}/${filename}/download`, '_blank');
3571
+ }
3572
+
3573
+ // Patch renderBreadcrumb to handle brain items
3574
+ const _origRenderBreadcrumb = renderBreadcrumb;
3575
+ renderBreadcrumb = function(trail) {
3576
+ const el = document.getElementById('breadcrumb');
3577
+ let html = '<a onclick="navigateToRoot()">Library</a>';
3578
+ for (const item of trail) {
3579
+ html += '<span class="sep">/</span>';
3580
+ if (item.id === '__brain') {
3581
+ if (item === trail[trail.length - 1]) {
3582
+ html += '<span class="current">Brain</span>';
3583
+ } else {
3584
+ html += '<a onclick="navigateToBrainRoot()">Brain</a>';
3585
+ }
3586
+ } else if (item.id && item.id.startsWith('__brain_')) {
3587
+ html += `<span class="current">${item.name}</span>`;
3588
+ } else if (item === trail[trail.length - 1]) {
3589
+ html += `<span class="current">${item.name}</span>`;
3590
+ } else {
3591
+ html += `<a onclick="navigateToFolder('${item.id}')">${item.name}</a>`;
3592
+ }
3593
+ }
3594
+ el.innerHTML = html;
3595
+ };
3596
+
3597
+ // Patch navigateToRoot/Folder/Unfiled to clear brain state
3598
+ const _origNavigateToRoot = navigateToRoot;
3599
+ navigateToRoot = async function() {
3600
+ currentBrainCategory = null;
3601
+ await _origNavigateToRoot();
3602
+ loadBrainCategories();
3603
+ };
3604
+
3605
+ const _origNavigateToFolder = navigateToFolder;
3606
+ navigateToFolder = async function(id) {
3607
+ currentBrainCategory = null;
3608
+ await _origNavigateToFolder(id);
3609
+ loadBrainCategories();
3610
+ };
3611
+
3612
+ const _origNavigateToUnfiled = navigateToUnfiled;
3613
+ navigateToUnfiled = async function() {
3614
+ currentBrainCategory = null;
3615
+ await _origNavigateToUnfiled();
3616
+ loadBrainCategories();
3617
+ };
3618
+
3619
+ // ── Init ─────────────────────────────────────────────────────────────────
3620
+
3621
+ async function init() {
3622
+ await loadTree();
3623
+ await loadRootContent();
3624
+ loadRecents();
3625
+ loadFavorites();
3626
+ loadBrainCategories();
3627
+
3628
+ // Handle initial hash route
3629
+ const hash = location.hash.replace('#', '');
3630
+ if (['contacts', 'calendar', 'credentials'].includes(hash)) {
3631
+ switchSection(hash);
3632
+ }
3633
+ }
3634
+
3635
+ init();
3636
+
3637
+ // Auto-refresh tree every 10s
3638
+ setInterval(() => loadTree(), 10000);
3639
+ </script>
3640
+ <script src="/public/share-modal.js"></script>
3641
+ </body>
3642
+ </html>