@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.
- 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/hooks/store.ts +3 -3
- package/templates/vite-react-typescript/src/index.css +0 -1
- package/templates/vite-react-typescript/src/views/Render.css +2 -1
- package/templates/vite-react-typescript/src/views/Render.tsx +2 -3
- package/templates/vite-react-typescript/src/views/Settings.tsx +2 -3
- package/templates/vite-react-typescript/AGENTS.md +0 -7
- /package/templates/vite-react-typescript/{gitignore → _gitignore} +0 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tos-weather-api
|
|
3
|
+
description: Integrate TelemetryOS Weather API for current conditions and forecasts. Use when building weather displays or any app that needs location-based weather data.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TelemetryOS Weather API
|
|
7
|
+
|
|
8
|
+
The Weather API provides current conditions, hourly forecasts, and daily forecasts for any location.
|
|
9
|
+
|
|
10
|
+
## Quick Reference
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { weather } from '@telemetryos/sdk'
|
|
14
|
+
|
|
15
|
+
// Current conditions
|
|
16
|
+
const conditions = await weather().getConditions({
|
|
17
|
+
city: 'New York',
|
|
18
|
+
units: 'imperial'
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
// Hourly forecast (next 24 hours)
|
|
22
|
+
const hourly = await weather().getHourlyForecast({
|
|
23
|
+
city: 'New York',
|
|
24
|
+
units: 'imperial',
|
|
25
|
+
hours: 24
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// Daily forecast (next 7 days)
|
|
29
|
+
const daily = await weather().getDailyForecast({
|
|
30
|
+
city: 'New York',
|
|
31
|
+
units: 'imperial',
|
|
32
|
+
days: 7
|
|
33
|
+
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Location Parameters
|
|
37
|
+
|
|
38
|
+
Specify location using ONE of these methods:
|
|
39
|
+
|
|
40
|
+
### By City Name
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// City only
|
|
44
|
+
{ city: 'New York' }
|
|
45
|
+
|
|
46
|
+
// City with country
|
|
47
|
+
{ city: 'London, UK' }
|
|
48
|
+
|
|
49
|
+
// City with state (US)
|
|
50
|
+
{ city: 'Portland, OR' }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### By Postal Code
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
{ postalCode: '10001' }
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### By Coordinates
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
{ lat: '40.7128', lon: '-74.0060' }
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Units
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// Fahrenheit, miles, etc.
|
|
69
|
+
{ units: 'imperial' }
|
|
70
|
+
|
|
71
|
+
// Celsius, kilometers, etc.
|
|
72
|
+
{ units: 'metric' }
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Response Types
|
|
76
|
+
|
|
77
|
+
### WeatherConditions (Current)
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
interface WeatherConditions {
|
|
81
|
+
EpochTime: number
|
|
82
|
+
WeatherText: string
|
|
83
|
+
WeatherIcon: number
|
|
84
|
+
HasPrecipitation: boolean
|
|
85
|
+
PrecipitationType: string | null
|
|
86
|
+
IsDayTime: boolean
|
|
87
|
+
Temperature: {
|
|
88
|
+
Metric: { Value: number; Unit: string }
|
|
89
|
+
Imperial: { Value: number; Unit: string }
|
|
90
|
+
}
|
|
91
|
+
RealFeelTemperature: {
|
|
92
|
+
Metric: { Value: number; Unit: string }
|
|
93
|
+
Imperial: { Value: number; Unit: string }
|
|
94
|
+
}
|
|
95
|
+
RelativeHumidity: number
|
|
96
|
+
Wind: {
|
|
97
|
+
Direction: { Degrees: number; English: string }
|
|
98
|
+
Speed: {
|
|
99
|
+
Metric: { Value: number; Unit: string }
|
|
100
|
+
Imperial: { Value: number; Unit: string }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
UVIndex: number
|
|
104
|
+
UVIndexText: string
|
|
105
|
+
Visibility: {
|
|
106
|
+
Metric: { Value: number; Unit: string }
|
|
107
|
+
Imperial: { Value: number; Unit: string }
|
|
108
|
+
}
|
|
109
|
+
CloudCover: number
|
|
110
|
+
Pressure: {
|
|
111
|
+
Metric: { Value: number; Unit: string }
|
|
112
|
+
Imperial: { Value: number; Unit: string }
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### WeatherForecast (Hourly/Daily)
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
interface WeatherForecast {
|
|
121
|
+
DateTime: string
|
|
122
|
+
EpochDateTime: number
|
|
123
|
+
WeatherIcon: number
|
|
124
|
+
IconPhrase: string
|
|
125
|
+
HasPrecipitation: boolean
|
|
126
|
+
PrecipitationType?: string
|
|
127
|
+
PrecipitationIntensity?: string
|
|
128
|
+
IsDaylight: boolean
|
|
129
|
+
Temperature: {
|
|
130
|
+
Value: number
|
|
131
|
+
Unit: string
|
|
132
|
+
}
|
|
133
|
+
RealFeelTemperature: {
|
|
134
|
+
Value: number
|
|
135
|
+
Unit: string
|
|
136
|
+
}
|
|
137
|
+
Wind: {
|
|
138
|
+
Speed: { Value: number; Unit: string }
|
|
139
|
+
Direction: { Degrees: number; English: string }
|
|
140
|
+
}
|
|
141
|
+
RelativeHumidity: number
|
|
142
|
+
PrecipitationProbability: number
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Complete Example
|
|
147
|
+
|
|
148
|
+
### Settings (City + Units Selection)
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// hooks/store.ts
|
|
152
|
+
import { createUseInstanceStoreState } from '@telemetryos/sdk/react'
|
|
153
|
+
|
|
154
|
+
export const useCityState = createUseInstanceStoreState<string>('city', '')
|
|
155
|
+
export const useUnitsState = createUseInstanceStoreState<'imperial' | 'metric'>('units', 'imperial')
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// views/Settings.tsx
|
|
160
|
+
import {
|
|
161
|
+
SettingsContainer,
|
|
162
|
+
SettingsField,
|
|
163
|
+
SettingsLabel,
|
|
164
|
+
SettingsInputFrame,
|
|
165
|
+
SettingsSelectFrame,
|
|
166
|
+
} from '@telemetryos/sdk/react'
|
|
167
|
+
import { useCityState, useUnitsState } from '../hooks/store'
|
|
168
|
+
|
|
169
|
+
export default function Settings() {
|
|
170
|
+
const [isLoadingCity, city, setCity] = useCityState()
|
|
171
|
+
const [isLoadingUnits, units, setUnits] = useUnitsState()
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<SettingsContainer>
|
|
175
|
+
<SettingsField>
|
|
176
|
+
<SettingsLabel>City</SettingsLabel>
|
|
177
|
+
<SettingsInputFrame>
|
|
178
|
+
<input
|
|
179
|
+
type="text"
|
|
180
|
+
placeholder="Enter city name..."
|
|
181
|
+
disabled={isLoadingCity}
|
|
182
|
+
value={city}
|
|
183
|
+
onChange={(e) => setCity(e.target.value)}
|
|
184
|
+
/>
|
|
185
|
+
</SettingsInputFrame>
|
|
186
|
+
</SettingsField>
|
|
187
|
+
|
|
188
|
+
<SettingsField>
|
|
189
|
+
<SettingsLabel>Temperature Units</SettingsLabel>
|
|
190
|
+
<SettingsSelectFrame>
|
|
191
|
+
<select
|
|
192
|
+
disabled={isLoadingUnits}
|
|
193
|
+
value={units}
|
|
194
|
+
onChange={(e) => setUnits(e.target.value as 'imperial' | 'metric')}
|
|
195
|
+
>
|
|
196
|
+
<option value="imperial">Fahrenheit</option>
|
|
197
|
+
<option value="metric">Celsius</option>
|
|
198
|
+
</select>
|
|
199
|
+
</SettingsSelectFrame>
|
|
200
|
+
</SettingsField>
|
|
201
|
+
</SettingsContainer>
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Render (Weather Display)
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
// views/Render.tsx
|
|
210
|
+
import { useEffect, useState } from 'react'
|
|
211
|
+
import { weather } from '@telemetryos/sdk'
|
|
212
|
+
import { useCityState, useUnitsState } from '../hooks/store'
|
|
213
|
+
|
|
214
|
+
interface WeatherData {
|
|
215
|
+
temperature: number
|
|
216
|
+
description: string
|
|
217
|
+
humidity: number
|
|
218
|
+
windSpeed: number
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export default function Render() {
|
|
222
|
+
const [isLoadingCity, city] = useCityState()
|
|
223
|
+
const [isLoadingUnits, units] = useUnitsState()
|
|
224
|
+
|
|
225
|
+
const [data, setData] = useState<WeatherData | null>(null)
|
|
226
|
+
const [loading, setLoading] = useState(false)
|
|
227
|
+
const [error, setError] = useState<string | null>(null)
|
|
228
|
+
|
|
229
|
+
useEffect(() => {
|
|
230
|
+
if (isLoadingCity || isLoadingUnits || !city) return
|
|
231
|
+
|
|
232
|
+
const fetchWeather = async () => {
|
|
233
|
+
setLoading(true)
|
|
234
|
+
setError(null)
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
const conditions = await weather().getConditions({ city, units })
|
|
238
|
+
|
|
239
|
+
const tempKey = units === 'imperial' ? 'Imperial' : 'Metric'
|
|
240
|
+
|
|
241
|
+
setData({
|
|
242
|
+
temperature: conditions.Temperature[tempKey].Value,
|
|
243
|
+
description: conditions.WeatherText,
|
|
244
|
+
humidity: conditions.RelativeHumidity,
|
|
245
|
+
windSpeed: conditions.Wind.Speed[tempKey].Value,
|
|
246
|
+
})
|
|
247
|
+
} catch (err) {
|
|
248
|
+
setError(err instanceof Error ? err.message : 'Failed to fetch weather')
|
|
249
|
+
} finally {
|
|
250
|
+
setLoading(false)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
fetchWeather()
|
|
255
|
+
|
|
256
|
+
// Refresh every 10 minutes
|
|
257
|
+
const interval = setInterval(fetchWeather, 10 * 60 * 1000)
|
|
258
|
+
return () => clearInterval(interval)
|
|
259
|
+
}, [city, units, isLoadingCity, isLoadingUnits])
|
|
260
|
+
|
|
261
|
+
// Loading states
|
|
262
|
+
if (isLoadingCity || isLoadingUnits) return <div>Loading config...</div>
|
|
263
|
+
if (!city) return <div>Configure city in Settings</div>
|
|
264
|
+
if (loading && !data) return <div>Loading weather...</div>
|
|
265
|
+
if (error && !data) return <div>Error: {error}</div>
|
|
266
|
+
|
|
267
|
+
const unitSymbol = units === 'imperial' ? '°F' : '°C'
|
|
268
|
+
const speedUnit = units === 'imperial' ? 'mph' : 'km/h'
|
|
269
|
+
|
|
270
|
+
return (
|
|
271
|
+
<div className="weather-display">
|
|
272
|
+
<h1>{city}</h1>
|
|
273
|
+
{data && (
|
|
274
|
+
<>
|
|
275
|
+
<div className="temperature">
|
|
276
|
+
{Math.round(data.temperature)}{unitSymbol}
|
|
277
|
+
</div>
|
|
278
|
+
<div className="description">{data.description}</div>
|
|
279
|
+
<div className="details">
|
|
280
|
+
<span>Humidity: {data.humidity}%</span>
|
|
281
|
+
<span>Wind: {data.windSpeed} {speedUnit}</span>
|
|
282
|
+
</div>
|
|
283
|
+
</>
|
|
284
|
+
)}
|
|
285
|
+
</div>
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Forecast Example
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
import { weather } from '@telemetryos/sdk'
|
|
294
|
+
|
|
295
|
+
// Get 5-day forecast
|
|
296
|
+
const forecast = await weather().getDailyForecast({
|
|
297
|
+
city: 'New York',
|
|
298
|
+
units: 'imperial',
|
|
299
|
+
days: 5
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
forecast.forEach(day => {
|
|
303
|
+
console.log(`${day.DateTime}: ${day.Temperature.Value}° - ${day.IconPhrase}`)
|
|
304
|
+
})
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Weather Icons
|
|
308
|
+
|
|
309
|
+
The API returns `WeatherIcon` as a number (1-44). Map to your icon set:
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
const iconMap: Record<number, string> = {
|
|
313
|
+
1: 'sunny',
|
|
314
|
+
2: 'mostly-sunny',
|
|
315
|
+
3: 'partly-sunny',
|
|
316
|
+
4: 'intermittent-clouds',
|
|
317
|
+
5: 'hazy-sunshine',
|
|
318
|
+
6: 'mostly-cloudy',
|
|
319
|
+
7: 'cloudy',
|
|
320
|
+
8: 'dreary',
|
|
321
|
+
11: 'fog',
|
|
322
|
+
12: 'showers',
|
|
323
|
+
13: 'mostly-cloudy-showers',
|
|
324
|
+
14: 'partly-sunny-showers',
|
|
325
|
+
15: 'thunderstorms',
|
|
326
|
+
16: 'mostly-cloudy-thunderstorms',
|
|
327
|
+
17: 'partly-sunny-thunderstorms',
|
|
328
|
+
18: 'rain',
|
|
329
|
+
19: 'flurries',
|
|
330
|
+
20: 'mostly-cloudy-flurries',
|
|
331
|
+
21: 'partly-sunny-flurries',
|
|
332
|
+
22: 'snow',
|
|
333
|
+
23: 'mostly-cloudy-snow',
|
|
334
|
+
24: 'ice',
|
|
335
|
+
25: 'sleet',
|
|
336
|
+
26: 'freezing-rain',
|
|
337
|
+
29: 'rain-and-snow',
|
|
338
|
+
30: 'hot',
|
|
339
|
+
31: 'cold',
|
|
340
|
+
32: 'windy',
|
|
341
|
+
33: 'clear-night',
|
|
342
|
+
34: 'mostly-clear-night',
|
|
343
|
+
35: 'partly-cloudy-night',
|
|
344
|
+
36: 'intermittent-clouds-night',
|
|
345
|
+
37: 'hazy-moonlight',
|
|
346
|
+
38: 'mostly-cloudy-night',
|
|
347
|
+
39: 'partly-cloudy-showers-night',
|
|
348
|
+
40: 'mostly-cloudy-showers-night',
|
|
349
|
+
41: 'partly-cloudy-thunderstorms-night',
|
|
350
|
+
42: 'mostly-cloudy-thunderstorms-night',
|
|
351
|
+
43: 'mostly-cloudy-flurries-night',
|
|
352
|
+
44: 'mostly-cloudy-snow-night',
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function getIconName(iconNumber: number): string {
|
|
356
|
+
return iconMap[iconNumber] || 'unknown'
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Error Handling
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
try {
|
|
364
|
+
const conditions = await weather().getConditions({ city, units })
|
|
365
|
+
} catch (err) {
|
|
366
|
+
if (err instanceof Error) {
|
|
367
|
+
if (err.message.includes('timeout')) {
|
|
368
|
+
// Request timed out (30 second limit)
|
|
369
|
+
} else if (err.message.includes('not found')) {
|
|
370
|
+
// City not found
|
|
371
|
+
} else {
|
|
372
|
+
// Other error
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## Tips
|
|
379
|
+
|
|
380
|
+
1. **Cache results** - Weather doesn't change rapidly; refresh every 10-30 minutes
|
|
381
|
+
2. **Handle loading** - Show skeleton or spinner while fetching
|
|
382
|
+
3. **Show stale data** - Display last known data while refreshing
|
|
383
|
+
4. **Validate city** - Weather API may fail for invalid city names
|
|
384
|
+
5. **Use coordinates** - More reliable than city names for precise locations
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createUseInstanceStoreState } from '@telemetryos/sdk/react'
|
|
2
2
|
|
|
3
|
-
export const useUiScaleStoreState =
|
|
3
|
+
export const useUiScaleStoreState = createUseInstanceStoreState<number>('ui-scale', 1)
|
|
4
4
|
|
|
5
|
-
export const useSubtitleStoreState =
|
|
5
|
+
export const useSubtitleStoreState = createUseInstanceStoreState<string>('subtitle', 'Change this line in settings ⚙️ ↗️')
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { store } from '@telemetryos/sdk'
|
|
2
1
|
import { useUiScaleToSetRem } from '@telemetryos/sdk/react'
|
|
3
2
|
import wordMarkPath from '../../assets/telemetryos-wordmark.svg'
|
|
4
3
|
import { useSubtitleStoreState, useUiScaleStoreState } from '../hooks/store'
|
|
5
4
|
import './Render.css'
|
|
6
5
|
|
|
7
6
|
export function Render() {
|
|
8
|
-
const [
|
|
7
|
+
const [, uiScale] = useUiScaleStoreState()
|
|
9
8
|
useUiScaleToSetRem(uiScale)
|
|
10
|
-
const [isLoading, subtitle] = useSubtitleStoreState(
|
|
9
|
+
const [isLoading, subtitle] = useSubtitleStoreState()
|
|
11
10
|
|
|
12
11
|
return (
|
|
13
12
|
<div className="render">
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { store } from '@telemetryos/sdk'
|
|
2
1
|
import {
|
|
3
2
|
SettingsContainer,
|
|
4
3
|
SettingsDivider,
|
|
@@ -10,8 +9,8 @@ import {
|
|
|
10
9
|
import { useSubtitleStoreState, useUiScaleStoreState } from '../hooks/store'
|
|
11
10
|
|
|
12
11
|
export function Settings() {
|
|
13
|
-
const [isLoadingUiScale, uiScale, setUiScale] = useUiScaleStoreState(
|
|
14
|
-
const [isLoading, subtitle, setSubtitle] = useSubtitleStoreState(
|
|
12
|
+
const [isLoadingUiScale, uiScale, setUiScale] = useUiScaleStoreState(5)
|
|
13
|
+
const [isLoading, subtitle, setSubtitle] = useSubtitleStoreState(250)
|
|
15
14
|
|
|
16
15
|
return (
|
|
17
16
|
<SettingsContainer>
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# Agent Guidelines
|
|
2
|
-
|
|
3
|
-
This document outlines guidelines for interacting with and developing for AI agents within this project.
|
|
4
|
-
|
|
5
|
-
## Important Notes
|
|
6
|
-
|
|
7
|
-
* **Claude-specific Guidelines:** If you are working with Claude models, please refer to `CLAUDE.md` for specific guidelines and best practices.
|
|
File without changes
|