@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.
Files changed (23) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/commands/init.js +11 -0
  3. package/dist/services/generate-application.d.ts +1 -0
  4. package/dist/services/generate-application.js +127 -4
  5. package/dist/utils/validate-project-name.d.ts +19 -0
  6. package/dist/utils/validate-project-name.js +44 -0
  7. package/package.json +2 -2
  8. package/templates/vite-react-typescript/CLAUDE.md +68 -1244
  9. package/templates/vite-react-typescript/_claude/settings.local.json +17 -0
  10. package/templates/vite-react-typescript/_claude/skills/tos-architecture/SKILL.md +313 -0
  11. package/templates/vite-react-typescript/_claude/skills/tos-debugging/SKILL.md +299 -0
  12. package/templates/vite-react-typescript/_claude/skills/tos-media-api/SKILL.md +335 -0
  13. package/templates/vite-react-typescript/_claude/skills/tos-proxy-fetch/SKILL.md +319 -0
  14. package/templates/vite-react-typescript/_claude/skills/tos-render-design/SKILL.md +332 -0
  15. package/templates/vite-react-typescript/_claude/skills/tos-requirements/SKILL.md +252 -0
  16. package/templates/vite-react-typescript/_claude/skills/tos-settings-ui/SKILL.md +636 -0
  17. package/templates/vite-react-typescript/_claude/skills/tos-store-sync/SKILL.md +359 -0
  18. package/templates/vite-react-typescript/_claude/skills/tos-weather-api/SKILL.md +384 -0
  19. package/templates/vite-react-typescript/src/views/Render.css +1 -1
  20. package/templates/vite-react-typescript/src/views/Render.tsx +1 -1
  21. package/templates/vite-react-typescript/src/views/Settings.tsx +2 -2
  22. package/templates/vite-react-typescript/AGENTS.md +0 -7
  23. /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.