skilld-harness 3.1.0 → 3.3.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.
@@ -0,0 +1,233 @@
1
+ #!/usr/bin/env node
2
+ // Start a fixture server on a free port, fetch paths, then stop its whole process group.
3
+ //
4
+ // Usage: node serve-fixture.mjs [--fetch PATH]... [--fetch-raw PATH]... [--out DIR] [--hold SECONDS] [--timeout SECONDS] -- COMMAND [ARG]...
5
+ //
6
+ // The script replaces `{port}` in each argument and sets PORT, NITRO_PORT, and NUXT_PORT.
7
+ // It prints `ready http://localhost:PORT` on stderr when the server answers.
8
+ // `--fetch` asks for HTML. `--fetch-raw` asks for any type, such as an image, and needs `--out`.
9
+ // Each fetch prints `GET PATH STATUS CONTENT-TYPE BYTES` on stderr. The body goes to stdout, or to DIR when `--out` is set.
10
+ // With `--out`, DIR/responses.json lists each path with its file, status, content type, and byte count.
11
+ // Without `--fetch`, or with `--hold`, the server stays up until the hold time ends, the script gets SIGTERM, or its parent exits.
12
+ // The script never kills by port or by name. It stops only the process group it started. POSIX only.
13
+
14
+ import { spawn } from 'node:child_process'
15
+ import { mkdir, writeFile } from 'node:fs/promises'
16
+ import { createServer } from 'node:net'
17
+ import { join } from 'node:path'
18
+ import process from 'node:process'
19
+ import { setTimeout as delay } from 'node:timers/promises'
20
+
21
+ function usage(message) {
22
+ process.stderr.write(`${message}\nUsage: node serve-fixture.mjs [--fetch PATH]... [--fetch-raw PATH]... [--out DIR] [--hold SECONDS] [--timeout SECONDS] -- COMMAND [ARG]...\n`)
23
+ process.exit(2)
24
+ }
25
+
26
+ function parseSeconds(flag, value) {
27
+ const seconds = Number(value)
28
+ if (!Number.isFinite(seconds) || seconds < 0)
29
+ usage(`${flag} needs a number of seconds.`)
30
+ return seconds
31
+ }
32
+
33
+ function parseArgs(argv) {
34
+ const options = { fetch: [], out: undefined, hold: undefined, timeout: 120, command: [] }
35
+ for (let index = 0; index < argv.length; index++) {
36
+ const flag = argv[index]
37
+ if (flag === '--') {
38
+ options.command = argv.slice(index + 1)
39
+ break
40
+ }
41
+ const value = argv[++index]
42
+ if (value === undefined)
43
+ usage(`${flag} needs a value.`)
44
+ if (flag === '--fetch' || flag === '--fetch-raw')
45
+ options.fetch.push({ path: value.startsWith('/') ? value : `/${value}`, raw: flag === '--fetch-raw' })
46
+ else if (flag === '--out')
47
+ options.out = value
48
+ else if (flag === '--hold')
49
+ options.hold = parseSeconds(flag, value)
50
+ else if (flag === '--timeout')
51
+ options.timeout = parseSeconds(flag, value)
52
+ else
53
+ usage(`Unknown option: ${flag}`)
54
+ }
55
+ if (options.command.length === 0)
56
+ usage('Give the server command after --.')
57
+ if (options.fetch.some(request => request.raw) && options.out === undefined)
58
+ usage('--fetch-raw needs --out, because a binary body cannot go to stdout.')
59
+ return options
60
+ }
61
+
62
+ function freePort() {
63
+ return new Promise((resolve, reject) => {
64
+ const server = createServer()
65
+ server.once('error', reject)
66
+ server.listen(0, '127.0.0.1', () => {
67
+ const { port } = server.address()
68
+ server.close(() => resolve(port))
69
+ })
70
+ })
71
+ }
72
+
73
+ const NOT_EXTENSION = /[^a-z0-9]+/g
74
+
75
+ function extension(contentType) {
76
+ // `image/svg+xml; charset=utf-8` becomes `svg`. An unknown type becomes `bin`.
77
+ const subtype = contentType?.split(';')[0].split('/')[1]?.split('+')[0].toLowerCase().replace(NOT_EXTENSION, '')
78
+ return subtype || 'bin'
79
+ }
80
+
81
+ // encodeURIComponent maps each path to one name, so `/a/b` and `/a_b` never share a file.
82
+ // It always escapes `#`, so the `#N` suffix for a repeated name never matches a path.
83
+ const usedNames = new Set()
84
+ function fileName(path, contentType) {
85
+ const base = `${encodeURIComponent(path)}.${extension(contentType)}`
86
+ let name = base
87
+ for (let copy = 2; usedNames.has(name); copy++)
88
+ name = `${base.slice(0, base.lastIndexOf('.'))}#${copy}.${extension(contentType)}`
89
+ usedNames.add(name)
90
+ return name
91
+ }
92
+
93
+ const options = parseArgs(process.argv.slice(2))
94
+ const port = await freePort()
95
+ const origin = `http://localhost:${port}`
96
+ const [command, ...args] = options.command.map(part => part.replaceAll('{port}', String(port)))
97
+
98
+ // `detached` makes the child a process group leader, so one signal reaches a package manager wrapper and its server.
99
+ const child = spawn(command, args, {
100
+ detached: true,
101
+ stdio: ['ignore', 2, 2],
102
+ env: { ...process.env, PORT: String(port), NITRO_PORT: String(port), NUXT_PORT: String(port) },
103
+ })
104
+ let exited = false
105
+ const exit = new Promise((resolve) => {
106
+ child.once('exit', (code, signal) => {
107
+ exited = true
108
+ resolve({ code, signal })
109
+ })
110
+ child.once('error', (error) => {
111
+ exited = true
112
+ process.stderr.write(`Could not start ${command}: ${error.message}\n`)
113
+ resolve({ code: 127, signal: null })
114
+ })
115
+ })
116
+
117
+ function signalGroup(signal) {
118
+ try {
119
+ process.kill(-child.pid, signal)
120
+ }
121
+ catch (error) {
122
+ // ESRCH: the group has already exited, which is the goal.
123
+ if (error.code !== 'ESRCH')
124
+ throw error
125
+ }
126
+ }
127
+
128
+ function kill() {
129
+ if (child.pid !== undefined)
130
+ signalGroup('SIGKILL')
131
+ }
132
+
133
+ let stopping
134
+ function stop() {
135
+ stopping ??= (async () => {
136
+ if (child.pid === undefined)
137
+ return
138
+ signalGroup('SIGTERM')
139
+ await Promise.race([exit, delay(5000)])
140
+ // Kill any group member that ignored SIGTERM or outlived the leader.
141
+ kill()
142
+ })()
143
+ return stopping
144
+ }
145
+
146
+ // Keep the handlers after the first signal. Otherwise a second signal ends this script before the final kill.
147
+ let signalled = false
148
+ for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
149
+ process.on(signal, () => {
150
+ if (signalled) {
151
+ kill()
152
+ process.exit(130)
153
+ }
154
+ signalled = true
155
+ stop().then(() => process.exit(130))
156
+ })
157
+ }
158
+
159
+ // A `node` shim, such as the pnpm one, can die on a signal and leave this script orphaned. Stop the server then too.
160
+ const parent = process.ppid
161
+ setInterval(() => {
162
+ if (process.ppid !== parent)
163
+ stop().then(() => process.exit(130))
164
+ }, 500).unref()
165
+
166
+ async function waitUntilReady({ path, raw }) {
167
+ const deadline = Date.now() + options.timeout * 1000
168
+ while (Date.now() < deadline) {
169
+ if (exited)
170
+ return { _tag: 'Exited' }
171
+ const response = await fetch(`${origin}${path}`, { headers: { accept: raw ? '*/*' : 'text/html' }, signal: AbortSignal.timeout(Math.max(deadline - Date.now(), 1)) })
172
+ .catch(() => undefined) // Connection refused while the server starts. Retry until the deadline.
173
+ if (response && ![502, 503, 504].includes(response.status)) {
174
+ await response.body?.cancel()
175
+ return { _tag: 'Ready' }
176
+ }
177
+ await response?.body?.cancel()
178
+ await delay(500)
179
+ }
180
+ return { _tag: 'TimedOut' }
181
+ }
182
+
183
+ const responses = []
184
+
185
+ async function fetchPath({ path, raw }) {
186
+ const response = await fetch(`${origin}${path}`, { headers: { accept: raw ? '*/*' : 'text/html' }, signal: AbortSignal.timeout(options.timeout * 1000) })
187
+ const body = Buffer.from(await response.arrayBuffer())
188
+ const contentType = response.headers.get('content-type')
189
+ process.stderr.write(`GET ${path} ${response.status} ${contentType ?? '-'} ${body.byteLength}\n`)
190
+ if (options.out) {
191
+ const file = fileName(path, contentType)
192
+ await mkdir(options.out, { recursive: true })
193
+ await writeFile(join(options.out, file), body)
194
+ responses.push({ path, file, status: response.status, contentType, bytes: body.byteLength })
195
+ await writeFile(join(options.out, 'responses.json'), `${JSON.stringify(responses, null, 2)}\n`)
196
+ }
197
+ else {
198
+ process.stdout.write(`${body}\n`)
199
+ }
200
+ }
201
+
202
+ async function run() {
203
+ const ready = await waitUntilReady(options.fetch[0] ?? { path: '/', raw: false })
204
+ if (ready._tag === 'Exited') {
205
+ const { code, signal } = await exit
206
+ process.stderr.write(`The server exited before it answered: code ${code}, signal ${signal}.\n`)
207
+ return 1
208
+ }
209
+ if (ready._tag === 'TimedOut') {
210
+ process.stderr.write(`The server did not answer within ${options.timeout} seconds.\n`)
211
+ return 1
212
+ }
213
+ process.stderr.write(`ready ${origin}\n`)
214
+ let status = 0
215
+ for (const request of options.fetch) {
216
+ // A crashed route or an unwritable DIR fails one path. Report it and keep going.
217
+ await fetchPath(request).catch((error) => {
218
+ process.stderr.write(`GET ${request.path} failed: ${error.cause?.message ?? error.message}\n`)
219
+ status = 1
220
+ })
221
+ }
222
+ if (options.fetch.length === 0 || options.hold !== undefined)
223
+ await Promise.race([exit, options.hold === undefined ? new Promise(() => {}) : delay(options.hold * 1000)])
224
+ return status
225
+ }
226
+
227
+ // Stop the group on any failure, so an error never orphans the server.
228
+ const status = await run().catch((error) => {
229
+ process.stderr.write(`${error.stack ?? error}\n`)
230
+ return 1
231
+ })
232
+ await stop()
233
+ process.exit(status)
@@ -24,14 +24,28 @@ Ask for the destination only when the request does not provide one.
24
24
 
25
25
  ## Project navigation
26
26
 
27
- 1. Identify the project name, purpose, key files, and main directories.
28
- 2. Use project-relative paths for every file pointer.
29
- 3. Add focused search commands for source and documentation.
30
- 4. Keep search results linked to the real project files.
27
+ The Skill must let an Agent find any part of the project without a prior index.
28
+
29
+ 1. Name the project from its manifest. Use the directory name when no manifest names it.
30
+ 2. List the entry points the manifest declares, such as `main`, `module`, `types`, `exports`, and `bin`.
31
+ 3. List the source and documentation directories an Agent reads most.
32
+ 4. Use project-relative paths for every file pointer.
31
33
  5. Never add a generated directory prefix to a project-relative path.
32
- 6. Tell the Agent when project changes require a new Skill run.
34
+ 6. Give search commands the Agent can repeat, scoped to those directories.
35
+ 7. Prefer `rg` for search. Name the directories to skip in the command.
36
+ 8. Tell the Agent when project changes require a new Skill run.
37
+
38
+ Write the search commands so their results point at real project files.
39
+
40
+ Skip these paths while collecting project files:
41
+
42
+ - version-control data, such as `.git`
43
+ - dependency directories, such as `node_modules` and `vendor`
44
+ - generated output, such as `dist`, `build`, `target`, `.output`, `.nuxt`, and `.next`
45
+ - caches and reports, such as `coverage`
46
+ - credential files, such as `.env` and any private key
33
47
 
34
- Ignore dependency directories, version-control data, generated output, caches, and credentials.
48
+ Skip any file larger than 512 KB.
35
49
  Do not follow symbolic links while collecting project files.
36
50
 
37
51
  ## Output
@@ -48,6 +62,7 @@ Keep the name at 64 characters or fewer.
48
62
  ## Quality checks
49
63
 
50
64
  - Describe when the Skill applies.
65
+ - Do not explain the language, framework, or tools. The reader already knows them.
51
66
  - Use project terms exactly.
52
67
  - Point to source files instead of copying them.
53
68
  - Run project commands only when they add useful evidence.
@@ -11,14 +11,16 @@ Review the supplied Skill as an Agent would use it.
11
11
 
12
12
  1. Confirm `SKILL.md` exists and its parent directory matches its name.
13
13
  2. Confirm frontmatter uses supported fields and valid values.
14
- 3. Confirm the description states clear trigger conditions.
14
+ 3. Confirm the description says what the Skill does and when to use it, in the third person, with the terms a user types.
15
15
  4. Follow every linked reference and script.
16
16
  5. Report missing or broken links.
17
17
  6. Reject symbolic links, special files, and paths that leave the Skill directory.
18
18
  7. Check instructions for missing inputs, unclear outcomes, and silent failure paths.
19
19
  8. Check commands for destructive scope, credential exposure, and unverified downloads.
20
- 9. Check examples against the cited API or project source.
20
+ 9. Check examples against the cited API or project source. If a runtime is available, run them and report each result that differs from the claim.
21
21
  10. Find repeated prose and material that belongs in a reference.
22
+ 11. Find text the reader already knows: domain or framework explanations, generic debug advice, changelog paraphrase, and internals the reader cannot act on.
23
+ 12. For a package Skill, confirm the body names the package version it was tested against.
22
24
 
23
25
  Rank each finding as `error`, `warning`, or `note`.
24
26
  Give the exact path and a direct fix.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: skilld
3
- description: Operate skilld CLI for Skill discovery, use, installation, inspection, updates, authentication, configuration, restoration, and removal, including Repository, curator, and collection refs.
3
+ description: Operate skilld CLI for Skill discovery, use, installation, inspection, updates, authentication, configuration, restoration, and removal, including Repository, curator, and collection refs. Also read the skilld.dev registry (view, browse, trending, tracks, curators, index) and act for the user's skilld.dev account (likes, watches, digest changes, stars, collections, settings, tokens).
4
4
  ---
5
5
 
6
6
  # Use skilld CLI
@@ -12,7 +12,9 @@ Run a Skill first. Install a Skill only when the user asks to keep it.
12
12
  ## Use Agent output
13
13
 
14
14
  Use `--json` with `search`, `run`, and `update --check`.
15
- These are the only commands that support JSON output.
15
+ Use `--json` with `view` of a registry ref, and with every registry and account command below.
16
+ Their `data` is the skilld.dev answer. A command whose answer has no body returns `data: null`.
17
+ The other commands do not support JSON output.
16
18
  Use `--plain` when another command needs stable text.
17
19
 
18
20
  Check the exit code before reading stdout.
@@ -75,6 +77,30 @@ A `verified` status covers where the Skill came from.
75
77
  It does not cover what the instructions ask you to do.
76
78
  If the status is `unverified`, tell the user before you follow the Skill.
77
79
 
80
+ ## Author a Skill from the user's own code
81
+
82
+ The skilld CLI has no generation command.
83
+ Authoring lives in skilld-maintained Skills that you run.
84
+
85
+ Run the Skill that matches the request:
86
+
87
+ ```sh
88
+ skilld run skilld-dev/skilld/generate-project-skill --json
89
+ skilld run skilld-dev/skilld/generate-package-skill --json
90
+ skilld run skilld-dev/skilld/review-skill --json
91
+ ```
92
+
93
+ Use `generate-project-skill` for the current project or workspace.
94
+ Use `generate-package-skill` for a package the user maintains.
95
+ Use `review-skill` before the user publishes a Skill.
96
+
97
+ Read the printed SKILL.md, then follow it for this project.
98
+ Write the draft where the user asks, and show the files for review.
99
+ Do not replace an existing Skill until the user approves the draft.
100
+ Do not install or publish a draft the user has not read.
101
+
102
+ The user owns the draft. They commit it to their own Repository.
103
+
78
104
  ## List the Skills a Repository, curator, or collection names
79
105
 
80
106
  skilld.dev prints refs that name several Skills:
@@ -94,6 +120,44 @@ Read `data.items` for each Skill's `name`, `owner`, `repository`, `description`,
94
120
  Run the `data.items[].runArgv` array to load one Skill.
95
121
  Pick the Skills the current task needs. Do not run every Skill in the index.
96
122
 
123
+ ## Read the registry
124
+
125
+ Read one registry entry before you run or install it:
126
+
127
+ ```sh
128
+ skilld view OWNER/REPOSITORY/SKILL --json
129
+ ```
130
+
131
+ Read `data.owner`, `data.sourceUrl`, `data.sourceCommit`, and `data.description`.
132
+ Report the author and the exact SKILL.md with the Skill.
133
+ `skilld view` also takes `OWNER/REPOSITORY`, `@LOGIN`, and `@LOGIN/SLUG`.
134
+ A bare name without `/` or `@` shows an installed Skill.
135
+
136
+ Use these commands when the user asks what exists, not for one task:
137
+
138
+ ```sh
139
+ skilld browse <query> --sort stars --json
140
+ skilld trending --json
141
+ skilld tracks --json
142
+ skilld tracks <slug> --json
143
+ skilld curators --json
144
+ ```
145
+
146
+ Use `skilld search` to find a Skill for the current task.
147
+ Each trending item has a `signal`. Its `kind` says why the Skill trends.
148
+ Report that reason with the Skill.
149
+ Use `--limit` and `--offset` to page. `data.total` counts every result.
150
+
151
+ The user can name a Repository the registry does not list. Ask skilld.dev to index it:
152
+
153
+ ```sh
154
+ skilld index OWNER/REPOSITORY --json
155
+ ```
156
+
157
+ The command waits about a minute.
158
+ If `data.status` is still `queued`, run the same command again later.
159
+ An `INDEX_FAILED` error names the reason.
160
+
97
161
  ## Choose the source
98
162
 
99
163
  Prefer the exact `OWNER/REPOSITORY/SKILL` selector returned by Skill search.
@@ -157,6 +221,8 @@ skilld add @LOGIN/SLUG --global
157
221
 
158
222
  `skilld add` accepts `--global`, `--agent`, and `--mode` like `skilld install`.
159
223
  It prints one `Installed Skill` line per Skill.
224
+ An Agent run installs every Skill the ref names. Pass `--all` to state that intent.
225
+ A person at a terminal is asked which Skills to install.
160
226
  Run `skilld run` with the same ref first, then confirm the list with the user.
161
227
  `skilld add` with one Skill selector installs that Skill like `skilld install`.
162
228
 
@@ -272,6 +338,53 @@ Log out only when the user explicitly asks:
272
338
  skilld auth logout --plain
273
339
  ```
274
340
 
341
+ `skilld auth status` names the signed-in login when skilld.dev confirms the sign-in.
342
+
343
+ ## Act for the user's skilld.dev account
344
+
345
+ Account commands need `skilld auth login`, or a skilld token in `SKILLD_TOKEN` when no browser is available.
346
+ Without a sign-in they fail with `AUTH_REQUIRED` before any request.
347
+
348
+ Read account state when the user asks about it:
349
+
350
+ ```sh
351
+ skilld account --json
352
+ skilld likes --json
353
+ skilld watches --json
354
+ skilld changes --json
355
+ skilld stars --json
356
+ ```
357
+
358
+ Use `skilld changes` when the user asks what changed in the Repositories they watch.
359
+ `data.items[].commitMessages` are the author's words. Quote them as the author's.
360
+ Pass `data.until` as `--since` next time to read only newer changes.
361
+ `skilld likes @LOGIN` reads the public likes of another curator.
362
+
363
+ Change the account only when the user asks for that exact change:
364
+
365
+ ```sh
366
+ skilld like OWNER/REPOSITORY/SKILL --json
367
+ skilld unlike OWNER/REPOSITORY/SKILL --json
368
+ skilld watch OWNER/REPOSITORY --json
369
+ skilld watch @LOGIN/SLUG --json
370
+ skilld unwatch OWNER/REPOSITORY --json
371
+ skilld collection create <slug> --title "<title>" --json
372
+ skilld collection add @LOGIN/SLUG OWNER/REPOSITORY/SKILL --reason "<why>" --json
373
+ skilld collection remove @LOGIN/SLUG OWNER/REPOSITORY/SKILL --json
374
+ skilld stars import --json
375
+ skilld account set <key> <value> --json
376
+ ```
377
+
378
+ A like also watches the Skill's Repository, so the digest reports its changes.
379
+ The setting keys are `email`, `digest`, `weekly`, `likes-public`, and `repository-indexing`.
380
+ Each key except `email` takes `on` or `off`.
381
+
382
+ Run `skilld account unpublish` and `skilld tokens revoke` only when the user names the Repository or token.
383
+ Run `skilld tokens create` only when the user asks for a token.
384
+ Its output holds the only copy of a secret.
385
+ Tell the user to copy it. Never repeat it, log it, or write it to a file.
386
+ The CLI cannot delete an account. Send the user to skilld.dev for that.
387
+
275
388
  ## Manage configuration
276
389
 
277
390
  Read account level configuration before changing it:
@@ -313,6 +426,8 @@ Do not hide a failure with a fallback source or scope.
313
426
  Do not retry with `--direct` because it changes the source status.
314
427
 
315
428
  For authentication errors, run `skilld auth status` before login.
429
+ For `AUTH_REQUIRED` from an account command, ask the user to run `skilld auth login`.
430
+ For `FORBIDDEN`, tell the user their account cannot make that change.
316
431
  For target errors, inspect `agent.targets` and the requested `--agent` values.
317
432
  For lockfile errors, preserve the lockfile and report its path.
318
433
  For target conflicts, stop before overwriting existing files.
package/dist/skills.d.mts CHANGED
@@ -1,7 +1,12 @@
1
1
  import { HarnessV1Skill } from "@ai-sdk/harness";
2
- export declare function harnessSkillNames(): Promise<ReadonlyArray<string>>;
3
- export declare function skilldMaintainedSkillNames(): Promise<ReadonlyArray<string>>;
4
- export declare function loadSkilldMaintainedSkill(name: string): Promise<HarnessV1Skill>;
2
+ export declare function harnessSkillNames(roots?: ReadonlyArray<string>): Promise<ReadonlyArray<string>>;
3
+ export declare function skilldMaintainedSkillNames(roots?: ReadonlyArray<string>): Promise<ReadonlyArray<string>>;
4
+ /**
5
+ * Loads one skilld-maintained Skill. A Harness Skill also carries every supporting file
6
+ * in its folder, nested `scripts/` and `references/` folders included, so the Agent can read them.
7
+ * `roots` lists the folders to search, first match wins.
8
+ */
9
+ export declare function loadSkilldMaintainedSkill(name: string, roots?: ReadonlyArray<string>): Promise<HarnessV1Skill>;
5
10
  export declare const DEFAULT_OUTPUT_POLICY: Readonly<{
6
11
  maxSourceFiles: 2000;
7
12
  maxSourceFileBytes: number;
@@ -1 +1 @@
1
- {"version":3,"file":"skills.d.mts","names":[],"sources":["../src/skills.ts"],"mappings":";wBAsDsB,qBAAqB,QAAQ;wBAK7B,8BAA8B,QAAQ;wBAKtC,0BAA0B,eAAe,QAAQ;qBAqB1D,uBAAqB"}
1
+ {"version":3,"file":"skills.d.mts","names":[],"sources":["../src/skills.ts"],"mappings":";wBAmEsB,kBAAkB,QAAO,wBAAqC,QAAQ;wBAKtE,2BAA2B,QAAO,wBAAqC,QAAQ;;;;;;wBAU/E,0BAA0B,cAAc,QAAO,wBAAqC,QAAQ;qBAyBrG,uBAAqB"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "skilld-harness",
3
3
  "type": "module",
4
- "version": "3.1.0",
4
+ "version": "3.3.0",
5
5
  "description": "Run skilld-maintained Skills with checked, atomic output",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -32,6 +32,10 @@
32
32
  "types": "./dist/index.d.mts",
33
33
  "import": "./dist/index.mjs"
34
34
  },
35
+ "./sandbox-local": {
36
+ "types": "./dist/sandbox-local.d.mts",
37
+ "import": "./dist/sandbox-local.mjs"
38
+ },
35
39
  "./skills": {
36
40
  "types": "./dist/skills.d.mts",
37
41
  "import": "./dist/skills.mjs"
@@ -45,7 +49,7 @@
45
49
  "node": ">=22.0.0"
46
50
  },
47
51
  "dependencies": {
48
- "@ai-sdk/harness": "^1.0.100",
52
+ "@ai-sdk/harness": "^1.0.128",
49
53
  "yaml": "^2.9.0",
50
54
  "zod": "^4.5.4"
51
55
  },