@telemetryos/cli 1.7.5 → 1.8.1
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/CHANGELOG.md +22 -0
- package/dist/commands/init.js +11 -0
- package/dist/services/generate-application.d.ts +1 -0
- package/dist/services/generate-application.js +127 -4
- package/dist/utils/validate-project-name.d.ts +19 -0
- package/dist/utils/validate-project-name.js +44 -0
- package/package.json +2 -2
- package/templates/vite-react-typescript/CLAUDE.md +68 -1244
- package/templates/vite-react-typescript/_claude/settings.local.json +17 -0
- package/templates/vite-react-typescript/_claude/skills/tos-architecture/SKILL.md +313 -0
- package/templates/vite-react-typescript/_claude/skills/tos-debugging/SKILL.md +299 -0
- package/templates/vite-react-typescript/_claude/skills/tos-media-api/SKILL.md +335 -0
- package/templates/vite-react-typescript/_claude/skills/tos-proxy-fetch/SKILL.md +319 -0
- package/templates/vite-react-typescript/_claude/skills/tos-render-design/SKILL.md +332 -0
- package/templates/vite-react-typescript/_claude/skills/tos-requirements/SKILL.md +252 -0
- package/templates/vite-react-typescript/_claude/skills/tos-settings-ui/SKILL.md +636 -0
- package/templates/vite-react-typescript/_claude/skills/tos-store-sync/SKILL.md +359 -0
- package/templates/vite-react-typescript/_claude/skills/tos-weather-api/SKILL.md +384 -0
- package/templates/vite-react-typescript/src/views/Render.css +1 -1
- package/templates/vite-react-typescript/src/views/Render.tsx +1 -1
- package/templates/vite-react-typescript/src/views/Settings.tsx +2 -2
- package/templates/vite-react-typescript/AGENTS.md +0 -7
- /package/templates/vite-react-typescript/{gitignore → _gitignore} +0 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tos-store-sync
|
|
3
|
+
description: REQUIRED for Settings↔Render data sync. MUST invoke BEFORE adding store keys or store hooks. Contains hook patterns, type definitions, and loading state handling.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TelemetryOS Store Sync
|
|
7
|
+
|
|
8
|
+
Store hooks enable real-time synchronization between Settings and Render views. When a user changes a setting, the Render view updates automatically.
|
|
9
|
+
|
|
10
|
+
## From Requirements to Implementation
|
|
11
|
+
|
|
12
|
+
When you have a requirements table from `tos-requirements`, translate each row to a store hook:
|
|
13
|
+
|
|
14
|
+
| Key | Category | Scope | Type | Default |
|
|
15
|
+
|-----|----------|-------|------|---------|
|
|
16
|
+
| city | Localization | instance | string | '' |
|
|
17
|
+
| apiKey | Data Config | application | string | '' |
|
|
18
|
+
| units | Localization | instance | 'imperial' \| 'metric' | 'imperial' |
|
|
19
|
+
| brightness | Display | device | number | 100 |
|
|
20
|
+
|
|
21
|
+
**Becomes:**
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// hooks/store.ts
|
|
25
|
+
import {
|
|
26
|
+
createUseInstanceStoreState,
|
|
27
|
+
createUseApplicationStoreState,
|
|
28
|
+
createUseDeviceStoreState,
|
|
29
|
+
} from '@telemetryos/sdk/react'
|
|
30
|
+
|
|
31
|
+
// Localization (instance scope)
|
|
32
|
+
export const useCityStoreState = createUseInstanceStoreState<string>('city', '')
|
|
33
|
+
export const useUnitsStoreState = createUseInstanceStoreState<'imperial' | 'metric'>('units', 'imperial')
|
|
34
|
+
|
|
35
|
+
// Data Config (application scope - shared across all instances)
|
|
36
|
+
export const useApiKeyStoreState = createUseApplicationStoreState<string>('apiKey', '')
|
|
37
|
+
|
|
38
|
+
// Display (device scope - Render only)
|
|
39
|
+
export const useBrightnessStoreState = createUseDeviceStoreState<number>('brightness', 100)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Usage:**
|
|
43
|
+
```typescript
|
|
44
|
+
// Instance-scoped (Settings + Render) - 250ms debounce for text input
|
|
45
|
+
const [, city, setCity] = useCityStoreState(250)
|
|
46
|
+
|
|
47
|
+
// Application-scoped (shared across all instances) - 250ms debounce for text input
|
|
48
|
+
const [, apiKey, setApiKey] = useApiKeyStoreState(250)
|
|
49
|
+
|
|
50
|
+
// Device-scoped (Render only - NOT available in Settings) - 5ms for slider
|
|
51
|
+
const [, brightness, setBrightness] = useBrightnessStoreState(5)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Quick Pattern
|
|
55
|
+
|
|
56
|
+
### 1. Define Hook (hooks/store.ts)
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { createUseInstanceStoreState } from '@telemetryos/sdk/react'
|
|
60
|
+
|
|
61
|
+
// One hook per store key
|
|
62
|
+
export const useCityStoreState = createUseInstanceStoreState<string>('city', '')
|
|
63
|
+
export const useUnitsStoreState = createUseInstanceStoreState<'imperial' | 'metric'>('units', 'imperial')
|
|
64
|
+
export const useRefreshIntervalStoreState = createUseInstanceStoreState<number>('refreshInterval', 30)
|
|
65
|
+
export const useShowForecastStoreState = createUseInstanceStoreState<boolean>('showForecast', true)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 2. Use in Settings (read + write)
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { useCityStoreState } from '../hooks/store'
|
|
72
|
+
|
|
73
|
+
export function Settings() {
|
|
74
|
+
const [isLoading, city, setCity] = useCityStoreState(250) // 250ms debounce for text input
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<input
|
|
78
|
+
disabled={isLoading}
|
|
79
|
+
value={city}
|
|
80
|
+
onChange={(e) => setCity(e.target.value)}
|
|
81
|
+
/>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 3. Use in Render (read only)
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { useCityStoreState } from '../hooks/store'
|
|
90
|
+
|
|
91
|
+
export function Render() {
|
|
92
|
+
const [isLoading, city] = useCityStoreState()
|
|
93
|
+
|
|
94
|
+
if (isLoading) return <div>Loading...</div>
|
|
95
|
+
if (!city) return <div>Configure city in Settings</div>
|
|
96
|
+
|
|
97
|
+
return <div>Weather for {city}</div>
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Store Scopes
|
|
102
|
+
|
|
103
|
+
### createUseInstanceStoreState (Most Common)
|
|
104
|
+
|
|
105
|
+
Use for Settings ↔ Render communication. Each app instance has its own storage.
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { createUseInstanceStoreState } from '@telemetryos/sdk/react'
|
|
109
|
+
|
|
110
|
+
export const useCityStoreState = createUseInstanceStoreState<string>('city', '')
|
|
111
|
+
|
|
112
|
+
// Usage - add 250ms debounce for text inputs
|
|
113
|
+
const [isLoading, city, setCity] = useCityStoreState(250)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Use cases:**
|
|
117
|
+
- App configuration (city, team name, colors)
|
|
118
|
+
- User preferences (units, refresh rate)
|
|
119
|
+
- Selected items (folder ID, content ID)
|
|
120
|
+
|
|
121
|
+
### createUseApplicationStoreState
|
|
122
|
+
|
|
123
|
+
Shared across ALL instances of this app in the account.
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { createUseApplicationStoreState } from '@telemetryos/sdk/react'
|
|
127
|
+
|
|
128
|
+
export const useApiKeyStoreState = createUseApplicationStoreState<string>('apiKey', '')
|
|
129
|
+
|
|
130
|
+
// Usage - add 250ms debounce for text inputs
|
|
131
|
+
const [isLoading, apiKey, setApiKey] = useApiKeyStoreState(250)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Use cases:**
|
|
135
|
+
- API keys (configured once, used everywhere)
|
|
136
|
+
- Account-wide defaults
|
|
137
|
+
- Shared lookup data
|
|
138
|
+
|
|
139
|
+
### createUseDeviceStoreState (Render Only)
|
|
140
|
+
|
|
141
|
+
Local to the physical device. NOT available in Settings. Use for interactive apps where the Render view needs to persist data beyond the current playlist display cycle.
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { createUseDeviceStoreState } from '@telemetryos/sdk/react'
|
|
145
|
+
|
|
146
|
+
export const useBrightnessStoreState = createUseDeviceStoreState<number>('brightness', 100)
|
|
147
|
+
|
|
148
|
+
// Only in Render.tsx - 5ms debounce for slider
|
|
149
|
+
const [isLoading, brightness, setBrightness] = useBrightnessStoreState(5)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Use cases:**
|
|
153
|
+
- Interactive app state (user selections, game progress, form inputs)
|
|
154
|
+
- Data that should persist when the app cycles out of a playlist and back
|
|
155
|
+
- Local cache for expensive computations or API responses
|
|
156
|
+
- Device-specific runtime state (not configurable via Settings)
|
|
157
|
+
|
|
158
|
+
**Note:** Device storage is only available in the Render mount point.
|
|
159
|
+
|
|
160
|
+
### createUseSharedStoreState
|
|
161
|
+
|
|
162
|
+
Inter-app communication. Apps can publish/subscribe to shared namespaces.
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { createUseSharedStoreState } from '@telemetryos/sdk/react'
|
|
166
|
+
|
|
167
|
+
export const useTempStoreState = createUseSharedStoreState<string>('temp', '', 'weather-data')
|
|
168
|
+
|
|
169
|
+
// Weather app publishes
|
|
170
|
+
const [, , setTemp] = useTempStoreState()
|
|
171
|
+
setTemp('72°F')
|
|
172
|
+
|
|
173
|
+
// Other apps subscribe
|
|
174
|
+
const [isLoading, temp] = useTempStoreState()
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Use cases:**
|
|
178
|
+
- Data sharing between apps
|
|
179
|
+
- Event broadcasting
|
|
180
|
+
- Coordinated updates
|
|
181
|
+
|
|
182
|
+
## Return Value
|
|
183
|
+
|
|
184
|
+
All hooks return a tuple:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
const [isLoading, value, setValue] = useCityStoreState()
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
| Index | Name | Type | Description |
|
|
191
|
+
|-------|------|------|-------------|
|
|
192
|
+
| 0 | isLoading | boolean | `true` until first value received |
|
|
193
|
+
| 1 | value | T | Current value (from store or optimistic update) |
|
|
194
|
+
| 2 | setValue | Dispatch<SetStateAction<T>> | Updates both local state and store |
|
|
195
|
+
|
|
196
|
+
The default debounce delay is 0ms (immediate updates). Pass a value for debounced updates:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
const [isLoading, city, setCity] = useCityStoreState(250) // 250ms debounce for text inputs
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## TypeScript Types
|
|
203
|
+
|
|
204
|
+
### Primitive Types
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
export const useNameStoreState = createUseInstanceStoreState<string>('name', '')
|
|
208
|
+
export const useCountStoreState = createUseInstanceStoreState<number>('count', 0)
|
|
209
|
+
export const useEnabledStoreState = createUseInstanceStoreState<boolean>('enabled', true)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Union Types (Enums)
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
export const useUnitsStoreState = createUseInstanceStoreState<'imperial' | 'metric'>('units', 'imperial')
|
|
216
|
+
export const useThemeStoreState = createUseInstanceStoreState<'light' | 'dark' | 'system'>('theme', 'system')
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Arrays
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
interface Slide {
|
|
223
|
+
id: string
|
|
224
|
+
title: string
|
|
225
|
+
imageUrl: string
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export const useSlidesStoreState = createUseInstanceStoreState<Slide[]>('slides', [])
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Complex Objects
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
interface WeatherConfig {
|
|
235
|
+
city: string
|
|
236
|
+
units: 'imperial' | 'metric'
|
|
237
|
+
showForecast: boolean
|
|
238
|
+
refreshInterval: number
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export const useWeatherConfigStoreState = createUseInstanceStoreState<WeatherConfig>('weatherConfig', {
|
|
242
|
+
city: '',
|
|
243
|
+
units: 'imperial',
|
|
244
|
+
showForecast: true,
|
|
245
|
+
refreshInterval: 30,
|
|
246
|
+
})
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Data Organization Patterns
|
|
250
|
+
|
|
251
|
+
### Recommended: Separate Keys
|
|
252
|
+
|
|
253
|
+
One store key per setting. Best for most cases.
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
// hooks/store.ts
|
|
257
|
+
export const useCityStoreState = createUseInstanceStoreState<string>('city', '')
|
|
258
|
+
export const useUnitsStoreState = createUseInstanceStoreState<'imperial' | 'metric'>('units', 'imperial')
|
|
259
|
+
export const useShowForecastStoreState = createUseInstanceStoreState<boolean>('showForecast', true)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**Benefits:**
|
|
263
|
+
- Granular updates (changing city doesn't re-render units)
|
|
264
|
+
- Simpler TypeScript types
|
|
265
|
+
- Easier to add/remove settings
|
|
266
|
+
|
|
267
|
+
### Alternative: Config Object
|
|
268
|
+
|
|
269
|
+
Single object for tightly related data.
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
interface WeatherConfig {
|
|
273
|
+
city: string
|
|
274
|
+
units: 'imperial' | 'metric'
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export const useWeatherConfigStoreState = createUseInstanceStoreState<WeatherConfig>('weatherConfig', {
|
|
278
|
+
city: '',
|
|
279
|
+
units: 'imperial',
|
|
280
|
+
})
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**Use when:**
|
|
284
|
+
- Settings are always read/written together
|
|
285
|
+
- You need atomic updates across multiple fields
|
|
286
|
+
|
|
287
|
+
### Array Pattern (Lists)
|
|
288
|
+
|
|
289
|
+
For managing lists of items (slideshow, playlist, etc.)
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
interface Slide {
|
|
293
|
+
id: string
|
|
294
|
+
title: string
|
|
295
|
+
duration: number
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export const useSlidesStoreState = createUseInstanceStoreState<Slide[]>('slides', [])
|
|
299
|
+
|
|
300
|
+
// In Settings - 250ms debounce since array contains text fields
|
|
301
|
+
const [isLoading, slides, setSlides] = useSlidesStoreState(250)
|
|
302
|
+
|
|
303
|
+
const addSlide = (slide: Slide) => setSlides([...slides, slide])
|
|
304
|
+
const removeSlide = (id: string) => setSlides(slides.filter(s => s.id !== id))
|
|
305
|
+
const updateSlide = (id: string, updates: Partial<Slide>) =>
|
|
306
|
+
setSlides(slides.map(s => s.id === id ? { ...s, ...updates } : s))
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Common Patterns
|
|
310
|
+
|
|
311
|
+
### Loading State
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
const [isLoading, city] = useCityStoreState()
|
|
315
|
+
|
|
316
|
+
if (isLoading) return <div>Loading...</div>
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Empty State
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
const [isLoading, city] = useCityStoreState()
|
|
323
|
+
|
|
324
|
+
if (isLoading) return <div>Loading...</div>
|
|
325
|
+
if (!city) return <div>Please configure city in Settings</div>
|
|
326
|
+
|
|
327
|
+
return <WeatherDisplay city={city} />
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Conditional Rendering
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
const [, showForecast] = useShowForecastStoreState()
|
|
334
|
+
|
|
335
|
+
return (
|
|
336
|
+
<div>
|
|
337
|
+
<CurrentWeather />
|
|
338
|
+
{showForecast && <ForecastDisplay />}
|
|
339
|
+
</div>
|
|
340
|
+
)
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Dependent Values
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
const [isLoadingCity, city] = useCityStoreState()
|
|
347
|
+
const [isLoadingUnits, units] = useUnitsStoreState()
|
|
348
|
+
|
|
349
|
+
useEffect(() => {
|
|
350
|
+
if (isLoadingCity || isLoadingUnits || !city) return
|
|
351
|
+
|
|
352
|
+
// Fetch weather when config is ready
|
|
353
|
+
fetchWeather(city, units)
|
|
354
|
+
}, [city, units, isLoadingCity, isLoadingUnits])
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## Advanced
|
|
358
|
+
|
|
359
|
+
For more control, the SDK also exports `useStoreState` and `createUseStoreState` which allow passing the store slice explicitly. The low-level `store()` API provides direct `get`, `set`, `subscribe`, and `delete` methods. See the store-hooks documentation for details.
|