@telemetryos/cli 1.7.4 → 1.8.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 (25) 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/hooks/store.ts +3 -3
  20. package/templates/vite-react-typescript/src/index.css +0 -1
  21. package/templates/vite-react-typescript/src/views/Render.css +2 -1
  22. package/templates/vite-react-typescript/src/views/Render.tsx +2 -3
  23. package/templates/vite-react-typescript/src/views/Settings.tsx +2 -3
  24. package/templates/vite-react-typescript/AGENTS.md +0 -7
  25. /package/templates/vite-react-typescript/{gitignore → _gitignore} +0 -0
@@ -1,1295 +1,119 @@
1
- # TelemetryOS SDK Reference
1
+ # TelemetryOS Application
2
2
 
3
- **Application:** [Your App Name]
4
- **Purpose:** [What this application does]
3
+ **Application:** {{name}}
5
4
 
6
- ## Platform Architecture
5
+ ## Quick Start
7
6
 
8
- TelemetryOS applications are web apps that run on digital signage devices. Applications have up to 4 components:
9
-
10
- 1. **Render** (`/render`) - Content displayed on devices (runs on device in Chrome/iframe)
11
- 2. **Settings** (`/settings`) - Config UI in Studio admin portal (runs in Studio browser)
12
- 3. **Workers** (optional) - Background JavaScript (runs on device, no DOM)
13
- 4. **Containers** (optional) - Docker containers for backend services (runs on device)
14
-
15
- **Runtime Environment:**
16
- - Chrome browser (platform-controlled version)
17
- - Iframe sandbox execution
18
- - Client-side only (no SSR, no Node.js APIs)
19
- - Modern web APIs available (Fetch, WebSockets, WebGL, Canvas)
20
- - External APIs require CORS proxy
21
-
22
- **Communication:**
23
- - Settings and Render share instance storage
24
- - Settings saves config → Render subscribes to config
25
- - Device storage only available in Render (not Settings)
26
-
27
- ## Project Structure
28
-
29
- ```
30
- project-root/
31
- ├── telemetry.config.json # Platform configuration
32
- ├── package.json
33
- ├── tsconfig.json
34
- ├── vite.config.ts
35
- ├── index.html
36
- └── src/
37
- ├── main.tsx # Entry point (configure SDK here)
38
- ├── App.tsx # Routing logic
39
- ├── views/
40
- │ ├── Settings.tsx # /settings mount point
41
- │ └── Render.tsx # /render mount point
42
- ├── components/ # Reusable components
43
- ├── hooks/
44
- │ └── store.ts # Store state hooks (createUseStoreState)
45
- ├── types/ # TypeScript interfaces
46
- └── utils/ # Helper functions
47
- ```
48
-
49
- ## Configuration Files
50
-
51
- ### telemetry.config.json (project root)
52
- ```json
53
- {
54
- "name": "app-name",
55
- "version": "1.0.0",
56
- "mountPoints": {
57
- "render": "/render",
58
- "settings": "/settings"
59
- },
60
- "backgroundWorkers": {
61
- "background": "workers/background.js"
62
- },
63
- "serverWorkers": {
64
- "api": "workers/api.js"
65
- },
66
- "devServer": {
67
- "runCommand": "vite --port 3000",
68
- "url": "http://localhost:3000"
69
- }
70
- }
71
- ```
72
-
73
- ### package.json scripts
74
- ```json
75
- {
76
- "scripts": {
77
- "dev": "vite",
78
- "build": "tsc && vite build",
79
- "preview": "vite preview"
80
- },
81
- "dependencies": {
82
- "@telemetryos/sdk": "latest",
83
- "react": "latest",
84
- "react-dom": "latest"
85
- },
86
- "devDependencies": {
87
- "@types/react": "latest",
88
- "@types/react-dom": "latest",
89
- "@vitejs/plugin-react": "latest",
90
- "typescript": "latest",
91
- "vite": "latest"
92
- }
93
- }
94
- ```
95
-
96
- ## Complete File Implementations
97
-
98
- ### src/main.tsx (Entry Point)
99
- ```typescript
100
- import { configure } from '@telemetryos/sdk';
101
- import React from 'react';
102
- import ReactDOM from 'react-dom/client';
103
- import App from './App';
104
- import './index.css';
105
-
106
- // Configure SDK ONCE before React renders
107
- // Name must match telemetry.config.json
108
- configure('app-name');
109
-
110
- ReactDOM.createRoot(document.getElementById('root')!).render(
111
- <React.StrictMode>
112
- <App />
113
- </React.StrictMode>
114
- );
115
- ```
116
-
117
- ### src/App.tsx (Routing)
118
- ```typescript
119
- import Settings from './views/Settings';
120
- import Render from './views/Render';
121
-
122
- export default function App() {
123
- const path = window.location.pathname;
124
-
125
- if (path === '/settings') return <Settings />;
126
- if (path === '/render') return <Render />;
127
-
128
- return <div>Invalid mount point: {path}</div>;
129
- }
130
- ```
131
-
132
- ### src/hooks/store.ts (Store Hooks)
133
- ```typescript
134
- import { createUseStoreState } from '@telemetryos/sdk/react'
135
-
136
- // Create typed hooks for each store key
137
- export const useTeamStoreState = createUseStoreState<string>('team', '')
138
- export const useLeagueStoreState = createUseStoreState<string>('league', 'nfl')
139
- ```
140
-
141
- ### src/views/Settings.tsx (Complete Reference)
142
- ```typescript
143
- import { store } from '@telemetryos/sdk'
144
- import {
145
- SettingsContainer,
146
- SettingsField,
147
- SettingsLabel,
148
- SettingsInputFrame,
149
- SettingsSelectFrame,
150
- } from '@telemetryos/sdk/react'
151
- import { useTeamStoreState, useLeagueStoreState } from '../hooks/store'
152
-
153
- export default function Settings() {
154
- const [isLoadingTeam, team, setTeam] = useTeamStoreState(store().instance)
155
- const [isLoadingLeague, league, setLeague] = useLeagueStoreState(store().instance)
156
-
157
- return (
158
- <SettingsContainer>
159
- <SettingsField>
160
- <SettingsLabel>Team Name</SettingsLabel>
161
- <SettingsInputFrame>
162
- <input
163
- type="text"
164
- placeholder="Enter team name..."
165
- disabled={isLoadingTeam}
166
- value={team}
167
- onChange={(e) => setTeam(e.target.value)}
168
- />
169
- </SettingsInputFrame>
170
- </SettingsField>
171
-
172
- <SettingsField>
173
- <SettingsLabel>League</SettingsLabel>
174
- <SettingsSelectFrame>
175
- <select
176
- disabled={isLoadingLeague}
177
- value={league}
178
- onChange={(e) => setLeague(e.target.value)}
179
- >
180
- <option value="nfl">NFL</option>
181
- <option value="nba">NBA</option>
182
- <option value="mlb">MLB</option>
183
- <option value="nhl">NHL</option>
184
- </select>
185
- </SettingsSelectFrame>
186
- </SettingsField>
187
- </SettingsContainer>
188
- )
189
- }
190
- ```
191
-
192
- ### src/views/Render.tsx (Complete Reference)
193
- ```typescript
194
- import { useEffect, useState } from 'react'
195
- import { proxy } from '@telemetryos/sdk'
196
- import { store } from '@telemetryos/sdk'
197
- import { useTeamStoreState, useLeagueStoreState } from '../hooks/store'
198
-
199
- interface GameScore {
200
- homeTeam: string
201
- awayTeam: string
202
- homeScore: number
203
- awayScore: number
204
- status: string
205
- }
206
-
207
- export default function Render() {
208
- // Use same hooks as Settings - automatically syncs when Settings changes
209
- const [isLoadingTeam, team] = useTeamStoreState(store().instance)
210
- const [isLoadingLeague, league] = useLeagueStoreState(store().instance)
211
-
212
- const [score, setScore] = useState<GameScore | null>(null)
213
- const [loading, setLoading] = useState(false)
214
- const [error, setError] = useState<string | null>(null)
215
-
216
- // Fetch scores when config changes
217
- useEffect(() => {
218
- if (!team) return
219
-
220
- const fetchScore = async () => {
221
- setLoading(true)
222
- setError(null)
223
-
224
- try {
225
- // Platform handles caching automatically - no manual cache needed
226
- const response = await proxy().fetch(
227
- `https://api.sportsdata.io/v3/${league}/scores/json/GamesByTeam/${team}`
228
- )
229
-
230
- if (!response.ok) throw new Error(`API error: ${response.status}`)
231
-
232
- const data = await response.json()
233
- if (data.length > 0) {
234
- const game = data[0]
235
- setScore({
236
- homeTeam: game.HomeTeam,
237
- awayTeam: game.AwayTeam,
238
- homeScore: game.HomeScore,
239
- awayScore: game.AwayScore,
240
- status: game.Status,
241
- })
242
- }
243
- } catch (err) {
244
- setError(err instanceof Error ? err.message : 'Unknown error')
245
- } finally {
246
- setLoading(false)
247
- }
248
- }
249
-
250
- fetchScore()
251
- }, [team, league])
252
-
253
- // Loading state
254
- if (isLoadingTeam || isLoadingLeague) return <div>Loading config...</div>
255
- if (!team) return <div>Configure team in Settings</div>
256
- if (loading && !score) return <div>Loading scores...</div>
257
- if (error && !score) return <div>Error: {error}</div>
258
-
259
- return (
260
- <div>
261
- <h1>{team} - {league.toUpperCase()}</h1>
262
- {score && (
263
- <div>
264
- <div>{score.awayTeam} @ {score.homeTeam}</div>
265
- <div>{score.awayScore} - {score.homeScore}</div>
266
- <div>{score.status}</div>
267
- </div>
268
- )}
269
- </div>
270
- )
271
- }
272
- ```
273
-
274
- ## SDK API Reference
275
-
276
- Import from `@telemetryos/sdk`.
277
-
278
- ### Initialization
279
-
280
- ```typescript
281
- configure(applicationName: string): void
282
- ```
283
- - Call once in main.tsx before React renders
284
- - Name must match telemetry.config.json
285
- - Throws if called multiple times
286
-
287
- ### Storage API
288
-
289
- **Type Signatures:**
290
- ```typescript
291
- store().application.set<T>(key: string, value: T): Promise<boolean>
292
- store().application.get<T>(key: string): Promise<T | undefined>
293
- store().application.subscribe<T>(key: string, handler: (value: T | undefined) => void): Promise<boolean>
294
- store().application.unsubscribe<T>(key: string, handler?: (value: T | undefined) => void): Promise<boolean>
295
- store().application.delete(key: string): Promise<boolean>
296
-
297
- // Same methods for instance, device, shared(namespace)
298
- ```
299
-
300
- **Four Scopes:**
301
-
302
- 1. **application** - Shared across all instances of app in account
303
- ```typescript
304
- await store().application.set('companyLogo', 'https://...');
305
- const logo = await store().application.get<string>('companyLogo');
306
- ```
307
-
308
- 2. **instance** - This specific app instance (Settings ↔ Render communication)
309
- ```typescript
310
- // Settings saves
311
- await store().instance.set('config', { city: 'NYC' });
312
-
313
- // Render subscribes
314
- const handler = (newConfig) => updateDisplay(newConfig);
315
- await store().instance.subscribe('config', handler);
316
-
317
- // Later: unsubscribe
318
- await store().instance.unsubscribe('config', handler);
319
- ```
320
-
321
- 3. **device** - This physical device only (NOT available in Settings)
322
- ```typescript
323
- // Only in Render mount point
324
- await store().device.set('cache', data);
325
- const cached = await store().device.get<CacheType>('cache');
326
- ```
327
-
328
- 4. **shared(namespace)** - Inter-app communication
329
- ```typescript
330
- // App A publishes
331
- await store().shared('weather').set('temp', '72°F');
332
-
333
- // App B subscribes
334
- store().shared('weather').subscribe('temp', (temp) => console.log(temp));
335
- ```
336
-
337
- **Constraints:**
338
- - All operations timeout after 30 seconds (throws Error)
339
- - Returns `Promise<boolean>` for set/delete/subscribe/unsubscribe (true = success)
340
- - Returns `Promise<T | undefined>` for get
341
- - To unsubscribe, call `unsubscribe(key, handler)` with the same handler function
342
- - Device scope throws Error in Settings mount point
343
-
344
- ### Proxy API
345
-
346
- ```typescript
347
- proxy().fetch(url: string, options?: RequestInit): Promise<Response>
348
- ```
349
-
350
- - Same interface as standard fetch()
351
- - Use when external APIs don't include CORS headers
352
- - Returns standard Response object
353
- - Regular `fetch()` works fine when CORS is not an issue (and has advanced caching in the player)
354
-
355
- **Example:**
356
- ```typescript
357
- import { proxy } from '@telemetryos/sdk';
358
-
359
- const response = await proxy().fetch('https://api.example.com/data');
360
- const json = await response.json();
361
- ```
362
-
363
- ### Media API
364
-
365
- ```typescript
366
- media().getAllFolders(): Promise<MediaFolder[]>
367
- media().getAllByFolderId(folderId: string): Promise<MediaContent[]>
368
- media().getAllByTag(tagName: string): Promise<MediaContent[]>
369
- media().getById(id: string): Promise<MediaContent>
370
-
371
- interface MediaContent {
372
- id: string;
373
- contentFolderId: string;
374
- contentType: string;
375
- name: string;
376
- description: string;
377
- thumbnailUrl: string;
378
- keys: string[];
379
- publicUrls: string[];
380
- hidden: boolean;
381
- validFrom?: Date;
382
- validTo?: Date;
383
- createdAt: Date;
384
- updatedAt: Date;
385
- }
386
-
387
- interface MediaFolder {
388
- id: string;
389
- parentId: string;
390
- name: string;
391
- size: number;
392
- default: boolean;
393
- createdAt: Date;
394
- updatedAt: Date;
395
- }
396
- ```
397
-
398
- ### Playlist API
399
-
400
- ```typescript
401
- playlist().nextPage(): Promise<boolean>
402
- playlist().previousPage(): Promise<boolean>
403
- playlist().setDuration(duration: number): Promise<boolean> // duration in milliseconds
404
- ```
405
-
406
- ### Overrides API
407
-
408
- ```typescript
409
- overrides().setOverride(name: string): Promise<boolean>
410
- overrides().clearOverride(name: string): Promise<boolean>
411
- ```
412
-
413
- Note: Override names must be pre-configured in Freeform Editor.
414
-
415
- ### Platform Information
416
-
417
- ```typescript
418
- accounts().getCurrent(): Promise<Account>
419
- users().getCurrent(): Promise<User>
420
- devices().getInformation(): Promise<DeviceInformation> // Render only
421
-
422
- interface DeviceInformation {
423
- deviceSerialNumber: string;
424
- deviceModel: string;
425
- deviceManufacturer: string;
426
- devicePlatform: string;
427
- }
428
- ```
429
-
430
- ### Environment API
431
-
432
- ```typescript
433
- environment().getColorScheme(): Promise<'light' | 'dark' | 'system'>
434
- environment().subscribeColorScheme(handler: (scheme: 'light' | 'dark' | 'system') => void): void
435
- environment().unsubscribeColorScheme(handler: (scheme: 'light' | 'dark' | 'system') => void): void
436
- ```
437
-
438
- **Example:**
439
- ```typescript
440
- import { environment } from '@telemetryos/sdk';
441
-
442
- // Get current color scheme
443
- const scheme = await environment().getColorScheme();
444
-
445
- // Subscribe to color scheme changes
446
- environment().subscribeColorScheme((newScheme) => {
447
- document.body.className = newScheme;
448
- });
449
- ```
450
-
451
- ### Weather API
452
-
453
- ```typescript
454
- weather().getConditions(params: WeatherRequestParams): Promise<WeatherConditions>
455
- weather().getDailyForecast(params: DailyForecastParams): Promise<WeatherForecast[]>
456
- weather().getHourlyForecast(params: HourlyForecastParams): Promise<WeatherForecast[]>
457
-
458
- interface WeatherRequestParams {
459
- city?: string; // City name (e.g., "New York" or "London, UK")
460
- postalCode?: string; // Alternative to city
461
- lat?: string; // Latitude (if city not provided)
462
- lon?: string; // Longitude (if city not provided)
463
- units?: 'imperial' | 'metric';
464
- language?: string;
465
- }
466
-
467
- interface DailyForecastParams extends WeatherRequestParams {
468
- days?: number; // Number of days to forecast
469
- }
470
-
471
- interface HourlyForecastParams extends WeatherRequestParams {
472
- hours?: number; // Number of hours to forecast
473
- }
474
- ```
475
-
476
- **Example:**
477
- ```typescript
478
- import { weather } from '@telemetryos/sdk';
479
-
480
- // Get current conditions
481
- const conditions = await weather().getConditions({
482
- city: 'New York',
483
- units: 'imperial'
484
- });
485
- console.log(`${conditions.Temp}°F - ${conditions.WeatherText}`);
486
-
487
- // Get 5-day forecast
488
- const forecast = await weather().getDailyForecast({
489
- city: 'London',
490
- units: 'metric',
491
- days: 5
492
- });
493
- ```
494
-
495
- ### Applications API
496
-
497
- ```typescript
498
- applications().getAllByMountPoint(mountPoint: string): Promise<Application[]>
499
- applications().getByName(name: string): Promise<Application | null>
500
- applications().setDependencies(specifiers: string[]): Promise<{ ready: string[], unavailable: string[] }>
501
-
502
- interface Application {
503
- name: string;
504
- mountPoints: Record<string, { path: string }>;
505
- }
506
- ```
507
-
508
- **Example:**
509
- ```typescript
510
- import { applications } from '@telemetryos/sdk';
511
-
512
- // Find all apps with a specific mount point
513
- const widgets = await applications().getAllByMountPoint('widget');
514
-
515
- // Get a specific app by name
516
- const mapApp = await applications().getByName('interactive-map');
517
-
518
- // Declare dependencies before loading sub-apps
519
- const result = await applications().setDependencies(['app-specifier-hash']);
520
- if (result.ready.includes('app-specifier-hash')) {
521
- // Safe to load in iframe
522
- }
7
+ ```bash
8
+ npm install # Install dependencies
9
+ npm run build # Build and check for TypeScript errors
10
+ tos serve # Start dev server (or: npm run dev)
523
11
  ```
524
12
 
525
- ## React Hooks for Store
526
-
527
- Import from `@telemetryos/sdk/react`. These hooks simplify store interactions by handling subscriptions, loading states, and cleanup automatically.
528
-
529
- ### useStoreState
530
-
531
- Syncs React state with a store key. Handles subscription/unsubscription automatically.
13
+ **IMPORTANT:** Always run `npm run build` after making changes to check for TypeScript errors. Do not rely solely on the dev server.
532
14
 
533
- ```typescript
534
- import { useStoreState } from '@telemetryos/sdk/react'
535
- import { store } from '@telemetryos/sdk'
536
-
537
- function MyComponent() {
538
- const [isLoading, value, setValue] = useStoreState<string>(
539
- store().instance, // Store slice
540
- 'myKey', // Key name
541
- 'default value', // Initial state (optional)
542
- 300 // Debounce delay in ms (optional)
543
- )
544
-
545
- return (
546
- <input
547
- disabled={isLoading}
548
- value={value}
549
- onChange={(e) => setValue(e.target.value)}
550
- />
551
- )
552
- }
553
- ```
15
+ **Development Host:** http://localhost:2026
16
+ - Settings: http://localhost:2026/settings
17
+ - Render: http://localhost:2026/render
554
18
 
555
- **Returns:** `[isLoading: boolean, value: T, setValue: Dispatch<SetStateAction<T>>]`
19
+ ## Architecture
556
20
 
557
- - `isLoading` - `true` until first value received from store
558
- - `value` - Current value (from store or local optimistic update)
559
- - `setValue` - Updates both local state and store (with optional debounce)
21
+ TelemetryOS apps have two mount points:
560
22
 
561
- ### createUseStoreState
23
+ | Mount | Purpose | Runs On |
24
+ |-------|---------|---------|
25
+ | `/render` | Content displayed on devices | Physical device (TV, kiosk) |
26
+ | `/settings` | Configuration UI | Studio admin portal |
562
27
 
563
- Factory function to create reusable, typed hooks for specific store keys. **This is the recommended pattern.**
28
+ Settings and Render communicate via instance store hooks.
564
29
 
565
- ```typescript
566
- // hooks/store.ts
567
- import { createUseStoreState } from '@telemetryos/sdk/react'
30
+ ## Project Structure
568
31
 
569
- // Create typed hooks for each store key
570
- export const useTeamStoreState = createUseStoreState<string>('team', '')
571
- export const useLeagueStoreState = createUseStoreState<string>('league', 'nfl')
572
- export const useRefreshIntervalStoreState = createUseStoreState<number>('refreshInterval', 30)
573
32
  ```
574
-
575
- ```typescript
576
- // views/Settings.tsx
577
- import { store } from '@telemetryos/sdk'
578
- import { useTeamStoreState, useLeagueStoreState } from '../hooks/store'
579
-
580
- function Settings() {
581
- const [isLoadingTeam, team, setTeam] = useTeamStoreState(store().instance)
582
- const [isLoadingLeague, league, setLeague] = useLeagueStoreState(store().instance)
583
- // ... use in components
584
- }
33
+ src/
34
+ ├── index.tsx # Entry point (configure SDK here)
35
+ ├── App.tsx # Mount point routing
36
+ ├── views/
37
+ │ ├── Settings.tsx # Configuration UI
38
+ │ └── Render.tsx # Display content
39
+ └── hooks/
40
+ └── store.ts # Store state hooks
585
41
  ```
586
42
 
587
- **Benefits:**
588
- - Type-safe: TypeScript knows the exact type of each store key
589
- - Reusable: Same hook works in Settings and Render
590
- - Automatic cleanup: No manual subscribe/unsubscribe needed
591
- - Immediate sync: Changes sync to store automatically (no save button needed)
43
+ ## Core Pattern
592
44
 
593
- ### Store Data Patterns
45
+ ### Store Hooks (Settings ↔ Render sync)
594
46
 
595
- **Recommended: Separate store entry per field**
596
47
  ```typescript
597
48
  // hooks/store.ts
598
- export const useTeamStoreState = createUseStoreState<string>('team', '')
599
- export const useLeagueStoreState = createUseStoreState<string>('league', 'nfl')
600
- export const useShowScoresStoreState = createUseStoreState<boolean>('showScores', true)
601
- ```
49
+ import { createUseInstanceStoreState } from '@telemetryos/sdk/react'
602
50
 
603
- **Alternative: Rich data object** (for tightly related data like slideshow items)
604
- ```typescript
605
- // hooks/store.ts
606
- interface SportsSlide {
607
- team: string
608
- league: string
609
- displaySeconds: number
610
- }
611
-
612
- export const useSlidesStoreState = createUseStoreState<SportsSlide[]>('slides', [])
51
+ export const useCityState = createUseInstanceStoreState<string>('city', '')
52
+ export const useUnitsState = createUseInstanceStoreState<'imperial' | 'metric'>('units', 'imperial')
613
53
  ```
614
54
 
615
- ## Settings Components
616
-
617
- Import from `@telemetryos/sdk/react`. These components ensure your Settings UI matches the Studio design system.
618
-
619
- **Important:** Always use these components for Settings. Raw HTML won't look correct in Studio.
620
-
621
- ### Container & Layout
622
-
623
- #### SettingsContainer
624
-
625
- Root wrapper for all Settings content. Handles color scheme synchronization with Studio.
626
-
627
55
  ```typescript
628
- import { SettingsContainer } from '@telemetryos/sdk/react'
56
+ // In Settings.tsx or Render.tsx
57
+ import { useCityState } from '../hooks/store'
629
58
 
630
- function Settings() {
631
- return (
632
- <SettingsContainer>
633
- {/* All settings content goes here */}
634
- </SettingsContainer>
635
- )
636
- }
59
+ const [isLoading, city, setCity] = useCityState()
637
60
  ```
638
61
 
639
- #### SettingsBox
640
-
641
- Container with border for grouping related settings.
62
+ ### Settings Components
642
63
 
643
64
  ```typescript
644
- import { SettingsBox } from '@telemetryos/sdk/react'
645
-
646
- <SettingsBox>
647
- {/* Group of related fields */}
648
- </SettingsBox>
649
- ```
650
-
651
- #### SettingsDivider
652
-
653
- Horizontal rule separator between sections.
654
-
655
- ```typescript
656
- import { SettingsDivider } from '@telemetryos/sdk/react'
657
-
658
- <SettingsDivider />
659
- ```
660
-
661
- ### Field Structure
662
-
663
- #### SettingsField, SettingsLabel
664
-
665
- Wrapper for each form field with its label.
666
-
667
- ```typescript
668
- import { SettingsField, SettingsLabel } from '@telemetryos/sdk/react'
669
-
670
- <SettingsField>
671
- <SettingsLabel>Field Label</SettingsLabel>
672
- {/* Input component goes here */}
673
- </SettingsField>
674
- ```
675
-
676
- ### Text Inputs
677
-
678
- #### SettingsInputFrame (Text Input)
679
-
680
- ```typescript
681
- import { store } from '@telemetryos/sdk'
682
65
  import {
683
66
  SettingsContainer,
684
67
  SettingsField,
685
68
  SettingsLabel,
686
69
  SettingsInputFrame,
687
70
  } from '@telemetryos/sdk/react'
688
- import { useTeamStoreState } from '../hooks/store'
689
-
690
- function Settings() {
691
- const [isLoading, team, setTeam] = useTeamStoreState(store().instance)
692
-
693
- return (
694
- <SettingsContainer>
695
- <SettingsField>
696
- <SettingsLabel>Team Name</SettingsLabel>
697
- <SettingsInputFrame>
698
- <input
699
- type="text"
700
- placeholder="Enter team name..."
701
- disabled={isLoading}
702
- value={team}
703
- onChange={(e) => setTeam(e.target.value)}
704
- />
705
- </SettingsInputFrame>
706
- </SettingsField>
707
- </SettingsContainer>
708
- )
709
- }
710
- ```
711
-
712
- #### SettingsTextAreaFrame (Multiline Text)
713
-
714
- ```typescript
715
- import { SettingsTextAreaFrame } from '@telemetryos/sdk/react'
716
- import { useDescriptionStoreState } from '../hooks/store'
717
-
718
- const [isLoading, description, setDescription] = useDescriptionStoreState(store().instance)
719
-
720
- <SettingsField>
721
- <SettingsLabel>Description</SettingsLabel>
722
- <SettingsTextAreaFrame>
723
- <textarea
724
- placeholder="Enter description..."
725
- disabled={isLoading}
726
- value={description}
727
- onChange={(e) => setDescription(e.target.value)}
728
- rows={4}
729
- />
730
- </SettingsTextAreaFrame>
731
- </SettingsField>
732
- ```
733
-
734
- ### Selection Inputs
735
-
736
- #### SettingsSelectFrame (Dropdown)
737
-
738
- ```typescript
739
- import { SettingsSelectFrame } from '@telemetryos/sdk/react'
740
- import { useLeagueStoreState } from '../hooks/store'
741
-
742
- const [isLoading, league, setLeague] = useLeagueStoreState(store().instance)
743
-
744
- <SettingsField>
745
- <SettingsLabel>League</SettingsLabel>
746
- <SettingsSelectFrame>
747
- <select
748
- disabled={isLoading}
749
- value={league}
750
- onChange={(e) => setLeague(e.target.value)}
751
- >
752
- <option value="nfl">NFL</option>
753
- <option value="nba">NBA</option>
754
- <option value="mlb">MLB</option>
755
- <option value="nhl">NHL</option>
756
- </select>
757
- </SettingsSelectFrame>
758
- </SettingsField>
759
- ```
760
-
761
- #### SettingsSliderFrame (Range Slider)
762
-
763
- ```typescript
764
- import { SettingsSliderFrame } from '@telemetryos/sdk/react'
765
- import { useVolumeStoreState } from '../hooks/store'
766
-
767
- const [isLoading, volume, setVolume] = useVolumeStoreState(store().instance)
768
-
769
- <SettingsField>
770
- <SettingsLabel>Volume</SettingsLabel>
771
- <SettingsSliderFrame>
772
- <input
773
- type="range"
774
- min="0"
775
- max="100"
776
- disabled={isLoading}
777
- value={volume}
778
- onChange={(e) => setVolume(Number(e.target.value))}
779
- />
780
- <span>{volume}%</span> {/* Optional value label */}
781
- </SettingsSliderFrame>
782
- </SettingsField>
783
- ```
784
-
785
- The frame uses flexbox layout, so you can optionally add a `<span>` after the input to display the current value.
786
-
787
- ### Toggle Inputs
788
-
789
- #### SettingsSwitchFrame, SettingsSwitchLabel (Toggle Switch)
790
-
791
- ```typescript
792
- import { SettingsSwitchFrame, SettingsSwitchLabel } from '@telemetryos/sdk/react'
793
- import { useShowScoresStoreState } from '../hooks/store'
794
-
795
- const [isLoading, showScores, setShowScores] = useShowScoresStoreState(store().instance)
796
-
797
- <SettingsField>
798
- <SettingsSwitchFrame>
799
- <input
800
- type="checkbox"
801
- role="switch"
802
- disabled={isLoading}
803
- checked={showScores}
804
- onChange={(e) => setShowScores(e.target.checked)}
805
- />
806
- <SettingsSwitchLabel>Show Live Scores</SettingsSwitchLabel>
807
- </SettingsSwitchFrame>
808
- </SettingsField>
809
71
  ```
810
72
 
811
- #### SettingsCheckboxFrame, SettingsCheckboxLabel (Checkbox)
73
+ ### External APIs
812
74
 
813
75
  ```typescript
814
- import { SettingsCheckboxFrame, SettingsCheckboxLabel } from '@telemetryos/sdk/react'
815
- import { useAutoRefreshStoreState } from '../hooks/store'
816
-
817
- const [isLoading, autoRefresh, setAutoRefresh] = useAutoRefreshStoreState(store().instance)
818
-
819
- <SettingsField>
820
- <SettingsCheckboxFrame>
821
- <input
822
- type="checkbox"
823
- disabled={isLoading}
824
- checked={autoRefresh}
825
- onChange={(e) => setAutoRefresh(e.target.checked)}
826
- />
827
- <SettingsCheckboxLabel>Enable Auto-Refresh</SettingsCheckboxLabel>
828
- </SettingsCheckboxFrame>
829
- </SettingsField>
830
- ```
831
-
832
- #### SettingsRadioFrame, SettingsRadioLabel (Radio Buttons)
833
-
834
- ```typescript
835
- import { SettingsRadioFrame, SettingsRadioLabel } from '@telemetryos/sdk/react'
836
- import { useDisplayModeStoreState } from '../hooks/store'
837
-
838
- const [isLoading, displayMode, setDisplayMode] = useDisplayModeStoreState(store().instance)
839
-
840
- <SettingsField>
841
- <SettingsLabel>Display Mode</SettingsLabel>
842
- <SettingsRadioFrame>
843
- <input
844
- type="radio"
845
- name="displayMode"
846
- value="compact"
847
- disabled={isLoading}
848
- checked={displayMode === 'compact'}
849
- onChange={(e) => setDisplayMode(e.target.value)}
850
- />
851
- <SettingsRadioLabel>Compact</SettingsRadioLabel>
852
- </SettingsRadioFrame>
853
- <SettingsRadioFrame>
854
- <input
855
- type="radio"
856
- name="displayMode"
857
- value="expanded"
858
- disabled={isLoading}
859
- checked={displayMode === 'expanded'}
860
- onChange={(e) => setDisplayMode(e.target.value)}
861
- />
862
- <SettingsRadioLabel>Expanded</SettingsRadioLabel>
863
- </SettingsRadioFrame>
864
- </SettingsField>
865
- ```
866
-
867
- ### Actions
868
-
869
- #### SettingsButtonFrame
870
-
871
- ```typescript
872
- import { SettingsButtonFrame } from '@telemetryos/sdk/react'
76
+ import { proxy } from '@telemetryos/sdk'
873
77
 
874
- <SettingsField>
875
- <SettingsButtonFrame>
876
- <button onClick={handleReset}>Reset to Defaults</button>
877
- </SettingsButtonFrame>
878
- </SettingsField>
78
+ // Use proxy for APIs without CORS headers
79
+ const response = await proxy().fetch('https://api.example.com/data')
879
80
  ```
880
81
 
881
82
  ## Hard Constraints
882
83
 
883
- **These cause runtime errors:**
884
-
885
- 1. **Device storage in Settings**
886
- - Settings runs in Studio browser, not on devices
887
- - `store().device.*` throws Error in Settings
888
- - Use `store().instance` or `store().application` instead
889
-
890
- 2. **CORS errors on external APIs**
891
- - Some external APIs don't include CORS headers
892
- - Use `proxy().fetch()` when you encounter CORS errors
893
- - Regular `fetch()` is fine when CORS is not an issue (and has advanced caching in the player)
894
-
895
- 3. **Missing configure()**
896
- - SDK methods throw "SDK not configured" Error
897
- - Call `configure()` once in main.tsx before React renders
898
-
899
- 4. **Subscription memory leaks**
900
- - Store a reference to your handler function
901
- - Must call `unsubscribe(key, handler)` on component unmount
902
- - Call unsubscribe in useEffect cleanup
903
-
904
- 5. **Timeout errors**
905
- - All SDK operations timeout after 30 seconds
906
- - Throws Error with message containing 'timeout'
907
- - Handle with try/catch
908
-
909
- ## TypeScript Patterns
910
-
911
- **Define interfaces for all configs and data:**
912
- ```typescript
913
- interface AppConfig {
914
- city: string;
915
- units: 'celsius' | 'fahrenheit';
916
- refreshInterval: number;
917
- }
918
-
919
- const config = await store().instance.get<AppConfig>('config');
920
- if (config) {
921
- console.log(config.city); // TypeScript knows this exists
922
- }
923
- ```
924
-
925
- **Component with proper types:**
926
- ```typescript
927
- interface Props {
928
- data: WeatherData;
929
- onRefresh: () => void;
930
- }
931
-
932
- export default function WeatherCard({ data, onRefresh }: Props) {
933
- return <div>{data.temperature}</div>;
934
- }
935
- ```
936
-
937
- ## React Patterns
938
-
939
- **Prefer SDK hooks over manual store operations:**
940
- ```typescript
941
- // RECOMMENDED: Use SDK hooks
942
- import { useTeamStoreState } from '../hooks/store'
943
- const [isLoading, team, setTeam] = useTeamStoreState(store().instance)
944
-
945
- // AVOID: Manual subscription (only for special cases)
946
- const [team, setTeam] = useState('')
947
- useEffect(() => {
948
- const handler = (value) => setTeam(value)
949
- store().instance.subscribe('team', handler)
950
- return () => store().instance.unsubscribe('team', handler)
951
- }, [])
952
- ```
953
-
954
- **Subscription behavior:**
955
- When you call `subscribe()`, the handler is immediately called with the current value. No separate `get()` call is needed:
956
- ```typescript
957
- // WRONG - unnecessary get() call
958
- useEffect(() => {
959
- store().instance.get('config').then(setConfig) // Not needed!
960
- store().instance.subscribe('config', handler)
961
- // ...
962
- }, [])
963
-
964
- // CORRECT - subscribe handles initial value
965
- useEffect(() => {
966
- const handler = (value) => setConfig(value)
967
- store().instance.subscribe('config', handler) // Immediately receives current value
968
- return () => store().instance.unsubscribe('config', handler)
969
- }, [])
970
- ```
971
-
972
- **No manual caching needed:**
973
- The platform automatically caches SDK API calls, `fetch()`, and `proxy().fetch()` requests. Don't implement your own cache:
974
- ```typescript
975
- // WRONG - manual caching
976
- const response = await fetch(url)
977
- await store().device.set('cached', data) // Don't do this!
978
-
979
- // CORRECT - just fetch, platform handles caching
980
- const response = await fetch(url)
981
- ```
982
-
983
- **Error handling:**
984
- ```typescript
985
- const [error, setError] = useState<string | null>(null)
986
-
987
- try {
988
- await store().instance.set('key', value)
989
- } catch (err) {
990
- setError(err instanceof Error ? err.message : 'Unknown error')
991
- }
992
- ```
993
-
994
- **Loading states:**
995
- ```typescript
996
- const [loading, setLoading] = useState(false)
997
-
998
- const handleAction = async () => {
999
- setLoading(true)
1000
- try {
1001
- await someAsyncOperation()
1002
- } finally {
1003
- setLoading(false)
1004
- }
1005
- }
1006
- ```
1007
-
1008
- ## Low-Level Store API (Alternative)
1009
-
1010
- For special cases where SDK hooks don't fit, you can use the store API directly. This requires manual subscription management.
1011
-
1012
- **Manual subscription pattern:**
1013
- ```typescript
1014
- import { useEffect, useState } from 'react'
1015
- import { store } from '@telemetryos/sdk'
1016
- import {
1017
- SettingsContainer,
1018
- SettingsField,
1019
- SettingsLabel,
1020
- SettingsInputFrame,
1021
- } from '@telemetryos/sdk/react'
1022
-
1023
- interface Config {
1024
- team: string
1025
- league: string
1026
- }
1027
-
1028
- export default function Settings() {
1029
- const [config, setConfig] = useState<Config>({ team: '', league: 'nfl' })
1030
- const [isLoading, setIsLoading] = useState(true)
1031
-
1032
- // Subscribe on mount - subscribe() immediately sends current value
1033
- useEffect(() => {
1034
- const handler = (value: Config | undefined) => {
1035
- if (value) setConfig(value)
1036
- setIsLoading(false)
1037
- }
1038
- store().instance.subscribe<Config>('config', handler)
1039
-
1040
- return () => {
1041
- store().instance.unsubscribe('config', handler)
1042
- }
1043
- }, [])
1044
-
1045
- // Update store on change
1046
- const updateConfig = (updates: Partial<Config>) => {
1047
- const newConfig = { ...config, ...updates }
1048
- setConfig(newConfig)
1049
- store().instance.set('config', newConfig)
1050
- }
1051
-
1052
- return (
1053
- <SettingsContainer>
1054
- <SettingsField>
1055
- <SettingsLabel>Team Name</SettingsLabel>
1056
- <SettingsInputFrame>
1057
- <input
1058
- type="text"
1059
- value={config.team}
1060
- onChange={(e) => updateConfig({ team: e.target.value })}
1061
- disabled={isLoading}
1062
- />
1063
- </SettingsInputFrame>
1064
- </SettingsField>
1065
- </SettingsContainer>
1066
- )
1067
- }
1068
- ```
1069
-
1070
- **Form submission pattern** (for cases requiring validation before save):
1071
- ```typescript
1072
- import { useState, FormEvent } from 'react'
1073
- import { store } from '@telemetryos/sdk'
1074
- import {
1075
- SettingsContainer,
1076
- SettingsField,
1077
- SettingsLabel,
1078
- SettingsInputFrame,
1079
- } from '@telemetryos/sdk/react'
1080
-
1081
- export default function Settings() {
1082
- const [team, setTeam] = useState('')
1083
- const [saving, setSaving] = useState(false)
1084
- const [error, setError] = useState<string | null>(null)
1085
-
1086
- const handleSubmit = async (e: FormEvent) => {
1087
- e.preventDefault()
1088
- setSaving(true)
1089
- setError(null)
1090
-
1091
- try {
1092
- // Validate before saving
1093
- if (team.length < 2) throw new Error('Team name too short')
1094
-
1095
- const success = await store().instance.set('team', team)
1096
- if (!success) throw new Error('Storage operation failed')
1097
- } catch (err) {
1098
- setError(err instanceof Error ? err.message : 'Unknown error')
1099
- } finally {
1100
- setSaving(false)
1101
- }
1102
- }
1103
-
1104
- return (
1105
- <SettingsContainer>
1106
- <form onSubmit={handleSubmit}>
1107
- {error && <div style={{ color: 'red' }}>{error}</div>}
1108
- <SettingsField>
1109
- <SettingsLabel>Team Name</SettingsLabel>
1110
- <SettingsInputFrame>
1111
- <input
1112
- type="text"
1113
- value={team}
1114
- onChange={(e) => setTeam(e.target.value)}
1115
- />
1116
- </SettingsInputFrame>
1117
- </SettingsField>
1118
- <button type="submit" disabled={saving}>
1119
- {saving ? 'Saving...' : 'Save'}
1120
- </button>
1121
- </form>
1122
- </SettingsContainer>
1123
- )
1124
- }
1125
- ```
1126
-
1127
- **When to use low-level API:**
1128
- - Complex validation logic before saving
1129
- - Batching multiple store operations
1130
- - Custom debouncing behavior
1131
- - Integration with form libraries
1132
-
1133
- **When to use SDK hooks (recommended):**
1134
- - Most Settings components
1135
- - Simple form fields
1136
- - Real-time sync between Settings and Render
1137
-
1138
- ## Code Style
1139
-
1140
- **Naming:**
1141
- - Components: PascalCase (`WeatherCard.tsx`)
1142
- - Functions: camelCase (`fetchWeatherData`)
1143
- - Constants: UPPER_SNAKE_CASE (`API_BASE_URL`)
1144
- - Interfaces: PascalCase (`WeatherData`, `AppConfig`)
1145
-
1146
- **Imports order:**
1147
- ```typescript
1148
- // 1. SDK imports
1149
- import { configure, store, proxy } from '@telemetryos/sdk'
1150
- import {
1151
- SettingsContainer,
1152
- SettingsField,
1153
- SettingsLabel,
1154
- SettingsInputFrame,
1155
- } from '@telemetryos/sdk/react'
1156
-
1157
- // 2. React imports
1158
- import { useEffect, useState } from 'react'
1159
-
1160
- // 3. Local imports (hooks, components, types)
1161
- import { useTeamStoreState } from '../hooks/store'
1162
- import ScoreCard from '@/components/ScoreCard'
1163
- import type { GameScore } from '@/types'
1164
- ```
1165
-
1166
- **TypeScript:**
1167
- - Use strict mode
1168
- - Define interfaces for all configs and data
1169
- - Use generics with storage: `get<Type>(key)`
1170
- - Prefer `interface` over `type` for objects
1171
-
1172
- **React:**
1173
- - Functional components only
1174
- - Use hooks (useState, useEffect, useMemo, useCallback)
1175
- - Implement loading, error, empty states
1176
- - Clean up subscriptions in useEffect return
1177
-
1178
- ## Development Commands
1179
-
1180
- ```bash
1181
- # Install dependencies
1182
- npm install
1183
-
1184
- # Start local dev server
1185
- tos serve
1186
- # Or: npm run dev
1187
-
1188
- # Build for production
1189
- npm run build
1190
-
1191
- # Type check
1192
- tsc --noEmit
1193
- ```
1194
-
1195
- **Local testing:**
1196
- - Settings: http://localhost:3000/settings
1197
- - Render: http://localhost:3000/render
1198
-
1199
- **Deployment:**
1200
- ```bash
1201
- git add .
1202
- git commit -m "Description"
1203
- git push origin main
1204
- # GitHub integration auto-deploys
1205
- ```
1206
-
1207
- ## Common Errors
1208
-
1209
- **"SDK not configured"**
1210
- → Call `configure('app-name')` in main.tsx before React renders
1211
-
1212
- **"device storage not available"**
1213
- → Using `store().device` in Settings - use `store().instance` instead
1214
-
1215
- **CORS error**
1216
- → External API doesn't include CORS headers - use `proxy().fetch()` for that API
1217
-
1218
- **"Request timeout"**
1219
- → SDK operation exceeded 30 seconds - handle with try/catch
1220
-
1221
- **Render not updating**
1222
- → Missing subscription - use `store().instance.subscribe()` in Render
1223
-
1224
- **Memory leak**
1225
- → Not calling `unsubscribe(key, handler)` in useEffect cleanup
1226
-
1227
- ## Project-Specific Context
1228
-
1229
- [Add your project details here:]
1230
-
1231
- **Application Name:** [Your app name]
1232
- **External APIs:**
1233
- - [API name]: [endpoint]
1234
- - Authentication: [method]
1235
- - Rate limits: [limits]
1236
-
1237
- **Custom Components:**
1238
- - [ComponentName]: [purpose]
1239
- - Location: [path]
1240
- - Props: [interface]
1241
-
1242
- **Business Logic:**
1243
- - [Key algorithms or calculations]
1244
- - [Data transformation rules]
84
+ 1. **No device storage in Settings** - Use instance store hooks instead
85
+ 2. **CORS on external APIs** - Use `proxy().fetch()` when needed
86
+ 3. **configure() required** - Call in index.tsx before React renders
1245
87
 
1246
- ## Technical References
88
+ ## Commands
1247
89
 
1248
- **Getting Started:**
1249
- - [Quick Start Guide](https://docs.telemetryos.com/docs/quick-start) - Build TelemetryOS applications in minutes
1250
- - [SDK Getting Started](https://docs.telemetryos.com/docs/sdk-getting-started) - Build custom screen applications
1251
- - [Building Applications](https://docs.telemetryos.com/docs/applications) - Build custom web applications for TelemetryOS
1252
- - [Generate New Application](https://docs.telemetryos.com/docs/generate-new-application) - Use the CLI to scaffold projects
90
+ | Command | Purpose |
91
+ |---------|---------|
92
+ | `/add-setting` | Add a Settings control |
93
+ | `/add-store-key` | Add a store key with hook |
94
+ | `/add-api-fetch` | Add external API integration |
95
+ | `/build-deploy` | Build and deploy workflow |
96
+ | `/debug-render` | Debug render view issues |
1253
97
 
1254
- **SDK Method Reference:**
1255
- - [SDK Method Reference](https://docs.telemetryos.com/docs/sdk-method-reference) - Complete reference for all SDK methods
1256
- - [Storage Methods](https://docs.telemetryos.com/docs/storage-methods) - Complete storage scope reference
1257
- - [Platform Methods](https://docs.telemetryos.com/docs/platform-methods) - Proxy, media, accounts, users, devices
1258
- - [Media Methods](https://docs.telemetryos.com/docs/media-methods) - Media content queries
1259
- - [Playlist Methods](https://docs.telemetryos.com/docs/playlist-methods) - Page navigation methods
1260
- - [Overrides Methods](https://docs.telemetryos.com/docs/overrides-methods) - Dynamic content control
1261
- - [Proxy Methods](https://docs.telemetryos.com/docs/proxy-methods) - Fetch external content through TelemetryOS proxy
1262
- - [Weather Methods](https://docs.telemetryos.com/docs/weather-methods) - Access weather data and forecasts
1263
- - [Client Methods](https://docs.telemetryos.com/docs/client-methods) - Low-level messaging for advanced use cases
98
+ ## Skills (REQUIRED)
1264
99
 
1265
- **Application Structure:**
1266
- - [Application Components](https://docs.telemetryos.com/docs/application-components) - Modular pieces of a TelemetryOS application
1267
- - [Mount Points](https://docs.telemetryos.com/docs/mount-points) - /render vs /settings execution contexts
1268
- - [Rendering](https://docs.telemetryos.com/docs/rendering) - Visual component displayed on playlist pages
1269
- - [Settings](https://docs.telemetryos.com/docs/settings) - Configuration UI in Studio side panel
1270
- - [Workers](https://docs.telemetryos.com/docs/workers) - Background JavaScript patterns
1271
- - [Containers](https://docs.telemetryos.com/docs/containers) - Docker integration patterns
1272
- - [Configuration](https://docs.telemetryos.com/docs/configuration) - telemetry.config.json schema
100
+ **IMPORTANT:** You MUST invoke the relevant skill BEFORE writing code for these tasks:
1273
101
 
1274
- **Development:**
1275
- - [Local Development](https://docs.telemetryos.com/docs/local-development) - Develop and test locally before deployment
1276
- - [CORS Guide](https://docs.telemetryos.com/docs/cors) - Why proxy().fetch() is required
1277
- - [Code Examples](https://docs.telemetryos.com/docs/code-examples) - Complete working examples
1278
- - [AI-Assisted Development](https://docs.telemetryos.com/docs/ai-assisted-development) - Accelerate development with Claude Code
1279
- - [GitHub Integration](https://docs.telemetryos.com/docs/github-integration) - Automated Git-to-Screen deployment
102
+ | Task | Required Skill | Why |
103
+ |------|----------------|-----|
104
+ | Building Render views | `tos-render-design` | Digital signage constraints, UI scaling, no hover/scroll |
105
+ | Adding ANY Settings UI | `tos-settings-ui` | SDK components are required - raw HTML won't work |
106
+ | Calling external APIs | `tos-proxy-fetch` | Proxy patterns prevent CORS errors |
107
+ | Adding store keys | `tos-store-sync` | Hook patterns ensure Settings↔Render sync |
108
+ | Weather integration | `tos-weather-api` | API-specific patterns and credentials |
109
+ | Media library access | `tos-media-api` | SDK media methods and types |
110
+ | Starting new project | `tos-requirements` | Gather requirements before coding |
111
+ | Debugging issues | `tos-debugging` | Common errors and fixes |
1280
112
 
1281
- **Platform Context:**
1282
- - [Offline Capabilities](https://docs.telemetryos.com/docs/offline-capabilities) - How apps run locally on devices
1283
- - [Languages Supported](https://docs.telemetryos.com/docs/languages-supported) - Runtime environment constraints
1284
- - [Use Cases](https://docs.telemetryos.com/docs/use-cases) - Real-world applications and use cases
1285
- - [Platform Architecture](https://docs.telemetryos.com/docs/platform-architecture) - Technical deep dive
113
+ **Never write Render layouts, Settings components, or proxy.fetch code without invoking the skill first.**
1286
114
 
1287
- **AI & Automation:**
1288
- - [LLMS.txt](https://docs.telemetryos.com/llms.txt) - Complete documentation index for AI agents
1289
- - [MCP Server](https://docs.telemetryos.com/docs/mcp-server) - Model Context Protocol server integration
1290
- - [Using AI with TelemetryOS](https://docs.telemetryos.com/docs/using-ai-with-telemetryos) - AI tools overview
115
+ ## Documentation
1291
116
 
1292
- **API Reference (for backend integrations):**
1293
- - [API Introduction](https://docs.telemetryos.com/reference/introduction) - Get started with the TelemetryOS API
1294
- - [Authentication](https://docs.telemetryos.com/reference/authentication) - API security and credentials
1295
- - [API Tokens](https://docs.telemetryos.com/docs/api-tokens) - Token management for programmatic access
117
+ - [SDK Getting Started](https://docs.telemetryos.com/docs/sdk-getting-started)
118
+ - [SDK Method Reference](https://docs.telemetryos.com/docs/sdk-method-reference)
119
+ - [Building Applications](https://docs.telemetryos.com/docs/applications)