handoff-app 2.0.0-9 → 2.0.0

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 (303) hide show
  1. package/{Changelog.md → CHANGELOG.md} +85 -1
  2. package/{License.md → LICENSE.md} +1 -1
  3. package/README.md +813 -406
  4. package/RECIPES.md +975 -0
  5. package/UPGRADE.md +57 -9
  6. package/components.json +20 -20
  7. package/config/docs/foundations/colors/guidelines.md +0 -1
  8. package/config/docs/foundations/colors.md +0 -1
  9. package/config/docs/foundations/effects/guidelines.md +0 -1
  10. package/config/docs/foundations/effects/resources.md +0 -1
  11. package/config/docs/foundations/effects.md +0 -1
  12. package/config/docs/foundations/fonts.md +0 -1
  13. package/config/docs/foundations/grid.md +0 -1
  14. package/config/docs/foundations/icon.md +0 -1
  15. package/config/docs/foundations/icons/guidelines.md +0 -1
  16. package/config/docs/foundations/icons.md +0 -1
  17. package/config/docs/foundations/images/guidelines.md +0 -1
  18. package/config/docs/foundations/images/resources.md +0 -1
  19. package/config/docs/foundations/images.md +0 -1
  20. package/config/docs/foundations/logo/resources.md +0 -1
  21. package/config/docs/foundations/logo.md +0 -1
  22. package/config/docs/foundations/typography/guidelines.md +0 -1
  23. package/config/docs/foundations/typography/resources.md +0 -1
  24. package/config/docs/foundations/typography.md +0 -1
  25. package/config/docs/foundations.md +0 -1
  26. package/config/docs/guidelines.md +0 -1
  27. package/config/docs/index.md +0 -1
  28. package/config/docs/system/tokens/foundations/colors.md +0 -4
  29. package/config/docs/system/tokens/foundations/effects.md +0 -4
  30. package/config/docs/system/tokens/foundations/typography.md +0 -2
  31. package/config/docs/system.md +0 -2
  32. package/config/templates/page.md +2 -1
  33. package/config/templates/page.mdx +2 -1
  34. package/dist/ai/connections.d.ts +66 -0
  35. package/dist/ai/connections.js +149 -0
  36. package/dist/ai/define.d.ts +10 -0
  37. package/dist/ai/define.js +13 -0
  38. package/dist/ai/types.d.ts +26 -0
  39. package/dist/ai/types.js +10 -0
  40. package/dist/app-builder/build.d.ts +14 -7
  41. package/dist/app-builder/build.js +195 -66
  42. package/dist/app-builder/client-config.js +3 -2
  43. package/dist/app-builder/paths.d.ts +0 -12
  44. package/dist/app-builder/paths.js +3 -20
  45. package/dist/app-builder/static-export.js +1 -1
  46. package/dist/app-builder/vercel-output.d.ts +1 -1
  47. package/dist/app-builder/vercel-output.js +5 -4
  48. package/dist/cache/build-cache.d.ts +27 -4
  49. package/dist/cache/build-cache.js +114 -47
  50. package/dist/cache/build-inputs.d.ts +13 -0
  51. package/dist/cache/build-inputs.js +19 -0
  52. package/dist/cache/index.d.ts +2 -1
  53. package/dist/cache/index.js +5 -1
  54. package/dist/cli/create.js +1 -2
  55. package/dist/commands/build/index.d.ts +1 -1
  56. package/dist/commands/build/index.js +1 -1
  57. package/dist/commands/utils.d.ts +3 -3
  58. package/dist/config/defaults.js +7 -4
  59. package/dist/config/from-env.d.ts +4 -0
  60. package/dist/config/from-env.js +38 -1
  61. package/dist/config/helpers.js +2 -2
  62. package/dist/config/index.d.ts +0 -1
  63. package/dist/config/index.js +1 -4
  64. package/dist/config/loader.js +26 -2
  65. package/dist/config/resolve-env.d.ts +2 -1
  66. package/dist/config/resolve-env.js +28 -12
  67. package/dist/config/runtime.js +1 -1
  68. package/dist/index.d.ts +12 -7
  69. package/dist/index.js +18 -22
  70. package/dist/nav/index.d.ts +2 -2
  71. package/dist/nav/registry.d.ts +3 -3
  72. package/dist/nav/registry.js +1 -1
  73. package/dist/nav/workspace.d.ts +3 -3
  74. package/dist/nav/workspace.js +1 -1
  75. package/dist/pipeline/figma.d.ts +13 -2
  76. package/dist/pipeline/figma.js +86 -46
  77. package/dist/pipeline/styles.js +1 -1
  78. package/dist/pipeline/validation.d.ts +1 -1
  79. package/dist/pipeline/validation.js +3 -3
  80. package/dist/providers.d.ts +10 -0
  81. package/dist/providers.js +13 -0
  82. package/dist/registry/asset-storage/define.d.ts +5 -5
  83. package/dist/registry/asset-storage/define.js +4 -4
  84. package/dist/registry/asset-storage/resolve.d.ts +9 -19
  85. package/dist/registry/asset-storage/resolve.js +33 -21
  86. package/dist/registry/asset-storage/types.d.ts +37 -17
  87. package/dist/registry/asset-storage/types.js +5 -5
  88. package/dist/registry/auth/ai-keys.d.ts +32 -0
  89. package/dist/registry/auth/ai-keys.js +69 -0
  90. package/dist/registry/auth/crypto.d.ts +17 -0
  91. package/dist/registry/auth/crypto.js +61 -1
  92. package/dist/registry/auth/index.d.ts +2 -1
  93. package/dist/registry/auth/index.js +11 -2
  94. package/dist/registry/checkout/index.d.ts +2 -1
  95. package/dist/registry/checkout/index.js +57 -127
  96. package/dist/registry/client.js +53 -17
  97. package/dist/registry/db/driver.js +3 -3
  98. package/dist/registry/db/schema.d.ts +261 -3
  99. package/dist/registry/db/schema.js +29 -4
  100. package/dist/registry/email/define.d.ts +10 -0
  101. package/dist/registry/email/define.js +13 -0
  102. package/dist/registry/email/providers/smtp.d.ts +10 -0
  103. package/dist/registry/email/providers/smtp.js +73 -0
  104. package/dist/registry/email/resolve.d.ts +28 -0
  105. package/dist/registry/email/resolve.js +74 -0
  106. package/dist/registry/email/types.d.ts +24 -0
  107. package/dist/registry/email/types.js +6 -0
  108. package/dist/registry/path.d.ts +2 -0
  109. package/dist/registry/path.js +11 -1
  110. package/dist/registry/publish/package.js +8 -0
  111. package/dist/registry/transfer.d.ts +4 -0
  112. package/dist/store/registry.d.ts +3 -3
  113. package/dist/store/registry.js +4 -4
  114. package/dist/transformers/preview/component/builder.d.ts +1 -9
  115. package/dist/transformers/preview/component/builder.js +17 -17
  116. package/dist/transformers/preview/component/css.d.ts +2 -1
  117. package/dist/transformers/preview/component/css.js +8 -5
  118. package/dist/transformers/preview/component/html.d.ts +3 -1
  119. package/dist/transformers/preview/component/html.js +6 -2
  120. package/dist/transformers/preview/component/javascript.d.ts +2 -1
  121. package/dist/transformers/preview/component/javascript.js +10 -4
  122. package/dist/transformers/utils/build.d.ts +5 -0
  123. package/dist/transformers/utils/build.js +21 -4
  124. package/dist/types/config.d.ts +129 -18
  125. package/dist/utils/path.d.ts +1 -5
  126. package/dist/utils/path.js +6 -23
  127. package/docs/api.md +6 -4
  128. package/docs/cli.md +275 -54
  129. package/drizzle/0007_add_ai_keys.sql +11 -0
  130. package/drizzle/0008_add_source_dir.sql +2 -0
  131. package/drizzle/meta/0007_snapshot.json +2013 -0
  132. package/drizzle/meta/0008_snapshot.json +2025 -0
  133. package/drizzle/meta/_journal.json +14 -0
  134. package/package.json +19 -8
  135. package/src/ai/README.md +42 -0
  136. package/src/ai/connections.ts +189 -0
  137. package/src/ai/define.ts +15 -0
  138. package/src/ai/types.ts +30 -0
  139. package/src/app/components/AiAssistant/AiAssistantPanel.tsx +144 -0
  140. package/src/app/components/AiAssistant/AiAssistantProvider.tsx +215 -0
  141. package/src/app/components/AiAssistant/AiConversation.tsx +737 -0
  142. package/src/app/components/AiAssistant/AiToolResult.tsx +99 -0
  143. package/src/app/components/AiAssistant/dockState.ts +86 -0
  144. package/src/app/components/AiAssistant/toolLabel.ts +24 -0
  145. package/src/app/components/AiAssistant/useAiChat.ts +147 -0
  146. package/src/app/components/AiAssistant/useAiConnections.ts +86 -0
  147. package/src/app/components/AiAssistant/usePageContext.ts +63 -0
  148. package/src/app/components/Auth/AccountLayout.tsx +58 -19
  149. package/src/app/components/Auth/AuthShell.tsx +27 -33
  150. package/src/app/components/Auth/RegistryInstallGate.tsx +4 -8
  151. package/src/app/components/Auth/installStatus.ts +29 -0
  152. package/src/app/components/Component/Preview.tsx +5 -2
  153. package/src/app/components/Foundations/ColorGrid.tsx +13 -14
  154. package/src/app/components/Layout/Header.tsx +21 -15
  155. package/src/app/components/Layout/Main.tsx +2 -2
  156. package/src/app/components/Markdown/MarkdownComponents.tsx +30 -32
  157. package/src/app/components/McpIntegration/McpConfigDialog.tsx +12 -51
  158. package/src/app/components/Navigation/MobileNav.tsx +2 -2
  159. package/src/app/components/Navigation/SideNav.tsx +1 -15
  160. package/src/app/components/context/NavProvider.tsx +7 -3
  161. package/src/app/components/ui/attachment.tsx +50 -0
  162. package/src/app/components/ui/bubble.tsx +57 -0
  163. package/src/app/components/ui/button.tsx +3 -1
  164. package/src/app/components/ui/confirm-dialog.tsx +87 -0
  165. package/src/app/components/ui/dialog.tsx +7 -2
  166. package/src/app/components/ui/marker.tsx +56 -0
  167. package/src/app/components/ui/sidebar.tsx +3 -1
  168. package/src/app/components/util/index.ts +3 -12
  169. package/src/app/css/index.css +64 -0
  170. package/src/app/lib/ai/auth.ts +33 -0
  171. package/src/app/lib/ai/context.ts +99 -0
  172. package/src/app/lib/ai/model.ts +79 -0
  173. package/src/app/lib/ai/prompt.ts +36 -0
  174. package/src/app/lib/ai/resolve.ts +97 -0
  175. package/src/app/lib/ai/tools.ts +120 -0
  176. package/src/app/lib/api/methods.ts +13 -0
  177. package/src/app/lib/asset-storage.ts +35 -36
  178. package/src/app/lib/auth/api.ts +4 -12
  179. package/src/app/lib/auth/email.ts +96 -23
  180. package/src/app/lib/docs-api/page-rendering.ts +1 -1
  181. package/src/app/lib/docs-api/registry.ts +2 -3
  182. package/src/app/lib/docs-api/runtime-config.ts +47 -21
  183. package/src/app/lib/mcp/components.ts +523 -0
  184. package/src/app/lib/mcp/page-url.ts +24 -0
  185. package/src/app/lib/mcp/pages.ts +84 -0
  186. package/src/app/lib/mcp/result.ts +33 -0
  187. package/src/app/lib/mcp/server.ts +39 -318
  188. package/src/app/lib/mcp/tokens.ts +334 -0
  189. package/src/app/lib/mcp/tool.ts +19 -0
  190. package/src/app/lib/registry-api/asset-transfer.ts +119 -11
  191. package/src/app/lib/registry-api/errors.ts +2 -0
  192. package/src/app/lib/registry-api/handler.ts +7 -1
  193. package/src/app/lib/registry-api/transfer.ts +28 -6
  194. package/src/app/lib/server-module.ts +34 -0
  195. package/src/app/lib/utils.ts +20 -0
  196. package/src/app/next.config.mjs +26 -24
  197. package/src/app/pages/[...slug]/index.tsx +2 -1
  198. package/src/app/pages/_app.tsx +6 -2
  199. package/src/app/pages/_document.tsx +6 -0
  200. package/src/app/pages/account/ai.tsx +183 -0
  201. package/src/app/pages/account/index.tsx +68 -68
  202. package/src/app/pages/account/tokens.tsx +37 -7
  203. package/src/app/pages/account/users.tsx +75 -15
  204. package/src/app/pages/api/account/ai/keys/[connectionId].ts +60 -0
  205. package/src/app/pages/api/account/ai/keys/index.ts +20 -0
  206. package/src/app/pages/api/admin/users/[id]/[action].ts +80 -11
  207. package/src/app/pages/api/admin/users/index.ts +2 -2
  208. package/src/app/pages/api/ai/chat.ts +115 -0
  209. package/src/app/pages/api/ai/connections.ts +23 -0
  210. package/src/app/pages/api/auth/request-reset.ts +14 -16
  211. package/src/app/pages/api/docs/nav.json.ts +1 -1
  212. package/src/app/pages/api/install.ts +3 -3
  213. package/src/app/pages/api/mcp.ts +6 -0
  214. package/src/app/pages/api/registry/transfer/assets/blobs/[hash]/complete.ts +11 -0
  215. package/src/app/pages/api/registry/transfer/assets/blobs/[hash]/upload.ts +12 -0
  216. package/src/app/pages/api/registry/transfer/assets/blobs/[hash].ts +1 -1
  217. package/src/app/pages/cli/device.tsx +10 -5
  218. package/src/app/pages/foundations/colors.tsx +9 -8
  219. package/src/app/pages/foundations/index.tsx +3 -3
  220. package/src/app/pages/index.tsx +2 -2
  221. package/src/app/pages/install.tsx +7 -2
  222. package/src/app/pages/login.tsx +32 -27
  223. package/src/app/pages/reset-password.tsx +21 -20
  224. package/src/app/pages/system/component/[component]/index.tsx +2 -5
  225. package/src/app/pages/system/index.tsx +11 -11
  226. package/src/app/pages/system/pattern/[pattern]/index.tsx +2 -2
  227. package/src/app/pages/system/tokens/components/[component]/index.tsx +0 -4
  228. package/src/app-builder/README.md +2 -1
  229. package/src/app-builder/build.ts +218 -73
  230. package/src/app-builder/client-config.ts +3 -2
  231. package/src/app-builder/paths.ts +3 -22
  232. package/src/app-builder/static-export.ts +1 -1
  233. package/src/app-builder/vercel-output.ts +5 -4
  234. package/src/cache/README.md +4 -1
  235. package/src/cache/build-cache.ts +128 -43
  236. package/src/cache/build-inputs.ts +28 -0
  237. package/src/cache/index.ts +4 -0
  238. package/src/cli/create.ts +1 -2
  239. package/src/commands/build/index.ts +1 -1
  240. package/src/config/README.md +10 -9
  241. package/src/config/defaults.ts +7 -4
  242. package/src/config/from-env.ts +33 -0
  243. package/src/config/helpers.ts +1 -3
  244. package/src/config/index.ts +0 -3
  245. package/src/config/loader.ts +25 -3
  246. package/src/config/resolve-env.ts +28 -15
  247. package/src/config/runtime.ts +1 -1
  248. package/src/index.ts +33 -29
  249. package/src/nav/index.ts +2 -2
  250. package/src/nav/registry.ts +3 -3
  251. package/src/nav/workspace.ts +3 -3
  252. package/src/pipeline/README.md +1 -1
  253. package/src/pipeline/figma.ts +98 -49
  254. package/src/pipeline/styles.ts +1 -1
  255. package/src/pipeline/validation.ts +3 -3
  256. package/src/providers.ts +18 -0
  257. package/src/registry/asset-storage/define.ts +6 -6
  258. package/src/registry/asset-storage/resolve.ts +38 -37
  259. package/src/registry/asset-storage/types.ts +39 -17
  260. package/src/registry/auth/ai-keys.ts +62 -0
  261. package/src/registry/auth/crypto.ts +63 -1
  262. package/src/registry/auth/index.ts +11 -1
  263. package/src/registry/checkout/index.ts +60 -138
  264. package/src/registry/client.ts +60 -20
  265. package/src/registry/db/driver.ts +2 -2
  266. package/src/registry/db/schema.ts +49 -22
  267. package/src/registry/email/define.ts +15 -0
  268. package/src/registry/email/providers/smtp.ts +39 -0
  269. package/src/registry/email/resolve.ts +94 -0
  270. package/src/registry/email/types.ts +28 -0
  271. package/src/registry/path.ts +11 -0
  272. package/src/registry/publish/package.ts +9 -0
  273. package/src/registry/transfer.ts +4 -0
  274. package/src/store/registry.ts +7 -7
  275. package/src/transformers/preview/component/builder.ts +19 -25
  276. package/src/transformers/preview/component/css.ts +9 -4
  277. package/src/transformers/preview/component/html.ts +6 -2
  278. package/src/transformers/preview/component/javascript.ts +14 -4
  279. package/src/transformers/utils/build.ts +16 -3
  280. package/src/types/config.ts +142 -20
  281. package/src/utils/README.md +1 -1
  282. package/src/utils/path.ts +6 -29
  283. package/templates/default/handoff.config.js.tpl +10 -2
  284. package/templates/default/handoff.config.ts.tpl +10 -2
  285. package/templates/default/handoff.config.with-examples.js.tpl +10 -2
  286. package/templates/default/handoff.config.with-examples.ts.tpl +10 -2
  287. package/templates/default/package.json.tpl +2 -2
  288. package/.editorconfig +0 -19
  289. package/.eslintrc.json +0 -4
  290. package/.prettierignore +0 -0
  291. package/.prettierrc.json +0 -8
  292. package/.vscode/settings.json +0 -17
  293. package/dist/config/validator.d.ts +0 -9
  294. package/dist/config/validator.js +0 -24
  295. package/dist/registry/asset-storage/adapters/vercel-blob.d.ts +0 -17
  296. package/dist/registry/asset-storage/adapters/vercel-blob.js +0 -87
  297. package/src/app/components/old/Header.tsx +0 -89
  298. package/src/app/lib/mcp/shape.ts +0 -596
  299. package/src/app/pages/assets/fonts.tsx +0 -129
  300. package/src/app/pages/assets/index.tsx +0 -91
  301. package/src/app/pages/assets/logos.tsx +0 -75
  302. package/src/config/validator.ts +0 -22
  303. package/src/registry/asset-storage/adapters/vercel-blob.ts +0 -51
@@ -115,6 +115,100 @@ export interface ConfigCatalog {
115
115
  export type RuntimeMode = 'workspace' | 'registry';
116
116
  /** Format used when synthesizing local workspace declarations. */
117
117
  export type DeclarationFormat = 'ts' | 'js' | 'cjs';
118
+ /** How a connection's credential is supplied. Declared, never inferred from a missing key. */
119
+ export type AiCredentialKind = 'service' | 'user' | 'none';
120
+ /**
121
+ * An email option: a literal, baked into the build, or an environment reference, read by the deployed
122
+ * app at request time.
123
+ */
124
+ export type HandoffEmailOption<T> = T | RuntimeEnvReference<T>;
125
+ interface HandoffEmailBase {
126
+ /** Sender address, such as `Handoff <no-reply@example.com>`. Use a profile for per-environment values. */
127
+ from?: string;
128
+ }
129
+ /** Send through an SMTP server, such as Amazon SES, SendGrid, Postmark, Mailgun or Microsoft 365. */
130
+ export interface HandoffSmtpEmail extends HandoffEmailBase {
131
+ provider: 'smtp';
132
+ options: {
133
+ host: HandoffEmailOption<string>;
134
+ /** @default 465, or 587 when `secure` is false */
135
+ port?: HandoffEmailOption<number>;
136
+ /**
137
+ * Use TLS from the start of the connection. When false, the connection upgrades with STARTTLS if
138
+ * the server offers it. @default true, or false when `port` is set to a port other than 465
139
+ */
140
+ secure?: HandoffEmailOption<boolean>;
141
+ user?: HandoffEmailOption<string>;
142
+ /** Environment reference to the SMTP password. */
143
+ password?: RuntimeEnvReference<string>;
144
+ };
145
+ }
146
+ /** Send through a server-only module that default-exports a `defineEmailProvider()` result. */
147
+ export interface HandoffCustomEmail extends HandoffEmailBase {
148
+ provider: 'custom';
149
+ /** Server-only module path. It must be its own file so the build can trace it into the deployed registry. */
150
+ module: string;
151
+ /**
152
+ * Options passed to the provider factory. Literals are baked. Environment references are resolved
153
+ * at request time. `apiKey` and `password` must be environment references.
154
+ */
155
+ options?: Record<string, unknown>;
156
+ }
157
+ /** Registry email settings. `provider` selects the shape of `options`. */
158
+ export type HandoffEmailConfig = HandoffSmtpEmail | HandoffCustomEmail;
159
+ /**
160
+ * One AI connection the deployment declares. Connections are the whole surface: a reader can add a
161
+ * key for a declared connection and nothing else, so the server never calls a URL a reader chose.
162
+ *
163
+ * Every connection speaks the OpenAI-compatible `/chat/completions` API. A provider that fits
164
+ * nothing else names a server-only `module` instead of a `baseUrl`.
165
+ */
166
+ export interface HandoffAiConnection {
167
+ /** Stable id. A model is addressed as `<id>/<model>`. */
168
+ id: string;
169
+ /** Name shown to a reader in the model picker and on the account page. */
170
+ label: string;
171
+ /** OpenAI-compatible base URL, such as `https://api.openai.com/v1`. Omit only when `module` is set. */
172
+ baseUrl?: string;
173
+ /**
174
+ * Server-only module path default-exporting a `defineAiProvider()` result, for a provider that is
175
+ * not OpenAI-compatible. It must be its own file so the build can trace it into the deployed
176
+ * registry.
177
+ */
178
+ module?: string;
179
+ /**
180
+ * Environment reference to the deployment's own key. Its presence makes the connection a service
181
+ * key: the deployment pays and every reader can use it. The name is baked; the value is read at
182
+ * request time and never leaves the server.
183
+ */
184
+ apiKey?: RuntimeEnvReference<string>;
185
+ /**
186
+ * Set to `'user'` when each reader supplies their own key. Without it, a connection that needs a
187
+ * reader's key looks like one that needs none, and the reader gets a raw 401 instead of an
188
+ * instruction.
189
+ */
190
+ credential?: 'user';
191
+ /** The models this connection offers. A reader cannot use a model that is not listed. */
192
+ models?: string[];
193
+ /**
194
+ * Options passed to a `module` provider factory. Literals are baked. Environment references are
195
+ * resolved at request time, so secrets must be environment references.
196
+ */
197
+ options?: Record<string, unknown>;
198
+ }
199
+ /**
200
+ * The AI assistant block. Presence of the block opts the build in, and `enabled: false` turns it
201
+ * off. Like `mcp`, `enabled` is config-only and baked at build time, so a build without it leaves
202
+ * out both the chat route and the header control rather than showing a control that fails.
203
+ */
204
+ export interface HandoffAiConfig {
205
+ /** @default true when the `ai` block is present */
206
+ enabled?: boolean;
207
+ /** Declared connections. Merged by `id` with any supplied through `HANDOFF_AI_CONNECTIONS`. */
208
+ connections?: HandoffAiConnection[];
209
+ /** The `<connectionId>/<model>` used when a reader has not picked one. */
210
+ defaultModel?: string;
211
+ }
118
212
  /**
119
213
  * User-facing `runtime` configuration block. A single optional block that selects the runtime
120
214
  * mode and carries mode-specific host/connection settings.
@@ -135,6 +229,8 @@ export interface HandoffRuntimeConfig {
135
229
  * @default true
136
230
  */
137
231
  mcp?: boolean;
232
+ /** The docs AI assistant. Omit the block to leave it out of the build. See {@link HandoffAiConfig}. */
233
+ ai?: HandoffAiConfig;
138
234
  /** Workspace-mode settings. */
139
235
  workspace?: {
140
236
  entries?: ConfigEntries;
@@ -142,9 +238,9 @@ export interface HandoffRuntimeConfig {
142
238
  };
143
239
  /** Registry-mode host settings. Env-var values are stored as names, never as secrets. */
144
240
  registry?: {
145
- /** Environment reference to the database URL. @default fromEnv('DATABASE_URL') */
146
- databaseUrl?: RuntimeEnvReference<string>;
147
241
  database?: {
242
+ /** Environment reference to the database URL. @default fromEnv('DATABASE_URL') */
243
+ url?: RuntimeEnvReference<string>;
148
244
  /**
149
245
  * PostgreSQL is the supported database. `driver` selects the built-in connection driver — how
150
246
  * to connect, not which engine — over the same package-owned Postgres schema and migrations.
@@ -159,20 +255,26 @@ export interface HandoffRuntimeConfig {
159
255
  */
160
256
  assetStorage?: {
161
257
  /**
162
- * Active storage provider for new uploads. `database` keeps bytes inline in Postgres; use the
163
- * pre-packaged `vercel-blob` adapter or a `custom` module for object storage.
258
+ * Active storage provider for new uploads. `database` keeps bytes inline in Postgres; use a
259
+ * `custom` module for object storage.
164
260
  * @default "database"
165
261
  */
166
- adapter?: 'database' | 'vercel-blob' | 'custom';
167
- /** For `adapter: "custom"` - server-only module path default-exporting a `defineAssetStorage` adapter. */
262
+ provider?: 'database' | 'custom';
263
+ /** For `provider: "custom"` - server-only module path default-exporting a `defineStorageProvider()` result. */
168
264
  module?: string;
169
- /** For `adapter: "vercel-blob"` - environment reference to the Blob read/write token. @default fromEnv('BLOB_READ_WRITE_TOKEN') */
170
- token?: RuntimeEnvReference<string>;
171
265
  /** Max bytes kept inline in the database `bytea` column (larger uploads are rejected). @default 4194304 */
172
266
  maxInlineBytes?: number;
173
- /** Non-secret options, JSON-encoded into the bundle. Environment references are rejected. Pass variable names instead. */
267
+ /**
268
+ * Options passed to the provider factory. Literals are baked. Environment references are resolved
269
+ * at request time, so secrets must be environment references.
270
+ */
174
271
  options?: Record<string, unknown>;
175
272
  };
273
+ /**
274
+ * Invitation and password-reset email. Email stays off until `from` is set and the required
275
+ * options of the provider have values. Until then, invitation links are shown to an administrator.
276
+ */
277
+ email?: HandoffEmailConfig;
176
278
  };
177
279
  /**
178
280
  * Connected-workspace settings pointing at a remote registry. A connected workspace is
@@ -185,11 +287,18 @@ export interface HandoffRuntimeConfig {
185
287
  accessToken?: EnvSecret<string>;
186
288
  };
187
289
  }
290
+ /** Connections to external services. */
291
+ export interface ConfigIntegrations {
292
+ /** The Figma file that `fetch` reads foundations from. In a terminal, `fetch` asks for an empty value. */
293
+ figma?: {
294
+ /** The Figma file ID. @default fromEnv('HANDOFF_FIGMA_PROJECT_ID', { default: null }) */
295
+ projectId?: EnvValue<string | null>;
296
+ /** A Figma personal access token. Accepts only an environment reference. @default fromEnv('HANDOFF_DEV_ACCESS_TOKEN', { default: null }) */
297
+ accessToken?: EnvSecret<string | null>;
298
+ };
299
+ }
188
300
  export interface Config {
189
- dev_access_token?: EnvSecret<string | null>;
190
- devAccessToken?: EnvSecret<string | null>;
191
- figma_project_id?: EnvValue<string | null>;
192
- figmaProjectId?: EnvValue<string | null>;
301
+ integrations?: ConfigIntegrations;
193
302
  exportsOutputDirectory?: EnvValue<string>;
194
303
  sitesOutputDirectory?: EnvValue<string>;
195
304
  useVariables?: EnvValue<boolean>;
@@ -391,11 +500,13 @@ export interface ClientRuntimeConfig {
391
500
  connected: boolean;
392
501
  }
393
502
  /** Values available after config loading. Deferred references retain their names. */
394
- export type ResolvedConfig = Omit<Config, 'dev_access_token' | 'devAccessToken' | 'figma_project_id' | 'figmaProjectId' | 'exportsOutputDirectory' | 'sitesOutputDirectory' | 'useVariables' | 'app' | 'runtime'> & {
395
- dev_access_token?: string | null;
396
- devAccessToken?: string | null;
397
- figma_project_id?: string | null;
398
- figmaProjectId?: string | null;
503
+ export type ResolvedConfig = Omit<Config, 'integrations' | 'exportsOutputDirectory' | 'sitesOutputDirectory' | 'useVariables' | 'app' | 'runtime'> & {
504
+ integrations?: {
505
+ figma?: {
506
+ projectId?: string | null;
507
+ accessToken?: string | null;
508
+ };
509
+ };
399
510
  exportsOutputDirectory?: string;
400
511
  sitesOutputDirectory?: string;
401
512
  useVariables?: boolean;
@@ -1,8 +1,4 @@
1
- /**
2
- * Generates a filesystem-safe directory name from an absolute path
3
- * @param workingPath - The absolute working path to transform
4
- * @returns A filesystem-safe string suitable for use as a directory name
5
- */
1
+ /** `<base name>-<path hash>`; the hash keeps directories with the same base name apart. */
6
2
  export declare function generateFilesystemSafeId(workingPath: string): string;
7
3
  /**
8
4
  * Normalize a filesystem path for cross-platform string comparison.
@@ -8,31 +8,14 @@ exports.normalizePathForCompare = normalizePathForCompare;
8
8
  exports.arePathsEqual = arePathsEqual;
9
9
  exports.resolveWorkingPath = resolveWorkingPath;
10
10
  exports.isPathInside = isPathInside;
11
+ const crypto_1 = require("crypto");
11
12
  const path_1 = __importDefault(require("path"));
12
- /**
13
- * Generates a filesystem-safe directory name from an absolute path
14
- * @param workingPath - The absolute working path to transform
15
- * @returns A filesystem-safe string suitable for use as a directory name
16
- */
13
+ /** `<base name>-<path hash>`; the hash keeps directories with the same base name apart. */
17
14
  function generateFilesystemSafeId(workingPath) {
18
- // Normalize path separators to forward slashes for consistent processing
19
- let safeId = workingPath.replace(/\\/g, '/');
20
- // Replace forward slashes with dashes
21
- safeId = safeId.replace(/\//g, '-');
22
- // Remove or replace invalid filesystem characters
23
- safeId = safeId.replace(/[<>:"|?*\x00]/g, '-');
24
- // Remove leading/trailing dashes and spaces
25
- safeId = safeId.replace(/^[\s-]+|[\s-]+$/g, '');
26
- // Handle Windows reserved names (case-insensitive)
27
- const reservedNames = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\.|$)/i;
28
- if (reservedNames.test(safeId)) {
29
- safeId = `project-${safeId}`;
30
- }
31
- // Ensure we have at least one character
32
- if (!safeId) {
33
- safeId = 'default-project';
34
- }
35
- return safeId;
15
+ const baseName = path_1.default.basename(path_1.default.resolve(workingPath));
16
+ const name = baseName.replace(/[^A-Za-z0-9._-]/g, '-').replace(/^[.-]+|[.-]+$/g, '') || 'project';
17
+ const hash = (0, crypto_1.createHash)('sha256').update(normalizePathForCompare(workingPath)).digest('hex').slice(0, 8);
18
+ return `${name}-${hash}`;
36
19
  }
37
20
  /**
38
21
  * Normalize a filesystem path for cross-platform string comparison.
package/docs/api.md CHANGED
@@ -80,10 +80,12 @@ class constructor
80
80
  handoff.fetch();
81
81
  ```
82
82
 
83
- Fetch will connect to the defined Figma file id provided in the
84
- `env`. If no env or file is found, it will interactively request one. Then it
85
- will export all of the tokens and generated data into an `exported` directory
86
- in the local working root.
83
+ Fetch reads the Figma file ID and access token from `integrations.figma`. By
84
+ default, they come from `HANDOFF_FIGMA_PROJECT_ID` and
85
+ `HANDOFF_DEV_ACCESS_TOKEN`. In a terminal, fetch asks for an empty value.
86
+ Without a terminal, it throws `HandoffConfigError`. Then it exports all of the
87
+ tokens and generated data into an `exported` directory in the local working
88
+ root.
87
89
 
88
90
  ### build
89
91
 
package/docs/cli.md CHANGED
@@ -1,78 +1,299 @@
1
1
  # Handoff CLI
2
2
 
3
- The Handoff cli allows you to interact with your Figma file and Handoff app
4
- via the command line. It allows you to run handoff in any folder with
5
- no other configuration or setup.
3
+ The `handoff-app` CLI fetches Figma foundations, builds and serves the
4
+ documentation, and transfers content between a workspace and a registry. The
5
+ [README](../README.md) explains the concepts and the configuration. This page
6
+ lists every command and option.
6
7
 
7
- ## Interacting with Figma
8
- The CLI will allow you to fetch all data from Figma, build the documentation
9
- app and run a local dev site. For these use the build, fetch, and serve commands
10
- documented below.
8
+ ## Run the CLI
11
9
 
12
- ## Configuring a handoff project
13
- The CLI will allow you to build the various kinds of configurations that you
14
- will need for interacting with Figma. Handoff has sane configuration defaults
15
- but the various kinds of configurations can be tailored or extended.
10
+ `init` installs `handoff-app` in the project and adds npm scripts for the
11
+ common commands. Use the npm scripts, so that local development and CI use the
12
+ project version:
16
13
 
17
- Handoff has 4 configuration areas -
14
+ ```bash
15
+ npm run build -- --target registry
16
+ ```
18
17
 
19
- `handoff.config.*` - Defines the general handoff configuration. Supported files are `handoff.config.ts`, `handoff.config.js`, `handoff.config.cjs`, and `handoff.config.json` (resolved in that precedence order). Pass `-c, --config <file>` to load one specific file instead, resolved from the working directory (`HANDOFF_WORKING_PATH`, or the directory the command runs in).
20
- `pages` - Markdown files that will create or customize pages in the documentation
21
- app
22
- `exportables` - JSON schemas for each component in your figma file that you
23
- want to pull into handoff.
24
- `integration` - scss mappings and html templates for making it easy to map
25
- handoff tokens to your frontend framework.
18
+ npm forwards the arguments after `--` to the CLI. For a command without an npm
19
+ script, use `npx`:
26
20
 
27
- The CLI exposes two ways to manage the config - `make` and `eject`.
21
+ ```bash
22
+ npx handoff-app validate:components
23
+ ```
28
24
 
29
- __Eject__ commands will take the default configuration and eject them into
30
- the current working directory. If you customize these configurations, then run
31
- handoff commands in that directory, these configs will be executed. `eject:config` now prompts for TypeScript or JavaScript config format.
32
- __Make__ commands will generate a boilerplate configuration in the current
33
- working directory. This is useful for extending handoff for different components
34
- or integrations.
25
+ Show the help of the CLI or of one command:
35
26
 
36
- ## Requirements
37
- Node 16+
27
+ ```bash
28
+ npx handoff-app --help
29
+ npx handoff-app publish --help
30
+ ```
38
31
 
39
- ## Install the CLI
32
+ Each command runs against the working directory. The working directory is the
33
+ directory that holds `handoff.config.ts`. It is the current directory, or the
34
+ directory that `HANDOFF_WORKING_PATH` sets.
40
35
 
41
- `npm install -g handoff-app`
36
+ ## Shared options
42
37
 
43
- ## Run the CLI
38
+ Every command except `init` accepts these options:
39
+
40
+ | Option | Description |
41
+ | --- | --- |
42
+ | `-c, --config <file>` | Load this config file, relative to the working directory, instead of `handoff.config.*` |
43
+ | `--profile <name>` | Merge `handoff.config.<name>.*` onto the base config, and use the registry login of this profile. Has priority over `HANDOFF_PROFILE`. |
44
+ | `-d, --debug` | Show debug logs |
45
+ | `-f, --force` | Force the action. The effect depends on the command. |
46
+ | `--help` | Show the help of the command |
47
+
48
+ `handoff-app --version` shows the CLI version.
49
+
50
+ `HANDOFF_LOG_LEVEL` (`debug`, `info`, `warn`, `error`, or `silent`) and
51
+ `HANDOFF_LOG_SCOPES` (`handoff`, `vite`, `next`) control the log output of
52
+ every command. The README lists all
53
+ [environment variables](../README.md#environment-variables).
54
+
55
+ ## Project
56
+
57
+ ### init
58
+
59
+ ```bash
60
+ npx handoff-app init
61
+ ```
62
+
63
+ Creates a project with an interactive wizard. The wizard asks for the project
64
+ name, sample content, JavaScript or TypeScript, an optional Vercel setup, and
65
+ optional Figma credentials. It installs `handoff-app` and adds these npm
66
+ scripts: `start`, `dev`, `fetch`, `build`, `login`, `logout`, `publish`,
67
+ `checkout`, and `db:migrate`.
68
+
69
+ ### fetch
70
+
71
+ ```bash
72
+ handoff-app fetch
73
+ ```
74
+
75
+ Fetches tokens and assets from the Figma file and writes them to `exported/`.
76
+ It reads `HANDOFF_FIGMA_PROJECT_ID` and `HANDOFF_DEV_ACCESS_TOKEN`. If a value
77
+ is empty, `fetch` asks for it in the terminal. Without a terminal, `fetch`
78
+ stops with an error that names the missing variable.
79
+
80
+ ### start
81
+
82
+ ```bash
83
+ handoff-app start
84
+ ```
85
+
86
+ Starts the workspace documentation server and the Handoff file watchers. While
87
+ the command runs, Handoff rebuilds changed files and updates the
88
+ documentation.
89
+
90
+ ### dev
91
+
92
+ ```bash
93
+ handoff-app dev
94
+ ```
95
+
96
+ Starts the workspace documentation server without the Handoff file watchers.
97
+
98
+ ### build
99
+
100
+ ```bash
101
+ handoff-app build [--target static|registry] [--package standalone|vercel] [--skip-components]
102
+ ```
103
+
104
+ Builds the documentation.
105
+
106
+ | Option | Description |
107
+ | --- | --- |
108
+ | `--target static` | Default. Builds a static site in `out/static`. |
109
+ | `--target registry` | Builds a standalone registry server in `out/registry`. The application is always in registry mode. |
110
+ | `--package vercel` | Writes a Vercel Build Output bundle to `.vercel/output` for the selected target |
111
+ | `--package standalone` | Writes the default output of the target |
112
+ | `--skip-components` | Builds the application without a component build first |
113
+ | `-f, --force` | Builds every component again, and ignores the build cache |
114
+
115
+ `--package` never changes the target.
116
+
117
+ ### build:app
118
+
119
+ ```bash
120
+ handoff-app build:app [--skip-components]
121
+ ```
122
+
123
+ Builds the static site. It is the same as `build --target static`.
124
+
125
+ ### build:components
126
+
127
+ ```bash
128
+ handoff-app build:components [component]
129
+ ```
130
+
131
+ Builds the catalog items without the documentation application. A name builds
132
+ only that item.
133
+
134
+ ### validate:components
135
+
136
+ ```bash
137
+ handoff-app validate:components [--skip-build]
138
+ ```
139
+
140
+ Validates the catalog items. `--skip-build` validates the existing build
141
+ output without a build first.
142
+
143
+ ## Content
144
+
145
+ ### make:component
146
+
147
+ ```bash
148
+ handoff-app make:component <name>
149
+ ```
150
+
151
+ Creates a Handlebars catalog item: a template, a CommonJS declaration, and,
152
+ if you select them, a JavaScript file and a Sass file. The item goes into the
153
+ first directory in `catalog.include`, or into `components/`. If that directory
154
+ is not registered, Handoff adds it to `catalog.include`. The name can contain
155
+ letters, numbers, hyphens, and underscores. `--force` overwrites an existing
156
+ template.
157
+
158
+ ### make:page
159
+
160
+ ```bash
161
+ handoff-app make:page <name> [parent]
162
+ ```
163
+
164
+ Creates a Markdown page in `pages/`, or in `pages/<parent>/`. If a default page
165
+ has the same name, the new page starts with its content. `--force` overwrites
166
+ an existing page.
167
+
168
+ ### scaffold
169
+
170
+ ```bash
171
+ handoff-app scaffold
172
+ ```
173
+
174
+ Creates catalog item stubs for the components in the fetched Figma data. The
175
+ command asks which components to create and how.
176
+
177
+ ### eject:config
178
+
179
+ ```bash
180
+ handoff-app eject:config
181
+ ```
182
+
183
+ Writes the default config to the working directory. The command asks for
184
+ TypeScript or JavaScript. `--force` overwrites an existing config file.
185
+
186
+ ### eject:pages
187
+
188
+ ```bash
189
+ handoff-app eject:pages
190
+ ```
191
+
192
+ Copies the default documentation pages to `pages/`, so that you can change
193
+ them. `--force` adds the default pages that you did not change.
194
+
195
+ ### eject:theme
196
+
197
+ ```bash
198
+ handoff-app eject:theme
199
+ ```
200
+
201
+ Copies the Sass file of the selected theme to `theme/default.scss`, so that
202
+ you can change it.
203
+
204
+ ## Registry
205
+
206
+ ### db:migrate
207
+
208
+ ```bash
209
+ DATABASE_URL="postgresql://…" handoff-app db:migrate
210
+ ```
211
+
212
+ Applies the registry database migrations. The command reads the project config
213
+ and the database variables, and uses the same database driver as the registry
214
+ build. Run it before the first deployment and before each upgrade. The build,
215
+ the server start, and the installer never run migrations.
216
+
217
+ ### login
218
+
219
+ ```bash
220
+ handoff-app login [--url <registry-url>] [--profile <name>] [--no-browser]
221
+ ```
222
+
223
+ Authorizes the CLI through the device flow of the registry. The CLI saves the
224
+ token in `.handoff/cli-auth.json`, under the selected profile.
225
+
226
+ | Option | Description |
227
+ | --- | --- |
228
+ | `--url <registry-url>` | Registry URL, with its base path when the registry has one. Without it, the CLI uses the configured registry URL. |
229
+ | `--profile <name>` | Saves the login under this profile. The profile needs no config file. |
230
+ | `--no-browser` | Shows the approval URL, but does not open the browser. `HANDOFF_LOGIN_NO_BROWSER=true` does the same. |
231
+
232
+ ### logout
233
+
234
+ ```bash
235
+ handoff-app logout [--profile <name>] [--url <registry-url>] [--all]
236
+ ```
237
+
238
+ Revokes the saved token when possible. Then it removes the saved login of the
239
+ selected profile.
240
+
241
+ | Option | Description |
242
+ | --- | --- |
243
+ | `--url <registry-url>` | Revokes the login only if it belongs to this exact registry URL |
244
+ | `--all` | Revokes and removes the logins of every profile |
245
+
246
+ ### publish
247
+
248
+ ```bash
249
+ handoff-app publish <catalog|pages|tokens|assets|all> [id...] [--dry-run] [--no-build]
250
+ ```
251
+
252
+ Builds the content and publishes it to the connected registry. `all` publishes
253
+ every kind in dependency order. One or more ids limit the publish to those
254
+ entities: catalog item ids, page ids, token set ids, or asset collections.
255
+ `tokens` and `assets` run the Figma pipeline first.
44
256
 
45
- `handoff-app --help`
257
+ | Option | Description |
258
+ | --- | --- |
259
+ | `--dry-run` | Lists the content that a real publish uploads. Does not connect to a registry. The build still runs. |
260
+ | `--no-build` | Publishes the existing build output without a build first |
46
261
 
47
- ## Commands and Flags
262
+ ### checkout
48
263
 
49
- Usage: handoff-app <cmd> <opts>
264
+ ```bash
265
+ handoff-app checkout <catalog|pages|tokens|assets|all> [id...] [--dry-run]
266
+ ```
50
267
 
51
- Commands:
52
- fetch [opts] - Fetches the design tokens from the design system
268
+ Writes content from the connected registry into the workspace. Checkout writes
269
+ declarations in the format that `runtime.workspace.declarationFormat` sets.
53
270
 
54
- build [--target static|registry] [opts] - Builds the design system for the target (default: static export)
55
- build:app [opts] - Builds the design system static application (alias of `build --target static`)
271
+ | Option | Description |
272
+ | --- | --- |
273
+ | `--dry-run` | Lists the files that a real checkout creates or overwrites. Writes nothing. |
56
274
 
57
- start [opts] - Starts the design system in development mode
275
+ ### Registry connection
58
276
 
59
- db:migrate [opts] - Runs registry database migrations (Drizzle / PostgreSQL). Reads the project config and database env vars, applies the package-owned migration set, and runs independently of `build`.
277
+ `publish` and `checkout` find the registry URL and the token in this order:
60
278
 
61
- login [--profile <name>] --url <registry-url> - Authorizes this workspace in the browser and stores a revocable token in `.handoff/cli-auth.json`, under the selected profile.
279
+ 1. `HANDOFF_REGISTRY_URL` and `HANDOFF_REGISTRY_ACCESS_TOKEN`, or
280
+ `runtime.registryConnection` in the config.
281
+ 2. The saved login of the selected profile, if it belongs to the same registry
282
+ URL. A profile without a login uses the default login.
283
+ 3. `HANDOFF_SYNC_SECRET`, when no token and no login is available.
62
284
 
63
- logout [--profile <name>] [--url <registry-url>] [--all] - Revokes the saved token when possible, then removes the local credential of the selected profile, or of every profile with `--all`.
285
+ ## Deprecated commands
64
286
 
65
- make
66
- make:component <name> [opts] - Creates a new catalog item with a declaration
67
- make:page <component> <state> [opts] - Creates a new page
287
+ These commands still work. Each shows a deprecation warning.
68
288
 
69
- eject - Ejects the default entire configuration to the current directory
70
- eject:config [opts] - Ejects the default configuration to the current directory
71
- eject:integration [opts] - Ejects the default integration to the current directory
72
- eject:pages [opts] - Ejects the default pages to the current directory
289
+ | Command | Replacement |
290
+ | --- | --- |
291
+ | `push [type] [id...]` | `publish` |
292
+ | `push:all` | `publish all` |
293
+ | `push:tokens [setId...]` | `publish tokens` |
294
+ | `push:assets [collection]` | `publish assets` |
295
+ | `pull [type] [id...]` | `checkout` |
73
296
 
74
- Options:
75
- -c, --config [file] Define the path to the config file
76
- -d, --debug Show debug logs
77
- -h, --help Show this help message
78
- -v, --version Show the handoff version number
297
+ The `push` and `pull` commands also accept `components` and `patterns` as the
298
+ type. They accept the options for features that Handoff 2 does not have, and
299
+ show a warning for each one.
@@ -0,0 +1,11 @@
1
+ CREATE TABLE "registry_ai_keys" (
2
+ "user_id" text NOT NULL,
3
+ "connection_id" text NOT NULL,
4
+ "encrypted_key" text NOT NULL,
5
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
6
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
7
+ CONSTRAINT "registry_ai_keys_user_id_connection_id_pk" PRIMARY KEY("user_id","connection_id"),
8
+ CONSTRAINT "registry_ai_keys_connection_not_blank" CHECK (length(btrim("registry_ai_keys"."connection_id")) > 0)
9
+ );
10
+ --> statement-breakpoint
11
+ ALTER TABLE "registry_ai_keys" ADD CONSTRAINT "registry_ai_keys_user_id_registry_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."registry_users"("id") ON DELETE cascade ON UPDATE no action;
@@ -0,0 +1,2 @@
1
+ ALTER TABLE "components" ADD COLUMN "source_dir" text;--> statement-breakpoint
2
+ ALTER TABLE "patterns" ADD COLUMN "source_dir" text;