@notis_ai/cli 0.2.14 → 0.2.16

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 (299) hide show
  1. package/README.md +83 -21
  2. package/dist/agent-hooks/notis-agent-hook.mjs +20168 -0
  3. package/{skills → dist/base-skills}/notis-apps/SKILL.md +167 -72
  4. package/{skills → dist/base-skills}/notis-cli/SKILL.md +86 -28
  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 +39 -18
  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 +1021 -424
  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-process-identity.js +111 -0
  25. package/src/runtime/app-dev-roots.js +284 -0
  26. package/src/runtime/app-dev-server.js +447 -29
  27. package/src/runtime/app-dev-sessions.js +105 -145
  28. package/src/runtime/app-platform.js +1314 -166
  29. package/src/runtime/app-registry-scaffolds.js +367 -0
  30. package/src/runtime/base-skills.d.ts +20 -0
  31. package/src/runtime/base-skills.js +167 -0
  32. package/src/runtime/login-listener.js +15 -0
  33. package/src/runtime/oauth.js +1506 -141
  34. package/src/runtime/output.js +7 -0
  35. package/src/runtime/ports.js +16 -0
  36. package/src/runtime/profiles.js +19 -16
  37. package/src/runtime/skill-sync/cloud-client.ts +96 -0
  38. package/src/runtime/skill-sync/index.ts +644 -0
  39. package/src/runtime/skill-sync/local-scanner.ts +1046 -0
  40. package/src/runtime/skill-sync/symlink-manager.ts +383 -0
  41. package/src/runtime/skill-sync/sync-plan.ts +22 -0
  42. package/src/runtime/skill-sync/types.ts +103 -0
  43. package/src/runtime/skill-sync/write-cloud-skill.ts +50 -0
  44. package/src/runtime/store-screenshot.js +6 -1
  45. package/src/runtime/sync-skills.d.ts +37 -0
  46. package/src/runtime/sync-skills.js +231 -0
  47. package/template/app/layout.tsx +5 -1
  48. package/template/notis.config.ts +1 -0
  49. package/template/packages/sdk/package.json +2 -1
  50. package/template/packages/sdk/src/components/DocumentEditor.tsx +13 -3
  51. package/template/packages/sdk/src/components/MarkdownEditor.tsx +121 -0
  52. package/template/packages/sdk/src/components/MultiSelectActionBar.tsx +43 -119
  53. package/template/packages/sdk/src/components/MultiSelectCheckbox.tsx +5 -1
  54. package/template/packages/sdk/src/components/MultiSelectDragOverlay.tsx +1 -1
  55. package/template/packages/sdk/src/components/NotisSelectionBoundary.tsx +55 -0
  56. package/template/packages/sdk/src/components/ShortcutHints.tsx +56 -0
  57. package/template/packages/sdk/src/config.ts +25 -0
  58. package/template/packages/sdk/src/documents.ts +7 -1
  59. package/template/packages/sdk/src/hooks/useActiveResource.ts +19 -0
  60. package/template/packages/sdk/src/hooks/useCollectionInteractions.ts +726 -0
  61. package/template/packages/sdk/src/hooks/useHandover.ts +3 -0
  62. package/template/packages/sdk/src/hooks/useMultiSelect.ts +44 -482
  63. package/template/packages/sdk/src/hooks/useNotis.ts +3 -0
  64. package/template/packages/sdk/src/hooks/useNotisNavigation.ts +7 -4
  65. package/template/packages/sdk/src/hooks/useTool.ts +5 -4
  66. package/template/packages/sdk/src/index.ts +47 -2
  67. package/template/packages/sdk/src/interactions/actions.ts +46 -0
  68. package/template/packages/sdk/src/interactions/shortcuts.tsx +634 -0
  69. package/template/packages/sdk/src/interactions.ts +41 -0
  70. package/template/packages/sdk/src/provider.tsx +2 -1
  71. package/template/packages/sdk/src/runtime.ts +92 -4
  72. package/dist/scaffolds/notis-database/CHANGELOG.md +0 -5
  73. package/dist/scaffolds/notis-database/app/globals.css +0 -44
  74. package/dist/scaffolds/notis-database/app/layout.tsx +0 -6
  75. package/dist/scaffolds/notis-database/app/page.tsx +0 -1088
  76. package/dist/scaffolds/notis-database/components/ui/badge.tsx +0 -28
  77. package/dist/scaffolds/notis-database/components/ui/button.tsx +0 -53
  78. package/dist/scaffolds/notis-database/components/ui/card.tsx +0 -56
  79. package/dist/scaffolds/notis-database/components/ui/table.tsx +0 -120
  80. package/dist/scaffolds/notis-database/components.json +0 -20
  81. package/dist/scaffolds/notis-database/index.html +0 -12
  82. package/dist/scaffolds/notis-database/lib/types.ts +0 -132
  83. package/dist/scaffolds/notis-database/lib/utils.ts +0 -6
  84. package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
  85. package/dist/scaffolds/notis-database/metadata/screenshot-2.png +0 -0
  86. package/dist/scaffolds/notis-database/metadata/screenshot-3.png +0 -0
  87. package/dist/scaffolds/notis-database/metadata/screenshot-4.png +0 -0
  88. package/dist/scaffolds/notis-database/metadata/screenshot-5.png +0 -0
  89. package/dist/scaffolds/notis-database/metadata/screenshot-fixtures.json +0 -1839
  90. package/dist/scaffolds/notis-database/notis.config.ts +0 -73
  91. package/dist/scaffolds/notis-database/package-lock.json +0 -3935
  92. package/dist/scaffolds/notis-database/package.json +0 -32
  93. package/dist/scaffolds/notis-database/packages/sdk/package.json +0 -36
  94. package/dist/scaffolds/notis-database/packages/sdk/src/components/DocumentEditor.tsx +0 -93
  95. package/dist/scaffolds/notis-database/packages/sdk/src/components/Markdown.tsx +0 -60
  96. package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
  97. package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
  98. package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
  99. package/dist/scaffolds/notis-database/packages/sdk/src/config.ts +0 -234
  100. package/dist/scaffolds/notis-database/packages/sdk/src/documents.ts +0 -250
  101. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useBackend.ts +0 -41
  102. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
  103. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  104. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
  105. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocument.ts +0 -78
  106. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocuments.ts +0 -121
  107. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useHandover.ts +0 -75
  108. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
  109. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotis.ts +0 -34
  110. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
  111. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTool.ts +0 -64
  112. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTools.ts +0 -56
  113. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  114. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
  115. package/dist/scaffolds/notis-database/packages/sdk/src/index.ts +0 -100
  116. package/dist/scaffolds/notis-database/packages/sdk/src/provider.tsx +0 -43
  117. package/dist/scaffolds/notis-database/packages/sdk/src/runtime.ts +0 -351
  118. package/dist/scaffolds/notis-database/packages/sdk/src/styles.css +0 -186
  119. package/dist/scaffolds/notis-database/packages/sdk/src/ui.ts +0 -15
  120. package/dist/scaffolds/notis-database/packages/sdk/src/vite.ts +0 -56
  121. package/dist/scaffolds/notis-database/packages/sdk/tsconfig.json +0 -15
  122. package/dist/scaffolds/notis-database/postcss.config.mjs +0 -8
  123. package/dist/scaffolds/notis-database/src/dev-main.tsx +0 -23
  124. package/dist/scaffolds/notis-database/src/mock-runtime.ts +0 -561
  125. package/dist/scaffolds/notis-database/tailwind.config.ts +0 -59
  126. package/dist/scaffolds/notis-database/tsconfig.json +0 -23
  127. package/dist/scaffolds/notis-database/vite.config.ts +0 -22
  128. package/dist/scaffolds/notis-journal/CHANGELOG.md +0 -29
  129. package/dist/scaffolds/notis-journal/app/globals.css +0 -37
  130. package/dist/scaffolds/notis-journal/app/insights/page.tsx +0 -513
  131. package/dist/scaffolds/notis-journal/app/journal-core.tsx +0 -362
  132. package/dist/scaffolds/notis-journal/app/journal-ui.tsx +0 -337
  133. package/dist/scaffolds/notis-journal/app/layout.tsx +0 -6
  134. package/dist/scaffolds/notis-journal/app/page.tsx +0 -486
  135. package/dist/scaffolds/notis-journal/components/ui/badge.tsx +0 -28
  136. package/dist/scaffolds/notis-journal/components/ui/button.tsx +0 -53
  137. package/dist/scaffolds/notis-journal/components/ui/card.tsx +0 -56
  138. package/dist/scaffolds/notis-journal/components.json +0 -20
  139. package/dist/scaffolds/notis-journal/index.html +0 -12
  140. package/dist/scaffolds/notis-journal/lib/utils.ts +0 -6
  141. package/dist/scaffolds/notis-journal/metadata/screenshot-1.png +0 -0
  142. package/dist/scaffolds/notis-journal/metadata/screenshot-2.png +0 -0
  143. package/dist/scaffolds/notis-journal/metadata/screenshot-3.png +0 -0
  144. package/dist/scaffolds/notis-journal/metadata/screenshot-4.png +0 -0
  145. package/dist/scaffolds/notis-journal/metadata/screenshot-5.png +0 -0
  146. package/dist/scaffolds/notis-journal/metadata/screenshot-6.png +0 -0
  147. package/dist/scaffolds/notis-journal/metadata/screenshot-fixtures.json +0 -132
  148. package/dist/scaffolds/notis-journal/notis.config.ts +0 -93
  149. package/dist/scaffolds/notis-journal/package-lock.json +0 -4615
  150. package/dist/scaffolds/notis-journal/package.json +0 -34
  151. package/dist/scaffolds/notis-journal/packages/sdk/package.json +0 -36
  152. package/dist/scaffolds/notis-journal/packages/sdk/src/components/DocumentEditor.tsx +0 -93
  153. package/dist/scaffolds/notis-journal/packages/sdk/src/components/Markdown.tsx +0 -60
  154. package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
  155. package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
  156. package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
  157. package/dist/scaffolds/notis-journal/packages/sdk/src/config.ts +0 -234
  158. package/dist/scaffolds/notis-journal/packages/sdk/src/documents.ts +0 -250
  159. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useBackend.ts +0 -41
  160. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
  161. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  162. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
  163. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocument.ts +0 -78
  164. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocuments.ts +0 -121
  165. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useHandover.ts +0 -75
  166. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
  167. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotis.ts +0 -34
  168. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
  169. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTool.ts +0 -64
  170. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTools.ts +0 -56
  171. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  172. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
  173. package/dist/scaffolds/notis-journal/packages/sdk/src/index.ts +0 -100
  174. package/dist/scaffolds/notis-journal/packages/sdk/src/provider.tsx +0 -43
  175. package/dist/scaffolds/notis-journal/packages/sdk/src/runtime.ts +0 -351
  176. package/dist/scaffolds/notis-journal/packages/sdk/src/styles.css +0 -186
  177. package/dist/scaffolds/notis-journal/packages/sdk/src/ui.ts +0 -15
  178. package/dist/scaffolds/notis-journal/packages/sdk/src/vite.ts +0 -56
  179. package/dist/scaffolds/notis-journal/packages/sdk/tsconfig.json +0 -15
  180. package/dist/scaffolds/notis-journal/postcss.config.mjs +0 -8
  181. package/dist/scaffolds/notis-journal/skills/journal-onboarding/SKILL.md +0 -120
  182. package/dist/scaffolds/notis-journal/src/dev-main.tsx +0 -58
  183. package/dist/scaffolds/notis-journal/src/mock-runtime.ts +0 -199
  184. package/dist/scaffolds/notis-journal/tailwind.config.ts +0 -58
  185. package/dist/scaffolds/notis-journal/tsconfig.json +0 -23
  186. package/dist/scaffolds/notis-journal/vite.config.ts +0 -10
  187. package/dist/scaffolds/notis-notes/CHANGELOG.md +0 -5
  188. package/dist/scaffolds/notis-notes/app/globals.css +0 -3
  189. package/dist/scaffolds/notis-notes/app/layout.tsx +0 -6
  190. package/dist/scaffolds/notis-notes/app/page.tsx +0 -1943
  191. package/dist/scaffolds/notis-notes/app/phosphor-icons.ts +0 -596
  192. package/dist/scaffolds/notis-notes/components/ui/badge.tsx +0 -28
  193. package/dist/scaffolds/notis-notes/components/ui/button.tsx +0 -53
  194. package/dist/scaffolds/notis-notes/components/ui/card.tsx +0 -56
  195. package/dist/scaffolds/notis-notes/components.json +0 -20
  196. package/dist/scaffolds/notis-notes/lib/utils.ts +0 -6
  197. package/dist/scaffolds/notis-notes/lib/visible-properties.ts +0 -144
  198. package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
  199. package/dist/scaffolds/notis-notes/metadata/screenshot-2.png +0 -0
  200. package/dist/scaffolds/notis-notes/metadata/screenshot-3.png +0 -0
  201. package/dist/scaffolds/notis-notes/metadata/screenshot-4.png +0 -0
  202. package/dist/scaffolds/notis-notes/metadata/screenshot-5.png +0 -0
  203. package/dist/scaffolds/notis-notes/metadata/screenshot-fixtures.json +0 -752
  204. package/dist/scaffolds/notis-notes/notis.config.ts +0 -80
  205. package/dist/scaffolds/notis-notes/package-lock.json +0 -4636
  206. package/dist/scaffolds/notis-notes/package.json +0 -35
  207. package/dist/scaffolds/notis-notes/packages/sdk/package.json +0 -36
  208. package/dist/scaffolds/notis-notes/packages/sdk/src/components/DocumentEditor.tsx +0 -93
  209. package/dist/scaffolds/notis-notes/packages/sdk/src/components/Markdown.tsx +0 -60
  210. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
  211. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
  212. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
  213. package/dist/scaffolds/notis-notes/packages/sdk/src/config.ts +0 -234
  214. package/dist/scaffolds/notis-notes/packages/sdk/src/documents.ts +0 -250
  215. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useBackend.ts +0 -41
  216. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
  217. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  218. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
  219. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocument.ts +0 -78
  220. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocuments.ts +0 -121
  221. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useHandover.ts +0 -75
  222. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
  223. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotis.ts +0 -34
  224. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
  225. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTool.ts +0 -64
  226. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTools.ts +0 -56
  227. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  228. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
  229. package/dist/scaffolds/notis-notes/packages/sdk/src/index.ts +0 -100
  230. package/dist/scaffolds/notis-notes/packages/sdk/src/provider.tsx +0 -43
  231. package/dist/scaffolds/notis-notes/packages/sdk/src/runtime.ts +0 -351
  232. package/dist/scaffolds/notis-notes/packages/sdk/src/styles.css +0 -186
  233. package/dist/scaffolds/notis-notes/packages/sdk/src/ui.ts +0 -15
  234. package/dist/scaffolds/notis-notes/packages/sdk/src/vite.ts +0 -56
  235. package/dist/scaffolds/notis-notes/packages/sdk/tsconfig.json +0 -15
  236. package/dist/scaffolds/notis-notes/postcss.config.mjs +0 -8
  237. package/dist/scaffolds/notis-notes/tailwind.config.ts +0 -58
  238. package/dist/scaffolds/notis-notes/tsconfig.json +0 -23
  239. package/dist/scaffolds/notis-notes/vite.config.ts +0 -10
  240. package/dist/scaffolds/notis-random/CHANGELOG.md +0 -15
  241. package/dist/scaffolds/notis-random/README.md +0 -33
  242. package/dist/scaffolds/notis-random/app/globals.css +0 -11
  243. package/dist/scaffolds/notis-random/app/history/page.tsx +0 -67
  244. package/dist/scaffolds/notis-random/app/layout.tsx +0 -7
  245. package/dist/scaffolds/notis-random/app/page.tsx +0 -289
  246. package/dist/scaffolds/notis-random/components/ui/button.tsx +0 -50
  247. package/dist/scaffolds/notis-random/components/ui/card.tsx +0 -16
  248. package/dist/scaffolds/notis-random/components/ui/input.tsx +0 -23
  249. package/dist/scaffolds/notis-random/components.json +0 -20
  250. package/dist/scaffolds/notis-random/index.html +0 -12
  251. package/dist/scaffolds/notis-random/lib/notis-tools.ts +0 -128
  252. package/dist/scaffolds/notis-random/lib/rng.ts +0 -202
  253. package/dist/scaffolds/notis-random/lib/roll-record.ts +0 -189
  254. package/dist/scaffolds/notis-random/lib/utils.ts +0 -25
  255. package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
  256. package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
  257. package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
  258. package/dist/scaffolds/notis-random/metadata/screenshot-4.png +0 -0
  259. package/dist/scaffolds/notis-random/metadata/screenshot-5.png +0 -0
  260. package/dist/scaffolds/notis-random/metadata/screenshot-fixtures.json +0 -753
  261. package/dist/scaffolds/notis-random/notis.config.ts +0 -86
  262. package/dist/scaffolds/notis-random/package-lock.json +0 -4513
  263. package/dist/scaffolds/notis-random/package.json +0 -36
  264. package/dist/scaffolds/notis-random/packages/sdk/package.json +0 -36
  265. package/dist/scaffolds/notis-random/packages/sdk/src/components/DocumentEditor.tsx +0 -93
  266. package/dist/scaffolds/notis-random/packages/sdk/src/components/Markdown.tsx +0 -60
  267. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
  268. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
  269. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
  270. package/dist/scaffolds/notis-random/packages/sdk/src/config.ts +0 -234
  271. package/dist/scaffolds/notis-random/packages/sdk/src/documents.ts +0 -250
  272. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useBackend.ts +0 -41
  273. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
  274. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  275. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
  276. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocument.ts +0 -78
  277. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocuments.ts +0 -121
  278. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useHandover.ts +0 -75
  279. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
  280. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotis.ts +0 -34
  281. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
  282. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTool.ts +0 -64
  283. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTools.ts +0 -56
  284. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  285. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
  286. package/dist/scaffolds/notis-random/packages/sdk/src/index.ts +0 -100
  287. package/dist/scaffolds/notis-random/packages/sdk/src/provider.tsx +0 -43
  288. package/dist/scaffolds/notis-random/packages/sdk/src/runtime.ts +0 -351
  289. package/dist/scaffolds/notis-random/packages/sdk/src/styles.css +0 -186
  290. package/dist/scaffolds/notis-random/packages/sdk/src/ui.ts +0 -15
  291. package/dist/scaffolds/notis-random/packages/sdk/src/vite.ts +0 -56
  292. package/dist/scaffolds/notis-random/packages/sdk/tsconfig.json +0 -15
  293. package/dist/scaffolds/notis-random/postcss.config.mjs +0 -6
  294. package/dist/scaffolds/notis-random/src/dev-main.tsx +0 -70
  295. package/dist/scaffolds/notis-random/src/mock-runtime.ts +0 -129
  296. package/dist/scaffolds/notis-random/tailwind.config.ts +0 -50
  297. package/dist/scaffolds/notis-random/tsconfig.json +0 -23
  298. package/dist/scaffolds/notis-random/vite.config.ts +0 -11
  299. package/dist/scaffolds.json +0 -49
@@ -12,9 +12,20 @@
12
12
  */
13
13
 
14
14
  import { createServer } from 'node:http';
15
- import { spawn } from 'node:child_process';
16
- import { existsSync, readFileSync, statSync, watch as fsWatch } from 'node:fs';
15
+ import { execFileSync, spawn } from 'node:child_process';
16
+ import {
17
+ appendFileSync,
18
+ existsSync,
19
+ mkdirSync,
20
+ readFileSync,
21
+ renameSync,
22
+ rmSync,
23
+ statSync,
24
+ watch as fsWatch,
25
+ } from 'node:fs';
26
+ import { freemem, loadavg, totalmem } from 'node:os';
17
27
  import { dirname, join, resolve } from 'node:path';
28
+ import { setTimeout as delay } from 'node:timers/promises';
18
29
  import { fileURLToPath } from 'node:url';
19
30
 
20
31
  import {
@@ -28,7 +39,11 @@ import {
28
39
  readLinkedState,
29
40
  writeLinkedState,
30
41
  } from './app-platform.js';
31
- import { linkAppDevSessionTarget } from './app-dev-sessions.js';
42
+ import {
43
+ linkAppDevSessionTarget,
44
+ readAppDevSessions,
45
+ } from './app-dev-sessions.js';
46
+ import { captureDesktopWatcherOwnership } from './app-dev-process-identity.js';
32
47
 
33
48
  const CONTENT_TYPES = {
34
49
  '.js': 'application/javascript; charset=utf-8',
@@ -41,6 +56,115 @@ const CLI_ROOT = resolve(RUNTIME_DIR, '../..');
41
56
  const REPO_ROOT = resolve(RUNTIME_DIR, '../../../..');
42
57
  const HARNESS_TEMPLATE_PATH = join(CLI_ROOT, 'template', '.harness', 'index.html.tmpl');
43
58
  const FALLBACK_REACT_VERSION = '19.0.0';
59
+ const BUILD_PROCESS_STOP_GRACE_MS = 1_000;
60
+ const DEV_DIAGNOSTIC_INTERVAL_MS = 30_000;
61
+ const DEV_DIAGNOSTIC_MAX_BYTES = 20 * 1024 * 1024;
62
+
63
+ function processGroupIsRunning(pid, signalProcess = process.kill) {
64
+ try {
65
+ signalProcess(-pid, 0);
66
+ return true;
67
+ } catch (error) {
68
+ return error?.code !== 'ESRCH';
69
+ }
70
+ }
71
+
72
+ function readProcessGroupRssBytes(groupPids) {
73
+ if (process.platform === 'win32' || groupPids.size === 0) return new Map();
74
+ try {
75
+ const output = execFileSync('ps', ['-axo', 'pgid=,rss='], {
76
+ encoding: 'utf8',
77
+ maxBuffer: 1024 * 1024,
78
+ stdio: ['ignore', 'pipe', 'ignore'],
79
+ });
80
+ const rssByGroup = new Map();
81
+ for (const line of output.split('\n')) {
82
+ const [rawGroupPid, rawRssKiB] = line.trim().split(/\s+/, 2);
83
+ const groupPid = Number.parseInt(rawGroupPid, 10);
84
+ if (!groupPids.has(groupPid)) continue;
85
+ const rssKiB = Number.parseInt(rawRssKiB, 10);
86
+ if (!Number.isFinite(rssKiB)) continue;
87
+ rssByGroup.set(groupPid, (rssByGroup.get(groupPid) || 0) + (rssKiB * 1024));
88
+ }
89
+ return rssByGroup;
90
+ } catch {
91
+ return new Map();
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Stop the npm wrapper and every Vite/esbuild descendant it launched.
97
+ *
98
+ * Killing only the npm PID leaves its watch process alive after a Desktop host
99
+ * restart. Each watcher therefore owns a separate POSIX process group. Windows
100
+ * uses taskkill's tree mode for the equivalent cleanup.
101
+ */
102
+ export async function terminateBuildProcessTree(child, {
103
+ platform = process.platform,
104
+ signalProcess = process.kill,
105
+ spawnProcess = spawn,
106
+ graceMs = BUILD_PROCESS_STOP_GRACE_MS,
107
+ } = {}) {
108
+ const pid = child?.pid;
109
+ if (!Number.isSafeInteger(pid) || pid <= 0) return;
110
+
111
+ if (platform === 'win32') {
112
+ await new Promise((resolvePromise) => {
113
+ let settled = false;
114
+ const finish = () => {
115
+ if (settled) return;
116
+ settled = true;
117
+ resolvePromise();
118
+ };
119
+ try {
120
+ const killer = spawnProcess('taskkill.exe', ['/pid', String(pid), '/t', '/f'], {
121
+ stdio: 'ignore',
122
+ windowsHide: true,
123
+ });
124
+ killer.once('error', () => {
125
+ try {
126
+ child.kill('SIGTERM');
127
+ } catch {
128
+ // The wrapper already exited.
129
+ }
130
+ finish();
131
+ });
132
+ killer.once('exit', finish);
133
+ setTimeout(finish, graceMs).unref?.();
134
+ } catch {
135
+ try {
136
+ child.kill('SIGTERM');
137
+ } catch {
138
+ // The wrapper already exited.
139
+ }
140
+ finish();
141
+ }
142
+ });
143
+ return;
144
+ }
145
+
146
+ try {
147
+ signalProcess(-pid, 'SIGTERM');
148
+ } catch (error) {
149
+ if (error?.code === 'ESRCH') return;
150
+ try {
151
+ child.kill('SIGTERM');
152
+ } catch {
153
+ return;
154
+ }
155
+ }
156
+
157
+ const deadline = Date.now() + graceMs;
158
+ while (processGroupIsRunning(pid, signalProcess) && Date.now() < deadline) {
159
+ await delay(25);
160
+ }
161
+ if (!processGroupIsRunning(pid, signalProcess)) return;
162
+ try {
163
+ signalProcess(-pid, 'SIGKILL');
164
+ } catch (error) {
165
+ if (error?.code !== 'ESRCH') throw error;
166
+ }
167
+ }
44
168
 
45
169
  function extFor(pathname) {
46
170
  const idx = pathname.lastIndexOf('.');
@@ -243,10 +367,11 @@ function buildHarnessDescriptor({ state, manifest, appConfig, route, scenario =
243
367
  icon: route.icon || null,
244
368
  parentSlug: route.parentSlug || null,
245
369
  default: Boolean(route.default),
370
+ resourceDeepLinks: route.resourceDeepLinks === true,
246
371
  collection: route.collection || null,
247
372
  },
248
373
  databases,
249
- context: { collectionItem: null, screenshotScenario: scenario },
374
+ context: { collectionItem: null, resourceId: null, screenshotScenario: scenario },
250
375
  tools,
251
376
  };
252
377
  }
@@ -297,7 +422,7 @@ function renderHarnessHtml({ state, manifest, appConfig, route, harnessOptions,
297
422
  /**
298
423
  * Start the dev server for one or more apps.
299
424
  *
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
425
+ * @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 }, diagnosticsFile?: string | null, desktopOwnerId?: string | null, desktopOwnerScope?: string | null, terminateBuildProcess?: typeof terminateBuildProcessTree, log?: (m: string) => void, logError?: (m: string) => void}} options
301
426
  */
302
427
  export async function startAppDevServer({
303
428
  apps,
@@ -305,6 +430,10 @@ export async function startAppDevServer({
305
430
  watch = true,
306
431
  sessionsFilePath,
307
432
  harness = {},
433
+ diagnosticsFile = process.env.NOTIS_DEV_DIAGNOSTICS_FILE || null,
434
+ desktopOwnerId = process.env.NOTIS_APPS_DEV_DESKTOP_OWNER_ID || null,
435
+ desktopOwnerScope = process.env.NOTIS_APPS_DEV_DESKTOP_OWNER_SCOPE || null,
436
+ terminateBuildProcess = terminateBuildProcessTree,
308
437
  log = (msg) => process.stdout.write(`${msg}\n`),
309
438
  logError = (msg) => process.stderr.write(`${msg}\n`),
310
439
  }) {
@@ -313,22 +442,97 @@ export async function startAppDevServer({
313
442
  }
314
443
 
315
444
  const appState = new Map();
316
- for (const app of apps) {
317
- appState.set(app.slug, {
445
+ let diagnosticsTimer = null;
446
+ let diagnosticWriteFailed = false;
447
+ let serverClosing = false;
448
+ // Sidebar reconciliation needs one stream for the shared host, not one
449
+ // long-lived HTTP/1.1 connection per discovered app. Keeping a stream per
450
+ // app exhausts Chromium's per-origin connection pool and can indefinitely
451
+ // queue the active view's bundle fetch during hot reload.
452
+ const hostSseClients = new Set();
453
+ const createAppState = (app) => {
454
+ let resolveBundleReady;
455
+ const bundleReadyPromise = new Promise((resolvePromise) => {
456
+ resolveBundleReady = resolvePromise;
457
+ });
458
+ return {
318
459
  slug: app.slug,
319
460
  projectDir: app.projectDir,
320
461
  appId: app.appId || null,
321
462
  targetAppId: app.targetAppId || null,
322
463
  userId: app.userId || null,
464
+ profileKey: app.profileKey || null,
465
+ sessionId: app.sessionId || null,
466
+ mountNonce: app.mountNonce || null,
323
467
  canonicalBundleDir: getBundleDir(app.projectDir),
324
468
  bundleDir: null,
325
469
  jsPath: null,
326
470
  sseClients: new Set(),
327
471
  watcher: null,
472
+ sourceWatchers: [],
473
+ prepareTimer: null,
474
+ reloadTimer: null,
328
475
  buildProcess: null,
476
+ watcherOwnership: null,
329
477
  lastMtimeMs: 0,
330
478
  watchPollTimer: null,
331
- });
479
+ bundleReady: false,
480
+ bundleReadyPromise,
481
+ resolveBundleReady,
482
+ buildProcessStopPromise: null,
483
+ };
484
+ };
485
+ for (const app of apps) {
486
+ appState.set(app.slug, createAppState(app));
487
+ }
488
+
489
+ function writeDevDiagnostic(event) {
490
+ if (!diagnosticsFile) return;
491
+ const memory = process.memoryUsage();
492
+ const watcherPids = new Set(
493
+ [...appState.values()]
494
+ .map((state) => state.buildProcess?.pid)
495
+ .filter((pid) => Number.isSafeInteger(pid) && pid > 0),
496
+ );
497
+ const watcherGroupRss = readProcessGroupRssBytes(watcherPids);
498
+ const record = {
499
+ at: new Date().toISOString(),
500
+ event,
501
+ host_pid: process.pid,
502
+ parent_pid: process.ppid,
503
+ rss_bytes: memory.rss,
504
+ heap_used_bytes: memory.heapUsed,
505
+ heap_total_bytes: memory.heapTotal,
506
+ external_bytes: memory.external,
507
+ system_free_bytes: freemem(),
508
+ system_total_bytes: totalmem(),
509
+ load_average_1m: loadavg()[0],
510
+ watcher_groups_rss_bytes: [...watcherGroupRss.values()].reduce((total, rss) => total + rss, 0),
511
+ apps: [...appState.values()].map((state) => ({
512
+ slug: state.slug,
513
+ project_dir: state.projectDir,
514
+ watcher_pid: state.buildProcess?.pid || null,
515
+ watcher_exit_code: state.buildProcess?.exitCode ?? null,
516
+ watcher_signal: state.buildProcess?.signalCode ?? null,
517
+ watcher_group_rss_bytes: watcherGroupRss.get(state.buildProcess?.pid) ?? null,
518
+ bundle_ready: state.bundleReady,
519
+ })),
520
+ };
521
+ try {
522
+ mkdirSync(dirname(diagnosticsFile), { recursive: true, mode: 0o700 });
523
+ if (existsSync(diagnosticsFile) && statSync(diagnosticsFile).size >= DEV_DIAGNOSTIC_MAX_BYTES) {
524
+ const previous = `${diagnosticsFile}.previous`;
525
+ rmSync(previous, { force: true });
526
+ renameSync(diagnosticsFile, previous);
527
+ }
528
+ appendFileSync(diagnosticsFile, `${JSON.stringify(record)}\n`, { mode: 0o600 });
529
+ diagnosticWriteFailed = false;
530
+ } catch (error) {
531
+ if (!diagnosticWriteFailed) {
532
+ diagnosticWriteFailed = true;
533
+ logError(`[notis apps dev] persistent diagnostics failed: ${error instanceof Error ? error.message : String(error)}`);
534
+ }
535
+ }
332
536
  }
333
537
 
334
538
  function broadcastReload(slug) {
@@ -342,6 +546,14 @@ export async function startAppDevServer({
342
546
  state.sseClients.delete(res);
343
547
  }
344
548
  }
549
+ const hostPayload = `event: reload\ndata: ${JSON.stringify({ slug, at: Date.now() })}\n\n`;
550
+ for (const res of hostSseClients) {
551
+ try {
552
+ res.write(hostPayload);
553
+ } catch {
554
+ hostSseClients.delete(res);
555
+ }
556
+ }
345
557
  }
346
558
 
347
559
  function matchAppRoute(pathname) {
@@ -374,6 +586,10 @@ export async function startAppDevServer({
374
586
  state.jsPath = join(bundleDir, 'app.js');
375
587
  try {
376
588
  state.lastMtimeMs = statSync(state.jsPath).mtimeMs;
589
+ if (!state.bundleReady) {
590
+ state.bundleReady = true;
591
+ state.resolveBundleReady();
592
+ }
377
593
  } catch {
378
594
  state.lastMtimeMs = 0;
379
595
  }
@@ -451,6 +667,9 @@ export async function startAppDevServer({
451
667
  try {
452
668
  const body = await readRequestJson(req);
453
669
  const appId = typeof body.app_id === 'string' ? body.app_id.trim() : '';
670
+ const version = Number.isInteger(body.version) && body.version > 0
671
+ ? body.version
672
+ : null;
454
673
  if (!appId) {
455
674
  res.writeHead(400, {
456
675
  ...headers,
@@ -459,20 +678,69 @@ export async function startAppDevServer({
459
678
  res.end(JSON.stringify({ error: 'app_id is required' }));
460
679
  return;
461
680
  }
681
+ if (version === null) {
682
+ res.writeHead(400, {
683
+ ...headers,
684
+ 'Content-Type': 'application/json; charset=utf-8',
685
+ });
686
+ res.end(JSON.stringify({ error: 'version must be a positive integer' }));
687
+ return;
688
+ }
689
+ const proofSession = readAppDevSessions(sessionsFilePath).sessions.find((session) => (
690
+ typeof body.session_id === 'string'
691
+ && typeof body.mount_nonce === 'string'
692
+ && session.sessionId === body.session_id
693
+ && session.mountNonce === body.mount_nonce
694
+ && session.devSlug === state.slug
695
+ && session.projectDir === state.projectDir
696
+ ));
697
+ if (!proofSession) {
698
+ res.writeHead(403, {
699
+ ...headers,
700
+ 'Content-Type': 'application/json; charset=utf-8',
701
+ });
702
+ res.end(JSON.stringify({ error: 'invalid app development session proof' }));
703
+ return;
704
+ }
705
+ const expectedAppId = proofSession.targetAppId || proofSession.appId;
706
+ if (!expectedAppId || appId !== expectedAppId) {
707
+ res.writeHead(409, {
708
+ ...headers,
709
+ 'Content-Type': 'application/json; charset=utf-8',
710
+ });
711
+ res.end(JSON.stringify({ error: 'app_id does not match this development session' }));
712
+ return;
713
+ }
462
714
 
463
- const currentState = readLinkedState(state.projectDir) || {};
715
+ const currentState = readLinkedState(
716
+ state.projectDir,
717
+ proofSession.profileKey || state.profileKey,
718
+ ) || {};
464
719
  const now = new Date().toISOString();
465
- writeLinkedState(state.projectDir, {
720
+ const promotedInPlace = proofSession.appId === appId;
721
+ const nextState = {
466
722
  ...currentState,
467
723
  app_id: appId,
468
- dev_app_id: state.appId || currentState.dev_app_id,
469
724
  linked_at: currentState.linked_at || now,
470
- dev_linked_at: currentState.dev_linked_at || now,
725
+ version,
726
+ deployed_at: now,
471
727
  updated_at: now,
472
- });
473
- state.targetAppId = appId;
728
+ };
729
+ if (promotedInPlace) {
730
+ delete nextState.dev_app_id;
731
+ delete nextState.dev_linked_at;
732
+ } else {
733
+ nextState.dev_app_id = proofSession.appId || currentState.dev_app_id;
734
+ nextState.dev_linked_at = currentState.dev_linked_at || now;
735
+ }
736
+ writeLinkedState(
737
+ state.projectDir,
738
+ nextState,
739
+ proofSession.profileKey || state.profileKey,
740
+ );
474
741
  linkAppDevSessionTarget({
475
- appId: state.appId,
742
+ sessionId: proofSession.sessionId,
743
+ appId: proofSession.appId,
476
744
  devSlug: state.slug,
477
745
  targetAppId: appId,
478
746
  lastHeartbeatAt: now,
@@ -480,8 +748,9 @@ export async function startAppDevServer({
480
748
  const response = {
481
749
  ok: true,
482
750
  app_id: appId,
483
- dev_app_id: state.appId || null,
751
+ dev_app_id: promotedInPlace ? null : proofSession.appId || null,
484
752
  target_app_id: appId,
753
+ version,
485
754
  };
486
755
  res.writeHead(200, {
487
756
  ...headers,
@@ -525,6 +794,18 @@ export async function startAppDevServer({
525
794
  return;
526
795
  }
527
796
 
797
+ if (url.pathname === '/events') {
798
+ res.writeHead(200, {
799
+ ...headers,
800
+ 'Content-Type': 'text/event-stream; charset=utf-8',
801
+ Connection: 'keep-alive',
802
+ });
803
+ res.write(': connected\n\n');
804
+ hostSseClients.add(res);
805
+ req.on('close', () => hostSseClients.delete(res));
806
+ return;
807
+ }
808
+
528
809
  const routed = matchAppRoute(url.pathname);
529
810
  if (!routed) {
530
811
  res.writeHead(404, headers);
@@ -644,15 +925,23 @@ export async function startAppDevServer({
644
925
  try {
645
926
  state.watcher = fsWatch(state.bundleDir, { persistent: false }, (_event, filename) => {
646
927
  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
- }
928
+ // Vite may emit the filesystem notification while its output file is
929
+ // still being replaced. Debounce the notification so consumers never
930
+ // fetch the previous or partially-written bundle under the new build
931
+ // revision.
932
+ if (state.reloadTimer) clearTimeout(state.reloadTimer);
933
+ state.reloadTimer = setTimeout(() => {
934
+ state.reloadTimer = null;
935
+ try {
936
+ const stat = statSync(state.jsPath);
937
+ if (stat.mtimeMs === state.lastMtimeMs) return;
938
+ state.lastMtimeMs = stat.mtimeMs;
939
+ log(`[notis apps dev] ${state.slug}: bundle updated — reloading portal`);
940
+ broadcastReload(state.slug);
941
+ } catch {
942
+ // Bundle file may be missing mid-write; the next change retries.
943
+ }
944
+ }, 100);
656
945
  });
657
946
  } catch (error) {
658
947
  logError(`[notis apps dev] ${state.slug}: watch failed: ${error.message}`);
@@ -669,21 +958,88 @@ export async function startAppDevServer({
669
958
  state.watchPollTimer = setTimeout(() => pollForBundleAndWatch(state), 300);
670
959
  }
671
960
 
961
+ function watchManifestInputs(state) {
962
+ const schedulePrepare = () => {
963
+ if (state.prepareTimer) clearTimeout(state.prepareTimer);
964
+ state.prepareTimer = setTimeout(() => {
965
+ state.prepareTimer = null;
966
+ void prepareArtifactBuild(state.projectDir).then(() => {
967
+ // Name, icon, and route metadata can change without changing the
968
+ // compiled JS bytes. Notify consumers immediately; a generated-entry
969
+ // change will produce the normal second reload after Vite rebuilds.
970
+ broadcastReload(state.slug);
971
+ }).catch((error) => {
972
+ const message = error instanceof Error ? error.message : String(error);
973
+ logError(`[notis apps dev] ${state.slug}: ${message}`);
974
+ });
975
+ }, 75);
976
+ };
977
+ try {
978
+ state.sourceWatchers.push(fsWatch(
979
+ state.projectDir,
980
+ { persistent: false },
981
+ (_event, filename) => {
982
+ if (filename && /^notis\.config\.(?:ts|js|mjs)$/.test(String(filename))) {
983
+ schedulePrepare();
984
+ }
985
+ },
986
+ ));
987
+ } catch (error) {
988
+ logError(`[notis apps dev] ${state.slug}: config watch failed: ${error.message}`);
989
+ }
990
+ const appDir = join(state.projectDir, 'app');
991
+ if (existsSync(appDir)) {
992
+ try {
993
+ state.sourceWatchers.push(fsWatch(
994
+ appDir,
995
+ { persistent: false, recursive: process.platform === 'darwin' || process.platform === 'win32' },
996
+ schedulePrepare,
997
+ ));
998
+ } catch (error) {
999
+ logError(`[notis apps dev] ${state.slug}: route watch failed: ${error.message}`);
1000
+ }
1001
+ }
1002
+ }
1003
+
672
1004
  for (const state of appState.values()) {
673
1005
  if (watch) {
674
1006
  await prepareArtifactBuild(state.projectDir);
1007
+ watchManifestInputs(state);
675
1008
  pollForBundleAndWatch(state);
676
1009
 
677
- state.buildProcess = spawn('npm', ['run', 'build', '--', '--watch'], {
1010
+ const buildProcess = spawn('npm', ['run', 'build', '--', '--watch'], {
678
1011
  cwd: state.projectDir,
1012
+ detached: process.platform !== 'win32',
679
1013
  stdio: 'inherit',
680
1014
  env: { ...process.env, NOTIS_DEV: '1' },
681
1015
  });
1016
+ state.buildProcess = buildProcess;
1017
+ for (let attempt = 0; attempt < 5 && !state.watcherOwnership; attempt += 1) {
1018
+ state.watcherOwnership = captureDesktopWatcherOwnership({
1019
+ pid: buildProcess.pid,
1020
+ projectDir: state.projectDir,
1021
+ desktopOwnerId,
1022
+ desktopOwnerScope,
1023
+ });
1024
+ if (!state.watcherOwnership && attempt < 4) await delay(10);
1025
+ }
682
1026
 
683
- state.buildProcess.on('exit', (code) => {
1027
+ buildProcess.on('exit', (code) => {
684
1028
  if (code !== 0 && code !== null) {
685
1029
  logError(`[notis apps dev] ${state.slug}: vite build --watch exited with code ${code}`);
686
1030
  }
1031
+ if (!serverClosing) {
1032
+ if (state.buildProcess === buildProcess) state.buildProcess = null;
1033
+ const stopPromise = terminateBuildProcess(buildProcess).catch((error) => {
1034
+ logError(`[notis apps dev] ${state.slug}: watcher cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
1035
+ });
1036
+ state.buildProcessStopPromise = stopPromise;
1037
+ void stopPromise.finally(() => {
1038
+ if (state.buildProcessStopPromise === stopPromise) {
1039
+ state.buildProcessStopPromise = null;
1040
+ }
1041
+ });
1042
+ }
687
1043
  });
688
1044
  } else {
689
1045
  updateBundleDir(state, resolveBundleDir(state));
@@ -692,10 +1048,57 @@ export async function startAppDevServer({
692
1048
  log(`[notis apps dev] ${state.slug}: serving bundle at http://127.0.0.1:${port}/a/${state.slug}/bundle/app.js`);
693
1049
  }
694
1050
 
1051
+ writeDevDiagnostic('started');
1052
+ if (diagnosticsFile) {
1053
+ diagnosticsTimer = setInterval(() => writeDevDiagnostic('sample'), DEV_DIAGNOSTIC_INTERVAL_MS);
1054
+ diagnosticsTimer.unref?.();
1055
+ }
1056
+
695
1057
  return {
696
1058
  port,
1059
+ updateApp(slug, updates = {}) {
1060
+ const state = appState.get(slug);
1061
+ if (!state) throw new Error(`unknown app: ${slug}`);
1062
+ for (const key of ['appId', 'targetAppId', 'userId', 'profileKey', 'sessionId', 'mountNonce']) {
1063
+ if (Object.prototype.hasOwnProperty.call(updates, key)) {
1064
+ state[key] = updates[key] || null;
1065
+ }
1066
+ }
1067
+ },
1068
+ isBundleReady(slug) {
1069
+ const state = appState.get(slug);
1070
+ if (!state) throw new Error(`unknown app: ${slug}`);
1071
+ return state.bundleReady;
1072
+ },
1073
+ waitForBundle(slug) {
1074
+ const state = appState.get(slug);
1075
+ if (!state) return Promise.reject(new Error(`unknown app: ${slug}`));
1076
+ return state.bundleReadyPromise;
1077
+ },
1078
+ getWatcherOwnership(slug) {
1079
+ const state = appState.get(slug);
1080
+ if (!state) throw new Error(`unknown app: ${slug}`);
1081
+ return state.watcherOwnership ? { ...state.watcherOwnership } : null;
1082
+ },
697
1083
  async close() {
1084
+ serverClosing = true;
1085
+ if (diagnosticsTimer) {
1086
+ clearInterval(diagnosticsTimer);
1087
+ diagnosticsTimer = null;
1088
+ }
1089
+ writeDevDiagnostic('stopping');
1090
+ const buildProcessStops = [];
698
1091
  for (const state of appState.values()) {
1092
+ if (state.prepareTimer) clearTimeout(state.prepareTimer);
1093
+ if (state.reloadTimer) clearTimeout(state.reloadTimer);
1094
+ for (const watcher of state.sourceWatchers) {
1095
+ try {
1096
+ watcher.close();
1097
+ } catch {
1098
+ // ignore
1099
+ }
1100
+ }
1101
+ state.sourceWatchers = [];
699
1102
  if (state.watchPollTimer) clearTimeout(state.watchPollTimer);
700
1103
  if (state.watcher) {
701
1104
  try {
@@ -712,11 +1115,26 @@ export async function startAppDevServer({
712
1115
  }
713
1116
  }
714
1117
  state.sseClients.clear();
715
- if (state.buildProcess && state.buildProcess.exitCode === null) {
716
- state.buildProcess.kill('SIGTERM');
1118
+ if (state.buildProcessStopPromise) {
1119
+ buildProcessStops.push(state.buildProcessStopPromise);
1120
+ }
1121
+ if (state.buildProcess) {
1122
+ const buildProcess = state.buildProcess;
1123
+ state.buildProcess = null;
1124
+ buildProcessStops.push(terminateBuildProcess(buildProcess));
1125
+ }
1126
+ }
1127
+ await Promise.allSettled(buildProcessStops);
1128
+ for (const res of hostSseClients) {
1129
+ try {
1130
+ res.end();
1131
+ } catch {
1132
+ // ignore
717
1133
  }
718
1134
  }
1135
+ hostSseClients.clear();
719
1136
  await new Promise((resolvePromise) => server.close(() => resolvePromise()));
1137
+ writeDevDiagnostic('stopped');
720
1138
  },
721
1139
  };
722
1140
  }