@try-works/dsh-righthand 0.1.5 → 0.1.7
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/README.md +5 -0
- package/lib/files-tools.d.ts +55 -0
- package/lib/files-tools.d.ts.map +1 -0
- package/lib/files-tools.js +259 -0
- package/lib/files-tools.js.map +1 -0
- package/lib/index.d.ts +10 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +10 -1
- package/lib/index.js.map +1 -1
- package/lib/secrets-tools.d.ts +2 -0
- package/lib/secrets-tools.d.ts.map +1 -1
- package/lib/secrets-tools.js +2 -1
- package/lib/secrets-tools.js.map +1 -1
- package/lib/sigv4.d.ts +37 -0
- package/lib/sigv4.d.ts.map +1 -0
- package/lib/sigv4.js +94 -0
- package/lib/sigv4.js.map +1 -0
- package/lib/weather-tools.d.ts +68 -0
- package/lib/weather-tools.d.ts.map +1 -0
- package/lib/weather-tools.js +208 -0
- package/lib/weather-tools.js.map +1 -0
- package/package.json +1 -1
- package/skills/dsh-righthand/SKILL.md +17 -1
- package/src/files-tools.ts +294 -0
- package/src/index.ts +12 -1
- package/src/secrets-tools.ts +2 -1
- package/src/sigv4.ts +122 -0
- package/src/weather-tools.ts +236 -0
package/lib/sigv4.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sigv4 — AWS Signature Version 4 for Cloudflare R2 (S3-compatible API).
|
|
3
|
+
* Dependency-free: node:crypto only. Exported pure so tests pin it against
|
|
4
|
+
* the AWS published test vector.
|
|
5
|
+
* @module dsh-righthand/sigv4
|
|
6
|
+
*/
|
|
7
|
+
import { createHmac, createHash } from 'node:crypto';
|
|
8
|
+
function hmac(key, data) {
|
|
9
|
+
return createHmac('sha256', key).update(data).digest();
|
|
10
|
+
}
|
|
11
|
+
function sha256Hex(data) {
|
|
12
|
+
return createHash('sha256').update(data).digest('hex');
|
|
13
|
+
}
|
|
14
|
+
/** URI-encode one path segment the S3 way. */
|
|
15
|
+
function uriEncode(segment) {
|
|
16
|
+
return encodeURIComponent(segment).replace(/[!'()*]/g, c => '%' + c.charCodeAt(0).toString(16).toUpperCase());
|
|
17
|
+
}
|
|
18
|
+
/** Canonical URI: slashes preserved, each segment encoded. */
|
|
19
|
+
function canonicalPath(pathname) {
|
|
20
|
+
if (pathname === '' || pathname === '/')
|
|
21
|
+
return '/';
|
|
22
|
+
return pathname.split('/').map(uriEncode).join('/');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Sign one S3 request. Returns the headers to send, including host,
|
|
26
|
+
* x-amz-content-sha256, x-amz-date and Authorization.
|
|
27
|
+
*/
|
|
28
|
+
export function signRequest(input) {
|
|
29
|
+
const parsed = new URL(input.url);
|
|
30
|
+
const date = input.amzDate.slice(0, 8);
|
|
31
|
+
const scope = date + '/' + input.region + '/s3/aws4_request';
|
|
32
|
+
const payloadHash = sha256Hex(input.payload);
|
|
33
|
+
const headers = {
|
|
34
|
+
...input.headers,
|
|
35
|
+
'x-amz-content-sha256': payloadHash,
|
|
36
|
+
'x-amz-date': input.amzDate,
|
|
37
|
+
};
|
|
38
|
+
const names = Object.keys(headers).sort();
|
|
39
|
+
const canonicalHeaders = names.map(n => n + ':' + headers[n].trim() + String.fromCharCode(10)).join('');
|
|
40
|
+
const signedHeaders = names.join(';');
|
|
41
|
+
const canonicalRequest = [
|
|
42
|
+
input.method,
|
|
43
|
+
canonicalPath(parsed.pathname),
|
|
44
|
+
parsed.searchParams.toString(),
|
|
45
|
+
canonicalHeaders,
|
|
46
|
+
signedHeaders,
|
|
47
|
+
payloadHash,
|
|
48
|
+
].join(String.fromCharCode(10));
|
|
49
|
+
const stringToSign = ['AWS4-HMAC-SHA256', input.amzDate, scope, sha256Hex(canonicalRequest)].join(String.fromCharCode(10));
|
|
50
|
+
const kDate = hmac('AWS4' + input.secretAccessKey, date);
|
|
51
|
+
const kRegion = hmac(kDate, input.region);
|
|
52
|
+
const kService = hmac(kRegion, 's3');
|
|
53
|
+
const kSigning = hmac(kService, 'aws4_request');
|
|
54
|
+
const signature = hmac(kSigning, stringToSign).toString('hex');
|
|
55
|
+
const authorization = 'AWS4-HMAC-SHA256 Credential=' + input.accessKeyId + '/' + scope
|
|
56
|
+
+ ', SignedHeaders=' + signedHeaders
|
|
57
|
+
+ ', Signature=' + signature;
|
|
58
|
+
return { headers: { ...headers, authorization } };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Presign a GET for query-string auth: a shareable URL valid for
|
|
62
|
+
* expiresSeconds (S3 max 7 days).
|
|
63
|
+
*/
|
|
64
|
+
export function presignGet(input) {
|
|
65
|
+
const parsed = new URL(input.url);
|
|
66
|
+
const date = input.amzDate.slice(0, 8);
|
|
67
|
+
const scope = date + '/' + input.region + '/s3/aws4_request';
|
|
68
|
+
const credential = input.accessKeyId + '/' + scope;
|
|
69
|
+
const query = {
|
|
70
|
+
'X-Amz-Algorithm': 'AWS4-HMAC-SHA256',
|
|
71
|
+
'X-Amz-Credential': credential,
|
|
72
|
+
'X-Amz-Date': input.amzDate,
|
|
73
|
+
'X-Amz-Expires': String(input.expiresSeconds),
|
|
74
|
+
'X-Amz-SignedHeaders': 'host',
|
|
75
|
+
};
|
|
76
|
+
const canonicalQuery = Object.keys(query).sort().map(k => uriEncode(k) + '=' + uriEncode(query[k])).join('&');
|
|
77
|
+
const canonicalRequest = [
|
|
78
|
+
'GET',
|
|
79
|
+
canonicalPath(parsed.pathname),
|
|
80
|
+
canonicalQuery,
|
|
81
|
+
'host:' + parsed.host + String.fromCharCode(10),
|
|
82
|
+
'host',
|
|
83
|
+
'UNSIGNED-PAYLOAD',
|
|
84
|
+
].join(String.fromCharCode(10));
|
|
85
|
+
const stringToSign = ['AWS4-HMAC-SHA256', input.amzDate, scope, sha256Hex(canonicalRequest)].join(String.fromCharCode(10));
|
|
86
|
+
const kDate = hmac('AWS4' + input.secretAccessKey, date);
|
|
87
|
+
const kRegion = hmac(kDate, input.region);
|
|
88
|
+
const kService = hmac(kRegion, 's3');
|
|
89
|
+
const kSigning = hmac(kService, 'aws4_request');
|
|
90
|
+
const signature = hmac(kSigning, stringToSign).toString('hex');
|
|
91
|
+
parsed.search = canonicalQuery + '&X-Amz-Signature=' + signature;
|
|
92
|
+
return parsed.toString();
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=sigv4.js.map
|
package/lib/sigv4.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sigv4.js","sourceRoot":"","sources":["../src/sigv4.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weather-tools — the worked example of the keyless data-adapter recipe
|
|
3
|
+
* (blueprint/data-adapter). Two tools over Open-Meteo, a keyless API:
|
|
4
|
+
* rh_weather_forecast — current conditions + 3-day min/max
|
|
5
|
+
* rh_weather_air — PM2.5 / PM10 / European AQI
|
|
6
|
+
*
|
|
7
|
+
* Every request goes through guardedFetch, the SSRF checklist from Mu's
|
|
8
|
+
* safefetch: public destinations only (loopback, private, link-local incl.
|
|
9
|
+
* the metadata address, multicast and unspecified are refused), every
|
|
10
|
+
* redirect hop revalidated, response size and time capped.
|
|
11
|
+
* @module dsh-righthand/weather-tools
|
|
12
|
+
*/
|
|
13
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
14
|
+
export declare const name = "righthand-weather";
|
|
15
|
+
export declare const inject: string[];
|
|
16
|
+
export interface WeatherConfig {
|
|
17
|
+
/** Endpoint URLs; defaults are Open-Meteo (keyless). Tests override these. */
|
|
18
|
+
forecastUrl?: string;
|
|
19
|
+
airUrl?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Whether an IP must not be reached: the Mu safefetch set — loopback,
|
|
23
|
+
* RFC1918 private, ULA, link-local (169.254/16, fe80::/10 — which includes
|
|
24
|
+
* the 169.254.169.254 metadata address), multicast, and unspecified.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isBlockedIP(ip: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Fetch one URL with the SSRF checklist. `fetchImpl` and `resolve` are
|
|
29
|
+
* injectable for tests; production uses the globals.
|
|
30
|
+
*/
|
|
31
|
+
export declare function guardedFetch(url: string, deps?: {
|
|
32
|
+
fetchImpl?: typeof fetch;
|
|
33
|
+
resolve?: (host: string) => Promise<string[]>;
|
|
34
|
+
}): Promise<string>;
|
|
35
|
+
/**
|
|
36
|
+
* Normalize the Open-Meteo forecast response into a stable digest shape:
|
|
37
|
+
* what changed is the wire JSON; what callers see is this.
|
|
38
|
+
*/
|
|
39
|
+
export interface ForecastOut {
|
|
40
|
+
latitude: number;
|
|
41
|
+
longitude: number;
|
|
42
|
+
timezone: string;
|
|
43
|
+
current: {
|
|
44
|
+
time: string;
|
|
45
|
+
temperature: number;
|
|
46
|
+
windspeed: number;
|
|
47
|
+
winddirection: number;
|
|
48
|
+
weathercode: number;
|
|
49
|
+
} | null;
|
|
50
|
+
daily: {
|
|
51
|
+
time: string[];
|
|
52
|
+
temperatureMax: number[];
|
|
53
|
+
temperatureMin: number[];
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export declare function normalizeForecast(json: any): ForecastOut;
|
|
57
|
+
/** Normalize the Open-Meteo air-quality response. */
|
|
58
|
+
export interface AirOut {
|
|
59
|
+
latitude: number;
|
|
60
|
+
longitude: number;
|
|
61
|
+
time: string;
|
|
62
|
+
pm2_5: number | null;
|
|
63
|
+
pm10: number | null;
|
|
64
|
+
aqi: number | null;
|
|
65
|
+
}
|
|
66
|
+
export declare function normalizeAir(json: any): AirOut;
|
|
67
|
+
export declare function apply(ctx: Context, config?: WeatherConfig): void;
|
|
68
|
+
//# sourceMappingURL=weather-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"weather-tools.d.ts","sourceRoot":"","sources":["../src/weather-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAKlD,eAAO,MAAM,IAAI,sBAAsB,CAAA;AACvC,eAAO,MAAM,MAAM,UAAY,CAAA;AAE/B,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAWD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAoB/C;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;CAAO,GACrF,OAAO,CAAC,MAAM,CAAC,CAgCjB;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IACpH,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,cAAc,EAAE,MAAM,EAAE,CAAC;QAAC,cAAc,EAAE,MAAM,EAAE,CAAA;KAAE,CAAA;CAC9E;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,GAAG,GAAG,WAAW,CAmBxD;AAED,qDAAqD;AACrD,MAAM,WAAW,MAAM;IACrB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CACnB;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,CAW9C;AAED,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,aAAkB,GAAG,IAAI,CAyEpE"}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weather-tools — the worked example of the keyless data-adapter recipe
|
|
3
|
+
* (blueprint/data-adapter). Two tools over Open-Meteo, a keyless API:
|
|
4
|
+
* rh_weather_forecast — current conditions + 3-day min/max
|
|
5
|
+
* rh_weather_air — PM2.5 / PM10 / European AQI
|
|
6
|
+
*
|
|
7
|
+
* Every request goes through guardedFetch, the SSRF checklist from Mu's
|
|
8
|
+
* safefetch: public destinations only (loopback, private, link-local incl.
|
|
9
|
+
* the metadata address, multicast and unspecified are refused), every
|
|
10
|
+
* redirect hop revalidated, response size and time capped.
|
|
11
|
+
* @module dsh-righthand/weather-tools
|
|
12
|
+
*/
|
|
13
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
14
|
+
import { lookup } from 'node:dns/promises';
|
|
15
|
+
import { isIP } from 'node:net';
|
|
16
|
+
export const name = 'righthand-weather';
|
|
17
|
+
export const inject = ['tools'];
|
|
18
|
+
const DEFAULTS = {
|
|
19
|
+
forecastUrl: 'https://api.open-meteo.com/v1/forecast',
|
|
20
|
+
airUrl: 'https://air-quality-api.open-meteo.com/v1/air-quality',
|
|
21
|
+
};
|
|
22
|
+
const MAX_BODY_BYTES = 2 * 1024 * 1024;
|
|
23
|
+
const MAX_REDIRECTS = 5;
|
|
24
|
+
const FETCH_TIMEOUT_MS = 10000;
|
|
25
|
+
/**
|
|
26
|
+
* Whether an IP must not be reached: the Mu safefetch set — loopback,
|
|
27
|
+
* RFC1918 private, ULA, link-local (169.254/16, fe80::/10 — which includes
|
|
28
|
+
* the 169.254.169.254 metadata address), multicast, and unspecified.
|
|
29
|
+
*/
|
|
30
|
+
export function isBlockedIP(ip) {
|
|
31
|
+
if (ip === '')
|
|
32
|
+
return true;
|
|
33
|
+
if (isIP(ip) === 4) {
|
|
34
|
+
const [a, b] = ip.split('.').map(Number);
|
|
35
|
+
if (a === 0 || a === 10 || a === 127)
|
|
36
|
+
return true;
|
|
37
|
+
if (a === 169 && b === 254)
|
|
38
|
+
return true;
|
|
39
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
40
|
+
return true;
|
|
41
|
+
if (a === 192 && b === 168)
|
|
42
|
+
return true;
|
|
43
|
+
if (a >= 224 && a <= 239)
|
|
44
|
+
return true;
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (isIP(ip) === 6) {
|
|
48
|
+
const lower = ip.toLowerCase();
|
|
49
|
+
if (lower === '::' || lower === '::1')
|
|
50
|
+
return true;
|
|
51
|
+
if (lower.startsWith('fc') || lower.startsWith('fd'))
|
|
52
|
+
return true;
|
|
53
|
+
if (lower.startsWith('fe8') || lower.startsWith('fe9') || lower.startsWith('fea') || lower.startsWith('feb'))
|
|
54
|
+
return true;
|
|
55
|
+
if (lower.startsWith('ff'))
|
|
56
|
+
return true;
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Fetch one URL with the SSRF checklist. `fetchImpl` and `resolve` are
|
|
63
|
+
* injectable for tests; production uses the globals.
|
|
64
|
+
*/
|
|
65
|
+
export async function guardedFetch(url, deps = {}) {
|
|
66
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
67
|
+
const resolve = deps.resolve ?? (async (host) => (await lookup(host, { all: true })).map(a => a.address));
|
|
68
|
+
let current = url;
|
|
69
|
+
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
|
|
70
|
+
const parsed = new URL(current);
|
|
71
|
+
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:')
|
|
72
|
+
throw new Error('blocked: non-http(s) URL');
|
|
73
|
+
const ips = await resolve(parsed.hostname);
|
|
74
|
+
if (ips.length === 0 || ips.some(isBlockedIP))
|
|
75
|
+
throw new Error('blocked: destination is not a public host');
|
|
76
|
+
const res = await fetchImpl(current, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), redirect: 'manual' });
|
|
77
|
+
if (res.status >= 300 && res.status < 400) {
|
|
78
|
+
const next = res.headers.get('location');
|
|
79
|
+
if (next === null)
|
|
80
|
+
throw new Error('blocked: redirect without location');
|
|
81
|
+
current = new URL(next, current).toString();
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (!res.ok)
|
|
85
|
+
throw new Error('HTTP ' + res.status + ' for ' + current);
|
|
86
|
+
const reader = res.body?.getReader();
|
|
87
|
+
if (reader === undefined)
|
|
88
|
+
return '';
|
|
89
|
+
const chunks = [];
|
|
90
|
+
let bytes = 0;
|
|
91
|
+
for (;;) {
|
|
92
|
+
const { done, value } = await reader.read();
|
|
93
|
+
if (done)
|
|
94
|
+
break;
|
|
95
|
+
bytes += value.length;
|
|
96
|
+
if (bytes > MAX_BODY_BYTES)
|
|
97
|
+
throw new Error('response exceeds ' + MAX_BODY_BYTES + ' bytes');
|
|
98
|
+
chunks.push(value);
|
|
99
|
+
}
|
|
100
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
101
|
+
}
|
|
102
|
+
throw new Error('too many redirects');
|
|
103
|
+
}
|
|
104
|
+
export function normalizeForecast(json) {
|
|
105
|
+
const cw = json.current_weather;
|
|
106
|
+
return {
|
|
107
|
+
latitude: Number(json.latitude ?? 0),
|
|
108
|
+
longitude: Number(json.longitude ?? 0),
|
|
109
|
+
timezone: json.timezone ?? '',
|
|
110
|
+
current: cw ? {
|
|
111
|
+
time: String(cw.time ?? ''),
|
|
112
|
+
temperature: Number(cw.temperature ?? 0),
|
|
113
|
+
windspeed: Number(cw.windspeed ?? 0),
|
|
114
|
+
winddirection: Number(cw.winddirection ?? 0),
|
|
115
|
+
weathercode: Number(cw.weathercode ?? 0),
|
|
116
|
+
} : null,
|
|
117
|
+
daily: {
|
|
118
|
+
time: (json.daily?.time ?? []).map(String),
|
|
119
|
+
temperatureMax: (json.daily?.temperature_2m_max ?? []).map(Number),
|
|
120
|
+
temperatureMin: (json.daily?.temperature_2m_min ?? []).map(Number),
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
export function normalizeAir(json) {
|
|
125
|
+
const c = json.current ?? {};
|
|
126
|
+
const num = (v) => (v === undefined || v === null ? null : Number(v));
|
|
127
|
+
return {
|
|
128
|
+
latitude: Number(json.latitude ?? 0),
|
|
129
|
+
longitude: Number(json.longitude ?? 0),
|
|
130
|
+
time: String(c.time ?? ''),
|
|
131
|
+
pm2_5: num(c.pm2_5),
|
|
132
|
+
pm10: num(c.pm10),
|
|
133
|
+
aqi: num(c.european_aqi),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
export function apply(ctx, config = {}) {
|
|
137
|
+
const cfg = { ...DEFAULTS, ...config };
|
|
138
|
+
ctx.tools.register(defineTool({
|
|
139
|
+
name: 'rh_weather_forecast',
|
|
140
|
+
description: 'Weather forecast for a location from Open-Meteo (keyless): current conditions plus 3 days of min/max temperatures.',
|
|
141
|
+
parameters: {
|
|
142
|
+
latitude: { type: 'number', required: true, description: 'Latitude, e.g. 51.5074 for London.' },
|
|
143
|
+
longitude: { type: 'number', required: true, description: 'Longitude, e.g. -0.1278 for London.' },
|
|
144
|
+
},
|
|
145
|
+
output: {
|
|
146
|
+
schema: {
|
|
147
|
+
type: 'object',
|
|
148
|
+
additionalProperties: false,
|
|
149
|
+
properties: {
|
|
150
|
+
latitude: { type: 'number', required: true },
|
|
151
|
+
longitude: { type: 'number', required: true },
|
|
152
|
+
timezone: { type: 'string', required: true },
|
|
153
|
+
current: { type: 'json' },
|
|
154
|
+
daily: { type: 'json', required: true },
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
render: (_args, value) => { const c = value.current; return [{ type: 'text', text: c ? (value.timezone + ': ' + c.temperature + ' C, wind ' + c.windspeed + ' km/h') : value.timezone + ': current conditions unavailable' }]; },
|
|
158
|
+
},
|
|
159
|
+
async execute(args) {
|
|
160
|
+
const url = cfg.forecastUrl
|
|
161
|
+
+ '?latitude=' + args.latitude
|
|
162
|
+
+ '&longitude=' + args.longitude
|
|
163
|
+
+ '¤t_weather=true&daily=temperature_2m_max,temperature_2m_min&forecast_days=3';
|
|
164
|
+
const body = await guardedFetch(url);
|
|
165
|
+
return normalizeForecast(JSON.parse(body));
|
|
166
|
+
},
|
|
167
|
+
}));
|
|
168
|
+
ctx.tools.register(defineTool({
|
|
169
|
+
name: 'rh_weather_air',
|
|
170
|
+
description: 'Air quality at a location from Open-Meteo (keyless): PM2.5, PM10 and the European AQI.',
|
|
171
|
+
parameters: {
|
|
172
|
+
latitude: { type: 'number', required: true, description: 'Latitude.' },
|
|
173
|
+
longitude: { type: 'number', required: true, description: 'Longitude.' },
|
|
174
|
+
},
|
|
175
|
+
output: {
|
|
176
|
+
schema: {
|
|
177
|
+
type: 'object',
|
|
178
|
+
additionalProperties: false,
|
|
179
|
+
properties: {
|
|
180
|
+
latitude: { type: 'number', required: true },
|
|
181
|
+
longitude: { type: 'number', required: true },
|
|
182
|
+
time: { type: 'string', required: true },
|
|
183
|
+
pm2_5: { type: 'number' },
|
|
184
|
+
pm10: { type: 'number' },
|
|
185
|
+
aqi: { type: 'number' },
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
render: (_args, value) => [{ type: 'text', text: 'PM2.5 ' + value.pm2_5 + ', PM10 ' + value.pm10 + ', AQI ' + value.aqi }],
|
|
189
|
+
},
|
|
190
|
+
async execute(args) {
|
|
191
|
+
const url = cfg.airUrl
|
|
192
|
+
+ '?latitude=' + args.latitude
|
|
193
|
+
+ '&longitude=' + args.longitude
|
|
194
|
+
+ '¤t=pm2_5,pm10,european_aqi';
|
|
195
|
+
const body = await guardedFetch(url);
|
|
196
|
+
const out = normalizeAir(JSON.parse(body));
|
|
197
|
+
return {
|
|
198
|
+
latitude: out.latitude,
|
|
199
|
+
longitude: out.longitude,
|
|
200
|
+
time: out.time,
|
|
201
|
+
...out.pm2_5 !== null ? { pm2_5: out.pm2_5 } : {},
|
|
202
|
+
...out.pm10 !== null ? { pm10: out.pm10 } : {},
|
|
203
|
+
...out.aqi !== null ? { aqi: out.aqi } : {},
|
|
204
|
+
};
|
|
205
|
+
},
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=weather-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"weather-tools.js","sourceRoot":"","sources":["../src/weather-tools.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@try-works/dsh-righthand",
|
|
3
3
|
"description": "DeepSeek Harness plugin: DSH-native righthand tools — durable KV store, credential/settings management, governed exec, and a tool guard",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.7",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|
|
@@ -17,6 +17,8 @@ The righthand toolkit: DSH-native tools over the harness's own services. Use thi
|
|
|
17
17
|
| settings | `rh_settings_get` / `rh_settings_set` | `ctx.settings` (namespace `righthand`) | Reading or patching righthand settings (`accountId`, `defaultZone`, `defaultScriptPrefix`). |
|
|
18
18
|
| exec | `rh_run` / `rh_run_bg` | `ctx.subprocess` + `ctx.jobs` | Running one command (argv array, no shell interpretation). `rh_run` collects bounded output; `rh_run_bg` starts an owner-scoped background job and returns its id. |
|
|
19
19
|
| text | `rh_text_summarise` / `rh_text_extract` / `rh_text_classify` / `rh_text_translate` | `ctx.llm` (one model call per verb) | Language work: shorten, turn into JSON matching a schema, sort into your labels with confidence, or translate preserving formatting. |
|
|
20
|
+
| weather | `rh_weather_forecast` / `rh_weather_air` | Open-Meteo (keyless) via the SSRF-guarded fetcher | Current conditions + 3-day min/max, or PM2.5/PM10/AQI, for a lat/lon. Keyless only — anything needing an API token is out of scope. |
|
|
21
|
+
| files | `rh_files_put` / `rh_files_get` / `rh_files_list` / `rh_files_share` / `rh_files_delete` | Cloudflare R2 (the user's own account) | Store/read/list/share/delete objects in an R2 bucket. `rh_files_share` returns a presigned download URL valid for N minutes (default 60, max 7 days) — anyone with the URL can read it for that window. |
|
|
20
22
|
| guard | (policy, not a tool) | `ctx.tools` `tools/pre-execute` | Configured via plugin config `rules`; gates tools by name prefix with `allow` / `deny` / `ask` modes. |
|
|
21
23
|
|
|
22
24
|
## Store semantics
|
|
@@ -25,6 +27,19 @@ The righthand toolkit: DSH-native tools over the harness's own services. Use thi
|
|
|
25
27
|
- `rh_store_get` returns `{ found, key, value?, updatedAt? }` — `found: false` means the key is absent, not an error.
|
|
26
28
|
- Writes are durable (backend flush before commit) and serialized on one write chain; values must be JSON-serializable.
|
|
27
29
|
|
|
30
|
+
## Files semantics
|
|
31
|
+
|
|
32
|
+
- Cloudflare R2, the user's own account: accountId from settings, credentials `R2_ACCESS_KEY_ID` / `R2_SECRET_ACCESS_KEY` from the credential provider, bucket from settings `defaultR2Bucket` (or plugin config).
|
|
33
|
+
- Every request is SigV4-signed (region `auto`); the signer is pinned to the AWS published test vector.
|
|
34
|
+
- Content is text (`text/plain` default, override with `contentType`); `rh_files_get` returns `found: false` for absent keys, not an error.
|
|
35
|
+
- `rh_files_share` presigns a GET — treat the URL as a secret for its validity window.
|
|
36
|
+
|
|
37
|
+
## Weather semantics
|
|
38
|
+
|
|
39
|
+
- Keyless only: Open-Meteo, no token. Every request goes through `guardedFetch` — public destinations only (loopback/private/link-local/multicast refused), every redirect hop revalidated, size and time capped.
|
|
40
|
+
- Results are normalized to a stable shape (the wire JSON is the provider's; callers see ours).
|
|
41
|
+
- Output schemas reject `null` — absent pollutant values are omitted, not null.
|
|
42
|
+
|
|
28
43
|
## Text semantics
|
|
29
44
|
|
|
30
45
|
- One model call per verb; each call's failure is contained (the other tools are unaffected).
|
|
@@ -52,6 +67,7 @@ The righthand toolkit: DSH-native tools over the harness's own services. Use thi
|
|
|
52
67
|
| `accountId` | `""` | Cloudflare account id used by righthand Cloudflare tools |
|
|
53
68
|
| `defaultScriptPrefix` | `"rh-"` | default name prefix for generated workers/scripts |
|
|
54
69
|
| `defaultZone` | `""` | default Cloudflare zone |
|
|
70
|
+
| `defaultR2Bucket` | `""` | default R2 bucket for `rh_files_*` |
|
|
55
71
|
|
|
56
72
|
## Guard policy
|
|
57
73
|
|
|
@@ -61,5 +77,5 @@ Rules come from the plugin config (`rules: [{ toolPrefix, mode, ask?, destructiv
|
|
|
61
77
|
|
|
62
78
|
## Service availability
|
|
63
79
|
|
|
64
|
-
- **web profile**: all twenty tools register (`storageDomain` is provided by the web app rows).
|
|
80
|
+
- **web profile**: all twenty-seven tools register (`storageDomain` is provided by the web app rows).
|
|
65
81
|
- **other profiles** (e.g. headless): the store and task families stay dormant; the other seven tools still register. The plugin never fails the boot.
|