@try-works/dsh-righthand 0.1.6 → 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 +3 -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 +6 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +6 -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/package.json +1 -1
- package/skills/dsh-righthand/SKILL.md +10 -1
- package/src/files-tools.ts +294 -0
- package/src/index.ts +7 -1
- package/src/secrets-tools.ts +2 -1
- package/src/sigv4.ts +122 -0
package/README.md
CHANGED
|
@@ -28,6 +28,7 @@ dsh --profile <profile> --patch ./cordis.patch.yml
|
|
|
28
28
|
| `rh_task_create` / `rh_task_list` / `rh_task_next` / `rh_task_update` / `rh_task_delete` | tasks | `ctx.storageDomain` (typed `righthand_tasks` domain; state machine open → done/failed) |
|
|
29
29
|
| `rh_text_summarise` / `rh_text_extract` / `rh_text_classify` / `rh_text_translate` | text | `ctx.llm` (one model call per verb) |
|
|
30
30
|
| `rh_weather_forecast` / `rh_weather_air` | weather | Open-Meteo (keyless), every request through the SSRF-guarded fetcher |
|
|
31
|
+
| `rh_files_put` / `rh_files_get` / `rh_files_list` / `rh_files_share` / `rh_files_delete` | files | Cloudflare R2 (S3-compatible, SigV4-signed; credentials from `R2_ACCESS_KEY_ID` / `R2_SECRET_ACCESS_KEY`, bucket from settings) |
|
|
31
32
|
| `rh_credential_describe` / `rh_credential_set` / `rh_credential_unset` | secrets | `ctx.credentials` (values never echoed) |
|
|
32
33
|
| `rh_settings_get` / `rh_settings_set` | secrets | `ctx.settings` (namespace `righthand`) |
|
|
33
34
|
| `rh_run` | exec | `ctx.subprocess` (collect mode, bounded output) |
|
|
@@ -67,6 +68,7 @@ Namespace "righthand" (registered by the secrets family):
|
|
|
67
68
|
| `accountId` | "" | Cloudflare account id used by righthand Cloudflare tools |
|
|
68
69
|
| `defaultScriptPrefix` | "rh-" | default name prefix for generated workers/scripts |
|
|
69
70
|
| `defaultZone` | "" | default Cloudflare zone |
|
|
71
|
+
| `defaultR2Bucket` | "" | default R2 bucket for `rh_files_*` |
|
|
70
72
|
|
|
71
73
|
`rh_settings_get` returns the resolved values; `rh_settings_set` merges a
|
|
72
74
|
partial patch (persisted by the harness settings provider).
|
|
@@ -84,6 +86,7 @@ canonical example of the current DSH-native form (tool-to-tool via
|
|
|
84
86
|
|---|---|
|
|
85
87
|
| `blueprint/daily-digest` | Collect sources, summarize, send on a schedule — extractive locally, LLM synthesis as escalation |
|
|
86
88
|
| `blueprint/data-adapter` | Wrap one keyless public API as a tool family: guarded fetch, normalize, one tool per query. Worked example ships in the plugin: `rh_weather_*` |
|
|
89
|
+
| (phase 5) `rh_files_*` on R2 | The first family on an actual Cloudflare primitive: S3-compatible API, SigV4 signing pinned to the AWS test vector, credentials from the credential provider |
|
|
87
90
|
| `blueprint/research-radar` | Multi-source research over a rolling window (Reddit, X, YouTube, HN, Polymarket, web): score, dedupe, synthesize with citations |
|
|
88
91
|
| `blueprint/web-scraper` | Fetch a page, extract title/headings/links/text, make it searchable — static HTML first, render/vision as escalation |
|
|
89
92
|
| `blueprint/vision-worker` | Cloudflare Worker exposing kimi-k2.6 vision: route image requests for text-only models, return a versioned `VisionEnvelope` |
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* files-tools — rh_files_* over Cloudflare R2 (the user's own account).
|
|
3
|
+
* The first family on an actual Cloudflare primitive. S3-compatible API,
|
|
4
|
+
* signed with SigV4 (src/sigv4.ts, pinned to the AWS test vector).
|
|
5
|
+
*
|
|
6
|
+
* Credentials resolve at call time from the credential provider
|
|
7
|
+
* (R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY); accountId and bucket come
|
|
8
|
+
* from the righthand settings namespace. The client is a pure factory so
|
|
9
|
+
* tests drive it with a stubbed fetch.
|
|
10
|
+
* @module dsh-righthand/files-tools
|
|
11
|
+
*/
|
|
12
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
13
|
+
export declare const name = "righthand-files";
|
|
14
|
+
export declare const inject: string[];
|
|
15
|
+
export declare const R2_ACCESS_KEY_REF = "R2_ACCESS_KEY_ID";
|
|
16
|
+
export declare const R2_SECRET_KEY_REF = "R2_SECRET_ACCESS_KEY";
|
|
17
|
+
export interface FilesConfig {
|
|
18
|
+
/** Bucket name; defaults to the righthand settings defaultR2Bucket. */
|
|
19
|
+
bucket?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface R2Options {
|
|
22
|
+
accountId: string;
|
|
23
|
+
accessKeyId: string;
|
|
24
|
+
secretAccessKey: string;
|
|
25
|
+
bucket: string;
|
|
26
|
+
fetchImpl?: typeof fetch;
|
|
27
|
+
now?: () => Date;
|
|
28
|
+
}
|
|
29
|
+
export interface ListEntry {
|
|
30
|
+
key: string;
|
|
31
|
+
size: number;
|
|
32
|
+
lastModified: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The R2 client factory. Pure over its options; every call signs fresh.
|
|
36
|
+
*/
|
|
37
|
+
export declare function createR2Client(opts: R2Options): {
|
|
38
|
+
put(key: string, content: string, contentType?: string): Promise<{
|
|
39
|
+
key: string;
|
|
40
|
+
size: number;
|
|
41
|
+
}>;
|
|
42
|
+
get(key: string): Promise<{
|
|
43
|
+
found: boolean;
|
|
44
|
+
content: string;
|
|
45
|
+
contentType: string;
|
|
46
|
+
}>;
|
|
47
|
+
list(prefix?: string, maxKeys?: number): Promise<ListEntry[]>;
|
|
48
|
+
share(key: string, expiresSeconds: number): string;
|
|
49
|
+
delete(key: string): Promise<{
|
|
50
|
+
key: string;
|
|
51
|
+
existed: boolean;
|
|
52
|
+
}>;
|
|
53
|
+
};
|
|
54
|
+
export declare function apply(ctx: Context, config?: FilesConfig): void;
|
|
55
|
+
//# sourceMappingURL=files-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files-tools.d.ts","sourceRoot":"","sources":["../src/files-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAMlD,eAAO,MAAM,IAAI,oBAAoB,CAAA;AACrC,eAAO,MAAM,MAAM,UAAuC,CAAA;AAE1D,eAAO,MAAM,iBAAiB,qBAAqB,CAAA;AACnD,eAAO,MAAM,iBAAiB,yBAAyB,CAAA;AAEvD,MAAM,WAAW,WAAW;IAC1B,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;CACjB;AAMD,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,EAAE,MAAM,CAAA;CACrB;AAmBD;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS;aAsB3B,MAAM,WAAW,MAAM,gBAAgB,MAAM,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;aAMtF,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;kBAOrE,MAAM,YAAY,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;eAmBxD,MAAM,kBAAkB,MAAM,GAAG,MAAM;gBAWhC,MAAM,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;EAOxE;AAGD,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,WAAgB,GAAG,IAAI,CAkJlE"}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* files-tools — rh_files_* over Cloudflare R2 (the user's own account).
|
|
3
|
+
* The first family on an actual Cloudflare primitive. S3-compatible API,
|
|
4
|
+
* signed with SigV4 (src/sigv4.ts, pinned to the AWS test vector).
|
|
5
|
+
*
|
|
6
|
+
* Credentials resolve at call time from the credential provider
|
|
7
|
+
* (R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY); accountId and bucket come
|
|
8
|
+
* from the righthand settings namespace. The client is a pure factory so
|
|
9
|
+
* tests drive it with a stubbed fetch.
|
|
10
|
+
* @module dsh-righthand/files-tools
|
|
11
|
+
*/
|
|
12
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
13
|
+
import { credentialRef } from '@deepseek-ai/dsh-credentials';
|
|
14
|
+
import { settingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
15
|
+
import { signRequest, presignGet } from "./sigv4.js";
|
|
16
|
+
export const name = 'righthand-files';
|
|
17
|
+
export const inject = ['tools', 'credentials', 'settings'];
|
|
18
|
+
export const R2_ACCESS_KEY_REF = 'R2_ACCESS_KEY_ID';
|
|
19
|
+
export const R2_SECRET_KEY_REF = 'R2_SECRET_ACCESS_KEY';
|
|
20
|
+
function amzDate(d) {
|
|
21
|
+
return d.toISOString().replace(/[:-]|\.\d{3}/g, '');
|
|
22
|
+
}
|
|
23
|
+
/** Minimal ListObjectsV2 XML parse: Key / Size / LastModified triples. */
|
|
24
|
+
function parseListXml(xml) {
|
|
25
|
+
const out = [];
|
|
26
|
+
const contents = xml.split('<Contents>').slice(1);
|
|
27
|
+
for (const block of contents) {
|
|
28
|
+
const grab = (tag) => {
|
|
29
|
+
const a = block.indexOf('<' + tag + '>');
|
|
30
|
+
const b = block.indexOf('</' + tag + '>');
|
|
31
|
+
return a < 0 || b < 0 ? '' : block.slice(a + tag.length + 2, b);
|
|
32
|
+
};
|
|
33
|
+
const key = grab('Key');
|
|
34
|
+
if (key === '')
|
|
35
|
+
continue;
|
|
36
|
+
out.push({ key, size: Number(grab('Size') ?? 0), lastModified: grab('LastModified') });
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The R2 client factory. Pure over its options; every call signs fresh.
|
|
42
|
+
*/
|
|
43
|
+
export function createR2Client(opts) {
|
|
44
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
45
|
+
const now = opts.now ?? (() => new Date());
|
|
46
|
+
const base = 'https://' + opts.accountId + '.r2.cloudflarestorage.com/' + opts.bucket;
|
|
47
|
+
async function request(method, key, payload, extraHeaders = {}) {
|
|
48
|
+
const url = base + '/' + key;
|
|
49
|
+
const parsed = new URL(url);
|
|
50
|
+
const signed = signRequest({
|
|
51
|
+
method,
|
|
52
|
+
url,
|
|
53
|
+
headers: { host: parsed.host, ...extraHeaders },
|
|
54
|
+
payload,
|
|
55
|
+
accessKeyId: opts.accessKeyId,
|
|
56
|
+
secretAccessKey: opts.secretAccessKey,
|
|
57
|
+
region: 'auto',
|
|
58
|
+
amzDate: amzDate(now()),
|
|
59
|
+
});
|
|
60
|
+
return fetchImpl(url, { method, headers: signed.headers, body: payload === '' ? undefined : payload });
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
async put(key, content, contentType) {
|
|
64
|
+
const res = await request('PUT', key, content, contentType ? { 'content-type': contentType } : {});
|
|
65
|
+
if (!res.ok)
|
|
66
|
+
throw new Error('R2 PUT failed: HTTP ' + res.status);
|
|
67
|
+
return { key, size: Buffer.byteLength(content) };
|
|
68
|
+
},
|
|
69
|
+
async get(key) {
|
|
70
|
+
const res = await request('GET', key, '');
|
|
71
|
+
if (res.status === 404)
|
|
72
|
+
return { found: false, content: '', contentType: '' };
|
|
73
|
+
if (!res.ok)
|
|
74
|
+
throw new Error('R2 GET failed: HTTP ' + res.status);
|
|
75
|
+
return { found: true, content: await res.text(), contentType: res.headers.get('content-type') ?? '' };
|
|
76
|
+
},
|
|
77
|
+
async list(prefix, maxKeys) {
|
|
78
|
+
const q = '?list-type=2' + (prefix ? '&prefix=' + encodeURIComponent(prefix) : '') + (maxKeys ? '&max-keys=' + maxKeys : '');
|
|
79
|
+
const url = base + '/' + q;
|
|
80
|
+
const parsed = new URL(url);
|
|
81
|
+
const signed = signRequest({
|
|
82
|
+
method: 'GET',
|
|
83
|
+
url,
|
|
84
|
+
headers: { host: parsed.host },
|
|
85
|
+
payload: '',
|
|
86
|
+
accessKeyId: opts.accessKeyId,
|
|
87
|
+
secretAccessKey: opts.secretAccessKey,
|
|
88
|
+
region: 'auto',
|
|
89
|
+
amzDate: amzDate(now()),
|
|
90
|
+
});
|
|
91
|
+
const res = await fetchImpl(url, { method: 'GET', headers: signed.headers });
|
|
92
|
+
if (!res.ok)
|
|
93
|
+
throw new Error('R2 list failed: HTTP ' + res.status);
|
|
94
|
+
return parseListXml(await res.text());
|
|
95
|
+
},
|
|
96
|
+
share(key, expiresSeconds) {
|
|
97
|
+
return presignGet({
|
|
98
|
+
url: base + '/' + key,
|
|
99
|
+
accessKeyId: opts.accessKeyId,
|
|
100
|
+
secretAccessKey: opts.secretAccessKey,
|
|
101
|
+
region: 'auto',
|
|
102
|
+
amzDate: amzDate(now()),
|
|
103
|
+
expiresSeconds,
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
async delete(key) {
|
|
107
|
+
const res = await request('DELETE', key, '');
|
|
108
|
+
if (res.status === 404)
|
|
109
|
+
return { key, existed: false };
|
|
110
|
+
if (!res.ok)
|
|
111
|
+
throw new Error('R2 DELETE failed: HTTP ' + res.status);
|
|
112
|
+
return { key, existed: true };
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export function apply(ctx, config = {}) {
|
|
117
|
+
const ns = settingsNamespace('righthand');
|
|
118
|
+
/** Resolve accountId + credentials + bucket at call time. */
|
|
119
|
+
async function client() {
|
|
120
|
+
const settings = ctx.settings.get(ns);
|
|
121
|
+
const accountId = settings?.accountId ?? '';
|
|
122
|
+
if (accountId === '')
|
|
123
|
+
throw new Error('righthand accountId is not set (rh_settings_set { accountId })');
|
|
124
|
+
const bucket = config.bucket ?? settings?.defaultR2Bucket ?? '';
|
|
125
|
+
if (bucket === '')
|
|
126
|
+
throw new Error('R2 bucket is not set (plugin config bucket or settings defaultR2Bucket)');
|
|
127
|
+
const access = await ctx.credentials.resolve(credentialRef(R2_ACCESS_KEY_REF));
|
|
128
|
+
const secret = await ctx.credentials.resolve(credentialRef(R2_SECRET_KEY_REF));
|
|
129
|
+
if (access === undefined || secret === undefined) {
|
|
130
|
+
throw new Error('R2 credentials are not set (rh_credential_set R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY)');
|
|
131
|
+
}
|
|
132
|
+
return createR2Client({ accountId, accessKeyId: access.value, secretAccessKey: secret.value, bucket });
|
|
133
|
+
}
|
|
134
|
+
ctx.tools.register(defineTool({
|
|
135
|
+
name: 'rh_files_put',
|
|
136
|
+
description: 'Store a file in the R2 bucket and return its key. Content is text (reports, CSV, transcripts, code); content type defaults to text/plain.',
|
|
137
|
+
parameters: {
|
|
138
|
+
key: { type: 'string', required: true, description: 'Object key, e.g. reports/2026-08-30.csv.' },
|
|
139
|
+
content: { type: 'string', required: true, description: 'The full text content to store.' },
|
|
140
|
+
contentType: { type: 'string', description: 'Content type, e.g. text/csv. Defaults to text/plain.' },
|
|
141
|
+
},
|
|
142
|
+
output: {
|
|
143
|
+
schema: {
|
|
144
|
+
type: 'object',
|
|
145
|
+
additionalProperties: false,
|
|
146
|
+
properties: {
|
|
147
|
+
key: { type: 'string', required: true },
|
|
148
|
+
size: { type: 'integer', required: true },
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
render: (_args, value) => [{ type: 'text', text: 'stored ' + value.key + ' (' + value.size + ' bytes)' }],
|
|
152
|
+
},
|
|
153
|
+
async execute(args) {
|
|
154
|
+
const c = await client();
|
|
155
|
+
return c.put(args.key, args.content, args.contentType);
|
|
156
|
+
},
|
|
157
|
+
}));
|
|
158
|
+
ctx.tools.register(defineTool({
|
|
159
|
+
name: 'rh_files_get',
|
|
160
|
+
description: 'Read a stored file back by its key. found: false means the key is absent, not an error.',
|
|
161
|
+
parameters: {
|
|
162
|
+
key: { type: 'string', required: true, description: 'The object key to read.' },
|
|
163
|
+
},
|
|
164
|
+
output: {
|
|
165
|
+
schema: {
|
|
166
|
+
type: 'object',
|
|
167
|
+
additionalProperties: false,
|
|
168
|
+
properties: {
|
|
169
|
+
found: { type: 'boolean', required: true },
|
|
170
|
+
key: { type: 'string', required: true },
|
|
171
|
+
content: { type: 'string' },
|
|
172
|
+
contentType: { type: 'string' },
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
render: (_args, value) => [{ type: 'text', text: value.found && typeof value.content === 'string' ? value.content : '(absent)' }],
|
|
176
|
+
},
|
|
177
|
+
async execute(args) {
|
|
178
|
+
const c = await client();
|
|
179
|
+
const out = await c.get(args.key);
|
|
180
|
+
return {
|
|
181
|
+
key: args.key,
|
|
182
|
+
found: out.found,
|
|
183
|
+
...out.found ? { content: out.content, contentType: out.contentType } : {},
|
|
184
|
+
};
|
|
185
|
+
},
|
|
186
|
+
}));
|
|
187
|
+
ctx.tools.register(defineTool({
|
|
188
|
+
name: 'rh_files_list',
|
|
189
|
+
description: 'List objects in the bucket, optionally by key prefix. Newest first is not guaranteed by S3; entries carry lastModified.',
|
|
190
|
+
parameters: {
|
|
191
|
+
prefix: { type: 'string', description: 'Only keys starting with this, e.g. reports/.' },
|
|
192
|
+
maxKeys: { type: 'integer', description: 'Max entries (default 100).' },
|
|
193
|
+
},
|
|
194
|
+
output: {
|
|
195
|
+
schema: {
|
|
196
|
+
type: 'object',
|
|
197
|
+
additionalProperties: false,
|
|
198
|
+
properties: {
|
|
199
|
+
entries: { type: 'array', required: true, items: { type: 'json' } },
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
render: (_args, value) => [{ type: 'text', text: value.entries.length + ' objects' }],
|
|
203
|
+
},
|
|
204
|
+
async execute(args) {
|
|
205
|
+
const c = await client();
|
|
206
|
+
const entries = await c.list(args.prefix, args.maxKeys);
|
|
207
|
+
return { entries: entries };
|
|
208
|
+
},
|
|
209
|
+
}));
|
|
210
|
+
ctx.tools.register(defineTool({
|
|
211
|
+
name: 'rh_files_share',
|
|
212
|
+
description: 'Get a presigned download URL for a key, valid for the given minutes (default 60, max 10080). Anyone with the URL can read the object for that window.',
|
|
213
|
+
parameters: {
|
|
214
|
+
key: { type: 'string', required: true, description: 'The object key to share.' },
|
|
215
|
+
minutes: { type: 'integer', description: 'Validity in minutes (default 60, max 10080 = 7 days).' },
|
|
216
|
+
},
|
|
217
|
+
output: {
|
|
218
|
+
schema: {
|
|
219
|
+
type: 'object',
|
|
220
|
+
additionalProperties: false,
|
|
221
|
+
properties: {
|
|
222
|
+
key: { type: 'string', required: true },
|
|
223
|
+
url: { type: 'string', required: true },
|
|
224
|
+
expiresAt: { type: 'string', required: true },
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
render: (_args, value) => [{ type: 'text', text: value.url }],
|
|
228
|
+
},
|
|
229
|
+
async execute(args) {
|
|
230
|
+
const minutes = Math.min(Math.max(args.minutes ?? 60, 1), 10080);
|
|
231
|
+
const c = await client();
|
|
232
|
+
const url = c.share(args.key, minutes * 60);
|
|
233
|
+
return { key: args.key, url, expiresAt: new Date(Date.now() + minutes * 60000).toISOString() };
|
|
234
|
+
},
|
|
235
|
+
}));
|
|
236
|
+
ctx.tools.register(defineTool({
|
|
237
|
+
name: 'rh_files_delete',
|
|
238
|
+
description: 'Delete an object by key. Returns whether it existed.',
|
|
239
|
+
parameters: {
|
|
240
|
+
key: { type: 'string', required: true, description: 'The object key to delete.' },
|
|
241
|
+
},
|
|
242
|
+
output: {
|
|
243
|
+
schema: {
|
|
244
|
+
type: 'object',
|
|
245
|
+
additionalProperties: false,
|
|
246
|
+
properties: {
|
|
247
|
+
key: { type: 'string', required: true },
|
|
248
|
+
existed: { type: 'boolean', required: true },
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
render: (_args, value) => [{ type: 'text', text: 'deleted ' + value.key + ': ' + value.existed }],
|
|
252
|
+
},
|
|
253
|
+
async execute(args) {
|
|
254
|
+
const c = await client();
|
|
255
|
+
return c.delete(args.key);
|
|
256
|
+
},
|
|
257
|
+
}));
|
|
258
|
+
}
|
|
259
|
+
//# sourceMappingURL=files-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files-tools.js","sourceRoot":"","sources":["../src/files-tools.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* - task-tools: rh_task_create/list/next/update/delete over ctx.storageDomain
|
|
18
18
|
* - text-tools: rh_text_summarise/extract/classify/translate over ctx.llm
|
|
19
19
|
* - weather-tools: rh_weather_forecast/air over Open-Meteo (keyless, SSRF-guarded)
|
|
20
|
+
* - files-tools: rh_files_put/get/list/share/delete over Cloudflare R2
|
|
20
21
|
* - exec-tools: rh_run / rh_run_bg over ctx.subprocess + ctx.jobs
|
|
21
22
|
* - guard-tools: tools/pre-execute policy (config.rules)
|
|
22
23
|
* - skills: the packaged dsh-righthand skill (ctx.skills)
|
|
@@ -31,6 +32,7 @@ import * as guardTools from './guard-tools.ts';
|
|
|
31
32
|
import * as taskTools from './task-tools.ts';
|
|
32
33
|
import * as textTools from './text-tools.ts';
|
|
33
34
|
import * as weatherTools from './weather-tools.ts';
|
|
35
|
+
import * as filesTools from './files-tools.ts';
|
|
34
36
|
import * as righthandSkills from './skills.ts';
|
|
35
37
|
import type { GuardRule } from './guard-tools.ts';
|
|
36
38
|
export declare const name = "dsh-righthand";
|
|
@@ -48,11 +50,14 @@ export interface RighthandConfig {
|
|
|
48
50
|
* instead of failing the whole plugin.
|
|
49
51
|
*/
|
|
50
52
|
export declare function apply(ctx: Context, config?: RighthandConfig): void;
|
|
51
|
-
export { storeTools, secretsTools, execTools, taskTools, textTools, weatherTools, guardTools, righthandSkills };
|
|
53
|
+
export { storeTools, secretsTools, execTools, taskTools, textTools, weatherTools, filesTools, guardTools, righthandSkills };
|
|
52
54
|
export { SKILL_NAME, SKILL_DESCRIPTION, skillDirectory, skillBody } from './skills.ts';
|
|
53
55
|
export type { GuardRule } from './guard-tools.ts';
|
|
54
56
|
export { guardFactsFor } from './guard-tools.ts';
|
|
55
57
|
export type { GuardFacts } from './guard-tools.ts';
|
|
56
58
|
export { isBlockedIP, guardedFetch, normalizeForecast, normalizeAir } from './weather-tools.ts';
|
|
59
|
+
export { createR2Client, R2_ACCESS_KEY_REF, R2_SECRET_KEY_REF } from './files-tools.ts';
|
|
60
|
+
export { signRequest, presignGet } from './sigv4.ts';
|
|
61
|
+
export type { R2Options, ListEntry } from './files-tools.ts';
|
|
57
62
|
export type { WeatherConfig, ForecastOut, AirOut } from './weather-tools.ts';
|
|
58
63
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAA;AAClD,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAA;AAC5C,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAA;AAC5C,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAA;AAC5C,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAA;AAClD,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,eAAe,MAAM,aAAa,CAAA;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAEjD,eAAO,MAAM,IAAI,kBAAkB,CAAA;AAEnC,iFAAiF;AACjF,eAAO,MAAM,MAAM,EAAE,MAAM,EAAO,CAAA;AAElC,qCAAqC;AACrC,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,KAAK,CAAC,EAAE,SAAS,EAAE,CAAA;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,eAAoB,GAAG,IAAI,CAUtE;AAGD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,CAAA;AAC3H,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACtF,YAAY,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAChD,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAC/F,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACvF,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACpD,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC5D,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA"}
|
package/lib/index.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* - task-tools: rh_task_create/list/next/update/delete over ctx.storageDomain
|
|
18
18
|
* - text-tools: rh_text_summarise/extract/classify/translate over ctx.llm
|
|
19
19
|
* - weather-tools: rh_weather_forecast/air over Open-Meteo (keyless, SSRF-guarded)
|
|
20
|
+
* - files-tools: rh_files_put/get/list/share/delete over Cloudflare R2
|
|
20
21
|
* - exec-tools: rh_run / rh_run_bg over ctx.subprocess + ctx.jobs
|
|
21
22
|
* - guard-tools: tools/pre-execute policy (config.rules)
|
|
22
23
|
* - skills: the packaged dsh-righthand skill (ctx.skills)
|
|
@@ -30,6 +31,7 @@ import * as guardTools from "./guard-tools.js";
|
|
|
30
31
|
import * as taskTools from "./task-tools.js";
|
|
31
32
|
import * as textTools from "./text-tools.js";
|
|
32
33
|
import * as weatherTools from "./weather-tools.js";
|
|
34
|
+
import * as filesTools from "./files-tools.js";
|
|
33
35
|
import * as righthandSkills from "./skills.js";
|
|
34
36
|
export const name = 'dsh-righthand';
|
|
35
37
|
/** The combined plugin needs no services itself; each child waits on its own. */
|
|
@@ -47,12 +49,15 @@ export function apply(ctx, config = {}) {
|
|
|
47
49
|
ctx.plugin(taskTools);
|
|
48
50
|
ctx.plugin(textTools);
|
|
49
51
|
ctx.plugin(weatherTools);
|
|
52
|
+
ctx.plugin(filesTools);
|
|
50
53
|
ctx.plugin(guardTools, { rules: config.rules ?? [] });
|
|
51
54
|
ctx.plugin(righthandSkills);
|
|
52
55
|
}
|
|
53
56
|
// Individual modules stay importable for selective mounting.
|
|
54
|
-
export { storeTools, secretsTools, execTools, taskTools, textTools, weatherTools, guardTools, righthandSkills };
|
|
57
|
+
export { storeTools, secretsTools, execTools, taskTools, textTools, weatherTools, filesTools, guardTools, righthandSkills };
|
|
55
58
|
export { SKILL_NAME, SKILL_DESCRIPTION, skillDirectory, skillBody } from "./skills.js";
|
|
56
59
|
export { guardFactsFor } from "./guard-tools.js";
|
|
57
60
|
export { isBlockedIP, guardedFetch, normalizeForecast, normalizeAir } from "./weather-tools.js";
|
|
61
|
+
export { createR2Client, R2_ACCESS_KEY_REF, R2_SECRET_KEY_REF } from "./files-tools.js";
|
|
62
|
+
export { signRequest, presignGet } from "./sigv4.js";
|
|
58
63
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAGH,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAA;AAClD,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAA;AAC5C,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAA;AAC5C,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAA;AAC5C,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAA;AAClD,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,eAAe,MAAM,aAAa,CAAA;AAG9C,MAAM,CAAC,MAAM,IAAI,GAAG,eAAe,CAAA;AAEnC,iFAAiF;AACjF,MAAM,CAAC,MAAM,MAAM,GAAa,EAAE,CAAA;AAQlC;;;;;GAKG;AACH,MAAM,UAAU,KAAK,CAAC,GAAY,EAAE,SAA0B,EAAE;IAC9D,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;IACtB,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;IACxB,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IACrB,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IACrB,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IACrB,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;IACxB,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;IACtB,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC,CAAA;IACrD,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,CAAA;AAC7B,CAAC;AAED,6DAA6D;AAC7D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,CAAA;AAC3H,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEtF,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAEhD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAC/F,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACvF,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA"}
|
package/lib/secrets-tools.d.ts
CHANGED
|
@@ -20,10 +20,12 @@ export declare const righthandSettingsSchema: z<Schemastery.ObjectS<{
|
|
|
20
20
|
accountId: z<string, string>;
|
|
21
21
|
defaultScriptPrefix: z<string, string>;
|
|
22
22
|
defaultZone: z<string, string>;
|
|
23
|
+
defaultR2Bucket: z<string, string>;
|
|
23
24
|
}>, Schemastery.ObjectT<{
|
|
24
25
|
accountId: z<string, string>;
|
|
25
26
|
defaultScriptPrefix: z<string, string>;
|
|
26
27
|
defaultZone: z<string, string>;
|
|
28
|
+
defaultR2Bucket: z<string, string>;
|
|
27
29
|
}>>;
|
|
28
30
|
/** Register the settings namespace + the secret/credential tools. */
|
|
29
31
|
export declare function apply(ctx: Context): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secrets-tools.d.ts","sourceRoot":"","sources":["../src/secrets-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAIlD,OAAO,CAAC,MAAM,0BAA0B,CAAA;AAExC,eAAO,MAAM,IAAI,sBAAsB,CAAA;AACvC,eAAO,MAAM,MAAM,UAAuC,CAAA;AAE1D,kDAAkD;AAClD,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAA;IACjB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,eAAO,MAAM,uBAAuB
|
|
1
|
+
{"version":3,"file":"secrets-tools.d.ts","sourceRoot":"","sources":["../src/secrets-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAIlD,OAAO,CAAC,MAAM,0BAA0B,CAAA;AAExC,eAAO,MAAM,IAAI,sBAAsB,CAAA;AACvC,eAAO,MAAM,MAAM,UAAuC,CAAA;AAE1D,kDAAkD;AAClD,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAA;IACjB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,eAAO,MAAM,uBAAuB;;;;;;;;;;GAKlC,CAAA;AAEF,qEAAqE;AACrE,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAkHxC"}
|
package/lib/secrets-tools.js
CHANGED
|
@@ -16,6 +16,7 @@ export const righthandSettingsSchema = z.object({
|
|
|
16
16
|
accountId: z.string().default(''),
|
|
17
17
|
defaultScriptPrefix: z.string().default('rh-'),
|
|
18
18
|
defaultZone: z.string().default(''),
|
|
19
|
+
defaultR2Bucket: z.string().default(''),
|
|
19
20
|
});
|
|
20
21
|
/** Register the settings namespace + the secret/credential tools. */
|
|
21
22
|
export function apply(ctx) {
|
|
@@ -104,7 +105,7 @@ export function apply(ctx) {
|
|
|
104
105
|
},
|
|
105
106
|
async execute() {
|
|
106
107
|
const v = scope.get();
|
|
107
|
-
return { accountId: v.accountId, defaultScriptPrefix: v.defaultScriptPrefix, defaultZone: v.defaultZone };
|
|
108
|
+
return { accountId: v.accountId, defaultScriptPrefix: v.defaultScriptPrefix, defaultZone: v.defaultZone, defaultR2Bucket: v.defaultR2Bucket };
|
|
108
109
|
},
|
|
109
110
|
}));
|
|
110
111
|
ctx.tools.register(defineTool({
|
package/lib/secrets-tools.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secrets-tools.js","sourceRoot":"","sources":["../src/secrets-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAA;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAC7D,OAAO,CAAC,MAAM,0BAA0B,CAAA;AAExC,MAAM,CAAC,MAAM,IAAI,GAAG,mBAAmB,CAAA;AACvC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAO,EAAE,aAAa,EAAE,UAAU,CAAC,CAAA;AAS1D,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACjC,mBAAmB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IAC9C,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"secrets-tools.js","sourceRoot":"","sources":["../src/secrets-tools.ts"],"names":[],"mappings":"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"}
|
package/lib/sigv4.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
export interface SignRequestInput {
|
|
8
|
+
method: string;
|
|
9
|
+
/** Full URL, e.g. https://<account>.r2.cloudflarestorage.com/<bucket>/<key> */
|
|
10
|
+
url: string;
|
|
11
|
+
/** Headers to sign (lowercase names), e.g. host, content-type. */
|
|
12
|
+
headers: Record<string, string>;
|
|
13
|
+
/** Exact payload bytes; '' for GET/DELETE without a body. */
|
|
14
|
+
payload: string;
|
|
15
|
+
accessKeyId: string;
|
|
16
|
+
secretAccessKey: string;
|
|
17
|
+
/** R2 uses region 'auto'. */
|
|
18
|
+
region: string;
|
|
19
|
+
/** yyyyMMdd'T'HHmmss'Z' — injectable for tests. */
|
|
20
|
+
amzDate: string;
|
|
21
|
+
}
|
|
22
|
+
export interface SignedRequest {
|
|
23
|
+
headers: Record<string, string>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Sign one S3 request. Returns the headers to send, including host,
|
|
27
|
+
* x-amz-content-sha256, x-amz-date and Authorization.
|
|
28
|
+
*/
|
|
29
|
+
export declare function signRequest(input: SignRequestInput): SignedRequest;
|
|
30
|
+
/**
|
|
31
|
+
* Presign a GET for query-string auth: a shareable URL valid for
|
|
32
|
+
* expiresSeconds (S3 max 7 days).
|
|
33
|
+
*/
|
|
34
|
+
export declare function presignGet(input: Omit<SignRequestInput, 'method' | 'headers' | 'payload'> & {
|
|
35
|
+
expiresSeconds: number;
|
|
36
|
+
}): string;
|
|
37
|
+
//# sourceMappingURL=sigv4.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sigv4.d.ts","sourceRoot":"","sources":["../src/sigv4.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAuBH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,+EAA+E;IAC/E,GAAG,EAAE,MAAM,CAAA;IACX,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,CAAA;IACvB,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,aAAa,CAgClE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,gBAAgB,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC,GAAG;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CA6B/H"}
|
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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"}
|
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",
|
|
@@ -18,6 +18,7 @@ The righthand toolkit: DSH-native tools over the harness's own services. Use thi
|
|
|
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
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. |
|
|
21
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. |
|
|
22
23
|
|
|
23
24
|
## Store semantics
|
|
@@ -26,6 +27,13 @@ The righthand toolkit: DSH-native tools over the harness's own services. Use thi
|
|
|
26
27
|
- `rh_store_get` returns `{ found, key, value?, updatedAt? }` — `found: false` means the key is absent, not an error.
|
|
27
28
|
- Writes are durable (backend flush before commit) and serialized on one write chain; values must be JSON-serializable.
|
|
28
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
|
+
|
|
29
37
|
## Weather semantics
|
|
30
38
|
|
|
31
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.
|
|
@@ -59,6 +67,7 @@ The righthand toolkit: DSH-native tools over the harness's own services. Use thi
|
|
|
59
67
|
| `accountId` | `""` | Cloudflare account id used by righthand Cloudflare tools |
|
|
60
68
|
| `defaultScriptPrefix` | `"rh-"` | default name prefix for generated workers/scripts |
|
|
61
69
|
| `defaultZone` | `""` | default Cloudflare zone |
|
|
70
|
+
| `defaultR2Bucket` | `""` | default R2 bucket for `rh_files_*` |
|
|
62
71
|
|
|
63
72
|
## Guard policy
|
|
64
73
|
|
|
@@ -68,5 +77,5 @@ Rules come from the plugin config (`rules: [{ toolPrefix, mode, ask?, destructiv
|
|
|
68
77
|
|
|
69
78
|
## Service availability
|
|
70
79
|
|
|
71
|
-
- **web profile**: all twenty-
|
|
80
|
+
- **web profile**: all twenty-seven tools register (`storageDomain` is provided by the web app rows).
|
|
72
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.
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* files-tools — rh_files_* over Cloudflare R2 (the user's own account).
|
|
3
|
+
* The first family on an actual Cloudflare primitive. S3-compatible API,
|
|
4
|
+
* signed with SigV4 (src/sigv4.ts, pinned to the AWS test vector).
|
|
5
|
+
*
|
|
6
|
+
* Credentials resolve at call time from the credential provider
|
|
7
|
+
* (R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY); accountId and bucket come
|
|
8
|
+
* from the righthand settings namespace. The client is a pure factory so
|
|
9
|
+
* tests drive it with a stubbed fetch.
|
|
10
|
+
* @module dsh-righthand/files-tools
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
14
|
+
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
15
|
+
import { credentialRef } from '@deepseek-ai/dsh-credentials'
|
|
16
|
+
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
|
|
17
|
+
import { signRequest, presignGet } from './sigv4.ts'
|
|
18
|
+
|
|
19
|
+
export const name = 'righthand-files'
|
|
20
|
+
export const inject = ['tools', 'credentials', 'settings']
|
|
21
|
+
|
|
22
|
+
export const R2_ACCESS_KEY_REF = 'R2_ACCESS_KEY_ID'
|
|
23
|
+
export const R2_SECRET_KEY_REF = 'R2_SECRET_ACCESS_KEY'
|
|
24
|
+
|
|
25
|
+
export interface FilesConfig {
|
|
26
|
+
/** Bucket name; defaults to the righthand settings defaultR2Bucket. */
|
|
27
|
+
bucket?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface R2Options {
|
|
31
|
+
accountId: string
|
|
32
|
+
accessKeyId: string
|
|
33
|
+
secretAccessKey: string
|
|
34
|
+
bucket: string
|
|
35
|
+
fetchImpl?: typeof fetch
|
|
36
|
+
now?: () => Date
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function amzDate(d: Date): string {
|
|
40
|
+
return d.toISOString().replace(/[:-]|\.\d{3}/g, '')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ListEntry {
|
|
44
|
+
key: string
|
|
45
|
+
size: number
|
|
46
|
+
lastModified: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Minimal ListObjectsV2 XML parse: Key / Size / LastModified triples. */
|
|
50
|
+
function parseListXml(xml: string): ListEntry[] {
|
|
51
|
+
const out: ListEntry[] = []
|
|
52
|
+
const contents = xml.split('<Contents>').slice(1)
|
|
53
|
+
for (const block of contents) {
|
|
54
|
+
const grab = (tag: string): string => {
|
|
55
|
+
const a = block.indexOf('<' + tag + '>')
|
|
56
|
+
const b = block.indexOf('</' + tag + '>')
|
|
57
|
+
return a < 0 || b < 0 ? '' : block.slice(a + tag.length + 2, b)
|
|
58
|
+
}
|
|
59
|
+
const key = grab('Key')
|
|
60
|
+
if (key === '') continue
|
|
61
|
+
out.push({ key, size: Number(grab('Size') ?? 0), lastModified: grab('LastModified') })
|
|
62
|
+
}
|
|
63
|
+
return out
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The R2 client factory. Pure over its options; every call signs fresh.
|
|
68
|
+
*/
|
|
69
|
+
export function createR2Client(opts: R2Options) {
|
|
70
|
+
const fetchImpl = opts.fetchImpl ?? fetch
|
|
71
|
+
const now = opts.now ?? (() => new Date())
|
|
72
|
+
const base = 'https://' + opts.accountId + '.r2.cloudflarestorage.com/' + opts.bucket
|
|
73
|
+
|
|
74
|
+
async function request(method: string, key: string, payload: string, extraHeaders: Record<string, string> = {}): Promise<Response> {
|
|
75
|
+
const url = base + '/' + key
|
|
76
|
+
const parsed = new URL(url)
|
|
77
|
+
const signed = signRequest({
|
|
78
|
+
method,
|
|
79
|
+
url,
|
|
80
|
+
headers: { host: parsed.host, ...extraHeaders },
|
|
81
|
+
payload,
|
|
82
|
+
accessKeyId: opts.accessKeyId,
|
|
83
|
+
secretAccessKey: opts.secretAccessKey,
|
|
84
|
+
region: 'auto',
|
|
85
|
+
amzDate: amzDate(now()),
|
|
86
|
+
})
|
|
87
|
+
return fetchImpl(url, { method, headers: signed.headers, body: payload === '' ? undefined : payload })
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
async put(key: string, content: string, contentType?: string): Promise<{ key: string; size: number }> {
|
|
92
|
+
const res = await request('PUT', key, content, contentType ? { 'content-type': contentType } : {})
|
|
93
|
+
if (!res.ok) throw new Error('R2 PUT failed: HTTP ' + res.status)
|
|
94
|
+
return { key, size: Buffer.byteLength(content) }
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
async get(key: string): Promise<{ found: boolean; content: string; contentType: string }> {
|
|
98
|
+
const res = await request('GET', key, '')
|
|
99
|
+
if (res.status === 404) return { found: false, content: '', contentType: '' }
|
|
100
|
+
if (!res.ok) throw new Error('R2 GET failed: HTTP ' + res.status)
|
|
101
|
+
return { found: true, content: await res.text(), contentType: res.headers.get('content-type') ?? '' }
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
async list(prefix?: string, maxKeys?: number): Promise<ListEntry[]> {
|
|
105
|
+
const q = '?list-type=2' + (prefix ? '&prefix=' + encodeURIComponent(prefix) : '') + (maxKeys ? '&max-keys=' + maxKeys : '')
|
|
106
|
+
const url = base + '/' + q
|
|
107
|
+
const parsed = new URL(url)
|
|
108
|
+
const signed = signRequest({
|
|
109
|
+
method: 'GET',
|
|
110
|
+
url,
|
|
111
|
+
headers: { host: parsed.host },
|
|
112
|
+
payload: '',
|
|
113
|
+
accessKeyId: opts.accessKeyId,
|
|
114
|
+
secretAccessKey: opts.secretAccessKey,
|
|
115
|
+
region: 'auto',
|
|
116
|
+
amzDate: amzDate(now()),
|
|
117
|
+
})
|
|
118
|
+
const res = await fetchImpl(url, { method: 'GET', headers: signed.headers })
|
|
119
|
+
if (!res.ok) throw new Error('R2 list failed: HTTP ' + res.status)
|
|
120
|
+
return parseListXml(await res.text())
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
share(key: string, expiresSeconds: number): string {
|
|
124
|
+
return presignGet({
|
|
125
|
+
url: base + '/' + key,
|
|
126
|
+
accessKeyId: opts.accessKeyId,
|
|
127
|
+
secretAccessKey: opts.secretAccessKey,
|
|
128
|
+
region: 'auto',
|
|
129
|
+
amzDate: amzDate(now()),
|
|
130
|
+
expiresSeconds,
|
|
131
|
+
})
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
async delete(key: string): Promise<{ key: string; existed: boolean }> {
|
|
135
|
+
const res = await request('DELETE', key, '')
|
|
136
|
+
if (res.status === 404) return { key, existed: false }
|
|
137
|
+
if (!res.ok) throw new Error('R2 DELETE failed: HTTP ' + res.status)
|
|
138
|
+
return { key, existed: true }
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
export function apply(ctx: Context, config: FilesConfig = {}): void {
|
|
145
|
+
const ns = settingsNamespace('righthand')
|
|
146
|
+
|
|
147
|
+
/** Resolve accountId + credentials + bucket at call time. */
|
|
148
|
+
async function client(): Promise<ReturnType<typeof createR2Client>> {
|
|
149
|
+
const settings = ctx.settings.get(ns) as { accountId?: string; defaultR2Bucket?: string } | undefined
|
|
150
|
+
const accountId = settings?.accountId ?? ''
|
|
151
|
+
if (accountId === '') throw new Error('righthand accountId is not set (rh_settings_set { accountId })')
|
|
152
|
+
const bucket = config.bucket ?? settings?.defaultR2Bucket ?? ''
|
|
153
|
+
if (bucket === '') throw new Error('R2 bucket is not set (plugin config bucket or settings defaultR2Bucket)')
|
|
154
|
+
const access = await ctx.credentials.resolve(credentialRef(R2_ACCESS_KEY_REF))
|
|
155
|
+
const secret = await ctx.credentials.resolve(credentialRef(R2_SECRET_KEY_REF))
|
|
156
|
+
if (access === undefined || secret === undefined) {
|
|
157
|
+
throw new Error('R2 credentials are not set (rh_credential_set R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY)')
|
|
158
|
+
}
|
|
159
|
+
return createR2Client({ accountId, accessKeyId: access.value, secretAccessKey: secret.value, bucket })
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
ctx.tools.register(defineTool({
|
|
163
|
+
name: 'rh_files_put',
|
|
164
|
+
description: 'Store a file in the R2 bucket and return its key. Content is text (reports, CSV, transcripts, code); content type defaults to text/plain.',
|
|
165
|
+
parameters: {
|
|
166
|
+
key: { type: 'string', required: true, description: 'Object key, e.g. reports/2026-08-30.csv.' },
|
|
167
|
+
content: { type: 'string', required: true, description: 'The full text content to store.' },
|
|
168
|
+
contentType: { type: 'string', description: 'Content type, e.g. text/csv. Defaults to text/plain.' },
|
|
169
|
+
},
|
|
170
|
+
output: {
|
|
171
|
+
schema: {
|
|
172
|
+
type: 'object',
|
|
173
|
+
additionalProperties: false,
|
|
174
|
+
properties: {
|
|
175
|
+
key: { type: 'string', required: true },
|
|
176
|
+
size: { type: 'integer', required: true },
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
render: (_args, value) => [{ type: 'text', text: 'stored ' + value.key + ' (' + value.size + ' bytes)' }],
|
|
180
|
+
},
|
|
181
|
+
async execute(args) {
|
|
182
|
+
const c = await client()
|
|
183
|
+
return c.put(args.key, args.content, args.contentType)
|
|
184
|
+
},
|
|
185
|
+
}))
|
|
186
|
+
|
|
187
|
+
ctx.tools.register(defineTool({
|
|
188
|
+
name: 'rh_files_get',
|
|
189
|
+
description: 'Read a stored file back by its key. found: false means the key is absent, not an error.',
|
|
190
|
+
parameters: {
|
|
191
|
+
key: { type: 'string', required: true, description: 'The object key to read.' },
|
|
192
|
+
},
|
|
193
|
+
output: {
|
|
194
|
+
schema: {
|
|
195
|
+
type: 'object',
|
|
196
|
+
additionalProperties: false,
|
|
197
|
+
properties: {
|
|
198
|
+
found: { type: 'boolean', required: true },
|
|
199
|
+
key: { type: 'string', required: true },
|
|
200
|
+
content: { type: 'string' },
|
|
201
|
+
contentType: { type: 'string' },
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
render: (_args, value) => [{ type: 'text', text: value.found && typeof value.content === 'string' ? value.content : '(absent)' }],
|
|
205
|
+
},
|
|
206
|
+
async execute(args) {
|
|
207
|
+
const c = await client()
|
|
208
|
+
const out = await c.get(args.key)
|
|
209
|
+
return {
|
|
210
|
+
key: args.key,
|
|
211
|
+
found: out.found,
|
|
212
|
+
...out.found ? { content: out.content, contentType: out.contentType } : {},
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
}))
|
|
216
|
+
|
|
217
|
+
ctx.tools.register(defineTool({
|
|
218
|
+
name: 'rh_files_list',
|
|
219
|
+
description: 'List objects in the bucket, optionally by key prefix. Newest first is not guaranteed by S3; entries carry lastModified.',
|
|
220
|
+
parameters: {
|
|
221
|
+
prefix: { type: 'string', description: 'Only keys starting with this, e.g. reports/.' },
|
|
222
|
+
maxKeys: { type: 'integer', description: 'Max entries (default 100).' },
|
|
223
|
+
},
|
|
224
|
+
output: {
|
|
225
|
+
schema: {
|
|
226
|
+
type: 'object',
|
|
227
|
+
additionalProperties: false,
|
|
228
|
+
properties: {
|
|
229
|
+
entries: { type: 'array', required: true, items: { type: 'json' } },
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
render: (_args, value) => [{ type: 'text', text: value.entries.length + ' objects' }],
|
|
233
|
+
},
|
|
234
|
+
async execute(args) {
|
|
235
|
+
const c = await client()
|
|
236
|
+
const entries = await c.list(args.prefix, args.maxKeys)
|
|
237
|
+
return { entries: entries as any[] }
|
|
238
|
+
},
|
|
239
|
+
}))
|
|
240
|
+
|
|
241
|
+
ctx.tools.register(defineTool({
|
|
242
|
+
name: 'rh_files_share',
|
|
243
|
+
description: 'Get a presigned download URL for a key, valid for the given minutes (default 60, max 10080). Anyone with the URL can read the object for that window.',
|
|
244
|
+
parameters: {
|
|
245
|
+
key: { type: 'string', required: true, description: 'The object key to share.' },
|
|
246
|
+
minutes: { type: 'integer', description: 'Validity in minutes (default 60, max 10080 = 7 days).' },
|
|
247
|
+
},
|
|
248
|
+
output: {
|
|
249
|
+
schema: {
|
|
250
|
+
type: 'object',
|
|
251
|
+
additionalProperties: false,
|
|
252
|
+
properties: {
|
|
253
|
+
key: { type: 'string', required: true },
|
|
254
|
+
url: { type: 'string', required: true },
|
|
255
|
+
expiresAt: { type: 'string', required: true },
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
render: (_args, value) => [{ type: 'text', text: value.url }],
|
|
259
|
+
},
|
|
260
|
+
async execute(args) {
|
|
261
|
+
const minutes = Math.min(Math.max(args.minutes ?? 60, 1), 10080)
|
|
262
|
+
const c = await client()
|
|
263
|
+
const url = c.share(args.key, minutes * 60)
|
|
264
|
+
return { key: args.key, url, expiresAt: new Date(Date.now() + minutes * 60000).toISOString() }
|
|
265
|
+
},
|
|
266
|
+
}))
|
|
267
|
+
|
|
268
|
+
ctx.tools.register(defineTool({
|
|
269
|
+
name: 'rh_files_delete',
|
|
270
|
+
description: 'Delete an object by key. Returns whether it existed.',
|
|
271
|
+
parameters: {
|
|
272
|
+
key: { type: 'string', required: true, description: 'The object key to delete.' },
|
|
273
|
+
},
|
|
274
|
+
output: {
|
|
275
|
+
schema: {
|
|
276
|
+
type: 'object',
|
|
277
|
+
additionalProperties: false,
|
|
278
|
+
properties: {
|
|
279
|
+
key: { type: 'string', required: true },
|
|
280
|
+
existed: { type: 'boolean', required: true },
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
render: (_args, value) => [{ type: 'text', text: 'deleted ' + value.key + ': ' + value.existed }],
|
|
284
|
+
},
|
|
285
|
+
async execute(args) {
|
|
286
|
+
const c = await client()
|
|
287
|
+
return c.delete(args.key)
|
|
288
|
+
},
|
|
289
|
+
}))
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* - task-tools: rh_task_create/list/next/update/delete over ctx.storageDomain
|
|
18
18
|
* - text-tools: rh_text_summarise/extract/classify/translate over ctx.llm
|
|
19
19
|
* - weather-tools: rh_weather_forecast/air over Open-Meteo (keyless, SSRF-guarded)
|
|
20
|
+
* - files-tools: rh_files_put/get/list/share/delete over Cloudflare R2
|
|
20
21
|
* - exec-tools: rh_run / rh_run_bg over ctx.subprocess + ctx.jobs
|
|
21
22
|
* - guard-tools: tools/pre-execute policy (config.rules)
|
|
22
23
|
* - skills: the packaged dsh-righthand skill (ctx.skills)
|
|
@@ -32,6 +33,7 @@ import * as guardTools from './guard-tools.ts'
|
|
|
32
33
|
import * as taskTools from './task-tools.ts'
|
|
33
34
|
import * as textTools from './text-tools.ts'
|
|
34
35
|
import * as weatherTools from './weather-tools.ts'
|
|
36
|
+
import * as filesTools from './files-tools.ts'
|
|
35
37
|
import * as righthandSkills from './skills.ts'
|
|
36
38
|
import type { GuardRule } from './guard-tools.ts'
|
|
37
39
|
|
|
@@ -59,16 +61,20 @@ export function apply(ctx: Context, config: RighthandConfig = {}): void {
|
|
|
59
61
|
ctx.plugin(taskTools)
|
|
60
62
|
ctx.plugin(textTools)
|
|
61
63
|
ctx.plugin(weatherTools)
|
|
64
|
+
ctx.plugin(filesTools)
|
|
62
65
|
ctx.plugin(guardTools, { rules: config.rules ?? [] })
|
|
63
66
|
ctx.plugin(righthandSkills)
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
// Individual modules stay importable for selective mounting.
|
|
67
|
-
export { storeTools, secretsTools, execTools, taskTools, textTools, weatherTools, guardTools, righthandSkills }
|
|
70
|
+
export { storeTools, secretsTools, execTools, taskTools, textTools, weatherTools, filesTools, guardTools, righthandSkills }
|
|
68
71
|
export { SKILL_NAME, SKILL_DESCRIPTION, skillDirectory, skillBody } from './skills.ts'
|
|
69
72
|
export type { GuardRule } from './guard-tools.ts'
|
|
70
73
|
export { guardFactsFor } from './guard-tools.ts'
|
|
71
74
|
export type { GuardFacts } from './guard-tools.ts'
|
|
72
75
|
export { isBlockedIP, guardedFetch, normalizeForecast, normalizeAir } from './weather-tools.ts'
|
|
76
|
+
export { createR2Client, R2_ACCESS_KEY_REF, R2_SECRET_KEY_REF } from './files-tools.ts'
|
|
77
|
+
export { signRequest, presignGet } from './sigv4.ts'
|
|
78
|
+
export type { R2Options, ListEntry } from './files-tools.ts'
|
|
73
79
|
export type { WeatherConfig, ForecastOut, AirOut } from './weather-tools.ts'
|
|
74
80
|
|
package/src/secrets-tools.ts
CHANGED
|
@@ -27,6 +27,7 @@ export const righthandSettingsSchema = z.object({
|
|
|
27
27
|
accountId: z.string().default(''),
|
|
28
28
|
defaultScriptPrefix: z.string().default('rh-'),
|
|
29
29
|
defaultZone: z.string().default(''),
|
|
30
|
+
defaultR2Bucket: z.string().default(''),
|
|
30
31
|
})
|
|
31
32
|
|
|
32
33
|
/** Register the settings namespace + the secret/credential tools. */
|
|
@@ -119,7 +120,7 @@ export function apply(ctx: Context): void {
|
|
|
119
120
|
},
|
|
120
121
|
async execute() {
|
|
121
122
|
const v = scope.get()
|
|
122
|
-
return { accountId: v.accountId, defaultScriptPrefix: v.defaultScriptPrefix, defaultZone: v.defaultZone }
|
|
123
|
+
return { accountId: v.accountId, defaultScriptPrefix: v.defaultScriptPrefix, defaultZone: v.defaultZone, defaultR2Bucket: v.defaultR2Bucket }
|
|
123
124
|
},
|
|
124
125
|
}))
|
|
125
126
|
|
package/src/sigv4.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
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
|
+
|
|
8
|
+
import { createHmac, createHash } from 'node:crypto'
|
|
9
|
+
|
|
10
|
+
function hmac(key: Buffer | string, data: string): Buffer {
|
|
11
|
+
return createHmac('sha256', key).update(data).digest()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function sha256Hex(data: string): string {
|
|
15
|
+
return createHash('sha256').update(data).digest('hex')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** URI-encode one path segment the S3 way. */
|
|
19
|
+
function uriEncode(segment: string): string {
|
|
20
|
+
return encodeURIComponent(segment).replace(/[!'()*]/g, c => '%' + c.charCodeAt(0).toString(16).toUpperCase())
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Canonical URI: slashes preserved, each segment encoded. */
|
|
24
|
+
function canonicalPath(pathname: string): string {
|
|
25
|
+
if (pathname === '' || pathname === '/') return '/'
|
|
26
|
+
return pathname.split('/').map(uriEncode).join('/')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SignRequestInput {
|
|
30
|
+
method: string
|
|
31
|
+
/** Full URL, e.g. https://<account>.r2.cloudflarestorage.com/<bucket>/<key> */
|
|
32
|
+
url: string
|
|
33
|
+
/** Headers to sign (lowercase names), e.g. host, content-type. */
|
|
34
|
+
headers: Record<string, string>
|
|
35
|
+
/** Exact payload bytes; '' for GET/DELETE without a body. */
|
|
36
|
+
payload: string
|
|
37
|
+
accessKeyId: string
|
|
38
|
+
secretAccessKey: string
|
|
39
|
+
/** R2 uses region 'auto'. */
|
|
40
|
+
region: string
|
|
41
|
+
/** yyyyMMdd'T'HHmmss'Z' — injectable for tests. */
|
|
42
|
+
amzDate: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface SignedRequest {
|
|
46
|
+
headers: Record<string, string>
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Sign one S3 request. Returns the headers to send, including host,
|
|
51
|
+
* x-amz-content-sha256, x-amz-date and Authorization.
|
|
52
|
+
*/
|
|
53
|
+
export function signRequest(input: SignRequestInput): SignedRequest {
|
|
54
|
+
const parsed = new URL(input.url)
|
|
55
|
+
const date = input.amzDate.slice(0, 8)
|
|
56
|
+
const scope = date + '/' + input.region + '/s3/aws4_request'
|
|
57
|
+
const payloadHash = sha256Hex(input.payload)
|
|
58
|
+
|
|
59
|
+
const headers: Record<string, string> = {
|
|
60
|
+
...input.headers,
|
|
61
|
+
'x-amz-content-sha256': payloadHash,
|
|
62
|
+
'x-amz-date': input.amzDate,
|
|
63
|
+
}
|
|
64
|
+
const names = Object.keys(headers).sort()
|
|
65
|
+
const canonicalHeaders = names.map(n => n + ':' + headers[n].trim() + String.fromCharCode(10)).join('')
|
|
66
|
+
const signedHeaders = names.join(';')
|
|
67
|
+
const canonicalRequest = [
|
|
68
|
+
input.method,
|
|
69
|
+
canonicalPath(parsed.pathname),
|
|
70
|
+
parsed.searchParams.toString(),
|
|
71
|
+
canonicalHeaders,
|
|
72
|
+
signedHeaders,
|
|
73
|
+
payloadHash,
|
|
74
|
+
].join(String.fromCharCode(10))
|
|
75
|
+
const stringToSign = ['AWS4-HMAC-SHA256', input.amzDate, scope, sha256Hex(canonicalRequest)].join(String.fromCharCode(10))
|
|
76
|
+
const kDate = hmac('AWS4' + input.secretAccessKey, date)
|
|
77
|
+
const kRegion = hmac(kDate, input.region)
|
|
78
|
+
const kService = hmac(kRegion, 's3')
|
|
79
|
+
const kSigning = hmac(kService, 'aws4_request')
|
|
80
|
+
const signature = hmac(kSigning, stringToSign).toString('hex')
|
|
81
|
+
const authorization = 'AWS4-HMAC-SHA256 Credential=' + input.accessKeyId + '/' + scope
|
|
82
|
+
+ ', SignedHeaders=' + signedHeaders
|
|
83
|
+
+ ', Signature=' + signature
|
|
84
|
+
return { headers: { ...headers, authorization } }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Presign a GET for query-string auth: a shareable URL valid for
|
|
89
|
+
* expiresSeconds (S3 max 7 days).
|
|
90
|
+
*/
|
|
91
|
+
export function presignGet(input: Omit<SignRequestInput, 'method' | 'headers' | 'payload'> & { expiresSeconds: number }): string {
|
|
92
|
+
const parsed = new URL(input.url)
|
|
93
|
+
const date = input.amzDate.slice(0, 8)
|
|
94
|
+
const scope = date + '/' + input.region + '/s3/aws4_request'
|
|
95
|
+
const credential = input.accessKeyId + '/' + scope
|
|
96
|
+
const query: Record<string, string> = {
|
|
97
|
+
'X-Amz-Algorithm': 'AWS4-HMAC-SHA256',
|
|
98
|
+
'X-Amz-Credential': credential,
|
|
99
|
+
'X-Amz-Date': input.amzDate,
|
|
100
|
+
'X-Amz-Expires': String(input.expiresSeconds),
|
|
101
|
+
'X-Amz-SignedHeaders': 'host',
|
|
102
|
+
}
|
|
103
|
+
const canonicalQuery = Object.keys(query).sort().map(k => uriEncode(k) + '=' + uriEncode(query[k])).join('&')
|
|
104
|
+
const canonicalRequest = [
|
|
105
|
+
'GET',
|
|
106
|
+
canonicalPath(parsed.pathname),
|
|
107
|
+
canonicalQuery,
|
|
108
|
+
'host:' + parsed.host + String.fromCharCode(10),
|
|
109
|
+
'host',
|
|
110
|
+
'UNSIGNED-PAYLOAD',
|
|
111
|
+
].join(String.fromCharCode(10))
|
|
112
|
+
const stringToSign = ['AWS4-HMAC-SHA256', input.amzDate, scope, sha256Hex(canonicalRequest)].join(String.fromCharCode(10))
|
|
113
|
+
const kDate = hmac('AWS4' + input.secretAccessKey, date)
|
|
114
|
+
const kRegion = hmac(kDate, input.region)
|
|
115
|
+
const kService = hmac(kRegion, 's3')
|
|
116
|
+
const kSigning = hmac(kService, 'aws4_request')
|
|
117
|
+
const signature = hmac(kSigning, stringToSign).toString('hex')
|
|
118
|
+
parsed.search = canonicalQuery + '&X-Amz-Signature=' + signature
|
|
119
|
+
return parsed.toString()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|