@notis_ai/cli 0.2.0-beta.137.1 → 0.2.0-beta.144.1

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 (294) hide show
  1. package/README.md +82 -20
  2. package/dist/agent-hooks/notis-agent-hook.mjs +19750 -0
  3. package/{skills → dist/base-skills}/notis-apps/SKILL.md +84 -53
  4. package/{skills → dist/base-skills}/notis-cli/SKILL.md +9 -9
  5. package/dist/base-skills/notis-query/SKILL.md +705 -0
  6. package/dist/skill-sync/index.js +1528 -0
  7. package/dist/skill-sync/index.js.map +7 -0
  8. package/package.json +5 -2
  9. package/skills/notis-apps/cli.md +38 -17
  10. package/skills/notis-cli/AGENT_INSTRUCTIONS.md +39 -0
  11. package/skills/notis-onboarding/BRIEF.md +16 -0
  12. package/src/agent-hook-entry.js +5 -0
  13. package/src/cli.js +23 -14
  14. package/src/command-specs/agents.js +392 -0
  15. package/src/command-specs/apps.js +871 -387
  16. package/src/command-specs/auth.js +63 -10
  17. package/src/command-specs/index.js +6 -0
  18. package/src/command-specs/onboarding.js +69 -5
  19. package/src/command-specs/skills.js +56 -0
  20. package/src/runtime/agent-memory-state.js +126 -0
  21. package/src/runtime/agent-setup.js +383 -0
  22. package/src/runtime/app-dev-consumers.js +154 -0
  23. package/src/runtime/app-dev-host-lock.js +80 -0
  24. package/src/runtime/app-dev-roots.js +284 -0
  25. package/src/runtime/app-dev-server.js +212 -22
  26. package/src/runtime/app-dev-sessions.js +105 -145
  27. package/src/runtime/app-platform.js +1291 -165
  28. package/src/runtime/app-registry-scaffolds.js +367 -0
  29. package/src/runtime/base-skills.d.ts +20 -0
  30. package/src/runtime/base-skills.js +167 -0
  31. package/src/runtime/login-listener.js +15 -0
  32. package/src/runtime/oauth.js +1506 -141
  33. package/src/runtime/output.js +7 -0
  34. package/src/runtime/ports.js +16 -0
  35. package/src/runtime/profiles.js +0 -5
  36. package/src/runtime/skill-sync/cloud-client.ts +96 -0
  37. package/src/runtime/skill-sync/index.ts +644 -0
  38. package/src/runtime/skill-sync/local-scanner.ts +1046 -0
  39. package/src/runtime/skill-sync/symlink-manager.ts +383 -0
  40. package/src/runtime/skill-sync/sync-plan.ts +22 -0
  41. package/src/runtime/skill-sync/types.ts +103 -0
  42. package/src/runtime/skill-sync/write-cloud-skill.ts +50 -0
  43. package/src/runtime/store-screenshot.js +6 -1
  44. package/src/runtime/sync-skills.d.ts +37 -0
  45. package/src/runtime/sync-skills.js +215 -0
  46. package/template/app/layout.tsx +5 -1
  47. package/template/notis.config.ts +1 -0
  48. package/template/packages/sdk/package.json +2 -1
  49. package/template/packages/sdk/src/components/MarkdownEditor.tsx +121 -0
  50. package/template/packages/sdk/src/components/MultiSelectActionBar.tsx +37 -119
  51. package/template/packages/sdk/src/components/MultiSelectCheckbox.tsx +5 -1
  52. package/template/packages/sdk/src/components/MultiSelectDragOverlay.tsx +1 -1
  53. package/template/packages/sdk/src/components/NotisSelectionBoundary.tsx +55 -0
  54. package/template/packages/sdk/src/components/ShortcutHints.tsx +56 -0
  55. package/template/packages/sdk/src/config.ts +19 -0
  56. package/template/packages/sdk/src/hooks/useActiveResource.ts +19 -0
  57. package/template/packages/sdk/src/hooks/useCollectionInteractions.ts +709 -0
  58. package/template/packages/sdk/src/hooks/useHandover.ts +3 -0
  59. package/template/packages/sdk/src/hooks/useMultiSelect.ts +44 -482
  60. package/template/packages/sdk/src/hooks/useTool.ts +5 -4
  61. package/template/packages/sdk/src/index.ts +47 -2
  62. package/template/packages/sdk/src/interactions/actions.ts +46 -0
  63. package/template/packages/sdk/src/interactions/shortcuts.tsx +628 -0
  64. package/template/packages/sdk/src/interactions.ts +41 -0
  65. package/template/packages/sdk/src/provider.tsx +2 -1
  66. package/template/packages/sdk/src/runtime.ts +85 -3
  67. package/dist/scaffolds/notis-database/CHANGELOG.md +0 -5
  68. package/dist/scaffolds/notis-database/app/globals.css +0 -44
  69. package/dist/scaffolds/notis-database/app/layout.tsx +0 -6
  70. package/dist/scaffolds/notis-database/app/page.tsx +0 -1088
  71. package/dist/scaffolds/notis-database/components/ui/badge.tsx +0 -28
  72. package/dist/scaffolds/notis-database/components/ui/button.tsx +0 -53
  73. package/dist/scaffolds/notis-database/components/ui/card.tsx +0 -56
  74. package/dist/scaffolds/notis-database/components/ui/table.tsx +0 -120
  75. package/dist/scaffolds/notis-database/components.json +0 -20
  76. package/dist/scaffolds/notis-database/index.html +0 -12
  77. package/dist/scaffolds/notis-database/lib/types.ts +0 -132
  78. package/dist/scaffolds/notis-database/lib/utils.ts +0 -6
  79. package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
  80. package/dist/scaffolds/notis-database/metadata/screenshot-2.png +0 -0
  81. package/dist/scaffolds/notis-database/metadata/screenshot-3.png +0 -0
  82. package/dist/scaffolds/notis-database/metadata/screenshot-4.png +0 -0
  83. package/dist/scaffolds/notis-database/metadata/screenshot-5.png +0 -0
  84. package/dist/scaffolds/notis-database/metadata/screenshot-fixtures.json +0 -1839
  85. package/dist/scaffolds/notis-database/notis.config.ts +0 -73
  86. package/dist/scaffolds/notis-database/package-lock.json +0 -3935
  87. package/dist/scaffolds/notis-database/package.json +0 -32
  88. package/dist/scaffolds/notis-database/packages/sdk/package.json +0 -36
  89. package/dist/scaffolds/notis-database/packages/sdk/src/components/DocumentEditor.tsx +0 -93
  90. package/dist/scaffolds/notis-database/packages/sdk/src/components/Markdown.tsx +0 -60
  91. package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
  92. package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
  93. package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
  94. package/dist/scaffolds/notis-database/packages/sdk/src/config.ts +0 -234
  95. package/dist/scaffolds/notis-database/packages/sdk/src/documents.ts +0 -250
  96. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useBackend.ts +0 -41
  97. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
  98. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  99. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
  100. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocument.ts +0 -78
  101. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocuments.ts +0 -121
  102. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useHandover.ts +0 -75
  103. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
  104. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotis.ts +0 -34
  105. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
  106. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTool.ts +0 -64
  107. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTools.ts +0 -56
  108. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  109. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
  110. package/dist/scaffolds/notis-database/packages/sdk/src/index.ts +0 -100
  111. package/dist/scaffolds/notis-database/packages/sdk/src/provider.tsx +0 -43
  112. package/dist/scaffolds/notis-database/packages/sdk/src/runtime.ts +0 -351
  113. package/dist/scaffolds/notis-database/packages/sdk/src/styles.css +0 -186
  114. package/dist/scaffolds/notis-database/packages/sdk/src/ui.ts +0 -15
  115. package/dist/scaffolds/notis-database/packages/sdk/src/vite.ts +0 -56
  116. package/dist/scaffolds/notis-database/packages/sdk/tsconfig.json +0 -15
  117. package/dist/scaffolds/notis-database/postcss.config.mjs +0 -8
  118. package/dist/scaffolds/notis-database/src/dev-main.tsx +0 -23
  119. package/dist/scaffolds/notis-database/src/mock-runtime.ts +0 -561
  120. package/dist/scaffolds/notis-database/tailwind.config.ts +0 -59
  121. package/dist/scaffolds/notis-database/tsconfig.json +0 -23
  122. package/dist/scaffolds/notis-database/vite.config.ts +0 -22
  123. package/dist/scaffolds/notis-journal/CHANGELOG.md +0 -29
  124. package/dist/scaffolds/notis-journal/app/globals.css +0 -37
  125. package/dist/scaffolds/notis-journal/app/insights/page.tsx +0 -513
  126. package/dist/scaffolds/notis-journal/app/journal-core.tsx +0 -362
  127. package/dist/scaffolds/notis-journal/app/journal-ui.tsx +0 -337
  128. package/dist/scaffolds/notis-journal/app/layout.tsx +0 -6
  129. package/dist/scaffolds/notis-journal/app/page.tsx +0 -486
  130. package/dist/scaffolds/notis-journal/components/ui/badge.tsx +0 -28
  131. package/dist/scaffolds/notis-journal/components/ui/button.tsx +0 -53
  132. package/dist/scaffolds/notis-journal/components/ui/card.tsx +0 -56
  133. package/dist/scaffolds/notis-journal/components.json +0 -20
  134. package/dist/scaffolds/notis-journal/index.html +0 -12
  135. package/dist/scaffolds/notis-journal/lib/utils.ts +0 -6
  136. package/dist/scaffolds/notis-journal/metadata/screenshot-1.png +0 -0
  137. package/dist/scaffolds/notis-journal/metadata/screenshot-2.png +0 -0
  138. package/dist/scaffolds/notis-journal/metadata/screenshot-3.png +0 -0
  139. package/dist/scaffolds/notis-journal/metadata/screenshot-4.png +0 -0
  140. package/dist/scaffolds/notis-journal/metadata/screenshot-5.png +0 -0
  141. package/dist/scaffolds/notis-journal/metadata/screenshot-6.png +0 -0
  142. package/dist/scaffolds/notis-journal/metadata/screenshot-fixtures.json +0 -132
  143. package/dist/scaffolds/notis-journal/notis.config.ts +0 -93
  144. package/dist/scaffolds/notis-journal/package-lock.json +0 -4615
  145. package/dist/scaffolds/notis-journal/package.json +0 -34
  146. package/dist/scaffolds/notis-journal/packages/sdk/package.json +0 -36
  147. package/dist/scaffolds/notis-journal/packages/sdk/src/components/DocumentEditor.tsx +0 -93
  148. package/dist/scaffolds/notis-journal/packages/sdk/src/components/Markdown.tsx +0 -60
  149. package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
  150. package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
  151. package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
  152. package/dist/scaffolds/notis-journal/packages/sdk/src/config.ts +0 -234
  153. package/dist/scaffolds/notis-journal/packages/sdk/src/documents.ts +0 -250
  154. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useBackend.ts +0 -41
  155. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
  156. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  157. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
  158. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocument.ts +0 -78
  159. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocuments.ts +0 -121
  160. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useHandover.ts +0 -75
  161. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
  162. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotis.ts +0 -34
  163. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
  164. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTool.ts +0 -64
  165. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTools.ts +0 -56
  166. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  167. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
  168. package/dist/scaffolds/notis-journal/packages/sdk/src/index.ts +0 -100
  169. package/dist/scaffolds/notis-journal/packages/sdk/src/provider.tsx +0 -43
  170. package/dist/scaffolds/notis-journal/packages/sdk/src/runtime.ts +0 -351
  171. package/dist/scaffolds/notis-journal/packages/sdk/src/styles.css +0 -186
  172. package/dist/scaffolds/notis-journal/packages/sdk/src/ui.ts +0 -15
  173. package/dist/scaffolds/notis-journal/packages/sdk/src/vite.ts +0 -56
  174. package/dist/scaffolds/notis-journal/packages/sdk/tsconfig.json +0 -15
  175. package/dist/scaffolds/notis-journal/postcss.config.mjs +0 -8
  176. package/dist/scaffolds/notis-journal/skills/journal-onboarding/SKILL.md +0 -120
  177. package/dist/scaffolds/notis-journal/src/dev-main.tsx +0 -58
  178. package/dist/scaffolds/notis-journal/src/mock-runtime.ts +0 -199
  179. package/dist/scaffolds/notis-journal/tailwind.config.ts +0 -58
  180. package/dist/scaffolds/notis-journal/tsconfig.json +0 -23
  181. package/dist/scaffolds/notis-journal/vite.config.ts +0 -10
  182. package/dist/scaffolds/notis-notes/CHANGELOG.md +0 -5
  183. package/dist/scaffolds/notis-notes/app/globals.css +0 -3
  184. package/dist/scaffolds/notis-notes/app/layout.tsx +0 -6
  185. package/dist/scaffolds/notis-notes/app/page.tsx +0 -1943
  186. package/dist/scaffolds/notis-notes/app/phosphor-icons.ts +0 -596
  187. package/dist/scaffolds/notis-notes/components/ui/badge.tsx +0 -28
  188. package/dist/scaffolds/notis-notes/components/ui/button.tsx +0 -53
  189. package/dist/scaffolds/notis-notes/components/ui/card.tsx +0 -56
  190. package/dist/scaffolds/notis-notes/components.json +0 -20
  191. package/dist/scaffolds/notis-notes/lib/utils.ts +0 -6
  192. package/dist/scaffolds/notis-notes/lib/visible-properties.ts +0 -144
  193. package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
  194. package/dist/scaffolds/notis-notes/metadata/screenshot-2.png +0 -0
  195. package/dist/scaffolds/notis-notes/metadata/screenshot-3.png +0 -0
  196. package/dist/scaffolds/notis-notes/metadata/screenshot-4.png +0 -0
  197. package/dist/scaffolds/notis-notes/metadata/screenshot-5.png +0 -0
  198. package/dist/scaffolds/notis-notes/metadata/screenshot-fixtures.json +0 -752
  199. package/dist/scaffolds/notis-notes/notis.config.ts +0 -80
  200. package/dist/scaffolds/notis-notes/package-lock.json +0 -4636
  201. package/dist/scaffolds/notis-notes/package.json +0 -35
  202. package/dist/scaffolds/notis-notes/packages/sdk/package.json +0 -36
  203. package/dist/scaffolds/notis-notes/packages/sdk/src/components/DocumentEditor.tsx +0 -93
  204. package/dist/scaffolds/notis-notes/packages/sdk/src/components/Markdown.tsx +0 -60
  205. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
  206. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
  207. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
  208. package/dist/scaffolds/notis-notes/packages/sdk/src/config.ts +0 -234
  209. package/dist/scaffolds/notis-notes/packages/sdk/src/documents.ts +0 -250
  210. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useBackend.ts +0 -41
  211. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
  212. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  213. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
  214. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocument.ts +0 -78
  215. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocuments.ts +0 -121
  216. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useHandover.ts +0 -75
  217. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
  218. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotis.ts +0 -34
  219. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
  220. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTool.ts +0 -64
  221. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTools.ts +0 -56
  222. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  223. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
  224. package/dist/scaffolds/notis-notes/packages/sdk/src/index.ts +0 -100
  225. package/dist/scaffolds/notis-notes/packages/sdk/src/provider.tsx +0 -43
  226. package/dist/scaffolds/notis-notes/packages/sdk/src/runtime.ts +0 -351
  227. package/dist/scaffolds/notis-notes/packages/sdk/src/styles.css +0 -186
  228. package/dist/scaffolds/notis-notes/packages/sdk/src/ui.ts +0 -15
  229. package/dist/scaffolds/notis-notes/packages/sdk/src/vite.ts +0 -56
  230. package/dist/scaffolds/notis-notes/packages/sdk/tsconfig.json +0 -15
  231. package/dist/scaffolds/notis-notes/postcss.config.mjs +0 -8
  232. package/dist/scaffolds/notis-notes/tailwind.config.ts +0 -58
  233. package/dist/scaffolds/notis-notes/tsconfig.json +0 -23
  234. package/dist/scaffolds/notis-notes/vite.config.ts +0 -10
  235. package/dist/scaffolds/notis-random/CHANGELOG.md +0 -15
  236. package/dist/scaffolds/notis-random/README.md +0 -33
  237. package/dist/scaffolds/notis-random/app/globals.css +0 -11
  238. package/dist/scaffolds/notis-random/app/history/page.tsx +0 -67
  239. package/dist/scaffolds/notis-random/app/layout.tsx +0 -7
  240. package/dist/scaffolds/notis-random/app/page.tsx +0 -289
  241. package/dist/scaffolds/notis-random/components/ui/button.tsx +0 -50
  242. package/dist/scaffolds/notis-random/components/ui/card.tsx +0 -16
  243. package/dist/scaffolds/notis-random/components/ui/input.tsx +0 -23
  244. package/dist/scaffolds/notis-random/components.json +0 -20
  245. package/dist/scaffolds/notis-random/index.html +0 -12
  246. package/dist/scaffolds/notis-random/lib/notis-tools.ts +0 -128
  247. package/dist/scaffolds/notis-random/lib/rng.ts +0 -202
  248. package/dist/scaffolds/notis-random/lib/roll-record.ts +0 -189
  249. package/dist/scaffolds/notis-random/lib/utils.ts +0 -25
  250. package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
  251. package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
  252. package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
  253. package/dist/scaffolds/notis-random/metadata/screenshot-4.png +0 -0
  254. package/dist/scaffolds/notis-random/metadata/screenshot-5.png +0 -0
  255. package/dist/scaffolds/notis-random/metadata/screenshot-fixtures.json +0 -753
  256. package/dist/scaffolds/notis-random/notis.config.ts +0 -86
  257. package/dist/scaffolds/notis-random/package-lock.json +0 -4513
  258. package/dist/scaffolds/notis-random/package.json +0 -36
  259. package/dist/scaffolds/notis-random/packages/sdk/package.json +0 -36
  260. package/dist/scaffolds/notis-random/packages/sdk/src/components/DocumentEditor.tsx +0 -93
  261. package/dist/scaffolds/notis-random/packages/sdk/src/components/Markdown.tsx +0 -60
  262. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
  263. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
  264. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
  265. package/dist/scaffolds/notis-random/packages/sdk/src/config.ts +0 -234
  266. package/dist/scaffolds/notis-random/packages/sdk/src/documents.ts +0 -250
  267. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useBackend.ts +0 -41
  268. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
  269. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  270. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
  271. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocument.ts +0 -78
  272. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocuments.ts +0 -121
  273. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useHandover.ts +0 -75
  274. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
  275. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotis.ts +0 -34
  276. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
  277. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTool.ts +0 -64
  278. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTools.ts +0 -56
  279. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  280. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
  281. package/dist/scaffolds/notis-random/packages/sdk/src/index.ts +0 -100
  282. package/dist/scaffolds/notis-random/packages/sdk/src/provider.tsx +0 -43
  283. package/dist/scaffolds/notis-random/packages/sdk/src/runtime.ts +0 -351
  284. package/dist/scaffolds/notis-random/packages/sdk/src/styles.css +0 -186
  285. package/dist/scaffolds/notis-random/packages/sdk/src/ui.ts +0 -15
  286. package/dist/scaffolds/notis-random/packages/sdk/src/vite.ts +0 -56
  287. package/dist/scaffolds/notis-random/packages/sdk/tsconfig.json +0 -15
  288. package/dist/scaffolds/notis-random/postcss.config.mjs +0 -6
  289. package/dist/scaffolds/notis-random/src/dev-main.tsx +0 -70
  290. package/dist/scaffolds/notis-random/src/mock-runtime.ts +0 -129
  291. package/dist/scaffolds/notis-random/tailwind.config.ts +0 -50
  292. package/dist/scaffolds/notis-random/tsconfig.json +0 -23
  293. package/dist/scaffolds/notis-random/vite.config.ts +0 -11
  294. package/dist/scaffolds.json +0 -49
@@ -0,0 +1,284 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import {
3
+ existsSync,
4
+ lstatSync,
5
+ mkdirSync,
6
+ readFileSync,
7
+ readdirSync,
8
+ realpathSync,
9
+ renameSync,
10
+ rmSync,
11
+ statSync,
12
+ writeFileSync,
13
+ } from 'node:fs';
14
+ import { homedir } from 'node:os';
15
+ import { dirname, join, parse as parsePath, resolve } from 'node:path';
16
+
17
+ import { usageError } from './errors.js';
18
+ import { CONFIG_DIR } from './profiles.js';
19
+
20
+ export const APP_DEV_ROOTS_VERSION = 1;
21
+ export const DEFAULT_APP_DEV_ROOT = join(homedir(), '.notis', 'apps');
22
+ export const DEFAULT_APP_DEV_ROOTS_FILE = join(CONFIG_DIR, 'app-dev-roots.json');
23
+ const CONFIG_FILENAMES = ['notis.config.ts', 'notis.config.js', 'notis.config.mjs'];
24
+ const LOCK_TIMEOUT_MS = 5_000;
25
+ const lockWait = new Int32Array(new SharedArrayBuffer(4));
26
+
27
+ function rootsFile(filePath) {
28
+ if (filePath) return resolve(filePath);
29
+ const envPath = process.env.NOTIS_APP_DEV_ROOTS_FILE;
30
+ return typeof envPath === 'string' && envPath.trim()
31
+ ? resolve(envPath.trim())
32
+ : DEFAULT_APP_DEV_ROOTS_FILE;
33
+ }
34
+
35
+ function legacyProjectsFile(filePath) {
36
+ const sessionsFile = filePath
37
+ || join(CONFIG_DIR, 'app-dev-sessions.json');
38
+ const parsed = parsePath(resolve(sessionsFile));
39
+ return join(parsed.dir, `${parsed.name}-projects${parsed.ext || '.json'}`);
40
+ }
41
+
42
+ function readLockOwner(lockPath) {
43
+ try {
44
+ return readFileSync(join(lockPath, 'owner'), 'utf8').trim();
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ function withLock(filePath, callback) {
51
+ mkdirSync(dirname(filePath), { recursive: true, mode: 0o700 });
52
+ const lockPath = `${filePath}.lock`;
53
+ const owner = `${process.pid}.${randomUUID()}`;
54
+ const startedAt = Date.now();
55
+ while (true) {
56
+ try {
57
+ mkdirSync(lockPath, { mode: 0o700 });
58
+ writeFileSync(join(lockPath, 'owner'), owner, { mode: 0o600 });
59
+ break;
60
+ } catch (error) {
61
+ if (error?.code !== 'EEXIST') throw error;
62
+ try {
63
+ const stat = lstatSync(lockPath);
64
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
65
+ throw new Error(`Refusing unsafe app development roots lock: ${lockPath}`);
66
+ }
67
+ const existingOwner = readLockOwner(lockPath);
68
+ const pid = Number.parseInt(String(existingOwner || '').split('.')[0] || '', 10);
69
+ if (Number.isInteger(pid) && pid > 0) {
70
+ try {
71
+ process.kill(pid, 0);
72
+ } catch (ownerError) {
73
+ if (ownerError?.code === 'ESRCH') {
74
+ rmSync(lockPath, { recursive: true, force: true });
75
+ continue;
76
+ }
77
+ }
78
+ }
79
+ } catch (statError) {
80
+ if (statError?.code === 'ENOENT') continue;
81
+ throw statError;
82
+ }
83
+ if (Date.now() - startedAt >= LOCK_TIMEOUT_MS) {
84
+ throw new Error(`Timed out waiting for app development roots lock: ${filePath}`);
85
+ }
86
+ Atomics.wait(lockWait, 0, 0, 10);
87
+ }
88
+ }
89
+ try {
90
+ return callback();
91
+ } finally {
92
+ if (readLockOwner(lockPath) === owner) {
93
+ rmSync(lockPath, { recursive: true, force: true });
94
+ }
95
+ }
96
+ }
97
+
98
+ function canonicalExistingDirectory(inputPath) {
99
+ const absolute = resolve(String(inputPath || ''));
100
+ let stat;
101
+ try {
102
+ stat = statSync(absolute);
103
+ } catch {
104
+ throw usageError(`App development root does not exist: ${absolute}`);
105
+ }
106
+ if (!stat.isDirectory()) {
107
+ throw usageError(`App development root is not a directory: ${absolute}`);
108
+ }
109
+ return realpathSync(absolute);
110
+ }
111
+
112
+ function normalizeRegistry(raw) {
113
+ const roots = Array.isArray(raw?.roots) ? raw.roots : [];
114
+ const seen = new Set();
115
+ return roots
116
+ .map((entry) => typeof entry === 'string' ? { path: entry } : entry)
117
+ .filter((entry) => entry && typeof entry.path === 'string' && entry.path.trim())
118
+ .map((entry) => ({
119
+ path: resolve(entry.path),
120
+ registeredAt: typeof entry.registeredAt === 'string'
121
+ ? entry.registeredAt
122
+ : new Date(0).toISOString(),
123
+ }))
124
+ .filter((entry) => {
125
+ if (seen.has(entry.path)) return false;
126
+ seen.add(entry.path);
127
+ return true;
128
+ });
129
+ }
130
+
131
+ function readRaw(filePath) {
132
+ if (!existsSync(filePath)) return { version: APP_DEV_ROOTS_VERSION, roots: [] };
133
+ try {
134
+ return {
135
+ version: APP_DEV_ROOTS_VERSION,
136
+ roots: normalizeRegistry(JSON.parse(readFileSync(filePath, 'utf8'))),
137
+ };
138
+ } catch {
139
+ return { version: APP_DEV_ROOTS_VERSION, roots: [] };
140
+ }
141
+ }
142
+
143
+ function writeRaw(registry, filePath) {
144
+ mkdirSync(dirname(filePath), { recursive: true, mode: 0o700 });
145
+ const normalized = {
146
+ version: APP_DEV_ROOTS_VERSION,
147
+ roots: normalizeRegistry(registry),
148
+ };
149
+ const temporary = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
150
+ writeFileSync(temporary, JSON.stringify(normalized, null, 2), { mode: 0o600 });
151
+ renameSync(temporary, filePath);
152
+ return normalized;
153
+ }
154
+
155
+ function migrateLegacyProjectsUnlocked(registry, options) {
156
+ const path = legacyProjectsFile(options.legacySessionsFilePath);
157
+ if (!existsSync(path)) return registry;
158
+ let projects = [];
159
+ try {
160
+ const raw = JSON.parse(readFileSync(path, 'utf8'));
161
+ projects = Array.isArray(raw?.projects) ? raw.projects : [];
162
+ } catch {
163
+ return registry;
164
+ }
165
+ const byPath = new Map(registry.roots.map((entry) => [entry.path, entry]));
166
+ for (const project of projects) {
167
+ if (typeof project?.projectDir !== 'string' || !project.projectDir.trim()) continue;
168
+ try {
169
+ const path = canonicalExistingDirectory(project.projectDir);
170
+ if (path === realpathIfExists(DEFAULT_APP_DEV_ROOT)) continue;
171
+ if (!byPath.has(path)) {
172
+ byPath.set(path, {
173
+ path,
174
+ registeredAt: typeof project.lastMountedAt === 'string'
175
+ ? project.lastMountedAt
176
+ : new Date().toISOString(),
177
+ });
178
+ }
179
+ } catch {
180
+ // Deleted or inaccessible legacy projects are intentionally not migrated.
181
+ }
182
+ }
183
+ rmSync(path, { force: true });
184
+ return { version: APP_DEV_ROOTS_VERSION, roots: [...byPath.values()] };
185
+ }
186
+
187
+ function realpathIfExists(path) {
188
+ try {
189
+ return realpathSync(path);
190
+ } catch {
191
+ return resolve(path);
192
+ }
193
+ }
194
+
195
+ export function readAppDevRoots(options = {}) {
196
+ const filePath = rootsFile(options.filePath);
197
+ let registry = readRaw(filePath);
198
+ if (options.migrateLegacy !== false) {
199
+ registry = withLock(filePath, () => {
200
+ const before = readRaw(filePath);
201
+ const after = migrateLegacyProjectsUnlocked(before, options);
202
+ const changed = JSON.stringify(before.roots) !== JSON.stringify(after.roots);
203
+ return changed ? writeRaw(after, filePath) : before;
204
+ });
205
+ }
206
+ const implicit = realpathIfExists(options.defaultRoot || DEFAULT_APP_DEV_ROOT);
207
+ const roots = [
208
+ { path: implicit, registeredAt: null, implicit: true },
209
+ ...registry.roots
210
+ .filter((entry) => entry.path !== implicit)
211
+ .map((entry) => ({ ...entry, implicit: false })),
212
+ ];
213
+ return { version: APP_DEV_ROOTS_VERSION, roots };
214
+ }
215
+
216
+ export function registerAppDevRoot(inputPath, options = {}) {
217
+ const path = canonicalExistingDirectory(inputPath);
218
+ const implicit = realpathIfExists(options.defaultRoot || DEFAULT_APP_DEV_ROOT);
219
+ if (path === implicit) return readAppDevRoots(options);
220
+ const filePath = rootsFile(options.filePath);
221
+ withLock(filePath, () => {
222
+ const registry = readRaw(filePath);
223
+ if (!registry.roots.some((entry) => entry.path === path)) {
224
+ registry.roots.push({ path, registeredAt: new Date().toISOString() });
225
+ writeRaw(registry, filePath);
226
+ }
227
+ });
228
+ return readAppDevRoots({ ...options, migrateLegacy: false });
229
+ }
230
+
231
+ export function removeAppDevRoot(inputPath, options = {}) {
232
+ const path = realpathIfExists(resolve(String(inputPath || '')));
233
+ const implicit = realpathIfExists(options.defaultRoot || DEFAULT_APP_DEV_ROOT);
234
+ if (path === implicit) {
235
+ throw usageError(`The default app development root cannot be removed: ${implicit}`);
236
+ }
237
+ const filePath = rootsFile(options.filePath);
238
+ let removed = false;
239
+ withLock(filePath, () => {
240
+ const registry = readRaw(filePath);
241
+ const next = registry.roots.filter((entry) => entry.path !== path);
242
+ removed = next.length !== registry.roots.length;
243
+ if (removed) writeRaw({ ...registry, roots: next }, filePath);
244
+ });
245
+ return { removed, path, registry: readAppDevRoots({ ...options, migrateLegacy: false }) };
246
+ }
247
+
248
+ function hasConfig(path) {
249
+ return CONFIG_FILENAMES.some((name) => existsSync(join(path, name)));
250
+ }
251
+
252
+ function childAppDirs(parent) {
253
+ if (!existsSync(parent)) return [];
254
+ let entries = [];
255
+ try {
256
+ entries = readdirSync(parent, { withFileTypes: true });
257
+ } catch {
258
+ return [];
259
+ }
260
+ return entries
261
+ .filter((entry) => entry.isDirectory())
262
+ .filter((entry) => !entry.name.startsWith('.') && !entry.name.startsWith('_'))
263
+ .filter((entry) => entry.name !== 'node_modules')
264
+ .map((entry) => join(parent, entry.name))
265
+ .filter(hasConfig);
266
+ }
267
+
268
+ export function discoverAppProjectsInRoot(inputRoot) {
269
+ const root = realpathIfExists(resolve(inputRoot));
270
+ const candidates = [];
271
+ if (hasConfig(root)) candidates.push(root);
272
+ candidates.push(...childAppDirs(root));
273
+ candidates.push(...childAppDirs(join(root, 'apps')));
274
+ return [...new Set(candidates.map(realpathIfExists))].sort();
275
+ }
276
+
277
+ export function discoverRegisteredAppProjects(options = {}) {
278
+ const roots = readAppDevRoots(options).roots;
279
+ return [...new Set(roots.flatMap((entry) => discoverAppProjectsInRoot(entry.path)))].sort();
280
+ }
281
+
282
+ export function getAppDevRootsFile(filePath) {
283
+ return rootsFile(filePath);
284
+ }
@@ -28,7 +28,10 @@ import {
28
28
  readLinkedState,
29
29
  writeLinkedState,
30
30
  } from './app-platform.js';
31
- import { linkAppDevSessionTarget } from './app-dev-sessions.js';
31
+ import {
32
+ linkAppDevSessionTarget,
33
+ readAppDevSessions,
34
+ } from './app-dev-sessions.js';
32
35
 
33
36
  const CONTENT_TYPES = {
34
37
  '.js': 'application/javascript; charset=utf-8',
@@ -297,7 +300,7 @@ function renderHarnessHtml({ state, manifest, appConfig, route, harnessOptions,
297
300
  /**
298
301
  * Start the dev server for one or more apps.
299
302
  *
300
- * @param {{apps: Array<{slug: string, projectDir: string, appId?: string, targetAppId?: string, userId?: string}>, port: number, watch?: boolean, sessionsFilePath?: string, harness?: { mode?: string, apiBase?: string, jwt?: string }, log?: (m: string) => void, logError?: (m: string) => void}} options
303
+ * @param {{apps: Array<{slug: string, projectDir: string, appId?: string, targetAppId?: string, userId?: string, profileKey?: string, sessionId?: string, mountNonce?: string}>, port: number, watch?: boolean, sessionsFilePath?: string, harness?: { mode?: string, apiBase?: string, jwt?: string }, log?: (m: string) => void, logError?: (m: string) => void}} options
301
304
  */
302
305
  export async function startAppDevServer({
303
306
  apps,
@@ -313,22 +316,43 @@ export async function startAppDevServer({
313
316
  }
314
317
 
315
318
  const appState = new Map();
316
- for (const app of apps) {
317
- appState.set(app.slug, {
319
+ // Sidebar reconciliation needs one stream for the shared host, not one
320
+ // long-lived HTTP/1.1 connection per discovered app. Keeping a stream per
321
+ // app exhausts Chromium's per-origin connection pool and can indefinitely
322
+ // queue the active view's bundle fetch during hot reload.
323
+ const hostSseClients = new Set();
324
+ const createAppState = (app) => {
325
+ let resolveBundleReady;
326
+ const bundleReadyPromise = new Promise((resolvePromise) => {
327
+ resolveBundleReady = resolvePromise;
328
+ });
329
+ return {
318
330
  slug: app.slug,
319
331
  projectDir: app.projectDir,
320
332
  appId: app.appId || null,
321
333
  targetAppId: app.targetAppId || null,
322
334
  userId: app.userId || null,
335
+ profileKey: app.profileKey || null,
336
+ sessionId: app.sessionId || null,
337
+ mountNonce: app.mountNonce || null,
323
338
  canonicalBundleDir: getBundleDir(app.projectDir),
324
339
  bundleDir: null,
325
340
  jsPath: null,
326
341
  sseClients: new Set(),
327
342
  watcher: null,
343
+ sourceWatchers: [],
344
+ prepareTimer: null,
345
+ reloadTimer: null,
328
346
  buildProcess: null,
329
347
  lastMtimeMs: 0,
330
348
  watchPollTimer: null,
331
- });
349
+ bundleReady: false,
350
+ bundleReadyPromise,
351
+ resolveBundleReady,
352
+ };
353
+ };
354
+ for (const app of apps) {
355
+ appState.set(app.slug, createAppState(app));
332
356
  }
333
357
 
334
358
  function broadcastReload(slug) {
@@ -342,6 +366,14 @@ export async function startAppDevServer({
342
366
  state.sseClients.delete(res);
343
367
  }
344
368
  }
369
+ const hostPayload = `event: reload\ndata: ${JSON.stringify({ slug, at: Date.now() })}\n\n`;
370
+ for (const res of hostSseClients) {
371
+ try {
372
+ res.write(hostPayload);
373
+ } catch {
374
+ hostSseClients.delete(res);
375
+ }
376
+ }
345
377
  }
346
378
 
347
379
  function matchAppRoute(pathname) {
@@ -374,6 +406,10 @@ export async function startAppDevServer({
374
406
  state.jsPath = join(bundleDir, 'app.js');
375
407
  try {
376
408
  state.lastMtimeMs = statSync(state.jsPath).mtimeMs;
409
+ if (!state.bundleReady) {
410
+ state.bundleReady = true;
411
+ state.resolveBundleReady();
412
+ }
377
413
  } catch {
378
414
  state.lastMtimeMs = 0;
379
415
  }
@@ -451,6 +487,9 @@ export async function startAppDevServer({
451
487
  try {
452
488
  const body = await readRequestJson(req);
453
489
  const appId = typeof body.app_id === 'string' ? body.app_id.trim() : '';
490
+ const version = Number.isInteger(body.version) && body.version > 0
491
+ ? body.version
492
+ : null;
454
493
  if (!appId) {
455
494
  res.writeHead(400, {
456
495
  ...headers,
@@ -459,20 +498,69 @@ export async function startAppDevServer({
459
498
  res.end(JSON.stringify({ error: 'app_id is required' }));
460
499
  return;
461
500
  }
501
+ if (version === null) {
502
+ res.writeHead(400, {
503
+ ...headers,
504
+ 'Content-Type': 'application/json; charset=utf-8',
505
+ });
506
+ res.end(JSON.stringify({ error: 'version must be a positive integer' }));
507
+ return;
508
+ }
509
+ const proofSession = readAppDevSessions(sessionsFilePath).sessions.find((session) => (
510
+ typeof body.session_id === 'string'
511
+ && typeof body.mount_nonce === 'string'
512
+ && session.sessionId === body.session_id
513
+ && session.mountNonce === body.mount_nonce
514
+ && session.devSlug === state.slug
515
+ && session.projectDir === state.projectDir
516
+ ));
517
+ if (!proofSession) {
518
+ res.writeHead(403, {
519
+ ...headers,
520
+ 'Content-Type': 'application/json; charset=utf-8',
521
+ });
522
+ res.end(JSON.stringify({ error: 'invalid app development session proof' }));
523
+ return;
524
+ }
525
+ const expectedAppId = proofSession.targetAppId || proofSession.appId;
526
+ if (!expectedAppId || appId !== expectedAppId) {
527
+ res.writeHead(409, {
528
+ ...headers,
529
+ 'Content-Type': 'application/json; charset=utf-8',
530
+ });
531
+ res.end(JSON.stringify({ error: 'app_id does not match this development session' }));
532
+ return;
533
+ }
462
534
 
463
- const currentState = readLinkedState(state.projectDir) || {};
535
+ const currentState = readLinkedState(
536
+ state.projectDir,
537
+ proofSession.profileKey || state.profileKey,
538
+ ) || {};
464
539
  const now = new Date().toISOString();
465
- writeLinkedState(state.projectDir, {
540
+ const promotedInPlace = proofSession.appId === appId;
541
+ const nextState = {
466
542
  ...currentState,
467
543
  app_id: appId,
468
- dev_app_id: state.appId || currentState.dev_app_id,
469
544
  linked_at: currentState.linked_at || now,
470
- dev_linked_at: currentState.dev_linked_at || now,
545
+ version,
546
+ deployed_at: now,
471
547
  updated_at: now,
472
- });
473
- state.targetAppId = appId;
548
+ };
549
+ if (promotedInPlace) {
550
+ delete nextState.dev_app_id;
551
+ delete nextState.dev_linked_at;
552
+ } else {
553
+ nextState.dev_app_id = proofSession.appId || currentState.dev_app_id;
554
+ nextState.dev_linked_at = currentState.dev_linked_at || now;
555
+ }
556
+ writeLinkedState(
557
+ state.projectDir,
558
+ nextState,
559
+ proofSession.profileKey || state.profileKey,
560
+ );
474
561
  linkAppDevSessionTarget({
475
- appId: state.appId,
562
+ sessionId: proofSession.sessionId,
563
+ appId: proofSession.appId,
476
564
  devSlug: state.slug,
477
565
  targetAppId: appId,
478
566
  lastHeartbeatAt: now,
@@ -480,8 +568,9 @@ export async function startAppDevServer({
480
568
  const response = {
481
569
  ok: true,
482
570
  app_id: appId,
483
- dev_app_id: state.appId || null,
571
+ dev_app_id: promotedInPlace ? null : proofSession.appId || null,
484
572
  target_app_id: appId,
573
+ version,
485
574
  };
486
575
  res.writeHead(200, {
487
576
  ...headers,
@@ -525,6 +614,18 @@ export async function startAppDevServer({
525
614
  return;
526
615
  }
527
616
 
617
+ if (url.pathname === '/events') {
618
+ res.writeHead(200, {
619
+ ...headers,
620
+ 'Content-Type': 'text/event-stream; charset=utf-8',
621
+ Connection: 'keep-alive',
622
+ });
623
+ res.write(': connected\n\n');
624
+ hostSseClients.add(res);
625
+ req.on('close', () => hostSseClients.delete(res));
626
+ return;
627
+ }
628
+
528
629
  const routed = matchAppRoute(url.pathname);
529
630
  if (!routed) {
530
631
  res.writeHead(404, headers);
@@ -644,15 +745,23 @@ export async function startAppDevServer({
644
745
  try {
645
746
  state.watcher = fsWatch(state.bundleDir, { persistent: false }, (_event, filename) => {
646
747
  if (!filename || filename !== 'app.js') return;
647
- try {
648
- const stat = statSync(state.jsPath);
649
- if (stat.mtimeMs === state.lastMtimeMs) return;
650
- state.lastMtimeMs = stat.mtimeMs;
651
- log(`[notis apps dev] ${state.slug}: bundle updated — reloading portal`);
652
- broadcastReload(state.slug);
653
- } catch {
654
- // Bundle file may be missing mid-write; ignore.
655
- }
748
+ // Vite may emit the filesystem notification while its output file is
749
+ // still being replaced. Debounce the notification so consumers never
750
+ // fetch the previous or partially-written bundle under the new build
751
+ // revision.
752
+ if (state.reloadTimer) clearTimeout(state.reloadTimer);
753
+ state.reloadTimer = setTimeout(() => {
754
+ state.reloadTimer = null;
755
+ try {
756
+ const stat = statSync(state.jsPath);
757
+ if (stat.mtimeMs === state.lastMtimeMs) return;
758
+ state.lastMtimeMs = stat.mtimeMs;
759
+ log(`[notis apps dev] ${state.slug}: bundle updated — reloading portal`);
760
+ broadcastReload(state.slug);
761
+ } catch {
762
+ // Bundle file may be missing mid-write; the next change retries.
763
+ }
764
+ }, 100);
656
765
  });
657
766
  } catch (error) {
658
767
  logError(`[notis apps dev] ${state.slug}: watch failed: ${error.message}`);
@@ -669,9 +778,53 @@ export async function startAppDevServer({
669
778
  state.watchPollTimer = setTimeout(() => pollForBundleAndWatch(state), 300);
670
779
  }
671
780
 
781
+ function watchManifestInputs(state) {
782
+ const schedulePrepare = () => {
783
+ if (state.prepareTimer) clearTimeout(state.prepareTimer);
784
+ state.prepareTimer = setTimeout(() => {
785
+ state.prepareTimer = null;
786
+ void prepareArtifactBuild(state.projectDir).then(() => {
787
+ // Name, icon, and route metadata can change without changing the
788
+ // compiled JS bytes. Notify consumers immediately; a generated-entry
789
+ // change will produce the normal second reload after Vite rebuilds.
790
+ broadcastReload(state.slug);
791
+ }).catch((error) => {
792
+ const message = error instanceof Error ? error.message : String(error);
793
+ logError(`[notis apps dev] ${state.slug}: ${message}`);
794
+ });
795
+ }, 75);
796
+ };
797
+ try {
798
+ state.sourceWatchers.push(fsWatch(
799
+ state.projectDir,
800
+ { persistent: false },
801
+ (_event, filename) => {
802
+ if (filename && /^notis\.config\.(?:ts|js|mjs)$/.test(String(filename))) {
803
+ schedulePrepare();
804
+ }
805
+ },
806
+ ));
807
+ } catch (error) {
808
+ logError(`[notis apps dev] ${state.slug}: config watch failed: ${error.message}`);
809
+ }
810
+ const appDir = join(state.projectDir, 'app');
811
+ if (existsSync(appDir)) {
812
+ try {
813
+ state.sourceWatchers.push(fsWatch(
814
+ appDir,
815
+ { persistent: false, recursive: process.platform === 'darwin' || process.platform === 'win32' },
816
+ schedulePrepare,
817
+ ));
818
+ } catch (error) {
819
+ logError(`[notis apps dev] ${state.slug}: route watch failed: ${error.message}`);
820
+ }
821
+ }
822
+ }
823
+
672
824
  for (const state of appState.values()) {
673
825
  if (watch) {
674
826
  await prepareArtifactBuild(state.projectDir);
827
+ watchManifestInputs(state);
675
828
  pollForBundleAndWatch(state);
676
829
 
677
830
  state.buildProcess = spawn('npm', ['run', 'build', '--', '--watch'], {
@@ -694,8 +847,37 @@ export async function startAppDevServer({
694
847
 
695
848
  return {
696
849
  port,
850
+ updateApp(slug, updates = {}) {
851
+ const state = appState.get(slug);
852
+ if (!state) throw new Error(`unknown app: ${slug}`);
853
+ for (const key of ['appId', 'targetAppId', 'userId', 'profileKey', 'sessionId', 'mountNonce']) {
854
+ if (Object.prototype.hasOwnProperty.call(updates, key)) {
855
+ state[key] = updates[key] || null;
856
+ }
857
+ }
858
+ },
859
+ isBundleReady(slug) {
860
+ const state = appState.get(slug);
861
+ if (!state) throw new Error(`unknown app: ${slug}`);
862
+ return state.bundleReady;
863
+ },
864
+ waitForBundle(slug) {
865
+ const state = appState.get(slug);
866
+ if (!state) return Promise.reject(new Error(`unknown app: ${slug}`));
867
+ return state.bundleReadyPromise;
868
+ },
697
869
  async close() {
698
870
  for (const state of appState.values()) {
871
+ if (state.prepareTimer) clearTimeout(state.prepareTimer);
872
+ if (state.reloadTimer) clearTimeout(state.reloadTimer);
873
+ for (const watcher of state.sourceWatchers) {
874
+ try {
875
+ watcher.close();
876
+ } catch {
877
+ // ignore
878
+ }
879
+ }
880
+ state.sourceWatchers = [];
699
881
  if (state.watchPollTimer) clearTimeout(state.watchPollTimer);
700
882
  if (state.watcher) {
701
883
  try {
@@ -716,6 +898,14 @@ export async function startAppDevServer({
716
898
  state.buildProcess.kill('SIGTERM');
717
899
  }
718
900
  }
901
+ for (const res of hostSseClients) {
902
+ try {
903
+ res.end();
904
+ } catch {
905
+ // ignore
906
+ }
907
+ }
908
+ hostSseClients.clear();
719
909
  await new Promise((resolvePromise) => server.close(() => resolvePromise()));
720
910
  },
721
911
  };