howone 0.1.22 → 0.1.25

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 (26) hide show
  1. package/package.json +1 -1
  2. package/templates/nextjs/lib/sdk.ts +3 -0
  3. package/templates/vite/.howone/skills/howone-sdk/01-architect/01-app-generation.md +183 -69
  4. package/templates/vite/.howone/skills/howone-sdk/01-architect/02-manifest-codegen.md +98 -23
  5. package/templates/vite/.howone/skills/howone-sdk/02-database/01-schema-design.md +463 -69
  6. package/templates/vite/.howone/skills/howone-sdk/02-database/02-schema-operations.md +366 -64
  7. package/templates/vite/.howone/skills/howone-sdk/02-database/03-data-access-patterns.md +204 -67
  8. package/templates/vite/.howone/skills/howone-sdk/02-database/04-query-dsl-and-responses.md +237 -0
  9. package/templates/vite/.howone/skills/howone-sdk/02-database/05-ai-persistence-patterns.md +372 -0
  10. package/templates/vite/.howone/skills/howone-sdk/03-sdk/01-client-setup.md +58 -36
  11. package/templates/vite/.howone/skills/howone-sdk/03-sdk/02-entity-operations.md +67 -0
  12. package/templates/vite/.howone/skills/howone-sdk/03-sdk/03-auth.md +267 -469
  13. package/templates/vite/.howone/skills/howone-sdk/03-sdk/04-react-integration.md +113 -322
  14. package/templates/vite/.howone/skills/howone-sdk/03-sdk/07-ai-action-calls.md +95 -48
  15. package/templates/vite/.howone/skills/howone-sdk/03-sdk/08-extension-boundaries.md +226 -0
  16. package/templates/vite/.howone/skills/howone-sdk/04-ai/01-ai-capability-architecture.md +205 -0
  17. package/templates/vite/.howone/skills/howone-sdk/04-ai/02-workflow-contract-rules.md +426 -0
  18. package/templates/vite/.howone/skills/howone-sdk/04-ai/03-ai-sdk-handoff.md +219 -0
  19. package/templates/vite/.howone/skills/howone-sdk/04-ai/04-service-capability-catalog.md +281 -0
  20. package/templates/vite/.howone/skills/howone-sdk/04-ai/05-workflow-operations.md +256 -0
  21. package/templates/vite/.howone/skills/howone-sdk/04-ai/06-ai-feature-playbooks.md +296 -0
  22. package/templates/vite/.howone/skills/howone-sdk/SKILL.md +83 -15
  23. package/templates/vite/.howone/skills/howone-sdk/agents/openai.yaml +2 -2
  24. package/templates/vite/package.json +1 -1
  25. package/templates/vite/src/lib/sdk.ts +3 -0
  26. package/templates/vite/.howone/skills/howone-sdk/04-ai/.gitkeep +0 -1
@@ -2,399 +2,190 @@
2
2
 
3
3
  ## What `@howone/sdk/react` Provides
4
4
 
5
- `@howone/sdk/react` is a **thin UI layer** for auth state, theme, and loading spinners. It does **not** provide entity, query, or AI data hooks.
5
+ Thin integration layer: auth context plus the HowOne floating brand button. **No** entity hooks,
6
+ AI hooks, toast system, redirect overlay, or app-owned UI.
6
7
 
7
8
  Exports:
8
- - `HowOneProvider` — all-in-one app provider (auth, theme, toasts, floating button)
9
- - `useHowoneContext` — access auth state (user, token, isAuthenticated, logout)
10
- - `FloatingButton` — a floating action button (shows Login when unauthenticated)
11
- - `Loading` — full-page or inline loading component
12
- - `LoadingSpinner` — headless spinner for composition
13
9
 
14
- ---
15
-
16
- ## Import
17
-
18
- ```ts
19
- import {
20
- HowOneProvider,
21
- useHowoneContext,
22
- FloatingButton,
23
- Loading,
24
- LoadingSpinner,
25
- } from '@howone/sdk/react'
26
- ```
10
+ - `HowOneProvider`
11
+ - `useHowoneContext`
12
+ - `FloatingButton`
27
13
 
28
14
  ---
29
15
 
30
- ## HowOneProvider
31
-
32
- Wrap your entire app. Provides auth, theme, and toast context to all children.
16
+ ## Auth: one SDK config + one Provider flag
33
17
 
34
- `HowOneProvider` reads SDK environment and project defaults from `createClient`. Configure
35
- `projectId` and `env` once in `src/lib/sdk.ts`; do not pass a separate env to the provider.
36
-
37
- ```tsx
38
- // main.tsx or app/layout.tsx
39
- import React from 'react'
40
- import ReactDOM from 'react-dom/client'
41
- import { HowOneProvider } from '@howone/sdk/react'
42
- import './lib/sdk'
43
- import App from './App'
44
-
45
- ReactDOM.createRoot(document.getElementById('root')!).render(
46
- <React.StrictMode>
47
- <HowOneProvider
48
- auth="required"
49
- brand="visible"
50
- theme="system"
51
- >
52
- <App />
53
- </HowOneProvider>
54
- </React.StrictMode>
55
- )
56
- ```
57
-
58
- ### HowOneProviderProps
18
+ **Step 1 `src/lib/sdk.ts` (required):**
59
19
 
60
20
  ```ts
61
- type HowOneAuthMode = 'required' | 'optional' | 'none'
62
- type HowOneThemeMode = 'dark' | 'light' | 'system' | 'inherit'
63
- type HowOneBrandMode = 'visible' | 'hidden'
64
-
65
- interface HowOneProviderProps {
66
- children: React.ReactNode
67
-
68
- // Optional override. Prefer configuring projectId once in createClient.
69
- projectId?: string
70
-
71
- // Auth behaviour
72
- auth?: HowOneAuthMode
73
- // 'required' — redirects unauthenticated users to login page
74
- // 'optional' — allows both authenticated and unauthenticated users
75
- // 'none' — disables auth entirely
76
-
77
- // Brand button (HowOne branding)
78
- brand?: HowOneBrandMode // default: 'visible'
79
- showBrandButton?: boolean // alias for brand
80
-
81
- // Theme
82
- theme?: HowOneThemeMode // default: 'system'
83
- themeStorageKey?: string // localStorage key for theme preference
84
- forceTheme?: boolean // ignore user preference and force theme
85
- }
21
+ const client = createClient({
22
+ projectId: import.meta.env.VITE_HOWONE_PROJECT_ID,
23
+ env: import.meta.env.VITE_HOWONE_ENV,
24
+ })
86
25
  ```
87
26
 
88
- ### Common provider configurations
27
+ **Step 2 Provider:**
89
28
 
90
29
  ```tsx
91
- // Public app no auth required
92
- <HowOneProvider auth="none" theme="system" brand="hidden">
93
- <App />
94
- </HowOneProvider>
95
-
96
- // Auth-gated app — redirect to login if not authenticated
97
- <HowOneProvider auth="required" theme="system">
98
- <App />
99
- </HowOneProvider>
100
-
101
- // Optional auth — user can browse without logging in
102
- <HowOneProvider auth="optional" theme="inherit" brand="visible">
103
- <App />
104
- </HowOneProvider>
30
+ import { HowOneProvider } from '@howone/sdk/react'
31
+ import './lib/sdk' // must import before Provider so auth config is registered
105
32
 
106
- // Dark mode forced
107
- <HowOneProvider auth="required" theme="dark" forceTheme>
33
+ <HowOneProvider auth="none" brand="visible">
108
34
  <App />
109
35
  </HowOneProvider>
110
36
  ```
111
37
 
112
- ---
113
-
114
- ## useHowoneContext
115
-
116
- Access auth state inside any component wrapped by `HowOneProvider`.
117
-
118
- ```ts
119
- type HowoneContextValue = {
120
- user: {
121
- id: string // backend owner id; same as userId when JWT has userId
122
- userId?: string // authenticated backend user id when present in JWT
123
- puid?: string // public UUID; do not use as public ownerId scope
124
- email: string
125
- name: string
126
- avatar: string
127
- appId?: string
128
- } | null
129
- token: string | null
130
- isAuthenticated: boolean
131
- logout: () => void
132
- }
133
- ```
38
+ | Layer | Setting | Meaning |
39
+ |-------|---------|---------|
40
+ | `createClient` | `{ projectId, env }` (default **hosted**) | Login/logout → HowOne `/auth` |
41
+ | `createClient` | `auth: 'custom'` | Login/logout → your `loginPath`; APIs still HowOne |
42
+ | `HowOneProvider` | `auth="required"` | Default with hosted redirect to HowOne login |
43
+ | `HowOneProvider` | `auth="none"` | Use with `auth: 'custom'`; guard routes yourself |
134
44
 
135
- ### Usage
45
+ Default (HowOne hosted login):
136
46
 
137
47
  ```tsx
138
- import { useHowoneContext } from '@howone/sdk/react'
139
-
140
- function UserMenu() {
141
- const { user, isAuthenticated, logout } = useHowoneContext()
142
-
143
- if (!isAuthenticated || !user) {
144
- return <a href="/login">Login</a>
145
- }
146
-
147
- return (
148
- <div>
149
- <img src={user.avatar} alt={user.name} />
150
- <span>{user.name}</span>
151
- <button onClick={logout}>Logout</button>
152
- </div>
153
- )
154
- }
48
+ createClient({ projectId, env })
49
+ <HowOneProvider auth="required" />
155
50
  ```
156
51
 
157
- ### Conditional rendering based on auth state
52
+ Custom login page (your UI, HowOne auth APIs, keep HowOne logo unless product asks to hide it):
158
53
 
159
54
  ```tsx
160
- function Dashboard() {
161
- const { isAuthenticated, user } = useHowoneContext()
162
-
163
- if (!isAuthenticated) {
164
- return <div>Please log in to continue.</div>
165
- }
166
-
167
- return <div>Welcome, {user?.name}!</div>
168
- }
55
+ createClient({ projectId, env, auth: 'custom', loginPath: '/login' })
56
+ <HowOneProvider auth="none" brand="visible" />
169
57
  ```
170
58
 
171
- ### Using token for direct API calls
59
+ ---
60
+
61
+ ## HowOneProvider
172
62
 
173
63
  ```tsx
174
- import { useHowoneContext } from '@howone/sdk/react'
64
+ <HowOneProvider
65
+ auth="none"
66
+ brand="visible"
67
+ onAuthRedirect={({ mode, returnUrl }) => {
68
+ // App may set its own loading/redirect state here.
69
+ }}
70
+ onAuthStateChange={(state) => {
71
+ // App may update analytics or local UI state here.
72
+ }}
73
+ >
74
+ <App />
75
+ </HowOneProvider>
76
+ ```
175
77
 
176
- function DebugPanel() {
177
- const { token } = useHowoneContext()
78
+ ### HowOneProviderProps
178
79
 
179
- async function copyToken() {
180
- if (token) await navigator.clipboard.writeText(token)
181
- }
80
+ ```ts
81
+ type HowOneProviderAuth = 'required' | 'optional' | 'none'
182
82
 
183
- return (
184
- <button onClick={copyToken} disabled={!token}>
185
- Copy JWT Token
186
- </button>
187
- )
83
+ interface HowOneProviderProps {
84
+ children: React.ReactNode
85
+ projectId?: string // prefer createClient projectId
86
+ auth?: HowOneProviderAuth
87
+ brand?: 'visible' | 'hidden'
88
+ showBrandButton?: boolean
89
+ theme?: 'dark' | 'light' | 'system' | 'inherit'
90
+ onAuthStateChange?: (state: AuthState) => void
91
+ onAuthRedirect?: (info: { mode: 'hosted' | 'custom'; returnUrl: string }) => void
188
92
  }
189
93
  ```
190
94
 
191
- ---
192
-
193
- ## FloatingButton
95
+ **Important:** Provider `auth` is only a **route guard**. Login/logout URLs come from `createClient({ auth: 'custom' })`.
194
96
 
195
- A floating action button that renders in the bottom corner of the screen. When unauthenticated, it shows a "Login" label by default.
97
+ The provider must not render app-owned UI. It does not own toasts, dialogs, pages, custom login UI,
98
+ or redirect overlays. It does keep the bottom-right HowOne logo by default through `FloatingButton`.
99
+ Use `brand="hidden"` or `showBrandButton={false}` only when the product explicitly asks to hide it.
196
100
 
197
- ```tsx
198
- import { FloatingButton } from '@howone/sdk/react'
199
-
200
- // Default — shows Login/brand button
201
- <FloatingButton />
202
-
203
- // Custom text and handler
204
- <FloatingButton
205
- text="Get Started"
206
- onClick={() => router.push('/signup')}
207
- />
208
-
209
- // Custom styling
210
- <FloatingButton
211
- text="Support"
212
- onClick={() => setShowChat(true)}
213
- className="bg-blue-600 hover:bg-blue-700"
214
- />
215
- ```
101
+ ---
216
102
 
217
- ### FloatingButtonProps
103
+ ## useHowoneContext
218
104
 
219
105
  ```ts
220
- interface FloatingButtonProps {
221
- text?: string // button label (default: 'Login' or brand text)
222
- onClick?: () => void
223
- className?: string // additional CSS classes
224
- }
106
+ const { user, token, isAuthenticated, logout } = useHowoneContext()
225
107
  ```
226
108
 
227
- ---
228
-
229
- ## Loading Components
230
-
231
- ### Loading — full-page or inline loading state
109
+ ### Logout
232
110
 
233
111
  ```tsx
234
- import { Loading } from '@howone/sdk/react'
112
+ <button onClick={() => void logout()}>Sign out</button>
113
+ ```
235
114
 
236
- // Full-screen overlay
237
- <Loading fullScreen label="Loading your workspace..." />
115
+ With `auth: 'custom'`, `logout()` clears session and navigates to `loginPath` — **not** howone.dev.
238
116
 
239
- // Inline with description
240
- <Loading
241
- label="Generating story..."
242
- description="This may take a moment"
243
- size="lg"
244
- tone="brand"
245
- />
117
+ Equivalent:
246
118
 
247
- // Minimal
248
- <Loading />
119
+ ```ts
120
+ await howone.auth.logout()
249
121
  ```
250
122
 
251
- ### LoadingSpinner headless spinner
123
+ ### Custom login page link
252
124
 
253
125
  ```tsx
254
- import { LoadingSpinner } from '@howone/sdk/react'
255
-
256
- // Sizes: 'sm' | 'md' | 'lg'
257
- // Tones: 'brand' | 'neutral' | 'inverse'
258
-
259
- <LoadingSpinner size="md" tone="brand" />
260
- <LoadingSpinner size="sm" tone="neutral" className="mr-2" />
261
- ```
126
+ import { useNavigate } from 'react-router-dom'
127
+ import howone from '@/lib/sdk'
262
128
 
263
- ### LoadingProps
129
+ function Header() {
130
+ const navigate = useNavigate()
131
+ const { isAuthenticated, logout } = useHowoneContext()
264
132
 
265
- ```ts
266
- type LoadingSize = 'sm' | 'md' | 'lg'
267
- type LoadingTone = 'brand' | 'neutral' | 'inverse'
268
-
269
- interface LoadingProps {
270
- size?: LoadingSize // default: 'md'
271
- tone?: LoadingTone // default: 'brand'
272
- label?: string // primary text
273
- description?: string // secondary text
274
- className?: string
275
- fullScreen?: boolean // renders over full viewport
276
- }
133
+ if (!isAuthenticated) {
134
+ return <button onClick={() => navigate(howone.auth.loginPath)}>Sign in</button>
135
+ }
277
136
 
278
- interface LoadingSpinnerProps {
279
- size?: LoadingSize
280
- tone?: LoadingTone
281
- className?: string
137
+ return <button onClick={() => void logout()}>Sign out</button>
282
138
  }
283
139
  ```
284
140
 
285
141
  ---
286
142
 
287
- ## Entity and AI Data in React
143
+ ## FloatingButton
144
+
145
+ The bottom-right HowOne logo is part of the SDK React integration and should remain visible by
146
+ default. It does not replace your login page and does not perform app auth. Hide it only with
147
+ `brand="hidden"` or `showBrandButton={false}`.
288
148
 
289
- `@howone/sdk/react` provides **no data hooks**. Use `useEffect`/`useState` or TanStack Query around the plain async SDK calls.
149
+ ---
290
150
 
291
- ### Pattern: auth-gated data fetch
151
+ ## Protected route pattern
292
152
 
293
153
  ```tsx
294
- import { useEffect, useState } from 'react'
295
- import { useHowoneContext } from '@howone/sdk/react'
296
- import { Loading } from '@howone/sdk/react'
297
- import howone, { type StoryRecord } from '@/lib/sdk'
298
-
299
- function MyStories() {
300
- const { isAuthenticated } = useHowoneContext()
301
- const [stories, setStories] = useState<StoryRecord[]>([])
302
- const [loading, setLoading] = useState(false)
154
+ function ProtectedPage() {
155
+ const [user, setUser] = useState(null)
156
+ const navigate = useNavigate()
303
157
 
304
158
  useEffect(() => {
305
- if (!isAuthenticated) return
306
-
307
- setLoading(true)
308
- howone.entities.Story.query.mine({ page: { number: 1, size: 20 } })
309
- .then(result => setStories(result.items))
310
- .finally(() => setLoading(false))
311
- }, [isAuthenticated])
312
-
313
- if (!isAuthenticated) return <div>Please log in.</div>
314
- if (loading) return <Loading label="Loading your stories..." />
315
-
316
- return (
317
- <ul>
318
- {stories.map(s => <li key={s.id}>{s.title}</li>)}
319
- </ul>
320
- )
321
- }
322
- ```
323
-
324
- ### Pattern: Full app layout
159
+ howone.me()
160
+ .then(setUser)
161
+ .catch(() => navigate(howone.auth.loginPath, { replace: true }))
162
+ }, [navigate])
325
163
 
326
- ```tsx
327
- // src/main.tsx
328
- import React from 'react'
329
- import ReactDOM from 'react-dom/client'
330
- import { HowOneProvider } from '@howone/sdk/react'
331
- import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
332
- import './lib/sdk'
333
- import App from './App'
334
-
335
- const queryClient = new QueryClient()
336
-
337
- ReactDOM.createRoot(document.getElementById('root')!).render(
338
- <React.StrictMode>
339
- <HowOneProvider
340
- auth="required"
341
- theme="system"
342
- brand="visible"
343
- >
344
- <QueryClientProvider client={queryClient}>
345
- <App />
346
- </QueryClientProvider>
347
- </HowOneProvider>
348
- </React.StrictMode>
349
- )
164
+ if (!user) return null
165
+ return <div>Welcome {user.name}</div>
166
+ }
350
167
  ```
351
168
 
352
- ### Pattern: conditional AI action based on auth
353
-
354
- ```tsx
355
- import { useState } from 'react'
356
- import { useHowoneContext } from '@howone/sdk/react'
357
- import howone from '@/lib/sdk'
169
+ ---
358
170
 
359
- function AIGenerateButton({ topic }: { topic: string }) {
360
- const { isAuthenticated } = useHowoneContext()
361
- const [loading, setLoading] = useState(false)
362
- const [result, setResult] = useState<string | null>(null)
363
-
364
- async function handleGenerate() {
365
- if (!isAuthenticated) {
366
- howone.auth.login()
367
- return
368
- }
369
- setLoading(true)
370
- try {
371
- const res = await howone.ai.generateStory.run({ topic, language: 'en' })
372
- if (res.success && res.finalResult) {
373
- setResult((res.finalResult as any).content)
374
- }
375
- } finally {
376
- setLoading(false)
377
- }
378
- }
171
+ ## Common mistakes
379
172
 
380
- return (
381
- <div>
382
- <button onClick={handleGenerate} disabled={loading}>
383
- {loading ? 'Generating...' : isAuthenticated ? 'Generate' : 'Login to Generate'}
384
- </button>
385
- {result && <p>{result}</p>}
386
- </div>
387
- )
388
- }
389
- ```
173
+ | Mistake | Fix |
174
+ |---------|-----|
175
+ | Custom UI but no `auth: 'custom'` | Add to `createClient` |
176
+ | `HowOneProvider auth="required"` without custom SDK auth | Hosted redirect to howone.ai |
177
+ | `useHowoneContext` without Provider | Wrap app in `HowOneProvider` |
178
+ | Import Provider before `./lib/sdk` | Import sdk module first |
179
+ | Manual redirect to howone.dev on logout | Use `howone.auth.logout()` |
180
+ | Deleting the bottom-right HowOne logo by default | Keep `brand="visible"` unless explicitly asked to hide it |
181
+ | Expecting SDK toast APIs | Implement visible feedback in the frontend app from callbacks/results |
390
182
 
391
183
  ---
392
184
 
393
- ## Common Mistakes
185
+ ## Import map
394
186
 
395
- | Mistake | Correct Pattern |
396
- |---|---|
397
- | `import { useHowoneContext } from '@howone/sdk'` | Import from `'@howone/sdk/react'` |
398
- | Expecting entity data hooks from `useHowoneContext` | Use `useEffect`/`useState` + `howone.entities.*` directly |
399
- | Placing `<HowOneProvider>` inside a component that re-renders | Place at root level (main.tsx / app layout) |
400
- | Using `user.id` without null-checking `user` | Guard: `if (!user) return` or use optional chaining `user?.id` |
187
+ | Need | Import |
188
+ |------|--------|
189
+ | Provider, context | `@howone/sdk/react` |
190
+ | Client, OTP, OAuth | `@howone/sdk` |
191
+ | App singleton | `@/lib/sdk` default export |