@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
@@ -1,34 +1,339 @@
1
- import { getJwtSubject } from '../runtime/profiles.js';
1
+ import { getCurrentProfileName, getJwtSubject, loadConfig } from '../runtime/profiles.js';
2
2
  import { reconcileAllSkills } from '../runtime/sync-skills.js';
3
+ import { ensureFreshOAuthCredential } from '../runtime/oauth.js';
4
+ import { installSkillSyncService } from '../runtime/skill-sync-service.js';
5
+ import { existsSync, lstatSync, readFileSync, readdirSync, readlinkSync, unlinkSync } from 'node:fs';
6
+ import { homedir } from 'node:os';
7
+ import { dirname, join, resolve } from 'node:path';
8
+ import { httpRequest } from '../runtime/transport.js';
9
+ import { usageError } from '../runtime/errors.js';
10
+ import { parseJson } from './helpers.js';
11
+
12
+ async function nativeSkillAuthoring(ctx, body, path = '/portal_skills/native-authoring') {
13
+ // A Space target, destination or link change touches Spaces: declare the protocol.
14
+ const spaceTarget = Boolean(body.target?.space_id || body.destination?.space_id || body.links);
15
+ const result = await httpRequest({ runtime: ctx.runtime, method: 'POST', path, body,
16
+ ...(spaceTarget ? { spacesProtocol: true } : {}) });
17
+ return ctx.output.emitSuccess({ command: ctx.spec.command_path.join(' '), data: result.payload,
18
+ humanSummary: JSON.stringify(result.payload, null, 2),
19
+ meta: { mutating: body.operation !== 'read' && !body.dry_run, request_id: result.requestId } });
20
+ }
21
+
22
+ function exactKeys(value, keys) {
23
+ return value && typeof value === 'object' && !Array.isArray(value)
24
+ && Object.keys(value).sort().join(',') === [...keys].sort().join(',');
25
+ }
26
+
27
+ function skillTarget(value, updating) {
28
+ const direct = updating ? ['skill_id', 'access_revision'] : ['skill_id'];
29
+ const space = updating ? ['space_id', 'binding_id', 'binding_revision', 'expected_skill_id', 'space_revision'] : ['space_id', 'binding_id'];
30
+ if (!exactKeys(value, direct) && !exactKeys(value, space)) {
31
+ throw usageError('Use one exact direct or Space target from the Skill inventory or skills read.');
32
+ }
33
+ return value;
34
+ }
35
+
36
+ function requestKey(ctx) {
37
+ const request = ctx.options.requestId || ctx.globalOptions.idempotencyKey;
38
+ if (typeof request !== 'string' || !request || request.length > 200) throw usageError('Pass --request-id <stable-key>.');
39
+ return request;
40
+ }
41
+
42
+ function linkLabel(link) {
43
+ return `${link.space_name || link.space_id}/${link.alias}${link.available === false ? ' (unavailable)' : ''}`;
44
+ }
45
+
46
+ /** One line per Skill: name, ID, kind, editability and its Space links. */
47
+ export function skillInventoryLines(page) {
48
+ const entries = Array.isArray(page?.skills) ? page.skills : [];
49
+ const lines = entries.map(entry => {
50
+ const state = entry.editable ? 'editable' : `read-only${entry.read_only_reason ? ` (${entry.read_only_reason})` : ''}`;
51
+ const links = (entry.links || []).map(linkLabel);
52
+ return `${entry.name || entry.skill_id} ${entry.skill_id} ${entry.kind} ${state} links: ${links.length ? links.join(', ') : 'none'}`;
53
+ });
54
+ if (!entries.length) lines.push('No Skills match.');
55
+ if (page.has_more && page.next) lines.push(`More Skills: rerun with --after ${page.next}`);
56
+ return lines.join('\n');
57
+ }
58
+
59
+ /** What a link change did and where the resource is listed now. */
60
+ export function linkChangeSummary(result) {
61
+ const parts = [];
62
+ const added = (result?.added || []).map(link => `${link.space_id}/${link.alias}`);
63
+ const removed = (result?.removed || []).map(link => `${link.space_id}/${link.alias}`);
64
+ if (added.length) parts.push(`added: ${added.join(', ')}`);
65
+ if (removed.length) parts.push(`removed: ${removed.join(', ')}`);
66
+ if (result?.became_standalone) parts.push('now your standalone Skill');
67
+ if (result?.replayed) parts.push('confirmed retry');
68
+ const current = (result?.links || []).map(linkLabel);
69
+ return `${parts.join('; ') || 'No change'}. Listed in: ${current.length ? current.join(', ') : 'none'}.`;
70
+ }
71
+
72
+ function linkEntries(value, option, required, optional = []) {
73
+ const parsed = parseJson(value, option);
74
+ const valid = entry => entry && typeof entry === 'object' && !Array.isArray(entry)
75
+ && required.every(key => key in entry) && Object.keys(entry).every(key => required.includes(key) || optional.includes(key));
76
+ if (!Array.isArray(parsed) || !parsed.length || !parsed.every(valid)) {
77
+ throw usageError(`${option} must be a JSON array of {${[...required, ...optional.map(key => `${key}?`)].join(', ')}} entries.`);
78
+ }
79
+ return parsed;
80
+ }
3
81
 
4
82
  async function loadSkillSyncEngine() {
5
83
  return import('../../dist/skill-sync/index.js');
6
84
  }
7
85
 
8
- async function syncSkillsHandler(ctx) {
86
+ function cliDefaultOwnsAgentFolders(runtime) {
87
+ if (runtime.profileSource !== 'explicit' || !runtime.profileName) return true;
88
+ try {
89
+ return runtime.profileName === getCurrentProfileName(loadConfig());
90
+ } catch {
91
+ return true;
92
+ }
93
+ }
94
+
95
+ // The cloud computer's root. Present only inside the user's Vercel sandbox.
96
+ const CLOUD_COMPUTER_ROOT = '/vercel/sandbox';
97
+
98
+ /** Remove agent-folder links into `~/.notis/skills` whose target no longer exists. */
99
+ export function removeDanglingNotisSkillLinks(home = homedir()) {
100
+ const notisRoot = join(home, '.notis', 'skills');
101
+ let removed = 0;
102
+ for (const agentDir of [join(home, '.claude', 'skills'), join(home, '.codex', 'skills')]) {
103
+ let entries;
104
+ try {
105
+ entries = readdirSync(agentDir);
106
+ } catch {
107
+ continue;
108
+ }
109
+ for (const name of entries) {
110
+ const entryPath = join(agentDir, name);
111
+ try {
112
+ if (!lstatSync(entryPath).isSymbolicLink() || existsSync(entryPath)) continue;
113
+ const target = resolve(dirname(entryPath), readlinkSync(entryPath));
114
+ if (target.startsWith(`${notisRoot}/`)) {
115
+ unlinkSync(entryPath);
116
+ removed += 1;
117
+ }
118
+ } catch {
119
+ // A link that vanished or cannot be read is left for the next run.
120
+ }
121
+ }
122
+ }
123
+ return removed;
124
+ }
125
+
126
+ export async function syncSkillsHandler(ctx, {
127
+ refresh = ensureFreshOAuthCredential, loadEngine = loadSkillSyncEngine,
128
+ reconcile = reconcileAllSkills, install = installSkillSyncService,
129
+ onCloudComputer = () => existsSync(CLOUD_COMPUTER_ROOT), home = homedir(),
130
+ } = {}) {
131
+ if (onCloudComputer()) {
132
+ // Notis publishes the account's Skills into ~/.notis/skills on the cloud
133
+ // computer and replaces that folder on every change. An account sync here
134
+ // would write into it and leave agent links dangling after the next change.
135
+ const removedStaleLinks = removeDanglingNotisSkillLinks(home);
136
+ return ctx.output.emitSuccess({
137
+ command: 'skills sync',
138
+ data: { syncEnabled: false, cloudComputer: true, baseSkills: [], removedStaleLinks },
139
+ humanSummary: 'Notis keeps Skills on the cloud computer current; nothing to sync here.',
140
+ renderHuman: () => 'Notis keeps Skills on the cloud computer current; nothing to sync here.',
141
+ });
142
+ }
143
+ await refresh(ctx.runtime);
9
144
  const userId = ctx.runtime.oauthUserId || getJwtSubject(ctx.runtime.jwt);
10
- const { runSkillSync } = await loadSkillSyncEngine();
11
- const result = await reconcileAllSkills({
145
+ const { runSkillSync, fetchSyncSettings } = await loadEngine();
146
+ const settings = await fetchSyncSettings(ctx.runtime.apiBase, ctx.runtime.jwt);
147
+ const result = await reconcile({
12
148
  serverUrl: ctx.runtime.apiBase,
13
149
  jwt: ctx.runtime.jwt,
14
- userId,
150
+ userId: settings.user_id || userId,
15
151
  honorSyncEnabled: Boolean(ctx.options.electronRepeat),
16
- runAccountSync: runSkillSync,
152
+ // Only the CLI default's skills go into the agent folders; another profile
153
+ // syncs its own folder.
154
+ linkAgentFolders: cliDefaultOwnsAgentFolders(ctx.runtime),
155
+ runAccountSync: (serverUrl, jwt, dependencies, options) => runSkillSync(
156
+ serverUrl, jwt, { ...dependencies, fetchSyncSettings: async () => settings }, options,
157
+ ),
17
158
  });
159
+ if (settings.sync_enabled && !ctx.options.electronRepeat) {
160
+ try {
161
+ result.automaticSync = await install(ctx.runtime);
162
+ } catch (error) {
163
+ result.automaticSync = { status: 'error', message: error.message };
164
+ }
165
+ }
18
166
 
167
+ const failures = [...(result.failedPushes || []), ...(result.failedLinks || [])];
19
168
  return ctx.output.emitSuccess({
20
169
  command: 'skills sync',
21
170
  data: result,
22
- humanSummary: result.syncEnabled
171
+ warnings: result.automaticSync?.status === 'error'
172
+ ? [`Skills synced, but automatic refresh could not start: ${result.automaticSync.message}`] : [],
173
+ humanSummary: failures.length ? `Skill sync completed with ${failures.length} reported failures; inspect failedPushes and failedLinks.` : result.syncEnabled
23
174
  ? `Synced account skills and kept ${result.baseSkills.length} base skills current.`
24
175
  : `Automatic Desktop sync is off; kept ${result.baseSkills.length} base skills current.`,
25
- renderHuman: () => result.syncEnabled
176
+ renderHuman: () => failures.length ? `Skill sync needs attention: ${failures.map((failure) => `${failure.name}: ${failure.error}`).join("; ")}` : result.syncEnabled
26
177
  ? `Skills synced. Base skills current: ${result.baseSkills.join(', ')}.`
27
178
  : `Automatic Desktop sync is off. Base skills remain current: ${result.baseSkills.join(', ')}.`,
28
179
  });
29
180
  }
30
181
 
31
182
  export const skillsCommandSpecs = [
183
+ {
184
+ command_path: ['skills', 'list'],
185
+ summary: 'List every Skill you can see, with its kind, editability and Space links.',
186
+ when_to_use: 'Find a Skill and its exact edit target before reading or editing it, or see what a Space and its sub-Spaces list. Links to Spaces you cannot open are omitted; an unavailable link gives no access.',
187
+ args_schema: { arguments: [], options: [
188
+ { flags: '--space <id>', description: 'Only Skills listed by this Space and, by default, its sub-Spaces.' },
189
+ { flags: '--no-child-spaces', description: 'With --space: direct links of that Space only.' },
190
+ { flags: '--include-disabled', description: 'Include Skills you disabled.' },
191
+ { flags: '--after <cursor>', description: 'Continue from the next cursor of the previous page.' },
192
+ { flags: '--limit <n>', description: 'Page size between 1 and 100 (default 50).' },
193
+ ] },
194
+ examples: ['notis skills list', 'notis skills list --space <space-id> --no-child-spaces --json'],
195
+ mutates: false, idempotent: true, require_auth: true,
196
+ backend_call: { type: 'http', path: '/portal_skills/inventory' },
197
+ async handler(ctx) {
198
+ const body = {};
199
+ if (ctx.options.space !== undefined) body.space_id = ctx.options.space;
200
+ if (ctx.options.childSpaces === false) {
201
+ if (body.space_id === undefined) throw usageError('--no-child-spaces needs --space <id>.');
202
+ body.include_child_spaces = false;
203
+ }
204
+ if (ctx.options.includeDisabled) body.include_disabled = true;
205
+ if (ctx.options.after !== undefined) body.after = ctx.options.after;
206
+ if (ctx.options.limit !== undefined) {
207
+ const limit = Number(ctx.options.limit);
208
+ if (!/^\d+$/.test(String(ctx.options.limit)) || limit < 1 || limit > 100) throw usageError('Pass --limit between 1 and 100.');
209
+ body.limit = limit;
210
+ }
211
+ const result = await httpRequest({ runtime: ctx.runtime, method: 'POST', path: '/portal_skills/inventory', body });
212
+ return ctx.output.emitSuccess({ command: 'skills list', data: result.payload,
213
+ humanSummary: skillInventoryLines(result.payload), meta: { mutating: false, request_id: result.requestId } });
214
+ },
215
+ },
216
+ {
217
+ command_path: ['skills', 'create'],
218
+ summary: 'Create a native Skill, standalone or linked to an editable Space.',
219
+ when_to_use: 'Create complete instructions or files without provider installation. Choose an optional Space destination, dry-run, and keep the exact request key on retry.',
220
+ args_schema: { arguments: [{ token: '<file>', key: 'file', description: 'JSON containing name, optional description, and skill_md or base64 files.' }],
221
+ options: [{ flags: '--request-id <key>', description: 'Stable creation intent key; reuse after an uncertain reply.' },
222
+ { flags: '--dry-run', description: 'Validate without creating an identity or uploading files.' }] },
223
+ examples: ['notis skills create ./skill.json --request-id create-1 --dry-run'],
224
+ mutates: true, idempotent: true, require_auth: true,
225
+ backend_call: { type: 'http', path: '/portal_skills/native-create' },
226
+ handler(ctx) {
227
+ const payload = parseJson(readFileSync(resolve(ctx.args.file), 'utf8'), 'Skill creation file');
228
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)
229
+ || Object.keys(payload).some(key => !['destination', 'name', 'description', 'skill_md', 'files'].includes(key))) {
230
+ throw usageError('Provide a name, complete instructions or files, and an optional current destination; never override ownership.');
231
+ }
232
+ const destination = payload.destination ?? { kind: 'standalone' };
233
+ if (!(exactKeys(destination, ['kind']) && destination.kind === 'standalone')
234
+ && !exactKeys(destination, ['space_id', 'alias', 'revision'])) throw usageError('Choose a standalone destination or exact Space, alias and revision.');
235
+ const request = ctx.options.requestId || ctx.globalOptions.idempotencyKey;
236
+ if (typeof request !== 'string' || !request || request.length > 200) throw usageError('Pass --request-id <stable-key>.');
237
+ return nativeSkillAuthoring(ctx, { ...payload, operation: 'create', destination,
238
+ request_id: request, dry_run: Boolean(ctx.options.dryRun) }, '/portal_skills/native-create');
239
+ },
240
+ },
241
+ {
242
+ command_path: ['skills', 'settings', 'read'],
243
+ summary: 'Read a native Skill personal enablement, agent targets and settings revision.',
244
+ when_to_use: 'Inspect direct-owner preferences without changing shared instructions or Space access.',
245
+ args_schema: { arguments: [{ token: '<skill-id>', key: 'skillId', description: 'Exact native Skill ID.' }], options: [] },
246
+ mutates: false, idempotent: true, require_auth: true,
247
+ backend_call: { type: 'http', path: '/portal_skills/native-settings' },
248
+ handler(ctx) { return nativeSkillAuthoring(ctx, { operation: 'read', target: { skill_id: ctx.args.skillId } }, '/portal_skills/native-settings'); },
249
+ },
250
+ {
251
+ command_path: ['skills', 'settings', 'update'],
252
+ summary: 'Update personal native Skill settings with a current revision and stable retry key.',
253
+ when_to_use: 'Enable or disable your agents without changing a shared Skill definition or another Space.',
254
+ args_schema: { arguments: [{ token: '<file>', key: 'file', description: 'JSON with target, revision and patch (enabled or agent_targets).' }],
255
+ options: [{ flags: '--request-id <key>', description: 'Stable settings intent; reuse after a lost reply.' },
256
+ { flags: '--dry-run', description: 'Validate current ownership and revision without changing settings.' }] },
257
+ mutates: true, idempotent: true, require_auth: true,
258
+ backend_call: { type: 'http', path: '/portal_skills/native-settings' },
259
+ handler(ctx) {
260
+ const payload = parseJson(readFileSync(resolve(ctx.args.file), 'utf8'), 'Skill settings file');
261
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)
262
+ || Object.keys(payload).sort().join(',') !== 'patch,revision,target'
263
+ || !payload.target || Object.keys(payload.target).sort().join(',') !== 'access_revision,skill_id') {
264
+ throw usageError('Use a current standalone target, settings revision and personal settings patch.');
265
+ }
266
+ const request = ctx.options.requestId || ctx.globalOptions.idempotencyKey;
267
+ if (typeof request !== 'string' || !request || request.length > 200) throw usageError('Pass --request-id <stable-key>.');
268
+ return nativeSkillAuthoring(ctx, { ...payload, operation: 'update', request_id: request, dry_run: Boolean(ctx.options.dryRun) }, '/portal_skills/native-settings');
269
+ },
270
+ },
271
+ {
272
+ command_path: ['skills', 'read'],
273
+ summary: 'Inspect a native Skill through direct ownership or an editable Space link.',
274
+ when_to_use: 'Read the exact target and content version before editing. A Space target uses its current Editor authority, never a personal fallback.',
275
+ args_schema: { arguments: [{ token: '[skill-id]', key: 'skillId', description: 'Exact directly owned Skill ID; omit when passing --target.' }],
276
+ options: [{ flags: '--files', description: 'Include every verified supporting file as base64.' },
277
+ { flags: '--target <json>', description: 'Exact {skill_id} or {space_id,binding_id} read target; do not combine with a Skill ID.' }] },
278
+ examples: ['notis skills read <skill-id> --files', 'notis skills read --target \'{"space_id":"<id>","binding_id":"<binding>"}\' --files'],
279
+ mutates: false, idempotent: true, require_auth: true,
280
+ backend_call: { type: 'http', path: '/portal_skills/native-authoring' },
281
+ handler(ctx) {
282
+ if (Boolean(ctx.args.skillId) === Boolean(ctx.options.target)) throw usageError('Pass either a Skill ID or --target <json>.');
283
+ const target = skillTarget(ctx.options.target ? parseJson(ctx.options.target, '--target') : { skill_id: ctx.args.skillId }, false);
284
+ return nativeSkillAuthoring(ctx, { operation: 'read', target,
285
+ include_files: Boolean(ctx.options.files) });
286
+ },
287
+ },
288
+ {
289
+ command_path: ['skills', 'update'],
290
+ summary: 'Edit a native Skill through its direct or Space target, preserving its ID and supporting files.',
291
+ when_to_use: 'Use the target/version from skills list or skills read, dry-run, then reuse the same request key and bytes to save or recover a lost reply. The same edit file works for a standalone or a Space target; an optional links key changes where the Skill is listed in the same transaction.',
292
+ args_schema: { arguments: [{ token: '<file>', key: 'file', description: 'JSON with direct or Space target, version, skill_md or base64 files, optional name/description and links {add,remove}.' }],
293
+ options: [{ flags: '--request-id <key>', description: 'Stable edit intent key; reuse after lost replies.' },
294
+ { flags: '--dry-run', description: 'Check current access, content and revisions without uploading or saving.' }] },
295
+ examples: ['notis skills update ./skill-edit.json --request-id edit-1 --dry-run'],
296
+ mutates: true, idempotent: true, require_auth: true,
297
+ backend_call: { type: 'http', path: '/portal_skills/native-authoring' },
298
+ handler(ctx) {
299
+ const payload = parseJson(readFileSync(resolve(ctx.args.file), 'utf8'), 'Skill edit file');
300
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)
301
+ || Object.keys(payload).some(key => !['target', 'version', 'skill_md', 'files', 'name', 'description', 'links'].includes(key))) {
302
+ throw usageError('Use an exact target, version, skill_md or files, and optional links; never override ownership.');
303
+ }
304
+ skillTarget(payload.target, true);
305
+ if ('links' in payload && (!payload.links || typeof payload.links !== 'object' || Array.isArray(payload.links)
306
+ || !Object.keys(payload.links).length || Object.keys(payload.links).some(key => !['add', 'remove'].includes(key)))) {
307
+ throw usageError('links must be an object with add and/or remove arrays.');
308
+ }
309
+ return nativeSkillAuthoring(ctx, { ...payload, operation: 'update', request_id: requestKey(ctx),
310
+ dry_run: Boolean(ctx.options.dryRun) });
311
+ },
312
+ },
313
+ {
314
+ command_path: ['skills', 'links'],
315
+ summary: 'Add, remove or move a Skill between Spaces; the Skill keeps its ID.',
316
+ when_to_use: 'Change where a Skill is listed without editing its content. A move is one add plus one remove in one transaction. You must be an Editor of every Space you touch and able to edit the Skill; removing its last available link keeps it as your own standalone Skill.',
317
+ args_schema: { arguments: [{ token: '<skill-id>', key: 'skillId', description: 'Exact Skill ID from skills list.' }], options: [
318
+ { flags: '--add <json>', description: 'JSON array of {space_id, space_revision, alias?} links to add; alias defaults to the Skill name.' },
319
+ { flags: '--remove <json>', description: 'JSON array of {binding_id, binding_revision} links to remove, from skills list.' },
320
+ { flags: '--request-id <key>', description: 'Stable intent key; reuse it unchanged when retrying this change.' },
321
+ ] },
322
+ examples: ['notis skills links <skill-id> --add \'[{"space_id":"<space-id>","space_revision":3}]\' --request-id link-1',
323
+ 'notis skills links <skill-id> --add \'[{"space_id":"<to-space-id>","space_revision":3}]\' --remove \'[{"binding_id":"<binding-id>","binding_revision":2}]\' --request-id move-1'],
324
+ mutates: true, idempotent: true, require_auth: true,
325
+ backend_call: { type: 'http', path: '/portal_spaces/resource-links' },
326
+ async handler(ctx) {
327
+ const links = {};
328
+ if (ctx.options.add !== undefined) links.add = linkEntries(ctx.options.add, '--add', ['space_id', 'space_revision'], ['alias']);
329
+ if (ctx.options.remove !== undefined) links.remove = linkEntries(ctx.options.remove, '--remove', ['binding_id', 'binding_revision']);
330
+ if (!Object.keys(links).length) throw usageError('Pass --add and/or --remove.');
331
+ const body = { kind: 'skill', resource_id: ctx.args.skillId, links, request_id: requestKey(ctx) };
332
+ const result = await httpRequest({ runtime: ctx.runtime, method: 'POST', path: '/portal_spaces/resource-links', body, spacesProtocol: true });
333
+ return ctx.output.emitSuccess({ command: 'skills links', data: result.payload,
334
+ humanSummary: linkChangeSummary(result.payload), meta: { mutating: true, request_id: result.requestId } });
335
+ },
336
+ },
32
337
  {
33
338
  command_path: ['skills', 'sync'],
34
339
  summary: 'Synchronize account skills and keep the three Notis base skills current.',
@@ -45,7 +350,7 @@ export const skillsCommandSpecs = [
45
350
  },
46
351
  examples: ['notis skills sync', 'notis skills sync --json'],
47
352
  output_schema:
48
- 'Returns account sync counts plus baseSkills, baseInstalled, baseLinked, and baseBackups.',
353
+ 'Returns account sync counts plus baseSkills, baseInstalled, baseKept (base skills left as installed because that copy is newer), baseLinked, and baseBackups. On the cloud computer returns cloudComputer: true and removedStaleLinks instead.',
49
354
  mutates: true,
50
355
  idempotent: true,
51
356
  require_auth: true,
@@ -28,7 +28,7 @@ function discoveryNames(payload) {
28
28
  )];
29
29
  }
30
30
 
31
- function selectDropboxTools(payloads) {
31
+ function selectDropboxTools(payloads, { allowIncomplete = false } = {}) {
32
32
  const names = [...new Set(payloads.flatMap(discoveryNames))];
33
33
  const find = (preferred, ...needles) => (
34
34
  names.find((name) => name.toUpperCase() === preferred)
@@ -47,7 +47,7 @@ function selectDropboxTools(payloads) {
47
47
  delete: find('DROPBOX_DELETE_FILE', 'DELETE', 'FILE'),
48
48
  };
49
49
  const missing = Object.entries(tools).filter(([, value]) => !value).map(([key]) => key);
50
- if (missing.length) {
50
+ if (missing.length && !allowIncomplete) {
51
51
  throw usageError(`Dropbox smoke could not discover: ${missing.join(', ')}.`);
52
52
  }
53
53
  return tools;
@@ -93,13 +93,16 @@ async function executeDropboxTool(ctx, toolName, args, {
93
93
  phase,
94
94
  }) {
95
95
  ctx.output.emitProgress({ phase, message: `${phase} via ${toolName}` });
96
- const baseIdempotencyKey = mutating ? nextIdempotencyKey(ctx.globalOptions) : null;
96
+ const baseIdempotencyKey = nextIdempotencyKey(ctx.globalOptions);
97
+ const phaseKey = `${baseIdempotencyKey}:${phase}`;
97
98
  const result = await runToolCommand({
98
99
  runtime: ctx.runtime,
99
100
  toolName: COMPOSIO_MULTI_EXECUTE_TOOL,
100
101
  arguments_: { tools: [{ tool_slug: toolName, arguments: args }] },
101
102
  mutating,
102
- idempotencyKey: baseIdempotencyKey ? `${baseIdempotencyKey}:${phase}` : null,
103
+ idempotencyKey: phaseKey.length <= 160 ? phaseKey : createHash('sha256').update(phaseKey).digest('hex'),
104
+ // The batch wrapper is server-classified as unknown even for a read.
105
+ sendIdempotencyKeyWhenReading: true,
103
106
  fileBindings,
104
107
  });
105
108
  return result;
@@ -162,6 +165,15 @@ async function smokeFileUploadHandler(ctx) {
162
165
  'path, parent_rev',
163
166
  ),
164
167
  ];
168
+ // Broad semantic matches may omit an accessible reader. Resolve that
169
+ // missing capability once, before parity, file transfer or provider writes.
170
+ if (!selectDropboxTools(discoveries, { allowIncomplete: true }).read) {
171
+ discoveries.push(await fetchDiscoveryWithRetry(
172
+ ctx,
173
+ 'Read a Dropbox file by its exact path and return its bytes or a temporary download URL',
174
+ 'path',
175
+ ));
176
+ }
165
177
  tools = selectDropboxTools(discoveries);
166
178
  const uploadSchema = await fetchToolSchema(ctx.runtime, tools.upload);
167
179
  if (uploadSchema.parameters?.properties?.content?.file_uploadable !== true) {