@notis_ai/cli 0.2.13 → 0.2.15

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 (295) hide show
  1. package/README.md +125 -21
  2. package/dist/agent-hooks/notis-agent-hook.mjs +19750 -0
  3. package/{skills → dist/base-skills}/notis-apps/SKILL.md +93 -58
  4. package/{skills → dist/base-skills}/notis-cli/SKILL.md +9 -9
  5. package/dist/base-skills/notis-query/SKILL.md +705 -0
  6. package/dist/skill-sync/index.js +1528 -0
  7. package/dist/skill-sync/index.js.map +7 -0
  8. package/package.json +5 -2
  9. package/skills/notis-apps/cli.md +44 -18
  10. package/skills/notis-cli/AGENT_INSTRUCTIONS.md +39 -0
  11. package/skills/notis-onboarding/BRIEF.md +16 -0
  12. package/src/agent-hook-entry.js +5 -0
  13. package/src/cli.js +23 -14
  14. package/src/command-specs/agents.js +392 -0
  15. package/src/command-specs/apps.js +1090 -418
  16. package/src/command-specs/auth.js +63 -10
  17. package/src/command-specs/handover.js +374 -0
  18. package/src/command-specs/index.js +9 -0
  19. package/src/command-specs/onboarding.js +69 -5
  20. package/src/command-specs/skills.js +56 -0
  21. package/src/command-specs/tools.js +6 -0
  22. package/src/runtime/agent-memory-state.js +126 -0
  23. package/src/runtime/agent-setup.js +383 -0
  24. package/src/runtime/app-dev-consumers.js +154 -0
  25. package/src/runtime/app-dev-host-lock.js +80 -0
  26. package/src/runtime/app-dev-roots.js +284 -0
  27. package/src/runtime/app-dev-server.js +229 -30
  28. package/src/runtime/app-dev-sessions.js +105 -145
  29. package/src/runtime/app-platform.js +1499 -161
  30. package/src/runtime/app-registry-scaffolds.js +367 -0
  31. package/src/runtime/base-skills.d.ts +20 -0
  32. package/src/runtime/base-skills.js +167 -0
  33. package/src/runtime/delegated-context.js +68 -0
  34. package/src/runtime/git.js +233 -0
  35. package/src/runtime/login-listener.js +15 -0
  36. package/src/runtime/oauth.js +1506 -141
  37. package/src/runtime/output.js +7 -0
  38. package/src/runtime/ports.js +16 -0
  39. package/src/runtime/profiles.js +0 -5
  40. package/src/runtime/skill-sync/cloud-client.ts +96 -0
  41. package/src/runtime/skill-sync/index.ts +644 -0
  42. package/src/runtime/skill-sync/local-scanner.ts +1046 -0
  43. package/src/runtime/skill-sync/symlink-manager.ts +383 -0
  44. package/src/runtime/skill-sync/sync-plan.ts +22 -0
  45. package/src/runtime/skill-sync/types.ts +103 -0
  46. package/src/runtime/skill-sync/write-cloud-skill.ts +50 -0
  47. package/src/runtime/store-screenshot.js +6 -1
  48. package/src/runtime/sync-skills.d.ts +37 -0
  49. package/src/runtime/sync-skills.js +215 -0
  50. package/src/runtime/transport.js +19 -2
  51. package/template/.harness/index.html.tmpl +116 -47
  52. package/template/app/layout.tsx +5 -1
  53. package/template/notis.config.ts +1 -0
  54. package/template/packages/sdk/package.json +2 -1
  55. package/template/packages/sdk/src/components/DocumentEditor.tsx +13 -3
  56. package/template/packages/sdk/src/components/MarkdownEditor.tsx +121 -0
  57. package/template/packages/sdk/src/components/MultiSelectActionBar.tsx +37 -119
  58. package/template/packages/sdk/src/components/MultiSelectCheckbox.tsx +5 -1
  59. package/template/packages/sdk/src/components/MultiSelectDragOverlay.tsx +1 -1
  60. package/template/packages/sdk/src/components/NotisSelectionBoundary.tsx +55 -0
  61. package/template/packages/sdk/src/components/ShortcutHints.tsx +56 -0
  62. package/template/packages/sdk/src/config.ts +71 -0
  63. package/template/packages/sdk/src/documents.ts +28 -1
  64. package/template/packages/sdk/src/hooks/useActiveResource.ts +19 -0
  65. package/template/packages/sdk/src/hooks/useCloudComputer.ts +97 -0
  66. package/template/packages/sdk/src/hooks/useCollectionInteractions.ts +709 -0
  67. package/template/packages/sdk/src/hooks/useDatabaseSubscription.ts +76 -0
  68. package/template/packages/sdk/src/hooks/useHandover.ts +78 -0
  69. package/template/packages/sdk/src/hooks/useMultiSelect.ts +44 -482
  70. package/template/packages/sdk/src/hooks/useTool.ts +5 -4
  71. package/template/packages/sdk/src/index.ts +64 -2
  72. package/template/packages/sdk/src/interactions/actions.ts +46 -0
  73. package/template/packages/sdk/src/interactions/shortcuts.tsx +628 -0
  74. package/template/packages/sdk/src/interactions.ts +41 -0
  75. package/template/packages/sdk/src/provider.tsx +2 -1
  76. package/template/packages/sdk/src/runtime.ts +219 -3
  77. package/dist/scaffolds/notis-database/CHANGELOG.md +0 -5
  78. package/dist/scaffolds/notis-database/app/globals.css +0 -44
  79. package/dist/scaffolds/notis-database/app/layout.tsx +0 -6
  80. package/dist/scaffolds/notis-database/app/page.tsx +0 -1088
  81. package/dist/scaffolds/notis-database/components/ui/badge.tsx +0 -28
  82. package/dist/scaffolds/notis-database/components/ui/button.tsx +0 -53
  83. package/dist/scaffolds/notis-database/components/ui/card.tsx +0 -56
  84. package/dist/scaffolds/notis-database/components/ui/table.tsx +0 -120
  85. package/dist/scaffolds/notis-database/components.json +0 -20
  86. package/dist/scaffolds/notis-database/index.html +0 -12
  87. package/dist/scaffolds/notis-database/lib/types.ts +0 -132
  88. package/dist/scaffolds/notis-database/lib/utils.ts +0 -6
  89. package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
  90. package/dist/scaffolds/notis-database/metadata/screenshot-2.png +0 -0
  91. package/dist/scaffolds/notis-database/metadata/screenshot-3.png +0 -0
  92. package/dist/scaffolds/notis-database/metadata/screenshot-4.png +0 -0
  93. package/dist/scaffolds/notis-database/metadata/screenshot-5.png +0 -0
  94. package/dist/scaffolds/notis-database/metadata/screenshot-fixtures.json +0 -1839
  95. package/dist/scaffolds/notis-database/notis.config.ts +0 -73
  96. package/dist/scaffolds/notis-database/package-lock.json +0 -3935
  97. package/dist/scaffolds/notis-database/package.json +0 -32
  98. package/dist/scaffolds/notis-database/packages/sdk/package.json +0 -36
  99. package/dist/scaffolds/notis-database/packages/sdk/src/components/DocumentEditor.tsx +0 -93
  100. package/dist/scaffolds/notis-database/packages/sdk/src/components/Markdown.tsx +0 -60
  101. package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
  102. package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
  103. package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
  104. package/dist/scaffolds/notis-database/packages/sdk/src/config.ts +0 -196
  105. package/dist/scaffolds/notis-database/packages/sdk/src/documents.ts +0 -229
  106. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useBackend.ts +0 -41
  107. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  108. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocument.ts +0 -78
  109. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocuments.ts +0 -121
  110. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
  111. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotis.ts +0 -34
  112. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
  113. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTool.ts +0 -64
  114. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTools.ts +0 -56
  115. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  116. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
  117. package/dist/scaffolds/notis-database/packages/sdk/src/index.ts +0 -83
  118. package/dist/scaffolds/notis-database/packages/sdk/src/provider.tsx +0 -43
  119. package/dist/scaffolds/notis-database/packages/sdk/src/runtime.ts +0 -220
  120. package/dist/scaffolds/notis-database/packages/sdk/src/styles.css +0 -186
  121. package/dist/scaffolds/notis-database/packages/sdk/src/ui.ts +0 -15
  122. package/dist/scaffolds/notis-database/packages/sdk/src/vite.ts +0 -56
  123. package/dist/scaffolds/notis-database/packages/sdk/tsconfig.json +0 -15
  124. package/dist/scaffolds/notis-database/postcss.config.mjs +0 -8
  125. package/dist/scaffolds/notis-database/src/dev-main.tsx +0 -23
  126. package/dist/scaffolds/notis-database/src/mock-runtime.ts +0 -561
  127. package/dist/scaffolds/notis-database/tailwind.config.ts +0 -59
  128. package/dist/scaffolds/notis-database/tsconfig.json +0 -23
  129. package/dist/scaffolds/notis-database/vite.config.ts +0 -22
  130. package/dist/scaffolds/notis-journal/CHANGELOG.md +0 -29
  131. package/dist/scaffolds/notis-journal/app/globals.css +0 -37
  132. package/dist/scaffolds/notis-journal/app/insights/page.tsx +0 -513
  133. package/dist/scaffolds/notis-journal/app/journal-core.tsx +0 -362
  134. package/dist/scaffolds/notis-journal/app/journal-ui.tsx +0 -337
  135. package/dist/scaffolds/notis-journal/app/layout.tsx +0 -6
  136. package/dist/scaffolds/notis-journal/app/page.tsx +0 -486
  137. package/dist/scaffolds/notis-journal/components/ui/badge.tsx +0 -28
  138. package/dist/scaffolds/notis-journal/components/ui/button.tsx +0 -53
  139. package/dist/scaffolds/notis-journal/components/ui/card.tsx +0 -56
  140. package/dist/scaffolds/notis-journal/components.json +0 -20
  141. package/dist/scaffolds/notis-journal/index.html +0 -12
  142. package/dist/scaffolds/notis-journal/lib/utils.ts +0 -6
  143. package/dist/scaffolds/notis-journal/metadata/screenshot-1.png +0 -0
  144. package/dist/scaffolds/notis-journal/metadata/screenshot-2.png +0 -0
  145. package/dist/scaffolds/notis-journal/metadata/screenshot-3.png +0 -0
  146. package/dist/scaffolds/notis-journal/metadata/screenshot-4.png +0 -0
  147. package/dist/scaffolds/notis-journal/metadata/screenshot-5.png +0 -0
  148. package/dist/scaffolds/notis-journal/metadata/screenshot-6.png +0 -0
  149. package/dist/scaffolds/notis-journal/metadata/screenshot-fixtures.json +0 -132
  150. package/dist/scaffolds/notis-journal/notis.config.ts +0 -93
  151. package/dist/scaffolds/notis-journal/package-lock.json +0 -4615
  152. package/dist/scaffolds/notis-journal/package.json +0 -34
  153. package/dist/scaffolds/notis-journal/packages/sdk/package.json +0 -36
  154. package/dist/scaffolds/notis-journal/packages/sdk/src/components/DocumentEditor.tsx +0 -93
  155. package/dist/scaffolds/notis-journal/packages/sdk/src/components/Markdown.tsx +0 -60
  156. package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
  157. package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
  158. package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
  159. package/dist/scaffolds/notis-journal/packages/sdk/src/config.ts +0 -196
  160. package/dist/scaffolds/notis-journal/packages/sdk/src/documents.ts +0 -229
  161. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useBackend.ts +0 -41
  162. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  163. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocument.ts +0 -78
  164. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocuments.ts +0 -121
  165. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
  166. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotis.ts +0 -34
  167. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
  168. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTool.ts +0 -64
  169. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTools.ts +0 -56
  170. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  171. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
  172. package/dist/scaffolds/notis-journal/packages/sdk/src/index.ts +0 -83
  173. package/dist/scaffolds/notis-journal/packages/sdk/src/provider.tsx +0 -43
  174. package/dist/scaffolds/notis-journal/packages/sdk/src/runtime.ts +0 -220
  175. package/dist/scaffolds/notis-journal/packages/sdk/src/styles.css +0 -186
  176. package/dist/scaffolds/notis-journal/packages/sdk/src/ui.ts +0 -15
  177. package/dist/scaffolds/notis-journal/packages/sdk/src/vite.ts +0 -56
  178. package/dist/scaffolds/notis-journal/packages/sdk/tsconfig.json +0 -15
  179. package/dist/scaffolds/notis-journal/postcss.config.mjs +0 -8
  180. package/dist/scaffolds/notis-journal/skills/journal-onboarding/SKILL.md +0 -120
  181. package/dist/scaffolds/notis-journal/src/dev-main.tsx +0 -58
  182. package/dist/scaffolds/notis-journal/src/mock-runtime.ts +0 -197
  183. package/dist/scaffolds/notis-journal/tailwind.config.ts +0 -58
  184. package/dist/scaffolds/notis-journal/tsconfig.json +0 -23
  185. package/dist/scaffolds/notis-journal/vite.config.ts +0 -10
  186. package/dist/scaffolds/notis-notes/CHANGELOG.md +0 -5
  187. package/dist/scaffolds/notis-notes/app/globals.css +0 -3
  188. package/dist/scaffolds/notis-notes/app/layout.tsx +0 -6
  189. package/dist/scaffolds/notis-notes/app/page.tsx +0 -1943
  190. package/dist/scaffolds/notis-notes/app/phosphor-icons.ts +0 -596
  191. package/dist/scaffolds/notis-notes/components/ui/badge.tsx +0 -28
  192. package/dist/scaffolds/notis-notes/components/ui/button.tsx +0 -53
  193. package/dist/scaffolds/notis-notes/components/ui/card.tsx +0 -56
  194. package/dist/scaffolds/notis-notes/components.json +0 -20
  195. package/dist/scaffolds/notis-notes/lib/utils.ts +0 -6
  196. package/dist/scaffolds/notis-notes/lib/visible-properties.ts +0 -144
  197. package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
  198. package/dist/scaffolds/notis-notes/metadata/screenshot-2.png +0 -0
  199. package/dist/scaffolds/notis-notes/metadata/screenshot-3.png +0 -0
  200. package/dist/scaffolds/notis-notes/metadata/screenshot-4.png +0 -0
  201. package/dist/scaffolds/notis-notes/metadata/screenshot-5.png +0 -0
  202. package/dist/scaffolds/notis-notes/metadata/screenshot-fixtures.json +0 -752
  203. package/dist/scaffolds/notis-notes/notis.config.ts +0 -80
  204. package/dist/scaffolds/notis-notes/package-lock.json +0 -4636
  205. package/dist/scaffolds/notis-notes/package.json +0 -35
  206. package/dist/scaffolds/notis-notes/packages/sdk/package.json +0 -36
  207. package/dist/scaffolds/notis-notes/packages/sdk/src/components/DocumentEditor.tsx +0 -93
  208. package/dist/scaffolds/notis-notes/packages/sdk/src/components/Markdown.tsx +0 -60
  209. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
  210. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
  211. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
  212. package/dist/scaffolds/notis-notes/packages/sdk/src/config.ts +0 -196
  213. package/dist/scaffolds/notis-notes/packages/sdk/src/documents.ts +0 -229
  214. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useBackend.ts +0 -41
  215. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  216. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocument.ts +0 -78
  217. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocuments.ts +0 -121
  218. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
  219. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotis.ts +0 -34
  220. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
  221. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTool.ts +0 -64
  222. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTools.ts +0 -56
  223. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  224. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
  225. package/dist/scaffolds/notis-notes/packages/sdk/src/index.ts +0 -83
  226. package/dist/scaffolds/notis-notes/packages/sdk/src/provider.tsx +0 -43
  227. package/dist/scaffolds/notis-notes/packages/sdk/src/runtime.ts +0 -220
  228. package/dist/scaffolds/notis-notes/packages/sdk/src/styles.css +0 -186
  229. package/dist/scaffolds/notis-notes/packages/sdk/src/ui.ts +0 -15
  230. package/dist/scaffolds/notis-notes/packages/sdk/src/vite.ts +0 -56
  231. package/dist/scaffolds/notis-notes/packages/sdk/tsconfig.json +0 -15
  232. package/dist/scaffolds/notis-notes/postcss.config.mjs +0 -8
  233. package/dist/scaffolds/notis-notes/tailwind.config.ts +0 -58
  234. package/dist/scaffolds/notis-notes/tsconfig.json +0 -23
  235. package/dist/scaffolds/notis-notes/vite.config.ts +0 -10
  236. package/dist/scaffolds/notis-random/CHANGELOG.md +0 -15
  237. package/dist/scaffolds/notis-random/README.md +0 -33
  238. package/dist/scaffolds/notis-random/app/globals.css +0 -11
  239. package/dist/scaffolds/notis-random/app/history/page.tsx +0 -67
  240. package/dist/scaffolds/notis-random/app/layout.tsx +0 -7
  241. package/dist/scaffolds/notis-random/app/page.tsx +0 -289
  242. package/dist/scaffolds/notis-random/components/ui/button.tsx +0 -50
  243. package/dist/scaffolds/notis-random/components/ui/card.tsx +0 -16
  244. package/dist/scaffolds/notis-random/components/ui/input.tsx +0 -23
  245. package/dist/scaffolds/notis-random/components.json +0 -20
  246. package/dist/scaffolds/notis-random/index.html +0 -12
  247. package/dist/scaffolds/notis-random/lib/notis-tools.ts +0 -128
  248. package/dist/scaffolds/notis-random/lib/rng.ts +0 -202
  249. package/dist/scaffolds/notis-random/lib/roll-record.ts +0 -189
  250. package/dist/scaffolds/notis-random/lib/utils.ts +0 -25
  251. package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
  252. package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
  253. package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
  254. package/dist/scaffolds/notis-random/metadata/screenshot-4.png +0 -0
  255. package/dist/scaffolds/notis-random/metadata/screenshot-5.png +0 -0
  256. package/dist/scaffolds/notis-random/metadata/screenshot-fixtures.json +0 -753
  257. package/dist/scaffolds/notis-random/notis.config.ts +0 -86
  258. package/dist/scaffolds/notis-random/package-lock.json +0 -4513
  259. package/dist/scaffolds/notis-random/package.json +0 -36
  260. package/dist/scaffolds/notis-random/packages/sdk/package.json +0 -36
  261. package/dist/scaffolds/notis-random/packages/sdk/src/components/DocumentEditor.tsx +0 -93
  262. package/dist/scaffolds/notis-random/packages/sdk/src/components/Markdown.tsx +0 -60
  263. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
  264. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
  265. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
  266. package/dist/scaffolds/notis-random/packages/sdk/src/config.ts +0 -196
  267. package/dist/scaffolds/notis-random/packages/sdk/src/documents.ts +0 -229
  268. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useBackend.ts +0 -41
  269. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  270. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocument.ts +0 -78
  271. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocuments.ts +0 -121
  272. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
  273. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotis.ts +0 -34
  274. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
  275. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTool.ts +0 -64
  276. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTools.ts +0 -56
  277. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  278. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
  279. package/dist/scaffolds/notis-random/packages/sdk/src/index.ts +0 -83
  280. package/dist/scaffolds/notis-random/packages/sdk/src/provider.tsx +0 -43
  281. package/dist/scaffolds/notis-random/packages/sdk/src/runtime.ts +0 -220
  282. package/dist/scaffolds/notis-random/packages/sdk/src/styles.css +0 -186
  283. package/dist/scaffolds/notis-random/packages/sdk/src/ui.ts +0 -15
  284. package/dist/scaffolds/notis-random/packages/sdk/src/vite.ts +0 -56
  285. package/dist/scaffolds/notis-random/packages/sdk/tsconfig.json +0 -15
  286. package/dist/scaffolds/notis-random/postcss.config.mjs +0 -6
  287. package/dist/scaffolds/notis-random/src/dev-main.tsx +0 -70
  288. package/dist/scaffolds/notis-random/src/mock-runtime.ts +0 -129
  289. package/dist/scaffolds/notis-random/tailwind.config.ts +0 -50
  290. package/dist/scaffolds/notis-random/tsconfig.json +0 -23
  291. package/dist/scaffolds/notis-random/vite.config.ts +0 -11
  292. package/dist/scaffolds.json +0 -49
  293. package/template/metadata/screenshot-1.png +0 -0
  294. package/template/metadata/screenshot-2.png +0 -0
  295. package/template/metadata/screenshot-3.png +0 -0
@@ -0,0 +1,383 @@
1
+ import { promises as fs } from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+
5
+ import {
6
+ AGENTS_DIR,
7
+ LEGACY_AGENTS_SKILLS_DIR,
8
+ NOTIS_SKILL_SYNC_ROOT,
9
+ safeName,
10
+ } from './local-scanner';
11
+ import { normalizeAgentTargets, type CloudSkill, type NotisSyncState } from './types';
12
+
13
+ const HOME_DIR = os.homedir();
14
+
15
+ const EXTERNAL_AGENT_SKILL_DIRS = {
16
+ claude_code: path.join(HOME_DIR, '.claude', 'skills'),
17
+ cursor: path.join(HOME_DIR, '.cursor', 'skills'),
18
+ codex: path.join(HOME_DIR, '.codex', 'skills'),
19
+ } as const;
20
+
21
+ type ExternalAgent = keyof typeof EXTERNAL_AGENT_SKILL_DIRS;
22
+
23
+ const EXTERNAL_AGENTS = Object.keys(EXTERNAL_AGENT_SKILL_DIRS) as ExternalAgent[];
24
+
25
+ export interface DeletedAgentSymlink {
26
+ skillId: string;
27
+ skillName: string;
28
+ agent: ExternalAgent;
29
+ }
30
+
31
+ type AgentSkillDirs = {
32
+ notis: string;
33
+ } & typeof EXTERNAL_AGENT_SKILL_DIRS;
34
+
35
+ export interface SymlinkSyncOptions {
36
+ agentSkillDirs?: Partial<AgentSkillDirs>;
37
+ legacyGlobalSkillsDir?: string;
38
+ removeUndesired?: boolean;
39
+ }
40
+
41
+ export interface RemoveSkillSymlinkOptions extends SymlinkSyncOptions {
42
+ ownership?: 'any-managed' | 'exact-skill-dir';
43
+ }
44
+
45
+ export interface SymlinkSyncResult {
46
+ linked: number;
47
+ removed: number;
48
+ skipped: number;
49
+ }
50
+
51
+ /** Remove only links into another account's managed mirror. This is safe even
52
+ * when automatic sync is disabled: it never changes either account mirror and
53
+ * preserves links owned by the currently authenticated account. */
54
+ export async function removeForeignAccountSymlinks(
55
+ skillsDir: string,
56
+ options: SymlinkSyncOptions = {},
57
+ ): Promise<number> {
58
+ const agentSkillDirs = { ...defaultAgentSkillDirs(skillsDir), ...options.agentSkillDirs };
59
+ const currentRoot = path.resolve(skillsDir);
60
+ const foreignCapableRoots = [
61
+ path.join(path.resolve(NOTIS_SKILL_SYNC_ROOT), 'users'),
62
+ path.join(path.resolve(AGENTS_DIR), 'notis', 'users'),
63
+ path.dirname(path.dirname(currentRoot)),
64
+ ];
65
+ let removed = 0;
66
+ const legacyGlobalSkillsDir = options.legacyGlobalSkillsDir || LEGACY_AGENTS_SKILLS_DIR;
67
+ for (const agentDir of new Set([...Object.values(agentSkillDirs), legacyGlobalSkillsDir])) {
68
+ let entries;
69
+ try {
70
+ entries = await fs.readdir(agentDir, { withFileTypes: true });
71
+ } catch (error) {
72
+ if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') continue;
73
+ throw error;
74
+ }
75
+ for (const entry of entries) {
76
+ if (!entry.isSymbolicLink()) continue;
77
+ const entryPath = path.join(agentDir, entry.name);
78
+ try {
79
+ const target = await fs.readlink(entryPath);
80
+ const resolvedTarget = path.resolve(path.dirname(entryPath), target);
81
+ const belongsToAnyAccount = foreignCapableRoots.some((root) => (
82
+ resolvedTarget.startsWith(`${root}${path.sep}`)
83
+ ));
84
+ const belongsToCurrentAccount = resolvedTarget === currentRoot
85
+ || resolvedTarget.startsWith(`${currentRoot}${path.sep}`);
86
+ if (belongsToAnyAccount && !belongsToCurrentAccount) {
87
+ await fs.unlink(entryPath);
88
+ removed += 1;
89
+ }
90
+ } catch (error) {
91
+ // A disappearing link is already safe. Permission and I/O failures
92
+ // must fail closed: otherwise a previous account's link can remain
93
+ // exposed while the repeating sync reports success.
94
+ if ((error as NodeJS.ErrnoException)?.code !== 'ENOENT') throw error;
95
+ }
96
+ }
97
+ }
98
+ return removed;
99
+ }
100
+
101
+ function managedSkillRoots(
102
+ skillsDir: string,
103
+ legacyGlobalSkillsDir: string = LEGACY_AGENTS_SKILLS_DIR,
104
+ ): string[] {
105
+ const resolvedSkillsDir = path.resolve(skillsDir);
106
+ const scopedUsersRoot = path.dirname(path.dirname(resolvedSkillsDir));
107
+ const roots = [
108
+ resolvedSkillsDir,
109
+ path.resolve(legacyGlobalSkillsDir),
110
+ path.join(path.resolve(NOTIS_SKILL_SYNC_ROOT), 'users'),
111
+ path.join(path.resolve(AGENTS_DIR), 'notis', 'users'),
112
+ ];
113
+ if (path.basename(scopedUsersRoot) === 'users') {
114
+ roots.push(scopedUsersRoot);
115
+ }
116
+ return roots;
117
+ }
118
+
119
+ async function isManagedSymlink(linkPath: string, managedRoots: string[]): Promise<boolean> {
120
+ try {
121
+ const stats = await fs.lstat(linkPath);
122
+ if (!stats.isSymbolicLink()) {
123
+ return false;
124
+ }
125
+
126
+ const target = await fs.readlink(linkPath);
127
+ const resolvedTarget = path.resolve(path.dirname(linkPath), target);
128
+ return managedRoots.some((root) => (
129
+ resolvedTarget === root || resolvedTarget.startsWith(`${root}${path.sep}`)
130
+ ));
131
+ } catch {
132
+ return false;
133
+ }
134
+ }
135
+
136
+ async function ensureCorrectSymlink(linkPath: string, targetPath: string): Promise<'linked' | 'skipped' | 'blocked'> {
137
+ try {
138
+ const stats = await fs.lstat(linkPath);
139
+ if (stats.isSymbolicLink()) {
140
+ const currentTarget = await fs.readlink(linkPath);
141
+ const resolvedTarget = path.resolve(path.dirname(linkPath), currentTarget);
142
+ if (resolvedTarget === targetPath) {
143
+ return 'skipped';
144
+ }
145
+ await fs.unlink(linkPath);
146
+ } else {
147
+ return 'blocked';
148
+ }
149
+ } catch {
150
+ // Missing path is expected.
151
+ }
152
+
153
+ const relativePath = path.relative(path.dirname(linkPath), targetPath);
154
+ await fs.symlink(relativePath, linkPath);
155
+ return 'linked';
156
+ }
157
+
158
+ function defaultAgentSkillDirs(skillsDir: string): AgentSkillDirs {
159
+ return {
160
+ notis: skillsDir,
161
+ ...EXTERNAL_AGENT_SKILL_DIRS,
162
+ };
163
+ }
164
+
165
+ async function removeUndesiredManagedSymlinks(
166
+ agentDir: string,
167
+ desiredSkills: Set<string>,
168
+ managedRoots: string[],
169
+ ): Promise<number> {
170
+ let removed = 0;
171
+ try {
172
+ await fs.mkdir(agentDir, { recursive: true });
173
+ const existingEntries = await fs.readdir(agentDir, { withFileTypes: true });
174
+ for (const entry of existingEntries) {
175
+ const entryPath = path.join(agentDir, entry.name);
176
+ if (!desiredSkills.has(entry.name) && await isManagedSymlink(entryPath, managedRoots)) {
177
+ await fs.unlink(entryPath);
178
+ removed += 1;
179
+ }
180
+ }
181
+ } catch {
182
+ // Missing or unreadable agent directories should not block skill sync.
183
+ }
184
+ return removed;
185
+ }
186
+
187
+ export async function removeAllSymlinksForSkill(
188
+ skillName: string,
189
+ skillsDir: string = LEGACY_AGENTS_SKILLS_DIR,
190
+ options: RemoveSkillSymlinkOptions = {},
191
+ ): Promise<number> {
192
+ let removed = 0;
193
+ const agentSkillDirs = { ...defaultAgentSkillDirs(skillsDir), ...options.agentSkillDirs };
194
+ const legacyGlobalSkillsDir = options.legacyGlobalSkillsDir || LEGACY_AGENTS_SKILLS_DIR;
195
+ const managedRoots = managedSkillRoots(skillsDir, legacyGlobalSkillsDir);
196
+ const agentDirs = new Set([...Object.values(agentSkillDirs), legacyGlobalSkillsDir]);
197
+ const expectedTarget = path.resolve(skillsDir, safeName(skillName, skillsDir));
198
+ for (const agentDir of agentDirs) {
199
+ const linkPath = path.join(agentDir, safeName(skillName, agentDir));
200
+ const owned = options.ownership === 'exact-skill-dir'
201
+ ? await isManagedSymlink(linkPath, [expectedTarget])
202
+ : await isManagedSymlink(linkPath, managedRoots);
203
+ if (owned) {
204
+ await fs.unlink(linkPath);
205
+ removed += 1;
206
+ }
207
+ }
208
+ return removed;
209
+ }
210
+
211
+ /**
212
+ * Detect per-agent skill symlinks that we created on a prior sync but that the user has since
213
+ * deleted locally (e.g. `rm ~/.claude/skills/<skill>`). The caller turns each result into a
214
+ * per-agent deactivation so the deletion sticks instead of being recreated on the next pass.
215
+ *
216
+ * A pair is flagged only when the skill is cloud-active for that agent, the previous sync state
217
+ * recorded it active for that agent (so we know we created the link), and the link is now truly
218
+ * absent (lstat ENOENT). Stale/wrong-target links and real files blocking the path are NOT
219
+ * deletions — those are repaired/skipped by syncSymlinks as before. Only external agents are
220
+ * considered; `notis` has no symlink (its dir is the source skills folder).
221
+ */
222
+ export async function detectDeletedAgentSymlinks(
223
+ cloudSkills: CloudSkill[],
224
+ previousState: NotisSyncState,
225
+ skillsDir: string = LEGACY_AGENTS_SKILLS_DIR,
226
+ options: SymlinkSyncOptions = {},
227
+ ): Promise<DeletedAgentSymlink[]> {
228
+ const agentSkillDirs = { ...defaultAgentSkillDirs(skillsDir), ...options.agentSkillDirs };
229
+ const deletions: DeletedAgentSymlink[] = [];
230
+
231
+ for (const agent of EXTERNAL_AGENTS) {
232
+ const agentDir = agentSkillDirs[agent];
233
+ if (path.resolve(agentDir) === path.resolve(skillsDir)) {
234
+ continue;
235
+ }
236
+
237
+ // A missing (or non-directory) agent skills directory is NOT a per-skill deletion: removing
238
+ // ~/.claude/skills wholesale would otherwise make every entry's link path ENOENT and
239
+ // deactivate every skill for that agent. syncSymlinks recreates the directory and its links,
240
+ // so only absent entries INSIDE an existing agent directory count as explicit user deletions.
241
+ try {
242
+ const dirStat = await fs.stat(agentDir);
243
+ if (!dirStat.isDirectory()) {
244
+ continue;
245
+ }
246
+ } catch {
247
+ continue;
248
+ }
249
+
250
+ for (const skill of cloudSkills) {
251
+ if (skill.status !== 'active') {
252
+ continue;
253
+ }
254
+ if (!skill.id || skill.id.startsWith('local-')) {
255
+ continue;
256
+ }
257
+ const previous = previousState.skills[skill.name];
258
+ if (!previous) {
259
+ continue;
260
+ }
261
+
262
+ const cloudTargets = normalizeAgentTargets(skill.agent_targets);
263
+ const previousTargets = normalizeAgentTargets(previous.agentTargets);
264
+ if (!cloudTargets[agent] || !previousTargets[agent]) {
265
+ continue;
266
+ }
267
+
268
+ let safeSkillName: string;
269
+ try {
270
+ safeSkillName = safeName(skill.name, skillsDir);
271
+ } catch {
272
+ continue;
273
+ }
274
+
275
+ const linkPath = path.join(agentDir, safeName(safeSkillName, agentDir));
276
+ try {
277
+ await fs.lstat(linkPath);
278
+ } catch (error) {
279
+ if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') {
280
+ deletions.push({ skillId: skill.id, skillName: skill.name, agent });
281
+ }
282
+ }
283
+ }
284
+ }
285
+
286
+ return deletions;
287
+ }
288
+
289
+ export async function syncSymlinks(
290
+ skills: CloudSkill[],
291
+ skillsDir: string = LEGACY_AGENTS_SKILLS_DIR,
292
+ options: SymlinkSyncOptions = {},
293
+ ): Promise<SymlinkSyncResult> {
294
+ await fs.mkdir(skillsDir, { recursive: true });
295
+
296
+ const result: SymlinkSyncResult = {
297
+ linked: 0,
298
+ removed: 0,
299
+ skipped: 0,
300
+ };
301
+ const agentSkillDirs = { ...defaultAgentSkillDirs(skillsDir), ...options.agentSkillDirs };
302
+ const legacyGlobalSkillsDir = options.legacyGlobalSkillsDir || LEGACY_AGENTS_SKILLS_DIR;
303
+ const managedRoots = managedSkillRoots(skillsDir, legacyGlobalSkillsDir);
304
+
305
+ const desiredByAgent = {
306
+ notis: new Set<string>(),
307
+ claude_code: new Set<string>(),
308
+ cursor: new Set<string>(),
309
+ codex: new Set<string>(),
310
+ };
311
+
312
+ for (const skill of skills) {
313
+ if (skill.status !== 'active') {
314
+ continue;
315
+ }
316
+
317
+ const safeSkillName = safeName(skill.name, skillsDir);
318
+ const targets = normalizeAgentTargets(skill.agent_targets);
319
+ if (targets.notis) {
320
+ desiredByAgent.notis.add(safeSkillName);
321
+ }
322
+ if (targets.claude_code) {
323
+ desiredByAgent.claude_code.add(safeSkillName);
324
+ }
325
+ if (targets.cursor) {
326
+ desiredByAgent.cursor.add(safeSkillName);
327
+ }
328
+ if (targets.codex) {
329
+ desiredByAgent.codex.add(safeSkillName);
330
+ }
331
+ }
332
+
333
+ for (const [agent, agentDir] of Object.entries(agentSkillDirs) as Array<[keyof AgentSkillDirs, string]>) {
334
+ if (path.resolve(agentDir) === path.resolve(skillsDir)) {
335
+ result.skipped += desiredByAgent[agent].size;
336
+ continue;
337
+ }
338
+
339
+ await fs.mkdir(agentDir, { recursive: true });
340
+
341
+ const desiredSkills = desiredByAgent[agent];
342
+ if (options.removeUndesired !== false) {
343
+ result.removed += await removeUndesiredManagedSymlinks(agentDir, desiredSkills, managedRoots);
344
+ }
345
+
346
+ for (const skillName of desiredSkills) {
347
+ const targetPath = path.join(skillsDir, skillName);
348
+ const linkPath = path.join(agentDir, safeName(skillName, agentDir));
349
+ try {
350
+ await fs.access(targetPath);
351
+ } catch {
352
+ result.skipped += 1;
353
+ continue;
354
+ }
355
+ const syncOutcome = await ensureCorrectSymlink(linkPath, targetPath);
356
+ if (syncOutcome === 'linked') {
357
+ result.linked += 1;
358
+ } else if (syncOutcome === 'blocked') {
359
+ console.warn(
360
+ `[skill-sync] Could not link "${skillName}" for ${agent}: non-symlink entry blocks ${linkPath}`,
361
+ );
362
+ result.skipped += 1;
363
+ } else {
364
+ result.skipped += 1;
365
+ }
366
+ }
367
+ }
368
+
369
+ if (
370
+ options.removeUndesired !== false
371
+ && !Object.values(agentSkillDirs).some(
372
+ (agentDir) => path.resolve(agentDir) === path.resolve(legacyGlobalSkillsDir),
373
+ )
374
+ ) {
375
+ result.removed += await removeUndesiredManagedSymlinks(
376
+ legacyGlobalSkillsDir,
377
+ new Set(),
378
+ managedRoots,
379
+ );
380
+ }
381
+
382
+ return result;
383
+ }
@@ -0,0 +1,22 @@
1
+ import type { LocalSkill, NotisSyncState } from "./types";
2
+
3
+ export function getPushCandidates(
4
+ localSkills: LocalSkill[],
5
+ syncState: NotisSyncState,
6
+ cloudCuratedSkillNames: ReadonlySet<string> = new Set(),
7
+ cloudSkillNames?: ReadonlySet<string>,
8
+ ): LocalSkill[] {
9
+ return localSkills.filter((skill) => {
10
+ if (cloudCuratedSkillNames.has(skill.name)) {
11
+ return false;
12
+ }
13
+ const previous = syncState.skills[skill.name];
14
+ if (!previous) {
15
+ return true;
16
+ }
17
+ if (cloudSkillNames && !cloudSkillNames.has(skill.name)) {
18
+ return false;
19
+ }
20
+ return previous.folderHash !== skill.folderHash;
21
+ });
22
+ }
@@ -0,0 +1,103 @@
1
+ export interface AgentTargets {
2
+ notis: boolean;
3
+ claude_code: boolean;
4
+ cursor: boolean;
5
+ codex: boolean;
6
+ }
7
+
8
+ export interface SyncedSkill {
9
+ cloudId: string;
10
+ folderHash: string;
11
+ agentTargets: AgentTargets;
12
+ syncedAt: string;
13
+ }
14
+
15
+ export interface NotisSyncState {
16
+ version: 1;
17
+ lastSyncedAt: string | null;
18
+ skills: Record<string, SyncedSkill>;
19
+ }
20
+
21
+ export interface LocalSkill {
22
+ name: string;
23
+ skillMd: string;
24
+ description: string;
25
+ folderHash: string;
26
+ directoryPath: string;
27
+ sourceUrl?: string;
28
+ bundleFiles?: BundleFile[];
29
+ }
30
+
31
+ export interface BundleFile {
32
+ path: string;
33
+ content_b64: string;
34
+ }
35
+
36
+ export interface CloudSkill {
37
+ id: string;
38
+ name: string;
39
+ description: string | null;
40
+ skill_md: string | null;
41
+ agent_targets?: Partial<AgentTargets> | null;
42
+ skill_folder_hash?: string | null;
43
+ skill_source_url?: string | null;
44
+ bundle_files?: BundleFile[] | null;
45
+ bundle_hydration_failed?: boolean | null;
46
+ source: string;
47
+ status: string;
48
+ }
49
+
50
+ export interface SyncSettings {
51
+ /** Server-verified canonical Notis account id used to scope local mirrors. */
52
+ user_id?: string;
53
+ sync_enabled: boolean;
54
+ last_synced_at: string | null;
55
+ }
56
+
57
+ export interface EntitlementAccessDetail {
58
+ status: "error";
59
+ code: "entitlement_upgrade_required";
60
+ upgrade_required: true;
61
+ entitlement: string;
62
+ required_plan: string;
63
+ upgrade_url: string;
64
+ message: string;
65
+ next_action: Record<string, unknown>;
66
+ }
67
+
68
+ export interface SyncPullResponse {
69
+ skills: CloudSkill[];
70
+ sync_enabled: boolean;
71
+ last_synced_at: string | null;
72
+ /** Legacy servers could return a successful empty denial. Current servers
73
+ * return HTTP 403; either form must preserve existing local state. */
74
+ entitlement_access?: EntitlementAccessDetail;
75
+ }
76
+
77
+ /** A skill the server could not sync, with a user-facing reason (e.g. an invalid
78
+ * SKILL.md description rejected by OpenAI). One bad skill no longer fails the batch. */
79
+ export interface SkillSyncFailure {
80
+ name: string;
81
+ error: string;
82
+ }
83
+
84
+ export interface SyncPushResponse {
85
+ skills: unknown[];
86
+ failed?: SkillSyncFailure[];
87
+ }
88
+
89
+ export const DEFAULT_AGENT_TARGETS: AgentTargets = {
90
+ notis: true,
91
+ claude_code: true,
92
+ cursor: true,
93
+ codex: true,
94
+ };
95
+
96
+ export function normalizeAgentTargets(targets?: Partial<AgentTargets> | null): AgentTargets {
97
+ return {
98
+ notis: Boolean(targets?.notis ?? DEFAULT_AGENT_TARGETS.notis),
99
+ claude_code: Boolean(targets?.claude_code ?? DEFAULT_AGENT_TARGETS.claude_code),
100
+ cursor: Boolean(targets?.cursor ?? DEFAULT_AGENT_TARGETS.cursor),
101
+ codex: Boolean(targets?.codex ?? DEFAULT_AGENT_TARGETS.codex),
102
+ };
103
+ }
@@ -0,0 +1,50 @@
1
+ import type { CloudSkill } from './types';
2
+
3
+ interface WriteCloudSkillDependencies {
4
+ downloadSkillBundle: (bundleUrl: string) => Promise<Buffer>;
5
+ writeCloudSkillToDisk: (skill: CloudSkill, bundleBytes?: Buffer) => Promise<boolean>;
6
+ onWarning?: (message: string, error: unknown) => void;
7
+ }
8
+
9
+ export async function writeCloudSkillWithBundleFallback(
10
+ skill: CloudSkill,
11
+ dependencies: WriteCloudSkillDependencies,
12
+ ): Promise<boolean> {
13
+ if (skill.skill_source_url) {
14
+ try {
15
+ const bundleBytes = await dependencies.downloadSkillBundle(skill.skill_source_url);
16
+ const wroteBundleToDisk = await dependencies.writeCloudSkillToDisk(skill, bundleBytes);
17
+ if (wroteBundleToDisk) {
18
+ return true;
19
+ }
20
+
21
+ dependencies.onWarning?.(
22
+ `Bundle sync for "${skill.name}" produced no local changes, falling back to SKILL.md payload.`,
23
+ new Error('Bundle write returned false'),
24
+ );
25
+ } catch (error) {
26
+ dependencies.onWarning?.(
27
+ `Failed to apply bundle sync for "${skill.name}", falling back to SKILL.md payload.`,
28
+ error,
29
+ );
30
+ }
31
+ }
32
+
33
+ if (skill.bundle_hydration_failed) {
34
+ dependencies.onWarning?.(
35
+ `Skipping markdown fallback for synced skill "${skill.name}" because its stored bundle could not be hydrated by the server.`,
36
+ new Error('Bundle hydration failed'),
37
+ );
38
+ return false;
39
+ }
40
+
41
+ try {
42
+ return await dependencies.writeCloudSkillToDisk(skill);
43
+ } catch (error) {
44
+ dependencies.onWarning?.(
45
+ `Failed to write synced skill "${skill.name}" to disk.`,
46
+ error,
47
+ );
48
+ return false;
49
+ }
50
+ }
@@ -1,4 +1,3 @@
1
- import sharp from 'sharp';
2
1
  import { fileURLToPath } from 'node:url';
3
2
 
4
3
  const STORE_SCREENSHOT_ASPECT = 16 / 10;
@@ -19,6 +18,10 @@ const ACCENT_PALETTES = {
19
18
 
20
19
  const ACCENT_NAMES = Object.keys(ACCENT_PALETTES);
21
20
 
21
+ async function loadSharp() {
22
+ return (await import('sharp')).default;
23
+ }
24
+
22
25
  function stableHash(value) {
23
26
  let hash = 2166136261;
24
27
  for (const character of String(value || 'notis-app')) {
@@ -59,6 +62,7 @@ export function storeScreenshotLayout(width = 2000, height = 1250) {
59
62
  }
60
63
 
61
64
  async function backdropImage(layout) {
65
+ const sharp = await loadSharp();
62
66
  return sharp(SHARED_BACKDROP_PATH)
63
67
  .resize(layout.width, layout.height, { fit: 'cover', position: 'centre' })
64
68
  .png()
@@ -88,6 +92,7 @@ export async function composeStoreScreenshot({
88
92
  focused = false,
89
93
  theme = 'light',
90
94
  }) {
95
+ const sharp = await loadSharp();
91
96
  const resolvedTheme = theme === 'dark' ? 'dark' : 'light';
92
97
  const layout = storeScreenshotLayout(width, height);
93
98
  const resolvedAccent = resolveStoreScreenshotAccent(accent, seed);
@@ -0,0 +1,37 @@
1
+ import type { BaseSkillReconcileResult } from './base-skills.js';
2
+
3
+ export interface ReconcileAllSkillsResult {
4
+ baseSkills: string[];
5
+ baseInstalled: number;
6
+ baseLinked: number;
7
+ baseBackups: string[];
8
+ }
9
+
10
+ export interface SkillSyncLockOptions {
11
+ home?: string;
12
+ timeoutMs?: number;
13
+ staleMs?: number;
14
+ pollMs?: number;
15
+ now?: () => number;
16
+ }
17
+
18
+ export function withSkillSyncLock<T>(
19
+ callback: () => Promise<T> | T,
20
+ options?: SkillSyncLockOptions,
21
+ ): Promise<T>;
22
+
23
+ export function reconcileAllSkills<T extends object>(options: {
24
+ serverUrl: string;
25
+ jwt: string;
26
+ honorSyncEnabled: boolean;
27
+ userId?: string | null;
28
+ home?: string;
29
+ runAccountSync: (
30
+ serverUrl: string,
31
+ jwt: string,
32
+ dependencies: Record<string, never>,
33
+ options: { honorSyncEnabled: boolean },
34
+ ) => Promise<T>;
35
+ reconcileBase?: (options: { userId?: string | null; home?: string }) => BaseSkillReconcileResult;
36
+ lockOptions?: Omit<SkillSyncLockOptions, 'home'>;
37
+ }): Promise<T & ReconcileAllSkillsResult>;