howone 0.2.2 → 0.2.5
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.
- package/package.json +1 -1
- package/templates/vite/.howone/skills/howone/01-architect/01-app-generation.md +36 -7
- package/templates/vite/.howone/skills/howone/01-architect/02-manifest-codegen.md +121 -436
- package/templates/vite/.howone/skills/howone/03-ai-capabilities/03-service-capability-catalog.md +37 -19
- package/templates/vite/.howone/skills/howone/03-ai-capabilities/04-workflow-operations.md +14 -5
- package/templates/vite/.howone/skills/howone/04-app-sdk/01-client-setup.md +94 -261
- package/templates/vite/.howone/skills/howone/04-app-sdk/02-entity-operations.md +85 -465
- package/templates/vite/.howone/skills/howone/04-app-sdk/03-auth.md +11 -7
- package/templates/vite/.howone/skills/howone/04-app-sdk/04-react-integration.md +84 -137
- package/templates/vite/.howone/skills/howone/04-app-sdk/05-file-upload.md +66 -273
- package/templates/vite/.howone/skills/howone/04-app-sdk/06-raw-http.md +72 -249
- package/templates/vite/.howone/skills/howone/04-app-sdk/07-ai-action-calls.md +135 -499
- package/templates/vite/.howone/skills/howone/04-app-sdk/08-ai-manifest-handoff.md +49 -196
- package/templates/vite/.howone/skills/howone/04-app-sdk/09-extension-boundaries.md +4 -4
- package/templates/vite/.howone/skills/howone/04-app-sdk/10-workflow-execute-sse.md +94 -61
- package/templates/vite/.howone/skills/howone/04-app-sdk/11-entity-data-access-patterns.md +4 -3
- package/templates/vite/.howone/skills/howone/SKILL.md +110 -4
- package/templates/vite/.howone/skills/howone/references/audio-generation.md +30 -0
- package/templates/vite/.howone/skills/howone/references/audio-recognition.md +30 -0
- package/templates/vite/.howone/skills/howone/references/common-errors.md +27 -0
- package/templates/vite/.howone/skills/howone/references/finance.md +28 -0
- package/templates/vite/.howone/skills/howone/references/image-editing.md +30 -0
- package/templates/vite/.howone/skills/howone/references/image-generation.md +30 -0
- package/templates/vite/.howone/skills/howone/references/version-evidence.md +47 -0
- package/templates/vite/.howone/skills/howone/references/video-generation.md +35 -0
- package/templates/vite/.howone/skills/howone/scripts/verify-project.mjs +151 -0
- package/templates/vite/package.json +1 -1
- package/templates/vite/src/App.tsx +9 -5
- package/templates/vite/src/lib/sdk.ts +7 -5
- package/templates/vite/bun.lock +0 -1478
package/templates/vite/.howone/skills/howone/03-ai-capabilities/03-service-capability-catalog.md
CHANGED
|
@@ -15,13 +15,13 @@ Source: `docs/ai-capability.md`.
|
|
|
15
15
|
| User asks for | Use capability family | Typical inputs | Typical outputs |
|
|
16
16
|
|---|---|---|---|
|
|
17
17
|
| Latest info, research, source-backed answer | Web search / crawling | `topic`, `prompt`, `url`, `search_level` | `answer`, `sources`, `page_content` |
|
|
18
|
-
| Generate artwork/photo/logo/mockup | Image generation | `
|
|
19
|
-
| Edit an image | Image editing | `
|
|
20
|
-
| OCR or visual analysis | Image analysis / OCR | `
|
|
21
|
-
| Generate short video | Video generation | `
|
|
18
|
+
| Generate artwork/photo/logo/mockup | Image generation | `prompt`, optional `reference_img_urls`, size/format | `generated_image_url` or `image_urls` |
|
|
19
|
+
| Edit an image | Image editing | `prompt`, `image_urls`, optional size/format | `edited_image_url` or `image_urls` |
|
|
20
|
+
| OCR or visual analysis | Image analysis / OCR | `prompt`, optional `image_urls` | `analysis_result` or `extracted_text` |
|
|
21
|
+
| Generate short video | Video generation | `prompt`, aspect/duration/frame URLs | `video_url` |
|
|
22
22
|
| Join clips / extract frames | Video editing | `video_urls` or `video_url` | `video_url` or `image_url` |
|
|
23
|
-
| Text to speech | Audio generation | `text_to_generate`, `
|
|
24
|
-
| Speech to text | Audio recognition | `
|
|
23
|
+
| Text to speech | Audio generation | `text_to_generate`, `languages`, `gender`, `audio_hint` | `audio_url` |
|
|
24
|
+
| Speech to text | Audio recognition | `audio_url`, `language`, optional diarization | `transcript_text`, optional `utterances` |
|
|
25
25
|
| Merge audio | Audio merging | `audio_urls` | `merged_audio_url` |
|
|
26
26
|
| Stock/index history | Financial data retrieval | `trading_symbol`, `unit`, `start`, `end` | `price_history` |
|
|
27
27
|
| Literature search/citations | Academic research | `query` | `papers`, `bibtex` |
|
|
@@ -29,6 +29,25 @@ Source: `docs/ai-capability.md`.
|
|
|
29
29
|
|
|
30
30
|
If the requested behavior is not in this table or the detailed sections below, do not invent it.
|
|
31
31
|
|
|
32
|
+
## Detailed Parameter References
|
|
33
|
+
|
|
34
|
+
The sections below establish family-level feasibility. Before designing one of these workflow
|
|
35
|
+
contracts, read its detailed service reference for exact parameter names, allowed values, defaults,
|
|
36
|
+
conditional requirements, outputs, and hard limits:
|
|
37
|
+
|
|
38
|
+
| Capability family | Read |
|
|
39
|
+
|---|---|
|
|
40
|
+
| Audio generation | `../references/audio-generation.md` |
|
|
41
|
+
| Audio recognition | `../references/audio-recognition.md` |
|
|
42
|
+
| Financial data retrieval | `../references/finance.md` |
|
|
43
|
+
| Image editing | `../references/image-editing.md` |
|
|
44
|
+
| Image generation | `../references/image-generation.md` |
|
|
45
|
+
| Video generation | `../references/video-generation.md` |
|
|
46
|
+
|
|
47
|
+
These are workflow-service parameters, not direct `@howone/sdk` method signatures. Translate only
|
|
48
|
+
the product-relevant subset into a loose capability schema, then use the synced AI manifest for app
|
|
49
|
+
bindings.
|
|
50
|
+
|
|
32
51
|
## Web Search And Crawling
|
|
33
52
|
|
|
34
53
|
Use for latest information, news, market context, source-backed answers, web page extraction.
|
|
@@ -59,10 +78,9 @@ Use for new images from prompts or prompt + reference URLs.
|
|
|
59
78
|
|
|
60
79
|
Inputs:
|
|
61
80
|
|
|
62
|
-
- `
|
|
63
|
-
- `
|
|
64
|
-
- `
|
|
65
|
-
- size/format options only when product exposes them.
|
|
81
|
+
- `prompt`: detailed prompt;
|
|
82
|
+
- `reference_img_urls`: optional URL array;
|
|
83
|
+
- optional `image_names`, `size`, and `output_format` when the product exposes them.
|
|
66
84
|
|
|
67
85
|
Outputs:
|
|
68
86
|
|
|
@@ -82,8 +100,8 @@ Use for modifying existing images.
|
|
|
82
100
|
|
|
83
101
|
Inputs:
|
|
84
102
|
|
|
85
|
-
- `
|
|
86
|
-
- `
|
|
103
|
+
- `image_urls`: one or more source image URLs;
|
|
104
|
+
- `prompt`: edit instruction;
|
|
87
105
|
- optional output size/format.
|
|
88
106
|
|
|
89
107
|
Outputs:
|
|
@@ -106,7 +124,7 @@ Use for visual understanding, image comparison, text extraction, quality review.
|
|
|
106
124
|
Inputs:
|
|
107
125
|
|
|
108
126
|
- `image_urls`;
|
|
109
|
-
- `
|
|
127
|
+
- `prompt`: analysis question or OCR instruction.
|
|
110
128
|
|
|
111
129
|
Outputs:
|
|
112
130
|
|
|
@@ -125,8 +143,8 @@ Use for short video clips from text or image frames.
|
|
|
125
143
|
|
|
126
144
|
Inputs:
|
|
127
145
|
|
|
128
|
-
- `
|
|
129
|
-
- optional `first_frame_url`, `last_frame_url`, `
|
|
146
|
+
- `prompt`;
|
|
147
|
+
- optional `first_frame_url`, `last_frame_url`, `reference_images`;
|
|
130
148
|
- optional `aspect_ratio`, `duration`, `negative_prompt`, `generate_audio`.
|
|
131
149
|
|
|
132
150
|
Outputs:
|
|
@@ -165,10 +183,10 @@ Use for text-to-speech.
|
|
|
165
183
|
Inputs:
|
|
166
184
|
|
|
167
185
|
- `text_to_generate`;
|
|
168
|
-
- `
|
|
186
|
+
- `languages`;
|
|
169
187
|
- `gender`;
|
|
170
188
|
- `audio_hint`;
|
|
171
|
-
- optional output format/name.
|
|
189
|
+
- optional `audio_output` with output format/name.
|
|
172
190
|
|
|
173
191
|
Outputs:
|
|
174
192
|
|
|
@@ -186,9 +204,9 @@ Use for speech-to-text.
|
|
|
186
204
|
|
|
187
205
|
Inputs:
|
|
188
206
|
|
|
189
|
-
- `
|
|
207
|
+
- `audio_url`;
|
|
190
208
|
- optional `language`;
|
|
191
|
-
- optional speaker diarization
|
|
209
|
+
- optional `with_speaker_info` for speaker diarization.
|
|
192
210
|
|
|
193
211
|
Outputs:
|
|
194
212
|
|
|
@@ -66,7 +66,7 @@ Tool behavior:
|
|
|
66
66
|
|
|
67
67
|
```text
|
|
68
68
|
previous config = current manifest capability.workflowId
|
|
69
|
-
new config = freshly generated UUID sent as
|
|
69
|
+
new config = freshly generated UUID sent as new_config_id
|
|
70
70
|
mode = update
|
|
71
71
|
original_config_id = previous config sent to EAX
|
|
72
72
|
workflow code = regenerated by EAX from the current capability contract and updatePrompt
|
|
@@ -108,6 +108,11 @@ Terminal result meaning:
|
|
|
108
108
|
| `completed` | workflow operation completed |
|
|
109
109
|
| `failed` / `canceled` / `timed_out` | report error and do not pretend workflow is ready |
|
|
110
110
|
|
|
111
|
+
A non-terminal submission is not a legal completion or pause boundary for the Coding Agent. Do not
|
|
112
|
+
end the run merely to wait for a host notification. Continue all implementation that does not need
|
|
113
|
+
the promoted workflow ID, including UI structure, persistence, loading/error states, and other app
|
|
114
|
+
logic. Defer only final workflow-ID binding and live execution verification until terminal success.
|
|
115
|
+
|
|
111
116
|
## Standard Flow
|
|
112
117
|
|
|
113
118
|
New AI feature:
|
|
@@ -116,7 +121,8 @@ New AI feature:
|
|
|
116
121
|
ai-capability-design apply_capability_patch
|
|
117
122
|
sync_ai_artifacts
|
|
118
123
|
external-ai-capability { cwd, capabilityNames }
|
|
119
|
-
|
|
124
|
+
continue independent app implementation; do not stop to wait
|
|
125
|
+
terminal result
|
|
120
126
|
sync_ai_artifacts
|
|
121
127
|
read .howone/ai/manifest.json
|
|
122
128
|
```
|
|
@@ -127,7 +133,8 @@ Schema-changing AI update:
|
|
|
127
133
|
ai-capability-design apply_capability_patch
|
|
128
134
|
sync_ai_artifacts
|
|
129
135
|
external-ai-capability { cwd, updates: [{ capabilityName, updatePrompt }] }
|
|
130
|
-
|
|
136
|
+
continue independent app implementation; do not stop to wait
|
|
137
|
+
terminal result
|
|
131
138
|
sync_ai_artifacts
|
|
132
139
|
read .howone/ai/manifest.json
|
|
133
140
|
```
|
|
@@ -136,13 +143,15 @@ Behavior-only update:
|
|
|
136
143
|
|
|
137
144
|
```text
|
|
138
145
|
external-ai-capability { cwd, updates: [{ capabilityName, updatePrompt }] }
|
|
139
|
-
|
|
146
|
+
continue independent app implementation; do not stop to wait
|
|
147
|
+
terminal result
|
|
140
148
|
sync_ai_artifacts
|
|
141
149
|
read .howone/ai/manifest.json
|
|
142
150
|
update SDK workflowId bindings from the newly synced manifest
|
|
143
151
|
```
|
|
144
152
|
|
|
145
|
-
SDK
|
|
153
|
+
Final workflow-ID SDK binding happens after terminal sync and belongs to the SDK track. Other app
|
|
154
|
+
source work proceeds while the external job is pending.
|
|
146
155
|
|
|
147
156
|
## Checklist
|
|
148
157
|
|
|
@@ -1,329 +1,162 @@
|
|
|
1
1
|
# Client Setup
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use this track after synced manifests exist and before writing app code that imports
|
|
4
|
+
`@howone/sdk`. Read [version-evidence.md](../../references/version-evidence.md) first when the installed
|
|
5
|
+
SDK version is uncertain.
|
|
4
6
|
|
|
5
|
-
##
|
|
7
|
+
## One client, one source of truth
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
### CreateClientOptions — full type
|
|
10
|
-
|
|
11
|
-
```ts
|
|
12
|
-
type Environment = 'local' | 'dev' | 'prod'
|
|
13
|
-
|
|
14
|
-
type CreateClientOptions = {
|
|
15
|
-
// ── Required ──────────────────────────────────────────────
|
|
16
|
-
projectId?: string // Your HowOne project ID (set via VITE_HOWONE_PROJECT_ID)
|
|
17
|
-
|
|
18
|
-
// ── Environment ───────────────────────────────────────────
|
|
19
|
-
env?: Environment | string // 'local' | 'dev' | 'prod' (set via VITE_HOWONE_ENV)
|
|
20
|
-
apiUrl?: string // Override the REST API base URL
|
|
21
|
-
aiUrl?: string // Override the AI/SSE base URL
|
|
22
|
-
|
|
23
|
-
// ── Behaviour ─────────────────────────────────────────────
|
|
24
|
-
caseStyle?: 'camel' | 'snake' // Default: 'camel'
|
|
25
|
-
mode?: 'auto' | 'standalone' | 'embedded'
|
|
26
|
-
|
|
27
|
-
// ── Auth (one parameter for custom login) ─────────────────
|
|
28
|
-
auth?: 'custom' | 'hosted' | 'headless' | 'none' | {
|
|
29
|
-
mode?: 'custom' | 'hosted' | 'headless' | 'none' | 'managed'
|
|
30
|
-
loginPath?: string // default '/login' when mode is 'custom'
|
|
31
|
-
logoutPath?: string
|
|
32
|
-
guard?: 'required' | 'optional' | 'none'
|
|
33
|
-
getToken?: () => Promise<string | null>
|
|
34
|
-
adapter?: AuthAdapter
|
|
35
|
-
tokenCacheMs?: number
|
|
36
|
-
}
|
|
37
|
-
loginPath?: string // shorthand when auth is 'custom'
|
|
38
|
-
logoutPath?: string
|
|
39
|
-
|
|
40
|
-
// ── Limit-exceeded callbacks ───────────────────────────────
|
|
41
|
-
limitExceeded?: {
|
|
42
|
-
onLimitExceeded?: (context: LimitExceededContext) => void
|
|
43
|
-
showUpgradeToast?: boolean
|
|
44
|
-
upgradeUrl?: string
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// ── Deprecated — do not use in new code ───────────────────
|
|
48
|
-
appId?: string // Use projectId
|
|
49
|
-
baseUrl?: string // Use apiUrl / aiUrl
|
|
50
|
-
apiBaseUrl?: string // Use apiUrl
|
|
51
|
-
aiBaseUrl?: string // Use aiUrl
|
|
52
|
-
authRequired?: boolean // Use auth.mode
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### What createClient returns
|
|
57
|
-
|
|
58
|
-
```ts
|
|
59
|
-
const client = createClient({ ... })
|
|
60
|
-
|
|
61
|
-
client.projectId // string — resolved project ID
|
|
62
|
-
client.appId // string — alias for projectId
|
|
63
|
-
client.caseStyle // 'camel' | 'snake'
|
|
64
|
-
|
|
65
|
-
// Entity factory
|
|
66
|
-
client.entity<TRecord, TCreate, TUpdate>(entityName: string): EntityClient
|
|
67
|
-
|
|
68
|
-
// Typed entity map (populated via withEntities)
|
|
69
|
-
client.entities: Record<string, EntityClient>
|
|
70
|
-
|
|
71
|
-
// Public entity namespace (never sends Authorization)
|
|
72
|
-
client.public.entity<TRecord, TPublicCreate, TPublicUpdate>(entityName: string): PublicEntityClient
|
|
73
|
-
client.public.entities: Record<string, PublicEntityClient>
|
|
74
|
-
client.public.raw: RawHttpClient
|
|
75
|
-
|
|
76
|
-
// Schema contract/version client
|
|
77
|
-
client.schema.listDefinitions()
|
|
78
|
-
client.schema.getDefinition(entityName)
|
|
79
|
-
client.schema.operate(operation)
|
|
80
|
-
client.schema.applyPatch(patch, { expectedVersionId, reason })
|
|
81
|
-
client.schema.getState()
|
|
82
|
-
client.schema.listVersions()
|
|
83
|
-
client.schema.getVersion(versionId)
|
|
84
|
-
client.schema.restore(versionId, reason?)
|
|
85
|
-
|
|
86
|
-
// AI action runner (low-level)
|
|
87
|
-
client.ai: AiClient
|
|
88
|
-
|
|
89
|
-
// HTTP utilities (low-level — see 04-app-sdk/06-raw-http.md)
|
|
90
|
-
client.raw: RawHttpClient
|
|
91
|
-
|
|
92
|
-
// File upload
|
|
93
|
-
client.upload.file(file, options?)
|
|
94
|
-
client.upload.image(file)
|
|
95
|
-
client.upload.batch(options)
|
|
96
|
-
|
|
97
|
-
// User profile
|
|
98
|
-
client.me(options?) // Promise<UserProfile | null>
|
|
99
|
-
client.requireMe(options?) // Promise<UserProfile> throws if unauthenticated
|
|
100
|
-
client.session.user() // alias for client.me()
|
|
101
|
-
|
|
102
|
-
// Auth helpers (behavior driven by createClient auth mode)
|
|
103
|
-
client.auth.mode // 'custom' | 'hosted' | 'headless' | 'none'
|
|
104
|
-
client.auth.loginPath // e.g. '/login'
|
|
105
|
-
client.auth.setToken(token: string | null)
|
|
106
|
-
client.auth.getToken(): string | null
|
|
107
|
-
client.auth.isAuthenticated(): boolean
|
|
108
|
-
client.auth.login(returnUrl?: string)
|
|
109
|
-
await client.auth.logout()
|
|
110
|
-
await client.auth.clearSession({ redirect?: false | string })
|
|
111
|
-
client.auth.subscribe((state) => { ... }) // auth state callback
|
|
112
|
-
|
|
113
|
-
// URL utilities
|
|
114
|
-
client.sanitizeUrl(opts?: { clearAll?: boolean; sensitiveParams?: string[] })
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
## Standard Vite Setup
|
|
9
|
+
Create the client once at module scope in `src/lib/sdk.ts`. Use the same composed instance for
|
|
10
|
+
entities, AI, uploads, auth, and raw requests. Do not create clients inside components or handlers.
|
|
120
11
|
|
|
121
12
|
```ts
|
|
122
13
|
// src/lib/sdk.ts
|
|
123
14
|
import {
|
|
124
15
|
createClient,
|
|
125
|
-
defineAiAction,
|
|
126
16
|
defineAiActions,
|
|
127
17
|
defineEntities,
|
|
128
|
-
pickEntityPayload,
|
|
129
|
-
runAiActionAndPersist,
|
|
130
|
-
type EntityRecord,
|
|
131
18
|
withAiActions,
|
|
132
19
|
withEntities,
|
|
133
20
|
} from '@howone/sdk'
|
|
134
|
-
import { z } from 'zod'
|
|
135
21
|
|
|
136
|
-
// ── 1. Create client ─────────────────────────────────────────
|
|
137
22
|
const client = createClient({
|
|
138
23
|
projectId: import.meta.env.VITE_HOWONE_PROJECT_ID,
|
|
139
24
|
env: import.meta.env.VITE_HOWONE_ENV,
|
|
140
25
|
})
|
|
141
26
|
|
|
142
|
-
// ── 2. Define entity types & bind ────────────────────────────
|
|
143
|
-
// (see 04-app-sdk/02-entity-operations.md for full details)
|
|
144
|
-
export type NoteRecord = EntityRecord & { title: string; body: string }
|
|
145
|
-
export type NoteCreate = { title: string; body: string }
|
|
146
|
-
export type NoteUpdate = Partial<NoteCreate>
|
|
147
|
-
|
|
148
27
|
export const entities = defineEntities({
|
|
149
|
-
|
|
28
|
+
// Generated bindings go here; read 01-architect/02-manifest-codegen.md.
|
|
150
29
|
})
|
|
30
|
+
export const ai = defineAiActions({})
|
|
151
31
|
|
|
152
|
-
// ── 3. Define AI actions ─────────────────────────────────────
|
|
153
|
-
// (see 04-app-sdk/07-ai-action-calls.md for full details)
|
|
154
|
-
export const summarizeInputSchema = z.object({ noteId: z.string() })
|
|
155
|
-
export type SummarizeInput = z.infer<typeof summarizeInputSchema>
|
|
156
|
-
|
|
157
|
-
export const ai = defineAiActions({
|
|
158
|
-
summarizeNote: defineAiAction('summarizeNote', {
|
|
159
|
-
inputSchema: summarizeInputSchema,
|
|
160
|
-
}),
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
// ── 4. Compose and export ────────────────────────────────────
|
|
164
32
|
const howone = withAiActions(withEntities(client, entities), ai)
|
|
165
33
|
export default howone
|
|
166
34
|
```
|
|
167
35
|
|
|
168
|
-
|
|
36
|
+
`projectId` is required. Missing or invalid Vite variables must fail loudly; do not add empty
|
|
37
|
+
fallbacks such as `?? ''` or silently default the environment to production.
|
|
169
38
|
|
|
170
|
-
|
|
171
|
-
|---|---|
|
|
172
|
-
| `pickEntityPayload(definition, payload)` | Keep only schema-declared business fields before create/update. |
|
|
173
|
-
| `validateEntityPayload(definition, payload)` | Return structured issues for unknown/system/ownership/missing required fields. |
|
|
174
|
-
| `assertEntityPayload(definition, payload)` | Throw structured `EntityPayloadValidationError` before unsafe writes. |
|
|
175
|
-
| `validatePublicEntityQuery(definition, options)` | Check public filters, sorts, scopes, and limits against `access.public`. |
|
|
176
|
-
| `assertPublicEntityQuery(definition, options)` | Throw before generating an invalid public query. |
|
|
177
|
-
| `runAiActionAndPersist(options)` | Standard pending-first AI execution + entity persistence helper. |
|
|
39
|
+
## Environment-owned routing
|
|
178
40
|
|
|
179
|
-
|
|
41
|
+
`env` selects the complete deployment target. The REST API, auth endpoints, uploads, and EAX
|
|
42
|
+
workflow endpoint must use the same environment. EAX URLs are intentionally not configurable per
|
|
43
|
+
request or through `createClient`:
|
|
180
44
|
|
|
181
|
-
|
|
45
|
+
| `env` | REST base | EAX workflow base |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `local` | `http://localhost:3002/api` | `https://eax-backend-orchestrator-dev.fly.dev` |
|
|
48
|
+
| `dev` | `https://api.howone.dev/api` | `https://eax-backend-orchestrator-dev.fly.dev` |
|
|
49
|
+
| `prod` | `https://api.howone.ai/api` | `https://eax-backend-orchestrator-prod.fly.dev` |
|
|
182
50
|
|
|
183
|
-
|
|
51
|
+
Never add `aiUrl`, `aiBaseUrl`, or an EAX URL environment variable. If a special deployment needs a
|
|
52
|
+
different REST API, `apiUrl` is the only supported URL override; it does not change EAX routing.
|
|
184
53
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
54
|
+
The selected values are inspectable without guessing:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
howone.runtime.environment // 'local' | 'dev' | 'prod'
|
|
58
|
+
howone.runtime.apiBaseUrl
|
|
59
|
+
howone.runtime.aiBaseUrl
|
|
60
|
+
howone.runtime.authRoot
|
|
61
|
+
howone.runtime.authCookieRoot
|
|
188
62
|
```
|
|
189
63
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
- **Do not** hardcode project IDs in source. Use the env var.
|
|
193
|
-
- `env` accepts `'local'`, `'dev'`, or `'prod'`. **Auth OTP/OAuth, entities, AI, and uploads all use this same env.**
|
|
194
|
-
- Import `src/lib/sdk.ts` before calling `loginWithEmailCode` / `unifiedAuth` so env is pinned (otherwise auth defaults to prod APIs).
|
|
64
|
+
`howone.runtime` is immutable. A second client created later cannot retarget an existing client's
|
|
65
|
+
requests. Prefer one client per app; if tests create several, assert each client's runtime.
|
|
195
66
|
|
|
196
|
-
|
|
197
|
-
|-------|----------|------------------|
|
|
198
|
-
| `local` | `http://localhost:3002/api` | `http://localhost:3002/api/auth/email/send-code` |
|
|
199
|
-
| `dev` | `https://api.howone.dev/api` | `https://api.howone.dev/api/auth/email/send-code` |
|
|
200
|
-
| `prod` | `https://api.howone.ai/api` | `https://api.howone.ai/api/auth/email/send-code` |
|
|
67
|
+
## Options that matter
|
|
201
68
|
|
|
202
|
-
|
|
69
|
+
```ts
|
|
70
|
+
type CreateClientOptions = {
|
|
71
|
+
projectId?: string
|
|
72
|
+
env?: 'local' | 'dev' | 'prod' | string
|
|
73
|
+
apiUrl?: string // REST-only exceptional override
|
|
74
|
+
apiBaseUrl?: string // deprecated alias for apiUrl
|
|
75
|
+
appId?: string // deprecated alias for projectId
|
|
76
|
+
caseStyle?: 'camel' | 'snake' // default 'camel'
|
|
77
|
+
caseDepth?: 'top-level' | 'deep' // default 'top-level'
|
|
78
|
+
auth?: 'hosted' | 'custom' | 'headless' | 'none' | {
|
|
79
|
+
mode?: 'hosted' | 'custom' | 'headless' | 'none'
|
|
80
|
+
guard?: 'required' | 'optional' | 'none'
|
|
81
|
+
loginPath?: string
|
|
82
|
+
logoutPath?: string
|
|
83
|
+
getToken?: () => Promise<string | null>
|
|
84
|
+
adapter?: AuthAdapter
|
|
85
|
+
tokenCacheMs?: number
|
|
86
|
+
}
|
|
87
|
+
loginPath?: string
|
|
88
|
+
logoutPath?: string
|
|
89
|
+
authRequired?: boolean // deprecated compatibility alias
|
|
90
|
+
limitExceeded?: LimitExceededHandlerOptions
|
|
91
|
+
}
|
|
92
|
+
```
|
|
203
93
|
|
|
204
|
-
|
|
94
|
+
Do not use the removed `mode: 'auto' | 'standalone' | 'embedded'` option. That was not a runtime
|
|
95
|
+
behavior switch. Authentication mode belongs under `auth.mode` and means hosted/custom/headless/none.
|
|
205
96
|
|
|
206
|
-
|
|
97
|
+
## Authentication posture
|
|
207
98
|
|
|
208
99
|
```ts
|
|
209
|
-
//
|
|
100
|
+
// Hosted login (default): Provider inherits a required guard.
|
|
210
101
|
createClient({ projectId, env })
|
|
211
102
|
|
|
212
|
-
//
|
|
103
|
+
// App-owned login page using HowOne auth APIs.
|
|
213
104
|
createClient({ projectId, env, auth: 'custom', loginPath: '/login' })
|
|
214
105
|
|
|
215
|
-
//
|
|
106
|
+
// External identity provider; adapter supplies a token.
|
|
216
107
|
createClient({
|
|
217
108
|
projectId,
|
|
218
109
|
env,
|
|
219
110
|
auth: {
|
|
220
111
|
mode: 'headless',
|
|
221
112
|
adapter: {
|
|
222
|
-
getToken:
|
|
113
|
+
getToken: () => externalAuth.getToken(),
|
|
223
114
|
setToken: (token) => externalAuth.setToken(token),
|
|
224
|
-
login: ({ returnUrl } = {}) => router.push(`/login?redirect=${encodeURIComponent(returnUrl ?? '/')}`),
|
|
225
|
-
logout: () => router.push('/'),
|
|
226
115
|
},
|
|
227
|
-
tokenCacheMs: 60_000,
|
|
228
116
|
},
|
|
229
117
|
})
|
|
230
118
|
|
|
231
|
-
//
|
|
119
|
+
// Deliberately public app.
|
|
232
120
|
createClient({ projectId, env, auth: 'none' })
|
|
233
121
|
```
|
|
234
122
|
|
|
235
|
-
|
|
123
|
+
`HowOneProvider` receives `client={howone}` and derives its default route guard from
|
|
124
|
+
`client.auth.guard`. Override `auth="required|optional|none"` only when the UI intentionally needs
|
|
125
|
+
a different guard. Do not configure auth once on the client and independently guess it on the
|
|
126
|
+
Provider.
|
|
236
127
|
|
|
237
|
-
##
|
|
128
|
+
## Compose manifest bindings
|
|
238
129
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const client = createClient({
|
|
242
|
-
projectId: import.meta.env.VITE_HOWONE_PROJECT_ID,
|
|
243
|
-
env: import.meta.env.VITE_HOWONE_ENV,
|
|
244
|
-
// Override URLs only for special deployments
|
|
245
|
-
// apiUrl: import.meta.env.VITE_HOWONE_API_URL,
|
|
246
|
-
// aiUrl: import.meta.env.VITE_HOWONE_AI_URL,
|
|
247
|
-
})
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
---
|
|
251
|
-
|
|
252
|
-
## UserProfile Type
|
|
130
|
+
Use contract wrappers for generated entities and actions. Public bindings are a separate namespace;
|
|
131
|
+
never call an authenticated entity client from a public page just because the entity name matches.
|
|
253
132
|
|
|
254
133
|
```ts
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
appId?: string
|
|
263
|
-
roles?: string[]
|
|
264
|
-
metadata?: Record<string, unknown>
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Usage
|
|
268
|
-
const me = await client.me() // null if not logged in
|
|
269
|
-
const me = await client.requireMe() // throws HowOneAuthError if not logged in
|
|
270
|
-
|
|
271
|
-
// Check auth state programmatically
|
|
272
|
-
const isLoggedIn = client.auth.isAuthenticated()
|
|
273
|
-
const token = client.auth.getToken()
|
|
274
|
-
|
|
275
|
-
// Manually set a token (e.g. after custom login flow)
|
|
276
|
-
client.auth.setToken(jwtToken)
|
|
277
|
-
|
|
278
|
-
// Trigger login redirect
|
|
279
|
-
client.auth.login('/dashboard') // optional return path
|
|
280
|
-
|
|
281
|
-
// Logout
|
|
282
|
-
client.auth.logout()
|
|
134
|
+
const howone = withAiActions(
|
|
135
|
+
withPublicEntities(
|
|
136
|
+
withEntities(client, entities),
|
|
137
|
+
publicEntities,
|
|
138
|
+
),
|
|
139
|
+
ai,
|
|
140
|
+
)
|
|
283
141
|
```
|
|
284
142
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
## Client Namespace Rules
|
|
288
|
-
|
|
289
|
-
- Use `client.entities.*` / `howone.entities.*` for authenticated app data. The backend derives owner from the JWT; do not pass owner filters or owner fields.
|
|
290
|
-
- Use `client.public.entities.*` / `howone.public.entities.*` for public landing pages, public article lists, scoped QR/profile pages, and public forms.
|
|
291
|
-
- Use `client.schema.*` only for backend contract management: definitions, schema operations, schema patch apply, versions, and restore.
|
|
292
|
-
- Use `client.raw.*` only for custom endpoints not covered by typed SDK methods.
|
|
293
|
-
|
|
294
|
-
---
|
|
295
|
-
|
|
296
|
-
## HowOneAuthError
|
|
143
|
+
The exact generated composition belongs in `01-architect/02-manifest-codegen.md`.
|
|
297
144
|
|
|
298
|
-
|
|
299
|
-
import { HowOneAuthError } from '@howone/sdk'
|
|
300
|
-
|
|
301
|
-
try {
|
|
302
|
-
const user = await client.requireMe()
|
|
303
|
-
} catch (err) {
|
|
304
|
-
if (err instanceof HowOneAuthError) {
|
|
305
|
-
// err.code === 'UNAUTHENTICATED'
|
|
306
|
-
client.auth.login()
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
```
|
|
145
|
+
## Runtime activation checklist
|
|
310
146
|
|
|
311
|
-
|
|
147
|
+
- Keep `src/lib/sdk.ts` imported before rendering `HowOneProvider`.
|
|
148
|
+
- Add a Provider whenever app code uses entities, AI, uploads, auth, session, or raw SDK requests.
|
|
149
|
+
- Use `auth: 'none'` only for a deliberately public app or when the app owns its route guard.
|
|
150
|
+
- Use real token acquisition for authenticated AI/entity/upload calls; do not substitute mock data.
|
|
151
|
+
- Run `node .howone/skills/howone/scripts/verify-project.mjs` before typecheck/build.
|
|
312
152
|
|
|
313
|
-
##
|
|
153
|
+
## Common mistakes
|
|
314
154
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
console.error('Limit exceeded:', context.source, context.message)
|
|
324
|
-
// context.source: 'axios-response' | 'workflow-executor-sse' | ...
|
|
325
|
-
// context.status: HTTP status code (if available)
|
|
326
|
-
},
|
|
327
|
-
},
|
|
328
|
-
})
|
|
329
|
-
```
|
|
155
|
+
| Mistake | Correct pattern |
|
|
156
|
+
|---|---|
|
|
157
|
+
| AI URL in `.env` or `createClient` | Set only `env`; EAX is environment-owned. |
|
|
158
|
+
| `HowOneProvider` without the composed client | `<HowOneProvider client={howone}>`. |
|
|
159
|
+
| Client created in a component | Module-level singleton in `src/lib/sdk.ts`. |
|
|
160
|
+
| Using `apiUrl` to point at EAX | `apiUrl` is REST-only; EAX remains the env mapping. |
|
|
161
|
+
| Passing a JSON Schema object to an AI action | Generate a Zod schema first. |
|
|
162
|
+
| Calling `client.entities.Unknown` after strict codegen | Use `client.entity<T...>('Unknown')` only for an explicit, reviewed escape hatch. |
|