@notis_ai/cli 0.2.17 → 0.2.19

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 (395) hide show
  1. package/README.md +542 -146
  2. package/bin/check-runtime.js +15 -0
  3. package/bin/notis.js +2 -0
  4. package/config/notis_app_design_rules.json +320 -0
  5. package/config/portal-route-roots.json +45 -0
  6. package/dist/agent-hooks/notis-agent-hook.mjs +42323 -18258
  7. package/dist/base-skills/notis-cli/.notis-base-skill.json +1 -0
  8. package/dist/base-skills/notis-cli/SKILL.md +51 -280
  9. package/dist/base-skills/notis-cli/references/app-delivery.md +17 -0
  10. package/dist/base-skills/notis-cli/references/intelligence.md +94 -0
  11. package/dist/base-skills/notis-cli/references/native-databases.md +21 -0
  12. package/dist/base-skills/notis-cli/references/tool-examples.md +56 -0
  13. package/dist/base-skills/notis-cli/references/troubleshooting.md +67 -0
  14. package/dist/base-skills/notis-query/.notis-base-skill.json +1 -0
  15. package/dist/base-skills/notis-query/SKILL.md +35 -699
  16. package/dist/base-skills/notis-query/references/database-discovery.md +59 -0
  17. package/dist/base-skills/notis-query/references/documents.md +74 -0
  18. package/dist/base-skills/notis-query/references/query.md +76 -0
  19. package/dist/base-skills/notis-spaces/.notis-base-skill.json +1 -0
  20. package/dist/base-skills/notis-spaces/SKILL.md +218 -0
  21. package/dist/base-skills/notis-spaces/references/architecture.md +116 -0
  22. package/dist/base-skills/notis-spaces/references/context.md +81 -0
  23. package/dist/base-skills/notis-spaces/references/design.md +141 -0
  24. package/dist/base-skills/notis-spaces/references/fixtures.md +29 -0
  25. package/dist/base-skills/notis-spaces/references/memory.md +58 -0
  26. package/dist/base-skills/notis-spaces/references/reading.md +73 -0
  27. package/dist/base-skills/notis-spaces/references/release.md +120 -0
  28. package/dist/base-skills/notis-spaces/references/reports.md +77 -0
  29. package/dist/base-skills/notis-spaces/references/sdk.md +273 -0
  30. package/dist/base-skills/notis-spaces/references/store.md +118 -0
  31. package/dist/base-skills/notis-spaces/references/troubleshooting.md +28 -0
  32. package/dist/base-skills/notis-spaces/references/views.md +229 -0
  33. package/{template/packages → dist}/sdk/package.json +9 -10
  34. package/dist/sdk/src/agentContext.ts +36 -0
  35. package/dist/sdk/src/bundleModule.ts +175 -0
  36. package/dist/sdk/src/components/Dialog.tsx +103 -0
  37. package/dist/sdk/src/components/DocumentEditor.tsx +113 -0
  38. package/dist/sdk/src/components/DocumentPage.tsx +184 -0
  39. package/{template/packages → dist}/sdk/src/components/Markdown.tsx +9 -3
  40. package/{template/packages → dist}/sdk/src/components/MarkdownEditor.tsx +26 -5
  41. package/{template/packages → dist}/sdk/src/components/MultiSelectActionBar.tsx +95 -12
  42. package/{template/packages → dist}/sdk/src/components/MultiSelectCheckbox.tsx +4 -2
  43. package/dist/sdk/src/components/NotisCommentBoundary.tsx +172 -0
  44. package/{template/packages → dist}/sdk/src/components/NotisSelectionBoundary.tsx +9 -5
  45. package/dist/sdk/src/components/RecordComponents.tsx +111 -0
  46. package/dist/sdk/src/components/Skeleton.tsx +38 -0
  47. package/dist/sdk/src/config.ts +31 -0
  48. package/{template/packages → dist}/sdk/src/documents.ts +21 -6
  49. package/dist/sdk/src/hooks/useAction.ts +28 -0
  50. package/dist/sdk/src/hooks/useAgentContext.ts +23 -0
  51. package/{template/packages → dist}/sdk/src/hooks/useBackend.ts +3 -2
  52. package/dist/sdk/src/hooks/useCloudComputer.ts +94 -0
  53. package/dist/sdk/src/hooks/useCloudComputerShell.ts +114 -0
  54. package/{template/packages → dist}/sdk/src/hooks/useCollectionInteractions.ts +157 -147
  55. package/dist/sdk/src/hooks/useDatabaseSchema.ts +50 -0
  56. package/{template/packages → dist}/sdk/src/hooks/useDatabaseSubscription.ts +2 -2
  57. package/dist/sdk/src/hooks/useDocument.ts +51 -0
  58. package/{template/packages → dist}/sdk/src/hooks/useDocuments.ts +25 -59
  59. package/{template/packages → dist}/sdk/src/hooks/useHandover.ts +7 -7
  60. package/dist/sdk/src/hooks/useLongPressSelection.ts +79 -0
  61. package/dist/sdk/src/hooks/useMarqueeSelection.ts +194 -0
  62. package/{template/packages → dist}/sdk/src/hooks/useMultiSelect.ts +2 -8
  63. package/dist/sdk/src/hooks/useNativeDocumentBody.ts +50 -0
  64. package/dist/sdk/src/hooks/useNativeDocuments.ts +143 -0
  65. package/{template/packages → dist}/sdk/src/hooks/useNotis.ts +6 -1
  66. package/dist/sdk/src/hooks/useNotisNavigation.ts +70 -0
  67. package/dist/sdk/src/hooks/useQuery.ts +81 -0
  68. package/dist/sdk/src/hooks/useToolQuery.ts +12 -0
  69. package/dist/sdk/src/hooks/useTopBarSearch.ts +102 -0
  70. package/dist/sdk/src/hooks/useTopBarTitleLeaf.ts +75 -0
  71. package/{template/packages → dist}/sdk/src/hooks/useUpsertDocument.ts +1 -1
  72. package/dist/sdk/src/hooks/useViewParams.ts +195 -0
  73. package/dist/sdk/src/hooks/useViewerReads.ts +129 -0
  74. package/{template/packages → dist}/sdk/src/index.ts +64 -1
  75. package/{template/packages → dist}/sdk/src/interactions/actions.ts +14 -1
  76. package/{template/packages → dist}/sdk/src/interactions/shortcuts.tsx +129 -23
  77. package/dist/sdk/src/interactions/visibility.ts +13 -0
  78. package/{template/packages → dist}/sdk/src/interactions.ts +5 -1
  79. package/dist/sdk/src/nativeData.ts +39 -0
  80. package/{template/packages → dist}/sdk/src/provider.tsx +9 -4
  81. package/dist/sdk/src/queryCache.ts +292 -0
  82. package/dist/sdk/src/queryErrors.ts +51 -0
  83. package/dist/sdk/src/runtime.ts +675 -0
  84. package/dist/sdk/src/runtimeModules.ts +33 -0
  85. package/dist/sdk/src/space.ts +442 -0
  86. package/dist/sdk/src/standardViewParams.ts +36 -0
  87. package/{template/packages → dist}/sdk/src/styles.css +81 -16
  88. package/dist/sdk/src/tailwind.ts +66 -0
  89. package/dist/sdk/src/ui/charts/RenderChartData.tsx +25 -0
  90. package/dist/sdk/src/ui/engine/core.ts +392 -0
  91. package/dist/sdk/src/ui/engine/dom.ts +183 -0
  92. package/dist/sdk/src/ui/engine/hooks.ts +195 -0
  93. package/dist/sdk/src/ui/engine/instance.ts +63 -0
  94. package/dist/sdk/src/ui/engine/keycapContext.ts +2 -0
  95. package/dist/sdk/src/ui/engine/registry.ts +262 -0
  96. package/dist/sdk/src/ui/engine/types.ts +62 -0
  97. package/dist/sdk/src/ui/icons.tsx +91 -0
  98. package/dist/sdk/src/ui/util.ts +83 -0
  99. package/dist/sdk/src/viewPeriod.ts +26 -0
  100. package/dist/sdk/src/vite.ts +92 -0
  101. package/dist/sdk/src/words/actions.ts +295 -0
  102. package/dist/sdk/src/words/clashes.ts +158 -0
  103. package/dist/sdk/src/words/format.ts +136 -0
  104. package/dist/sdk/src/words/index.ts +13 -0
  105. package/dist/sdk/src/words/keymap.ts +72 -0
  106. package/dist/sdk/src/words/keys.ts +297 -0
  107. package/dist/sdk/src/words/shell.ts +46 -0
  108. package/dist/sdk/src/words/types.ts +65 -0
  109. package/dist/skill-sync/index.js +832 -151
  110. package/dist/skill-sync/index.js.map +4 -4
  111. package/dist/skill-sync-worker.mjs +3941 -0
  112. package/dist/space-harness/host.js +1460 -0
  113. package/dist/space-sdk/dist/chunk-ZSN57ATH.js +5 -0
  114. package/dist/space-sdk/dist/index.js +15 -0
  115. package/dist/space-sdk/dist/ui.js +18 -0
  116. package/dist/space-sdk/package.json +33 -0
  117. package/dist/space-sdk/src/agentContext.d.ts +36 -0
  118. package/dist/space-sdk/src/components/Dialog.d.ts +20 -0
  119. package/dist/space-sdk/src/components/DocumentEditor.d.ts +46 -0
  120. package/dist/space-sdk/src/components/DocumentPage.d.ts +53 -0
  121. package/dist/space-sdk/src/components/Markdown.d.ts +18 -0
  122. package/dist/space-sdk/src/components/MarkdownEditor.d.ts +11 -0
  123. package/dist/space-sdk/src/components/MultiSelectActionBar.d.ts +31 -0
  124. package/dist/space-sdk/src/components/MultiSelectCheckbox.d.ts +14 -0
  125. package/dist/space-sdk/src/components/MultiSelectDragOverlay.d.ts +8 -0
  126. package/dist/space-sdk/src/components/NotisCommentBoundary.d.ts +36 -0
  127. package/dist/space-sdk/src/components/NotisSelectionBoundary.d.ts +16 -0
  128. package/dist/space-sdk/src/components/RecordComponents.d.ts +64 -0
  129. package/dist/space-sdk/src/components/ShortcutHints.d.ts +12 -0
  130. package/dist/space-sdk/src/components/Skeleton.d.ts +23 -0
  131. package/dist/space-sdk/src/config.ts +31 -0
  132. package/dist/space-sdk/src/documents.d.ts +59 -0
  133. package/dist/space-sdk/src/hooks/useAction.d.ts +8 -0
  134. package/dist/space-sdk/src/hooks/useActiveResource.d.ts +3 -0
  135. package/dist/space-sdk/src/hooks/useAgentContext.d.ts +7 -0
  136. package/dist/space-sdk/src/hooks/useBackend.d.ts +23 -0
  137. package/dist/space-sdk/src/hooks/useCloudComputer.d.ts +45 -0
  138. package/dist/space-sdk/src/hooks/useCloudComputerShell.d.ts +49 -0
  139. package/dist/space-sdk/src/hooks/useCollectionInteractions.d.ts +118 -0
  140. package/dist/space-sdk/src/hooks/useDatabaseSchema.d.ts +14 -0
  141. package/dist/space-sdk/src/hooks/useDatabaseSubscription.d.ts +26 -0
  142. package/dist/space-sdk/src/hooks/useDocument.d.ts +22 -0
  143. package/dist/space-sdk/src/hooks/useDocuments.d.ts +31 -0
  144. package/dist/space-sdk/src/hooks/useHandover.d.ts +43 -0
  145. package/dist/space-sdk/src/hooks/useLongPressSelection.d.ts +9 -0
  146. package/dist/space-sdk/src/hooks/useMarqueeSelection.d.ts +57 -0
  147. package/dist/space-sdk/src/hooks/useMultiSelect.d.ts +51 -0
  148. package/dist/space-sdk/src/hooks/useNativeDocumentBody.d.ts +24 -0
  149. package/dist/space-sdk/src/hooks/useNativeDocuments.d.ts +55 -0
  150. package/dist/space-sdk/src/hooks/useNotis.d.ts +25 -0
  151. package/dist/space-sdk/src/hooks/useNotisNavigation.d.ts +33 -0
  152. package/dist/space-sdk/src/hooks/useQuery.d.ts +22 -0
  153. package/dist/space-sdk/src/hooks/useTool.d.ts +20 -0
  154. package/dist/space-sdk/src/hooks/useToolQuery.d.ts +3 -0
  155. package/dist/space-sdk/src/hooks/useTools.d.ts +15 -0
  156. package/dist/space-sdk/src/hooks/useTopBarSearch.d.ts +48 -0
  157. package/dist/space-sdk/src/hooks/useTopBarTitleLeaf.d.ts +24 -0
  158. package/dist/space-sdk/src/hooks/useUpsertDocument.d.ts +27 -0
  159. package/dist/space-sdk/src/hooks/useViewParams.d.ts +76 -0
  160. package/dist/space-sdk/src/hooks/useViewerReads.d.ts +38 -0
  161. package/dist/space-sdk/src/index.d.ts +98 -0
  162. package/dist/space-sdk/src/interactions/actions.d.ts +33 -0
  163. package/dist/space-sdk/src/interactions/shortcuts.d.ts +42 -0
  164. package/dist/space-sdk/src/interactions/visibility.d.ts +2 -0
  165. package/dist/space-sdk/src/nativeData.d.ts +66 -0
  166. package/dist/space-sdk/src/provider.d.ts +17 -0
  167. package/dist/space-sdk/src/queryCache.d.ts +45 -0
  168. package/dist/space-sdk/src/queryErrors.ts +51 -0
  169. package/dist/space-sdk/src/runtime.d.ts +615 -0
  170. package/dist/space-sdk/src/runtimeModules.ts +33 -0
  171. package/dist/space-sdk/src/space.ts +442 -0
  172. package/dist/space-sdk/src/standardViewParams.d.ts +15 -0
  173. package/dist/space-sdk/src/tailwind-candidates.d.ts +3418 -0
  174. package/dist/space-sdk/src/tailwind.ts +66 -0
  175. package/dist/space-sdk/src/ui/actions/ActionToast.d.ts +15 -0
  176. package/dist/space-sdk/src/ui/actions/actionWords.d.ts +16 -0
  177. package/dist/space-sdk/src/ui/actions/index.d.ts +7 -0
  178. package/dist/space-sdk/src/ui/actions/useActions.d.ts +69 -0
  179. package/dist/space-sdk/src/ui/actions/useToast.d.ts +41 -0
  180. package/dist/space-sdk/src/ui/blocks/Tabs.d.ts +139 -0
  181. package/dist/space-sdk/src/ui/blocks/blocks.d.ts +217 -0
  182. package/dist/space-sdk/src/ui/blocks/index.d.ts +4 -0
  183. package/dist/space-sdk/src/ui/board/BoardView.d.ts +36 -0
  184. package/dist/space-sdk/src/ui/board/boardModel.d.ts +46 -0
  185. package/dist/space-sdk/src/ui/board/boardWords.d.ts +9 -0
  186. package/dist/space-sdk/src/ui/board/index.d.ts +5 -0
  187. package/dist/space-sdk/src/ui/calendar/CalendarView.d.ts +49 -0
  188. package/dist/space-sdk/src/ui/calendar/DayPicker.d.ts +23 -0
  189. package/dist/space-sdk/src/ui/calendar/calendarMath.d.ts +81 -0
  190. package/dist/space-sdk/src/ui/charts/Chart.d.ts +203 -0
  191. package/dist/space-sdk/src/ui/charts/ChartTable.d.ts +37 -0
  192. package/dist/space-sdk/src/ui/charts/RenderChartData.d.ts +16 -0
  193. package/dist/space-sdk/src/ui/charts/StatRow.d.ts +59 -0
  194. package/dist/space-sdk/src/ui/charts/chartData.d.ts +84 -0
  195. package/dist/space-sdk/src/ui/comments/CommentPane.d.ts +58 -0
  196. package/dist/space-sdk/src/ui/comments/commentModel.d.ts +157 -0
  197. package/dist/space-sdk/src/ui/comments/commentWords.d.ts +60 -0
  198. package/dist/space-sdk/src/ui/comments/index.d.ts +4 -0
  199. package/dist/space-sdk/src/ui/comments/useComments.d.ts +146 -0
  200. package/dist/space-sdk/src/ui/engine/HelpSheet.d.ts +12 -0
  201. package/dist/space-sdk/src/ui/engine/KeyboardEngine.d.ts +27 -0
  202. package/dist/space-sdk/src/ui/engine/core.d.ts +73 -0
  203. package/dist/space-sdk/src/ui/engine/hooks.d.ts +64 -0
  204. package/dist/space-sdk/src/ui/engine/index.d.ts +14 -0
  205. package/dist/space-sdk/src/ui/engine/instance.d.ts +21 -0
  206. package/dist/space-sdk/src/ui/engine/keycapContext.d.ts +2 -0
  207. package/dist/space-sdk/src/ui/engine/registry.d.ts +77 -0
  208. package/dist/space-sdk/src/ui/engine/step.d.ts +11 -0
  209. package/dist/space-sdk/src/ui/engine/types.d.ts +54 -0
  210. package/dist/space-sdk/src/ui/fields/fieldWords.d.ts +56 -0
  211. package/dist/space-sdk/src/ui/fields/fields.d.ts +162 -0
  212. package/dist/space-sdk/src/ui/fields/index.d.ts +5 -0
  213. package/dist/space-sdk/src/ui/fields/useFieldDraft.d.ts +22 -0
  214. package/dist/space-sdk/src/ui/gallery/AttachmentGallery.d.ts +53 -0
  215. package/dist/space-sdk/src/ui/gallery/ImageViewer.d.ts +27 -0
  216. package/dist/space-sdk/src/ui/gallery/galleryWords.d.ts +95 -0
  217. package/dist/space-sdk/src/ui/gallery/index.d.ts +5 -0
  218. package/dist/space-sdk/src/ui/icons.d.ts +13 -0
  219. package/dist/space-sdk/src/ui/index.d.ts +54 -0
  220. package/dist/space-sdk/src/ui/keycap/Keycap.d.ts +22 -0
  221. package/dist/space-sdk/src/ui/keycap/index.d.ts +2 -0
  222. package/dist/space-sdk/src/ui/layout/Panels.d.ts +50 -0
  223. package/dist/space-sdk/src/ui/layout/chatPanelEdge.d.ts +77 -0
  224. package/dist/space-sdk/src/ui/layout/index.d.ts +5 -0
  225. package/dist/space-sdk/src/ui/layout/panelMath.d.ts +91 -0
  226. package/dist/space-sdk/src/ui/list/ListReader.d.ts +380 -0
  227. package/dist/space-sdk/src/ui/list/Row.d.ts +145 -0
  228. package/dist/space-sdk/src/ui/list/SelectionBar.d.ts +65 -0
  229. package/dist/space-sdk/src/ui/list/index.d.ts +14 -0
  230. package/dist/space-sdk/src/ui/list/rowState.d.ts +22 -0
  231. package/dist/space-sdk/src/ui/list/useListKeys.d.ts +89 -0
  232. package/dist/space-sdk/src/ui/list/useReorder.d.ts +86 -0
  233. package/dist/space-sdk/src/ui/list/useSelection.d.ts +90 -0
  234. package/dist/space-sdk/src/ui/open/OpenAsMenu.d.ts +15 -0
  235. package/dist/space-sdk/src/ui/open/index.d.ts +6 -0
  236. package/dist/space-sdk/src/ui/open/openModeStore.d.ts +39 -0
  237. package/dist/space-sdk/src/ui/open/useOpenMode.d.ts +17 -0
  238. package/dist/space-sdk/src/ui/overlay/Menu.d.ts +88 -0
  239. package/dist/space-sdk/src/ui/overlay/Popover.d.ts +28 -0
  240. package/dist/space-sdk/src/ui/overlay/Tooltip.d.ts +19 -0
  241. package/dist/space-sdk/src/ui/overlay/index.d.ts +9 -0
  242. package/dist/space-sdk/src/ui/overlay/layerRoot.d.ts +24 -0
  243. package/dist/space-sdk/src/ui/overlay/position.d.ts +31 -0
  244. package/dist/space-sdk/src/ui/period/PeriodPicker.d.ts +30 -0
  245. package/dist/space-sdk/src/ui/period/dateFormat.d.ts +50 -0
  246. package/dist/space-sdk/src/ui/period/dateWords.d.ts +88 -0
  247. package/dist/space-sdk/src/ui/period/periods.d.ts +70 -0
  248. package/dist/space-sdk/src/ui/period/usePeriod.d.ts +23 -0
  249. package/dist/space-sdk/src/ui/present/index.d.ts +3 -0
  250. package/dist/space-sdk/src/ui/present/presentWords.d.ts +27 -0
  251. package/dist/space-sdk/src/ui/present/screens.d.ts +36 -0
  252. package/dist/space-sdk/src/ui/present/useAudienceScreen.d.ts +38 -0
  253. package/dist/space-sdk/src/ui/styles.d.ts +21 -0
  254. package/dist/space-sdk/src/ui/timeline/TimelineView.d.ts +44 -0
  255. package/dist/space-sdk/src/ui/timeline/timelineMath.d.ts +123 -0
  256. package/dist/space-sdk/src/viewPeriod.d.ts +8 -0
  257. package/dist/space-sdk/src/vite.ts +92 -0
  258. package/dist/space-sdk/src/words/actions.ts +295 -0
  259. package/dist/space-sdk/src/words/clashes.ts +158 -0
  260. package/dist/space-sdk/src/words/format.ts +136 -0
  261. package/dist/space-sdk/src/words/index.ts +13 -0
  262. package/dist/space-sdk/src/words/keymap.ts +72 -0
  263. package/dist/space-sdk/src/words/keys.ts +297 -0
  264. package/dist/space-sdk/src/words/shell.ts +46 -0
  265. package/dist/space-sdk/src/words/types.ts +65 -0
  266. package/dist/view-renderer/dist/host.css +1 -0
  267. package/dist/view-renderer/dist/host.js +2374 -0
  268. package/dist/view-renderer/dist/theme.css +73 -0
  269. package/dist/view-renderer/src/dom.js +212 -0
  270. package/dist/view-renderer/src/errors.js +8 -0
  271. package/dist/view-renderer/src/host.tsx +250 -0
  272. package/dist/view-renderer/src/index.js +4 -0
  273. package/dist/view-renderer/src/png.js +213 -0
  274. package/dist/view-renderer/src/pool.js +110 -0
  275. package/dist/view-renderer/src/remote-broker.js +59 -0
  276. package/dist/view-renderer/src/renderer.js +271 -0
  277. package/package.json +21 -11
  278. package/skills/notis-cli/AGENT_INSTRUCTIONS.md +63 -12
  279. package/skills/notis-onboarding/BRIEF.md +14 -9
  280. package/skills/notis-query/cli.md +1 -1
  281. package/skills/notis-spaces/cli.md +540 -0
  282. package/src/cli.js +8 -8
  283. package/src/command-specs/agents.js +121 -8
  284. package/src/command-specs/auth.js +3 -3
  285. package/src/command-specs/diagnostics.js +97 -20
  286. package/src/command-specs/documents.js +158 -0
  287. package/src/command-specs/files.js +29 -0
  288. package/src/command-specs/handover.js +331 -79
  289. package/src/command-specs/helpers.js +50 -40
  290. package/src/command-specs/index.js +14 -5
  291. package/src/command-specs/meta.js +38 -9
  292. package/src/command-specs/onboarding.js +1 -1
  293. package/src/command-specs/profile.js +42 -1
  294. package/src/command-specs/skills.js +314 -9
  295. package/src/command-specs/smoke.js +16 -4
  296. package/src/command-specs/spaces.js +783 -0
  297. package/src/command-specs/tools.js +118 -21
  298. package/src/command-specs/version-history.js +20 -0
  299. package/src/command-specs/views.js +69 -0
  300. package/src/runtime/agent-browser.js +178 -267
  301. package/src/runtime/agent-setup.js +172 -13
  302. package/src/runtime/app-boundary-validator.js +338 -2
  303. package/src/runtime/app-data-literals.js +82 -0
  304. package/src/runtime/app-platform.js +750 -1568
  305. package/src/runtime/auth-recovery.js +17 -0
  306. package/src/runtime/base-skills.d.ts +11 -0
  307. package/src/runtime/base-skills.js +100 -3
  308. package/src/runtime/computer-identity.d.ts +30 -0
  309. package/src/runtime/computer-identity.js +151 -0
  310. package/src/runtime/config-lock.js +135 -0
  311. package/src/runtime/confined-space-build.js +74 -0
  312. package/src/runtime/confined-space-verify.js +49 -0
  313. package/src/runtime/delegated-context.js +5 -21
  314. package/src/runtime/native-record-files.js +47 -0
  315. package/src/runtime/oauth.js +342 -31
  316. package/src/runtime/profiles.js +19 -112
  317. package/src/runtime/retired-sdk-files.js +24 -0
  318. package/src/runtime/retry.js +150 -0
  319. package/src/runtime/skill-sync/cloud-client.ts +33 -8
  320. package/src/runtime/skill-sync/index.ts +443 -106
  321. package/src/runtime/skill-sync/local-scanner.ts +248 -32
  322. package/src/runtime/skill-sync/native-identity.ts +102 -0
  323. package/src/runtime/skill-sync/native-plan.ts +70 -0
  324. package/src/runtime/skill-sync/symlink-manager.ts +66 -16
  325. package/src/runtime/skill-sync/sync-plan.ts +34 -2
  326. package/src/runtime/skill-sync/types.ts +54 -0
  327. package/src/runtime/skill-sync/write-cloud-skill.ts +4 -4
  328. package/src/runtime/skill-sync-service.js +109 -0
  329. package/src/runtime/source-root.js +32 -0
  330. package/src/runtime/space-collection-scaffold.js +107 -0
  331. package/src/runtime/space-database-structures.js +229 -0
  332. package/src/runtime/space-fixture-broker.js +68 -0
  333. package/src/runtime/space-lock.js +337 -0
  334. package/src/runtime/space-platform.js +133 -0
  335. package/src/runtime/space-source.js +290 -0
  336. package/src/runtime/space-test-server.js +354 -0
  337. package/src/runtime/space-verification.js +94 -0
  338. package/src/runtime/space-verify-browser-worker.js +51 -0
  339. package/src/runtime/space-view-manifest.js +368 -0
  340. package/src/runtime/sync-skills.d.ts +5 -1
  341. package/src/runtime/sync-skills.js +11 -2
  342. package/src/runtime/tool-confirmation.js +138 -0
  343. package/src/runtime/transport.js +118 -24
  344. package/src/runtime/upsell.js +219 -0
  345. package/src/runtime/view-render.js +49 -0
  346. package/src/skill-sync-worker-entry.js +2 -0
  347. package/src/skill-sync-worker.js +114 -0
  348. package/src/space-harness-host.ts +122 -0
  349. package/template/.harness/index.html.tmpl +9 -352
  350. package/template/app/globals.css +29 -3
  351. package/template/app/layout.tsx +2 -6
  352. package/template/components/ui/badge.tsx +7 -4
  353. package/template/components/ui/button.tsx +1 -1
  354. package/template/components/ui/native-select.tsx +24 -0
  355. package/template/notis.config.ts +4 -36
  356. package/template/package-lock.json +1800 -1867
  357. package/template/package.json +16 -14
  358. package/template/postcss.config.mjs +1 -1
  359. package/template/tailwind.config.ts +1 -6
  360. package/template/tsconfig.json +1 -0
  361. package/template/vite.config.ts +2 -2
  362. package/dist/base-skills/notis-apps/SKILL.md +0 -613
  363. package/skills/notis-apps/cli.md +0 -285
  364. package/src/command-specs/apps.js +0 -3000
  365. package/src/runtime/app-changelog.js +0 -79
  366. package/src/runtime/app-dev-consumers.js +0 -154
  367. package/src/runtime/app-dev-host-lock.js +0 -80
  368. package/src/runtime/app-dev-process-identity.js +0 -111
  369. package/src/runtime/app-dev-roots.js +0 -284
  370. package/src/runtime/app-dev-server.js +0 -1140
  371. package/src/runtime/app-dev-sessions.js +0 -185
  372. package/src/runtime/app-registry-scaffolds.js +0 -367
  373. package/src/runtime/assets/store-screenshot-dark.png +0 -0
  374. package/src/runtime/cli-mode.generated.js +0 -5
  375. package/src/runtime/cli-mode.js +0 -34
  376. package/src/runtime/store-screenshot.js +0 -143
  377. package/template/CHANGELOG.md +0 -5
  378. package/template/app/page.tsx +0 -60
  379. package/template/components/ui/card.tsx +0 -56
  380. package/template/packages/sdk/src/components/DocumentEditor.tsx +0 -103
  381. package/template/packages/sdk/src/config.ts +0 -259
  382. package/template/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
  383. package/template/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  384. package/template/packages/sdk/src/hooks/useDocument.ts +0 -78
  385. package/template/packages/sdk/src/hooks/useNotisNavigation.ts +0 -52
  386. package/template/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  387. package/template/packages/sdk/src/runtime.ts +0 -439
  388. package/template/packages/sdk/src/vite.ts +0 -56
  389. package/template/packages/sdk/tsconfig.json +0 -15
  390. /package/{template/packages → dist}/sdk/src/components/MultiSelectDragOverlay.tsx +0 -0
  391. /package/{template/packages → dist}/sdk/src/components/ShortcutHints.tsx +0 -0
  392. /package/{template/packages → dist}/sdk/src/hooks/useActiveResource.ts +0 -0
  393. /package/{template/packages → dist}/sdk/src/hooks/useTool.ts +0 -0
  394. /package/{template/packages → dist}/sdk/src/hooks/useTools.ts +0 -0
  395. /package/{template/packages → dist}/sdk/src/ui.ts +0 -0
@@ -2,7 +2,6 @@ import { randomUUID } from 'node:crypto';
2
2
  import { createReadStream } from 'node:fs';
3
3
  import { dirname } from 'node:path';
4
4
  import { CliError, EXIT_CODES } from './errors.js';
5
- import { delegatedContextReason } from './delegated-context.js';
6
5
  import {
7
6
  credentialIsExpired,
8
7
  getJwtExpiration,
@@ -14,6 +13,16 @@ import {
14
13
  } from './profiles.js';
15
14
  import { createExpiredAuthError, createInvalidAuthHints } from './auth-recovery.js';
16
15
  import { refreshOAuthCredential } from './oauth.js';
16
+ import { findUpsell, renderUpsell } from './upsell.js';
17
+ import {
18
+ callCarriesToolRequestId,
19
+ canReplayRequest,
20
+ findToolOutcomeUnknown,
21
+ isTransientFailure,
22
+ retriesEnabled,
23
+ waitForNextAttempt,
24
+ withAttempts,
25
+ } from './retry.js';
17
26
 
18
27
  function escapeMultipartHeaderValue(value) {
19
28
  return String(value ?? '')
@@ -82,10 +91,30 @@ function createMultipartFileUpload(payload, bindingMetadata, fileBindings) {
82
91
  };
83
92
  }
84
93
 
85
- function normalizeBackendError(status, payload, runtime) {
94
+ // The Spaces client protocol this CLI speaks. The server refuses every other
95
+ // version with a 410 `client_update_required` answer, so an older CLI is told
96
+ // to update instead of half-working.
97
+ export const SPACES_PROTOCOL = 2;
98
+ export const CLI_UPDATE_COMMAND = 'npx --package @notis_ai/cli@latest -- notis';
99
+
100
+ export function normalizeBackendError(status, payload, runtime) {
86
101
  const backendError = payload?.error;
102
+ if (backendError === 'client_update_required') {
103
+ return new CliError({
104
+ code: 'cli_update_required',
105
+ message: `This Notis CLI is too old for Spaces. Run the latest one with: ${CLI_UPDATE_COMMAND}`,
106
+ exitCode: EXIT_CODES.usage,
107
+ details: payload || {},
108
+ hints: [{ command: CLI_UPDATE_COMMAND, reason: 'Run your command again with the latest Notis CLI' }],
109
+ });
110
+ }
111
+ // Space errors answer {error: <stable code>, message: <readable text>}: show
112
+ // the text (it can name the Space involved); the code stays in details.
113
+ const codedMessage = typeof backendError === 'string' && /^[a-z][a-z0-9_]*$/.test(backendError)
114
+ && typeof payload?.message === 'string' && payload.message ? payload.message : null;
87
115
  const message =
88
116
  backendError?.message ||
117
+ codedMessage ||
89
118
  backendError ||
90
119
  payload?.message ||
91
120
  payload?.error ||
@@ -115,6 +144,19 @@ function normalizeBackendError(status, payload, runtime) {
115
144
  }
116
145
 
117
146
  if (status === 403) {
147
+ const upsellBody = findUpsell(payload);
148
+ if (upsellBody) {
149
+ // Same code as every 403 so callers that treat a refusal as "not
150
+ // available" keep working; the message and hints are the shared upsell.
151
+ const rendered = renderUpsell(upsellBody, { apiBase: runtime?.apiBase });
152
+ return new CliError({
153
+ code: 'forbidden',
154
+ message: rendered.text,
155
+ exitCode: EXIT_CODES.auth,
156
+ details: { ...(payload || {}), upsell: rendered.upsell },
157
+ hints: rendered.hints,
158
+ });
159
+ }
118
160
  return new CliError({
119
161
  code: 'forbidden',
120
162
  message,
@@ -258,6 +300,9 @@ export async function httpRequest({
258
300
  body,
259
301
  multipart = false,
260
302
  requireAuth = true,
303
+ spacesProtocol,
304
+ // This attempt's own limit; defaults to the whole --timeout-ms budget.
305
+ timeoutMs = runtime.timeoutMs,
261
306
  }) {
262
307
  // Refresh before spending the credential rather than after a rejection: the
263
308
  // rotating refresh token is shared with every other `notis` process reading
@@ -281,18 +326,19 @@ export async function httpRequest({
281
326
  }
282
327
 
283
328
  let controller = new AbortController();
284
- let timeout = setTimeout(() => controller.abort(), runtime.timeoutMs);
329
+ let timeout = setTimeout(() => controller.abort(), timeoutMs);
285
330
  const requestId = `req_${randomUUID().replace(/-/g, '')}`;
286
331
  const resetTimeout = () => {
287
332
  clearTimeout(timeout);
288
333
  controller = new AbortController();
289
- timeout = setTimeout(() => controller.abort(), runtime.timeoutMs);
334
+ timeout = setTimeout(() => controller.abort(), timeoutMs);
290
335
  };
291
336
 
292
337
  const headers = {
293
338
  'X-Notis-CLI-Version': runtime.cliVersion,
294
339
  'X-Notis-Request-Id': requestId,
295
340
  };
341
+ if (spacesProtocol) headers['X-Notis-Spaces-Protocol'] = String(SPACES_PROTOCOL);
296
342
  if (!multipart) {
297
343
  headers['Content-Type'] = 'application/json';
298
344
  }
@@ -378,7 +424,10 @@ export async function httpRequest({
378
424
  ) {
379
425
  throw createExpiredAuthError(runtime);
380
426
  }
381
- throw normalizeBackendError(response.status, payload, runtime);
427
+ const backendError = normalizeBackendError(response.status, payload, runtime);
428
+ // Kept off the serialized error; the retry policy reads the HTTP status.
429
+ backendError.httpStatus = response.status;
430
+ throw backendError;
382
431
  }
383
432
 
384
433
  // A successful status is not a successful tool call until its response
@@ -430,48 +479,93 @@ export async function callTool({
430
479
  idempotencyKey,
431
480
  fileBindings = [],
432
481
  }) {
433
- const requestId = idempotencyKey || (runtime.mutating ? randomUUID() : null);
434
- const payload = {
482
+ const firstKey = idempotencyKey || (runtime.mutating ? randomUUID() : null);
483
+ const buildPayload = (key) => ({
435
484
  tool_name: toolName,
436
485
  arguments: argumentsPayload,
437
- idempotency_key: requestId,
486
+ idempotency_key: key,
438
487
  cli_context: {
439
488
  output_mode: runtime.outputMode,
440
489
  profile: runtime.profileName,
441
490
  cwd: process.cwd(),
442
491
  agent_mode: runtime.agentMode,
443
492
  cli_version: runtime.cliVersion,
444
- // Reported so the server can refuse work-handing tools from a run Notis
445
- // is itself driving. Advisory -- a determined agent can unset the markers
446
- // -- so the server treats it as one layer, not the whole guard.
447
- ...(delegatedContextReason() ? { delegated_context: true } : {}),
448
493
  ...(runtime.debugEntitlementOverride
449
494
  ? { debug_entitlement_override: runtime.debugEntitlementOverride }
450
495
  : {}),
451
496
  },
452
- };
497
+ });
453
498
 
454
- if (Array.isArray(fileBindings) && fileBindings.length) {
455
- const bindingMetadata = fileBindings.map((binding) => {
499
+ const hasFiles = Array.isArray(fileBindings) && fileBindings.length > 0;
500
+ const bindingMetadata = hasFiles
501
+ ? fileBindings.map((binding) => {
456
502
  const {
457
503
  contentType,
458
504
  localPath,
459
505
  ...metadata
460
506
  } = binding;
461
507
  return metadata;
462
- });
508
+ })
509
+ : [];
463
510
 
464
- return httpRequest({
511
+ const send = (key, timeoutMs) => (hasFiles
512
+ ? httpRequest({
465
513
  runtime,
466
514
  path: '/cli_tools',
467
- body: () => createMultipartFileUpload(payload, bindingMetadata, fileBindings),
515
+ // A function, so every attempt streams the files again from the start.
516
+ body: () => createMultipartFileUpload(buildPayload(key), bindingMetadata, fileBindings),
468
517
  multipart: true,
469
- });
518
+ timeoutMs,
519
+ })
520
+ : httpRequest({
521
+ runtime,
522
+ path: '/cli_tools',
523
+ body: buildPayload(key),
524
+ timeoutMs,
525
+ }));
526
+
527
+ if (!retriesEnabled(runtime)) {
528
+ let result;
529
+ try {
530
+ result = await send(firstKey, runtime.timeoutMs);
531
+ } catch (error) {
532
+ throw isTransientFailure(error) ? withAttempts(error, 1) : error;
533
+ }
534
+ return { ...result, idempotencyKey: firstKey, attempts: 1 };
470
535
  }
471
536
 
472
- return httpRequest({
473
- runtime,
474
- path: '/cli_tools',
475
- body: payload,
476
- });
537
+ // --timeout-ms bounds the whole call, every attempt included.
538
+ const budgetMs = runtime.timeoutMs;
539
+ const deadline = Date.now() + budgetMs;
540
+ let key = firstKey;
541
+ for (let attempt = 1; ; attempt += 1) {
542
+ let result;
543
+ try {
544
+ result = await send(key, Math.max(1, deadline - Date.now()));
545
+ } catch (error) {
546
+ const transient = isTransientFailure(error);
547
+ if (
548
+ transient
549
+ && canReplayRequest({ mutating: runtime.mutating, idempotencyKey: key })
550
+ && await waitForNextAttempt({ attempt, deadline, budgetMs })
551
+ ) {
552
+ continue;
553
+ }
554
+ throw transient ? withAttempts(error, attempt) : error;
555
+ }
556
+
557
+ // The stored answer under this key is the unknown outcome itself, so only
558
+ // a fresh key reaches the tool again; its request_id receipt dedupes it.
559
+ if (
560
+ runtime.mutating
561
+ && key
562
+ && findToolOutcomeUnknown(result.payload)
563
+ && callCarriesToolRequestId(argumentsPayload)
564
+ && await waitForNextAttempt({ attempt, deadline, budgetMs })
565
+ ) {
566
+ key = randomUUID();
567
+ continue;
568
+ }
569
+ return { ...result, idempotencyKey: key, attempts: attempt };
570
+ }
477
571
  }
@@ -0,0 +1,219 @@
1
+ // One renderer for every upgrade_required refusal the CLI shows. The server
2
+ // builds the body (server/lib/tool_entitlements.py entitlement_upgrade_required_body,
3
+ // plus Cloud Computer overrides); this module only finds it in a payload and
4
+ // turns it into the lines and hints a person or agent acts on.
5
+
6
+ const UPSELL_CODES = new Set([
7
+ 'entitlement_upgrade_required',
8
+ 'cloud_computer_team_limit',
9
+ ]);
10
+
11
+ const NESTED_KEYS = ['error', 'details', 'data', 'result', 'response', 'entitlement_access'];
12
+
13
+ const PORTAL_ORIGINS = {
14
+ 'api.notis.ai': 'https://app.notis.ai',
15
+ 'api-beta.notis.ai': 'https://beta.notis.ai',
16
+ };
17
+
18
+ function isUpsellBody(value) {
19
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
20
+ if (value.upgrade_required === true) return true;
21
+ return typeof value.code === 'string' && UPSELL_CODES.has(value.code)
22
+ && (typeof value.message === 'string' || value.next_action);
23
+ }
24
+
25
+ function parseJsonString(value) {
26
+ if (typeof value !== 'string') return null;
27
+ const trimmed = value.trim();
28
+ if (!trimmed.startsWith('{')) return null;
29
+ try {
30
+ return JSON.parse(trimmed);
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+
36
+ // Returns the upsell body inside a tool result, a COMPOSIO_MULTI_EXECUTE_TOOL
37
+ // envelope or a backend error payload, or null when there is none.
38
+ export function findUpsell(value, depth = 0) {
39
+ if (depth > 8 || value === null || value === undefined) return null;
40
+ const parsed = parseJsonString(value);
41
+ if (parsed) return findUpsell(parsed, depth + 1);
42
+ if (Array.isArray(value)) {
43
+ for (const item of value) {
44
+ const found = findUpsell(item, depth + 1);
45
+ if (found) return found;
46
+ }
47
+ return null;
48
+ }
49
+ if (typeof value !== 'object') return null;
50
+ if (isUpsellBody(value)) {
51
+ // A backend error wraps the body as {code, message, details: {...body}}:
52
+ // prefer the inner body, which carries next_action.
53
+ const inner = value.details && typeof value.details === 'object' ? findUpsell(value.details, depth + 1) : null;
54
+ return inner && inner.next_action && !value.next_action ? { ...value, ...inner } : value;
55
+ }
56
+ for (const key of NESTED_KEYS) {
57
+ if (key in value) {
58
+ const found = findUpsell(value[key], depth + 1);
59
+ if (found) return found;
60
+ }
61
+ }
62
+ if (Array.isArray(value.results)) return findUpsell(value.results, depth + 1);
63
+ return null;
64
+ }
65
+
66
+ export function portalOriginFor(apiBase) {
67
+ try {
68
+ return PORTAL_ORIGINS[new URL(apiBase).hostname] || null;
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+
74
+ function planLabel(plan) {
75
+ if (typeof plan !== 'string' || !plan.trim()) return null;
76
+ const value = plan.trim().replace(/[_-]+/g, ' ');
77
+ return value.charAt(0).toUpperCase() + value.slice(1);
78
+ }
79
+
80
+ function formatPrice(price) {
81
+ if (price === null || price === undefined || price === '') return null;
82
+ if (typeof price === 'string' || typeof price === 'number') return String(price);
83
+ if (typeof price !== 'object') return null;
84
+ if (typeof price.label === 'string' && price.label) return price.label;
85
+ const parts = [];
86
+ if (price.monthly_usd !== undefined && price.monthly_usd !== null) parts.push(`$${price.monthly_usd} a month`);
87
+ if (price.yearly_usd !== undefined && price.yearly_usd !== null) parts.push(`$${price.yearly_usd} a year`);
88
+ if (price.hourly_usd !== undefined && price.hourly_usd !== null) parts.push(`$${price.hourly_usd} an hour`);
89
+ return parts.length ? parts.join(', ') : null;
90
+ }
91
+
92
+ function formatUnlocks(unlocks) {
93
+ if (typeof unlocks === 'string' && unlocks.trim()) return [unlocks.trim()];
94
+ if (Array.isArray(unlocks)) return unlocks.filter((item) => typeof item === 'string' && item.trim());
95
+ return [];
96
+ }
97
+
98
+ function upgradeLink(body, apiBase) {
99
+ const url = typeof body.upgrade_url === 'string' && body.upgrade_url ? body.upgrade_url : '/subscribe';
100
+ if (/^https?:\/\//.test(url)) return url;
101
+ const origin = portalOriginFor(apiBase);
102
+ return origin ? `${origin}${url.startsWith('/') ? '' : '/'}${url}` : null;
103
+ }
104
+
105
+ // Portal routes a next_action may name with `page` instead of a full url.
106
+ const ACTION_PAGES = {
107
+ billing: { path: '/settings/billing', where: 'Settings, then Billing' },
108
+ };
109
+
110
+ const SUPPORT_URL = 'mailto:support@notis.ai';
111
+
112
+ function portalLink(path, apiBase) {
113
+ const origin = portalOriginFor(apiBase);
114
+ return origin ? `${origin}${path}` : null;
115
+ }
116
+
117
+ function toolCommand(tool, args = {}) {
118
+ return `notis tools exec ${tool} --arguments '${JSON.stringify(args)}'`;
119
+ }
120
+
121
+ // Builds {message, lines, hints, upsell} for one upsell body. `lines` is the
122
+ // readable block for people; `hints` follow the CLI's {command, reason} shape
123
+ // so agents see the same next step in machine output.
124
+ export function renderUpsell(body, { apiBase } = {}) {
125
+ const plan = planLabel(body.required_plan);
126
+ const message = typeof body.message === 'string' && body.message
127
+ ? body.message
128
+ : (plan ? `Upgrade to ${plan} to use this.` : 'This needs a paid plan.');
129
+ const lines = [message];
130
+ const hints = [];
131
+
132
+ const unlocks = formatUnlocks(body.unlocks);
133
+ if (unlocks.length) {
134
+ lines.push(plan ? `${plan} unlocks:` : 'The plan unlocks:');
135
+ for (const item of unlocks) lines.push(` - ${item}`);
136
+ }
137
+ const price = formatPrice(body.price);
138
+ if (price) lines.push(`Price: ${price}`);
139
+
140
+ const nextAction = body.next_action && typeof body.next_action === 'object' ? body.next_action : {};
141
+ const link = upgradeLink(body, apiBase);
142
+ const portalHint = {
143
+ command: toolCommand('LOCAL_NOTIS_GET_PORTAL_URL', { page: 'subscribe' }),
144
+ reason: 'Get the upgrade page link',
145
+ };
146
+ let actionUrl = null;
147
+
148
+ switch (nextAction.action) {
149
+ case 'contact_billing_owner': {
150
+ const ask = typeof nextAction.message === 'string' && nextAction.message
151
+ ? nextAction.message
152
+ : 'Ask your team owner to upgrade.';
153
+ lines.push(`Ask your team owner: ${ask}`);
154
+ hints.push({ message: `Ask your team owner: ${ask}` });
155
+ break;
156
+ }
157
+ case 'activate_paid_billing':
158
+ case 'switch_trial_plan': {
159
+ const tool = nextAction.tool || 'LOCAL_NOTIS_ACTIVATE_PAID_BILLING';
160
+ const args = nextAction.action === 'switch_trial_plan' && nextAction.target_plan
161
+ ? { target_plan: nextAction.target_plan }
162
+ : {};
163
+ const command = toolCommand(tool, args);
164
+ lines.push(`Start my plan now: ${command}`);
165
+ if (link) lines.push(`Or open the upgrade page: ${link}`);
166
+ hints.push({ command, reason: 'Start my plan now' });
167
+ if (!link) hints.push(portalHint);
168
+ break;
169
+ }
170
+ case 'update_card': {
171
+ const label = nextAction.label || 'Update card';
172
+ const page = ACTION_PAGES[nextAction.page] || ACTION_PAGES.billing;
173
+ actionUrl = /^https?:\/\//.test(nextAction.url || '') ? nextAction.url : portalLink(page.path, apiBase);
174
+ if (actionUrl) {
175
+ lines.push(`${label}: ${actionUrl}`);
176
+ hints.push({ message: `${label}: ${actionUrl}` });
177
+ } else {
178
+ lines.push(`${label}: open ${page.where}, in the Notis app.`);
179
+ hints.push({ command: toolCommand('LOCAL_NOTIS_GET_PORTAL_URL', { page: page.path }), reason: 'Get the billing page link' });
180
+ }
181
+ break;
182
+ }
183
+ case 'contact_us': {
184
+ const label = nextAction.label || 'Contact us';
185
+ actionUrl = typeof nextAction.url === 'string' && nextAction.url ? nextAction.url : SUPPORT_URL;
186
+ lines.push(`${label}: ${actionUrl}`);
187
+ hints.push({ message: `${label}: ${actionUrl}` });
188
+ break;
189
+ }
190
+ case 'retry_entitlement_check':
191
+ lines.push('Your plan could not be checked right now. Try again in a moment.');
192
+ break;
193
+ default: {
194
+ if (link) {
195
+ lines.push(`Open the upgrade page: ${link}`);
196
+ hints.push({ message: `Open the upgrade page: ${link}` });
197
+ } else {
198
+ lines.push('Open the upgrade page in the Notis app.');
199
+ hints.push(portalHint);
200
+ }
201
+ }
202
+ }
203
+
204
+ return {
205
+ message,
206
+ lines,
207
+ text: lines.join('\n'),
208
+ hints,
209
+ upsell: {
210
+ code: body.code || 'entitlement_upgrade_required',
211
+ entitlement: body.entitlement || null,
212
+ required_plan: body.required_plan || null,
213
+ lock: body.lock || null,
214
+ upgrade_url: link || body.upgrade_url || '/subscribe',
215
+ next_action: nextAction.action || null,
216
+ ...(actionUrl ? { action_url: actionUrl } : {}),
217
+ },
218
+ };
219
+ }
@@ -0,0 +1,49 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { mkdir, writeFile, unlink } from 'node:fs/promises';
3
+ import { resolve, join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { httpRequest } from './transport.js';
6
+ import { CliError, EXIT_CODES, usageError } from './errors.js';
7
+
8
+ async function rendererLibrary() {
9
+ const packaged = new URL('../../dist/view-renderer/src/index.js', import.meta.url);
10
+ if (existsSync(fileURLToPath(packaged))) return import(packaged.href);
11
+ const development = new URL('../../../view-renderer/src/index.js', import.meta.url);
12
+ if (existsSync(fileURLToPath(development))) return import(development.href);
13
+ throw usageError('The shared view renderer is missing. Rebuild or reinstall this CLI package.');
14
+ }
15
+
16
+ /** Root command registration supplies flags/output formatting; this adapter holds no browser JWT. */
17
+ export async function renderViewWithCliRuntime({ runtime, url, outputs = ['markdown', 'screenshot'], width = 1440,
18
+ directory, signal, loadLibrary = rendererLibrary, request = httpRequest }) {
19
+ if (typeof url !== 'string' || !url.trim() || ![390, 1440].includes(width)) throw usageError('Choose a view link and width 390 or 1440.');
20
+ const ticket = (await request({ runtime, method: 'POST', path: '/portal_view_render/prepare', spacesProtocol: true,
21
+ body: { url, outputs, width }, signal })).payload;
22
+ if (typeof ticket?.credential !== 'string') throw usageError('The short-lived render ticket is missing.');
23
+ const library = await loadLibrary();
24
+ const broker = library.createRemoteRenderBroker({ apiBase: runtime.apiBase, credential: ticket.credential });
25
+ let result;
26
+ try {
27
+ result = await library.renderView({ broker, outputs, width, signal });
28
+ await broker.complete(result, { signal });
29
+ }
30
+ catch (error) {
31
+ if (error instanceof library.ViewRenderError) throw new CliError({ code: error.code,
32
+ message: error.message, exitCode: EXIT_CODES.backend, retryable: error.status >= 500 || error.status === 429, cause: error });
33
+ throw error;
34
+ }
35
+ if (!directory) return { result, artifacts: null };
36
+ const root = resolve(directory), created = [], artifacts = {};
37
+ await mkdir(root, { recursive: true });
38
+ try {
39
+ for (const [kind, name, bytes] of [
40
+ ['markdown', 'view.md', result.markdown === undefined ? undefined : Buffer.from(result.markdown, 'utf8')],
41
+ ['screenshot', 'view.png', result.screenshot ? Buffer.from(result.screenshot.data_base64, 'base64') : undefined],
42
+ ]) {
43
+ if (bytes === undefined) continue;
44
+ const path = join(root, name);
45
+ await writeFile(path, bytes, { flag: 'wx', mode: 0o600 }); created.push(path); artifacts[kind] = path;
46
+ }
47
+ } catch (error) { await Promise.allSettled(created.map(path => unlink(path))); throw error; }
48
+ return { result, artifacts };
49
+ }
@@ -0,0 +1,2 @@
1
+ import { main } from './skill-sync-worker.js';
2
+ await main();
@@ -0,0 +1,114 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { getCurrentProfileName, getProfile, loadConfig, resolveRuntimeProfile } from './runtime/profiles.js';
6
+ import { ensureFreshOAuthCredential } from './runtime/oauth.js';
7
+ import { runSkillSync, fetchSyncSettings } from '../dist/skill-sync/index.js';
8
+ import { withSkillSyncLock } from './runtime/sync-skills.js';
9
+
10
+ // The server rejected the stored sign-in itself. Presenting it again can only
11
+ // fail the same way, so the worker waits for the sign-in to change instead of
12
+ // asking every minute (docs/no-dead-ends.md).
13
+ const REJECTED_SIGN_IN_CODES = new Set(['invalid_grant', 'invalid_client', 'unauthorized_client']);
14
+
15
+ function currentCliProfile() {
16
+ try {
17
+ return getCurrentProfileName(loadConfig());
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+
23
+ /**
24
+ * A fingerprint of the profile's stored sign-in that changes whenever it is
25
+ * renewed or replaced, by any process, and holds no credential.
26
+ */
27
+ export function signInFingerprint(profileName, config = loadConfig()) {
28
+ const profile = getProfile(config, profileName);
29
+ if (!profile.oauth_client_id && !profile.oauth_access_expires_at) return null;
30
+ return createHash('sha256').update(JSON.stringify([
31
+ profile.oauth_issuer ?? null,
32
+ profile.oauth_client_id ?? null,
33
+ profile.oauth_user_id ?? null,
34
+ profile.oauth_access_expires_at ?? null,
35
+ profile.oauth_refresh_expires_at ?? null,
36
+ ])).digest('hex').slice(0, 16);
37
+ }
38
+
39
+ export async function runAutomaticSkillSync({ profile, apiBase, userId }, {
40
+ resolveRuntime = resolveRuntimeProfile, refresh = ensureFreshOAuthCredential,
41
+ settings = fetchSyncSettings, sync = runSkillSync, lock = withSkillSyncLock,
42
+ currentProfile = currentCliProfile,
43
+ } = {}) {
44
+ const runtime = resolveRuntime({ profile }, { requireAuth: true });
45
+ if (runtime.credentialKind !== 'oauth' || runtime.apiBase !== apiBase
46
+ || runtime.oauthUserId !== userId) throw new Error('Automatic skill sync account changed; run notis skills sync to rebind');
47
+ await refresh(runtime);
48
+ const saved = await settings(runtime.apiBase, runtime.jwt);
49
+ if (saved.user_id !== userId) throw new Error('Automatic skill sync identity mismatch');
50
+ // Do not gather, unlink foreign accounts or write anything when opted out.
51
+ if (!saved.sync_enabled) return { status: 'disabled' };
52
+ // The service stays bound to its profile; after `notis profile use` moves the
53
+ // default away, it keeps that account's folder in sync without linking it.
54
+ const linkAgentFolders = (currentProfile() ?? profile) === profile;
55
+ const result = await lock(() => sync(runtime.apiBase, runtime.jwt, {
56
+ fetchSyncSettings: async () => saved,
57
+ }, { honorSyncEnabled: true, ...(linkAgentFolders ? {} : { linkAgentFolders: false }) }));
58
+ return { status: (result.failedLinks?.length || result.failedPushes?.length) ? 'partial' : 'synced', ...result };
59
+ }
60
+
61
+ function readStatus(target) {
62
+ try {
63
+ return JSON.parse(readFileSync(target, 'utf8'));
64
+ } catch {
65
+ return null;
66
+ }
67
+ }
68
+
69
+ export async function main(args = process.argv.slice(2), {
70
+ home = homedir(), run = runAutomaticSkillSync, fingerprint = signInFingerprint,
71
+ deadlineMs = 240_000,
72
+ } = {}) {
73
+ const [profile, apiBase, userId] = args;
74
+ const root = join(home, '.notis', 'skills', 'service');
75
+ const target = join(root, 'status.json');
76
+ const record = (value) => {
77
+ mkdirSync(root, { recursive: true, mode: 0o700 });
78
+ const temporary = `${target}.${process.pid}.tmp`;
79
+ writeFileSync(temporary, JSON.stringify({ ...value, profile, at: new Date().toISOString() }, null, 2), { mode: 0o600 });
80
+ renameSync(temporary, target);
81
+ };
82
+ let signIn = null;
83
+ try {
84
+ signIn = fingerprint(profile);
85
+ } catch {
86
+ signIn = null;
87
+ }
88
+ const previous = readStatus(target);
89
+ if (
90
+ signIn
91
+ && previous?.status === 'error'
92
+ && previous.profile === profile
93
+ && REJECTED_SIGN_IN_CODES.has(previous.code)
94
+ && previous.sign_in === signIn
95
+ ) {
96
+ // Same rejected sign-in: wait for `notis login`, or for any process to
97
+ // renew it, before presenting it again.
98
+ return;
99
+ }
100
+ const deadline = setTimeout(() => {
101
+ record({ status: 'error', code: 'sync_timeout' });
102
+ process.exit(1);
103
+ }, deadlineMs);
104
+ try {
105
+ record(await run({ profile, apiBase, userId }));
106
+ } catch (error) {
107
+ // Persist only a classified error, never a bearer, signed URL or response body.
108
+ const code = error.code || 'sync_failed';
109
+ record({ status: 'error', code, ...(REJECTED_SIGN_IN_CODES.has(code) && signIn ? { sign_in: signIn } : {}) });
110
+ process.exitCode = 1;
111
+ } finally {
112
+ clearTimeout(deadline);
113
+ }
114
+ }