fix: remove vulnerable dependency chains

Replace build-time shell utilities with Node filesystem operations and retire the native sandbox example in favor of existing isolation options. Upgrade glob matching and worker-test dependencies and refresh the lockfiles.
This commit is contained in:
Armin Ronacher 2026-10-31 17:54:29 +01:00
parent 6de14ec098
commit b4c2e92acb
24 changed files with 260 additions and 857 deletions

492
package-lock.json generated
View file

@ -13,18 +13,15 @@
"packages/coding-agent/examples/extensions/with-deps",
"packages/coding-agent/examples/extensions/custom-provider-anthropic",
"packages/coding-agent/examples/extensions/custom-provider-gitlab-duo",
"packages/coding-agent/examples/extensions/sandbox",
"packages/coding-agent/examples/extensions/gondolin"
],
"devDependencies": {
"@anthropic-ai/sandbox-runtime": "0.0.26",
"@biomejs/biome": "2.3.5",
"@biomejs/wasm-nodejs": "2.3.5",
"@types/node": "22.19.19",
"esbuild": "0.28.2",
"husky": "9.1.7",
"pnpm": "11.15.1",
"shx": "0.3.4",
"typescript": "7.0.2",
"yaml": "2.9.0"
},
@ -32,26 +29,6 @@
"node": ">=22.19.0"
}
},
"node_modules/@anthropic-ai/sandbox-runtime": {
"version": "0.0.26",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sandbox-runtime/-/sandbox-runtime-0.0.26.tgz",
"integrity": "sha512-DYV5LSsVMnzq0lbfaYMSpxZPUMAx4+hy343dRss+pVCLIfF62qOhxpYfZ5TmOk1GTDQm5f9wPprMNSStmnsV4w==",
"license": "Apache-2.0",
"dependencies": {
"@pondwader/socks5-server": "^1.0.10",
"@types/lodash-es": "^4.17.12",
"commander": "^12.1.0",
"lodash-es": "^4.17.21",
"shell-quote": "^1.8.3",
"zod": "^3.24.1"
},
"bin": {
"srt": "dist/cli.js"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@anthropic-ai/sdk": {
"version": "0.129.0",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.129.0.tgz",
@ -620,26 +597,26 @@
}
},
"node_modules/@cloudflare/vitest-plugin": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/@cloudflare/vitest-plugin/-/vitest-plugin-1.4.4.tgz",
"integrity": "sha512-nXNIlPiLvYFx/AH4/Bb/yntVkeloYcf1+tBi3l6Gp3TDCDtLxXAugrUtO7eTOWbHP6UIx5f9npy05OrACbZ5wg==",
"version": "1.4.10",
"resolved": "https://registry.npmjs.org/@cloudflare/vitest-plugin/-/vitest-plugin-1.4.10.tgz",
"integrity": "sha512-F6klOeT/Wx0gKvZ4zpYVBixP6TjcT4OD/L/RJK3Yq3SwRmd+v96/y037xUEsvGytdUisL9X5Jw3ADuNdvwBHdg==",
"dev": true,
"license": "MIT",
"dependencies": {
"cjs-module-lexer": "1.2.3",
"esbuild": "0.28.2",
"miniflare": "5.20261012.1-beta",
"wrangler": "4.151.1",
"zod": "4.6.5"
"miniflare": "5.20261023.2-beta",
"wrangler": "4.155.0",
"zod": "4.6.6"
},
"peerDependencies": {
"vitest": "^4.1.0 || ^5.0.0"
}
},
"node_modules/@cloudflare/vitest-plugin/node_modules/zod": {
"version": "4.6.5",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz",
"integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==",
"version": "4.6.6",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.6.6.tgz",
"integrity": "sha512-4UmzQUF5fxvfcYHlbjBmejU+w+hbDnWFx4aS8kBx7nPBHcO2SqPbpAG8snbdg+Xse4pRefR98hgjFgIMCI/UhA==",
"dev": true,
"license": "MIT",
"funding": {
@ -647,9 +624,9 @@
}
},
"node_modules/@cloudflare/workerd-darwin-64": {
"version": "1.20261012.1",
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20261012.1.tgz",
"integrity": "sha512-dkTL9aY3V4D40b+jBpGPCistWUPFQ/M61wlWi6i1o8MvHFFgEw7EQkcaYEv6Yd3bkPDD/3TFT4hRsMb2xVZSwQ==",
"version": "1.20261023.1",
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20261023.1.tgz",
"integrity": "sha512-lsEx0i7nR0PNG/erUFFHmQmiidEhLfDE5uQmAL9421/y4/ycuxDDOSaxu3VGGYFFPK5sduyUeMmjIlX4CwOBFg==",
"cpu": [
"x64"
],
@ -664,9 +641,9 @@
}
},
"node_modules/@cloudflare/workerd-darwin-arm64": {
"version": "1.20261012.1",
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20261012.1.tgz",
"integrity": "sha512-2/nVGcKXjNNI+DhxKSepTKvFng9H7aN2WXv6Zu67iqgI6JrxypNt5kTPv4ZHosxrcfyKMci/2RfN7GIrN0BitQ==",
"version": "1.20261023.1",
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20261023.1.tgz",
"integrity": "sha512-5e79fHgabdDD8EgmjOhJ1S+xMsf6DbC7fL7IgTBizMnzOp0P2isDn7pYmUxI9AJZE9FVQ4RUilH/I36YNg+GQQ==",
"cpu": [
"arm64"
],
@ -681,9 +658,9 @@
}
},
"node_modules/@cloudflare/workerd-linux-64": {
"version": "1.20261012.1",
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20261012.1.tgz",
"integrity": "sha512-uMe7ONHnLxStD1Ye0YFHBO36uj4cStv5P53rW+5GVWXFT8BRjqVixlH0IqCAHe1Pmcwyd1+7fVoGde89MZSCJg==",
"version": "1.20261023.1",
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20261023.1.tgz",
"integrity": "sha512-VHQ6jKFSihjR2+x1buby8XTdBLLJztVnYiB7OLOTYO8aDXB+c1Rd2410PdgFuFYc4AgFEk6PCJLyCLL5L1KBnw==",
"cpu": [
"x64"
],
@ -698,9 +675,9 @@
}
},
"node_modules/@cloudflare/workerd-linux-arm64": {
"version": "1.20261012.1",
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20261012.1.tgz",
"integrity": "sha512-I0VMdcXk7ygG5vYDce0ePTBFdnBXC0i9WiBY5mWiv0iTinbfea6/rHqSQ7B1UMpjgrYVs9E0m4bZ9ufsk5GCFA==",
"version": "1.20261023.1",
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20261023.1.tgz",
"integrity": "sha512-ilpRHzfDN8YFRM5CLRKuFw40x/LJBS89yVdhdQmxehlVDuBqjKpJB++zPFbTgKRRFaNupgnXc8RHktXAIhgCmQ==",
"cpu": [
"arm64"
],
@ -715,9 +692,9 @@
}
},
"node_modules/@cloudflare/workerd-windows-64": {
"version": "1.20261012.1",
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20261012.1.tgz",
"integrity": "sha512-c5hQvNx574UEoknaCFvMa/NdLrrYiALNbe6sx1okW8uOiRYWxrFuA8WpXFnBJ5XJypm0TEAHYaUTGv3rRLpY7Q==",
"version": "1.20261023.1",
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20261023.1.tgz",
"integrity": "sha512-279WLVjNsMycAwr0EZNiNEf0l2SY6NUtsN80NwZzbTHzakmiN+WSx4w38muxPm+PAU0oSPPAPXKVD+H6Hya8xw==",
"cpu": [
"x64"
],
@ -1454,6 +1431,9 @@
"arm"
],
"dev": true,
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -1471,6 +1451,9 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -1488,6 +1471,9 @@
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -1505,6 +1491,9 @@
"riscv64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -1522,6 +1511,9 @@
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -1539,6 +1531,9 @@
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -1556,6 +1551,9 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -1573,6 +1571,9 @@
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -1590,6 +1591,9 @@
"arm"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@ -1613,6 +1617,9 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@ -1636,6 +1643,9 @@
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@ -1659,6 +1669,9 @@
"riscv64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@ -1682,6 +1695,9 @@
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@ -1705,6 +1721,9 @@
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@ -1728,6 +1747,9 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@ -1751,6 +1773,9 @@
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@ -1863,6 +1888,12 @@
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@isaacs/nonrecursive-glob-match": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@isaacs/nonrecursive-glob-match/-/nonrecursive-glob-match-1.0.3.tgz",
"integrity": "sha512-SI7kyKHq4XXDRozw+r9GlRZkia1ZkFrpLW98r2IC2Bpr8YkFGE3fvjzmxCG0x78A++69HmDNrDvSMYBZmHGpKA==",
"license": "BlueOak-1.0.0"
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
@ -1901,12 +1932,6 @@
"url": "https://github.com/sponsors/oxc-project"
}
},
"node_modules/@pondwader/socks5-server": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/@pondwader/socks5-server/-/socks5-server-1.0.10.tgz",
"integrity": "sha512-bQY06wzzR8D2+vVCUoBsr5QS2U6UgPUQRmErNwtsuI6vLcyRKkafjkr3KxbtGFf9aBBIV2mcvlsKD1UYaIV+sg==",
"license": "MIT"
},
"node_modules/@poppinss/colors": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz",
@ -2441,21 +2466,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/lodash": {
"version": "4.17.24",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz",
"integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==",
"license": "MIT"
},
"node_modules/@types/lodash-es": {
"version": "4.17.12",
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
"license": "MIT",
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/ms": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
@ -3201,22 +3211,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/commander": {
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true,
"license": "MIT"
},
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@ -3366,16 +3360,6 @@
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-module-lexer": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz",
@ -3537,13 +3521,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true,
"license": "ISC"
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@ -3559,16 +3536,6 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gaxios": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz",
@ -3616,59 +3583,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"dev": true,
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "1.1.21",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz",
"integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/glob/node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/google-auth-library": {
"version": "10.6.2",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz",
@ -3710,19 +3624,6 @@
"node": ">=18"
}
},
"node_modules/hasown": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"dev": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/highlight.js": {
"version": "10.7.3",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
@ -3826,18 +3727,6 @@
"node": ">= 4"
}
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
"dev": true,
"license": "ISC",
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@ -3852,32 +3741,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/interpret": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
"integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/is-core-module": {
"version": "2.17.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.17.0.tgz",
"integrity": "sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==",
"dev": true,
"license": "MIT",
"dependencies": {
"hasown": "^2.0.4"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@ -4219,12 +4082,6 @@
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lodash-es": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
"integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
"license": "MIT"
},
"node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
@ -4288,16 +4145,16 @@
}
},
"node_modules/miniflare": {
"version": "5.20261012.1-beta",
"resolved": "https://registry.npmjs.org/miniflare/-/miniflare-5.20261012.1-beta.tgz",
"integrity": "sha512-8Canhy6g9+vAhqMc0o80YphqYIwpiA3aAp8aX+uDXU7J/qbUeoZzcII9c2V1P4uT/eHD4SB93qet1o1bRY3BuQ==",
"version": "5.20261023.2-beta",
"resolved": "https://registry.npmjs.org/miniflare/-/miniflare-5.20261023.2-beta.tgz",
"integrity": "sha512-sXpZloQnIUe4raXXdwwqd2ioRHox9a/1B4nB2/p4JWKDwGuVgEK4DugCNgIeQf0iVuSgBItdYpodJe4iwY6SrQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@cspotcode/source-map-support": "0.8.1",
"sharp": "0.35.5",
"undici": "7.29.1",
"workerd": "1.20261012.1",
"undici": "7.30.1",
"workerd": "1.20261023.1",
"ws": "8.21.0",
"youch": "4.1.0-beta.10"
},
@ -4306,9 +4163,9 @@
}
},
"node_modules/miniflare/node_modules/undici": {
"version": "7.29.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.29.1.tgz",
"integrity": "sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==",
"version": "7.30.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.30.1.tgz",
"integrity": "sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg==",
"dev": true,
"license": "MIT",
"engines": {
@ -4316,12 +4173,13 @@
}
},
"node_modules/minimatch": {
"version": "10.2.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
"integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-11.0.3.tgz",
"integrity": "sha512-iny/40PEGkNJQ5/Pv6z3nm6FbRAby32GeZRjCUJk2WnNDbgPSTcdI+DDvLqsgBdD4cD5sMtMheqPPnehCCa8Nw==",
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^5.0.8"
"@isaacs/nonrecursive-glob-match": "^1.0.3",
"brace-expansion": "^5.0.12"
},
"engines": {
"node": "18 || 20 || >=22"
@ -4493,16 +4351,6 @@
"integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==",
"license": "MIT"
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@ -4512,13 +4360,6 @@
"node": ">=8"
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true,
"license": "MIT"
},
"node_modules/path-to-regexp": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
@ -4545,10 +4386,6 @@
"resolved": "packages/coding-agent/examples/extensions/gondolin",
"link": true
},
"node_modules/pi-extension-sandbox": {
"resolved": "packages/coding-agent/examples/extensions/sandbox",
"link": true
},
"node_modules/pi-extension-with-deps": {
"resolved": "packages/coding-agent/examples/extensions/with-deps",
"link": true
@ -4757,40 +4594,6 @@
"node": ">= 6"
}
},
"node_modules/rechoir": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
"integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==",
"dev": true,
"dependencies": {
"resolve": "^1.1.6"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/resolve": {
"version": "1.22.12",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
"integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"is-core-module": "^2.16.1",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/retry": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
@ -4943,53 +4746,6 @@
"node": ">=8"
}
},
"node_modules/shell-quote": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.12.0.tgz",
"integrity": "sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/shelljs": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz",
"integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"glob": "^7.0.0",
"interpret": "^1.0.0",
"rechoir": "^0.6.2"
},
"bin": {
"shjs": "bin/shjs"
},
"engines": {
"node": ">=4"
}
},
"node_modules/shx": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz",
"integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==",
"dev": true,
"license": "MIT",
"dependencies": {
"minimist": "^1.2.3",
"shelljs": "^0.8.5"
},
"bin": {
"shx": "lib/cli.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/siginfo": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
@ -5134,19 +4890,6 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/tar-fs": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
@ -5563,9 +5306,9 @@
}
},
"node_modules/workerd": {
"version": "1.20261012.1",
"resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20261012.1.tgz",
"integrity": "sha512-tEHoMPsMga0seVjxfsR8Pd3f8IRqYwkq++dTUKI5Gdgj6zuSDluYwisydq+dn90SsIACwBquJUQ1nBpcOqaWTA==",
"version": "1.20261023.1",
"resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20261023.1.tgz",
"integrity": "sha512-NeofJFyaYNpqKpFRJW3ApuonaerPd6/JWlfTtLDYf2A6ygqrDEFnYgEXmc0hhOxqnJsSM+kXJN6VebPojzRrsg==",
"dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
@ -5576,17 +5319,17 @@
"node": ">=16"
},
"optionalDependencies": {
"@cloudflare/workerd-darwin-64": "1.20261012.1",
"@cloudflare/workerd-darwin-arm64": "1.20261012.1",
"@cloudflare/workerd-linux-64": "1.20261012.1",
"@cloudflare/workerd-linux-arm64": "1.20261012.1",
"@cloudflare/workerd-windows-64": "1.20261012.1"
"@cloudflare/workerd-darwin-64": "1.20261023.1",
"@cloudflare/workerd-darwin-arm64": "1.20261023.1",
"@cloudflare/workerd-linux-64": "1.20261023.1",
"@cloudflare/workerd-linux-arm64": "1.20261023.1",
"@cloudflare/workerd-windows-64": "1.20261023.1"
}
},
"node_modules/wrangler": {
"version": "4.151.1",
"resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.151.1.tgz",
"integrity": "sha512-OAsa9UAHZshM6mq2PXcVoyxXgWThAanZMBc/IirtqEWd/BauRtmUKzAniOu/pU0pd3pxKuMZ2vsbF4eo6RiNDA==",
"version": "4.155.0",
"resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.155.0.tgz",
"integrity": "sha512-gBKtAg2e0abQDvXPRQu611YMTd/cfAaJTOLAs/89tTxFxEeUyC260MRJRK+m3gLmQjDYWFwJv+ErwuJ/GuALpA==",
"dev": true,
"license": "MIT OR Apache-2.0",
"dependencies": {
@ -5594,10 +5337,10 @@
"@cloudflare/unenv-preset": "2.16.2",
"blake3-wasm": "2.1.5",
"esbuild": "0.28.2",
"miniflare": "5.20261012.1-beta",
"miniflare": "5.20261023.2-beta",
"path-to-regexp": "6.3.0",
"unenv": "2.0.0-rc.24",
"workerd": "1.20261012.1"
"workerd": "1.20261023.1"
},
"bin": {
"cf-wrangler": "bin/cf-wrangler.js",
@ -5611,7 +5354,7 @@
"fsevents": "2.3.3"
},
"peerDependencies": {
"@cloudflare/workers-types": "^5.20261006.1"
"@cloudflare/workers-types": "^5.20261023.1"
},
"peerDependenciesMeta": {
"@cloudflare/workers-types": {
@ -5688,9 +5431,10 @@
}
},
"node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"version": "4.6.7",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz",
"integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==",
"devOptional": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
@ -5817,7 +5561,6 @@
},
"devDependencies": {
"@types/ws": "8.18.2",
"shx": "0.3.4",
"vitest": "4.1.12",
"ws": "8.21.0"
},
@ -5841,7 +5584,6 @@
"quickjs-wasi": "3.6.2"
},
"devDependencies": {
"shx": "0.3.4",
"vitest": "4.1.12"
},
"engines": {
@ -5868,7 +5610,7 @@
"hosted-git-info": "9.0.3",
"ignore": "7.0.8",
"jiti": "2.7.0",
"minimatch": "10.2.6",
"minimatch": "11.0.3",
"proper-lockfile": "4.1.2",
"quickjs-wasi": "3.6.2",
"semver": "7.8.5",
@ -5886,7 +5628,6 @@
"@types/proper-lockfile": "4.1.4",
"@types/semver": "7.7.1",
"marked": "18.0.11",
"shx": "0.3.4",
"vitest": "4.1.12"
},
"engines": {
@ -5911,13 +5652,6 @@
"@earendil-works/gondolin": "0.14.0"
}
},
"packages/coding-agent/examples/extensions/sandbox": {
"name": "pi-extension-sandbox",
"version": "1.4.0",
"dependencies": {
"@anthropic-ai/sandbox-runtime": "0.0.26"
}
},
"packages/coding-agent/examples/extensions/with-deps": {
"name": "pi-extension-with-deps",
"version": "1.4.0",
@ -5986,9 +5720,8 @@
"typebox": "1.3.27"
},
"devDependencies": {
"@cloudflare/vitest-plugin": "1.4.4",
"@cloudflare/vitest-plugin": "1.4.10",
"fake-indexeddb": "6.2.6",
"shx": "0.3.4",
"vitest": "4.1.12"
},
"engines": {
@ -6004,7 +5737,6 @@
"@earendil-works/pi-durable": "1.4.0"
},
"devDependencies": {
"shx": "0.3.4",
"vitest": "4.1.12"
},
"engines": {
@ -6019,7 +5751,6 @@
"@earendil-works/pi-coding-agent": "1.4.0",
"@types/node": "22.19.19",
"@vitest-evals/core": "0.15.0",
"shx": "0.3.4",
"vitest": "4.1.12",
"vitest-evals": "0.15.0"
}
@ -6033,7 +5764,6 @@
},
"devDependencies": {
"@types/cross-spawn": "6.0.6",
"shx": "0.3.4",
"vitest": "4.1.12"
},
"engines": {

View file

@ -8,7 +8,6 @@
"packages/coding-agent/examples/extensions/with-deps",
"packages/coding-agent/examples/extensions/custom-provider-anthropic",
"packages/coding-agent/examples/extensions/custom-provider-gitlab-duo",
"packages/coding-agent/examples/extensions/sandbox",
"packages/coding-agent/examples/extensions/gondolin"
],
"scripts": {
@ -57,14 +56,12 @@
"prepare": "husky"
},
"devDependencies": {
"@anthropic-ai/sandbox-runtime": "0.0.26",
"@biomejs/biome": "2.3.5",
"@biomejs/wasm-nodejs": "2.3.5",
"@types/node": "22.19.19",
"esbuild": "0.28.2",
"husky": "9.1.7",
"pnpm": "11.15.1",
"shx": "0.3.4",
"typescript": "7.0.2",
"yaml": "2.9.0"
},

View file

@ -17,7 +17,7 @@
"README.md"
],
"scripts": {
"clean": "shx rm -rf dist",
"clean": "node ../../scripts/package-files.mjs clean dist",
"build": "tsc -p tsconfig.build.json",
"test": "vitest --run",
"prepublishOnly": "npm run build"

View file

@ -56,13 +56,13 @@
"README.md"
],
"scripts": {
"clean": "shx rm -rf dist",
"clean": "node ../../scripts/package-files.mjs clean dist",
"generate-models": "node catalog/generate.ts --strict",
"hydrate-model-data": "node catalog/generate.ts --strict --data-only",
"generate-model-catalog": "node catalog/generate.ts --strict --json-only --json-output ../../.artifacts/model-catalog",
"check:model-data": "node scripts/check-model-data.ts",
"build": "npm run generate-models && npm run build:offline",
"build:offline": "npm run check:model-data && tsc -p tsconfig.build.json && shx rm -rf dist/providers/data && shx cp -r src/providers/data dist/providers/data",
"build:offline": "npm run check:model-data && tsc -p tsconfig.build.json && node ../../scripts/package-files.mjs clean dist/providers/data && node ../../scripts/package-files.mjs copy src/providers/data dist/providers",
"test": "vitest --run",
"prepublishOnly": "npm run clean && npm run build"
},

View file

@ -67,7 +67,7 @@
"src/delta/README.md"
],
"scripts": {
"clean": "shx rm -rf dist",
"clean": "node ../../scripts/package-files.mjs clean dist",
"build": "tsc -p tsconfig.build.json",
"test": "vitest --run",
"prepublishOnly": "npm run clean && npm run build"
@ -103,7 +103,6 @@
},
"devDependencies": {
"@types/ws": "8.18.2",
"shx": "0.3.4",
"vitest": "4.1.12",
"ws": "8.21.0"
}

View file

@ -42,7 +42,7 @@
"README.md"
],
"scripts": {
"clean": "shx rm -rf dist",
"clean": "node ../../scripts/package-files.mjs clean dist",
"build": "tsc -p tsconfig.build.json",
"test": "vitest --run",
"prepublishOnly": "npm run clean && npm run build"
@ -70,7 +70,6 @@
"quickjs-wasi": "3.6.2"
},
"devDependencies": {
"shx": "0.3.4",
"vitest": "4.1.12"
}
}

View file

@ -19,6 +19,7 @@
### Fixed
- Updated glob matching to prevent denial of service from amplified glob patterns.
- Fixed a saved "Do not trust" decision being ignored for projects without trust-requiring resources, for example a folder with only `AGENTS.md`. pi auto-trusted such projects, so `ctx.isProjectTrusted()` returned true and package commands treated them as trusted. A saved decision in `trust.json` now takes precedence; auto-trust applies only when no decision is saved ([#10947](https://github.com/earendil-works/pi/issues/10947))
- Fixed `Shift+Enter` queueing a follow-up instead of inserting a newline in Apple Terminal setups that send `ESC` + Return. pi now checks the local Shift key state for that sequence, as it already did for plain Return; `Alt+Enter` without Shift still queues a follow-up ([#11559](https://github.com/earendil-works/pi/issues/11559))
- Fixed the working status (OSC 7501) disappearing in Rex and iTerm2 during runs in regular TUI mode. Rendered messages wrote OSC 133 prompt markers to the terminal, which the terminal treats as a new shell prompt; regular mode no longer writes them ([#11519](https://github.com/earendil-works/pi/issues/11519))
@ -48,6 +49,10 @@
- Fixed `tool_search` and codemode `searchTools()` ranking a tool below tools that only mention its name in their descriptions, for example `peekaboo app` ranking `mcp__peekaboo__app` 14th. A tool matching more of the query's words now ranks above one matching fewer, a query word in a tool's name scores more than any number of mentions in its description, a tool whose whole name is in the query gets a large bonus, and long server instructions no longer lower the scores of that server's tools ([#11331](https://github.com/earendil-works/pi/issues/11331))
- Fixed `tool_search` and codemode `searchTools()` finding nothing for Chinese, Japanese, Thai, and other non-ASCII queries, because the tokenizer kept only ASCII letters and digits. Words in any script now match, text without spaces between words such as `搜索记录` is split into words, and canonically equivalent text such as `café` and `cafe\u0301` matches ([#10816](https://github.com/earendil-works/pi/issues/10816), [#11037](https://github.com/earendil-works/pi/pull/11037) by [@haoqixu](https://github.com/haoqixu))
### Removed
- Removed the native OS `sandbox/` extension example and its dependency. Use the [Gondolin example](examples/extensions/gondolin/) or the [containerization guide](docs/containerization.md).
## [1.4.0] - 2026-10-21
### New Features

View file

@ -3310,7 +3310,6 @@ All examples in [examples/extensions/](../examples/extensions/).
| **Remote & Sandbox** |||
| `ssh.ts` | SSH remote execution | `registerFlag`, `on("user_bash")`, `on("before_agent_start")`, tool operations |
| `interactive-shell.ts` | Persistent shell session | `on("user_bash")` |
| `sandbox/` | Sandboxed tool execution | Tool operations |
| `gondolin/` | Route built-in tools and `!` commands into a Gondolin micro-VM | Tool operations, built-in tool overrides, `on("user_bash")` |
| `subagent/` | Spawn sub-agents | `registerTool`, `exec` |
| **Games** |||

View file

@ -23,9 +23,10 @@ cp permission-gate.ts ~/.pi/agent/extensions/
| `protected-paths.ts` | Blocks writes to protected paths (.env, .git/, node_modules/) |
| `confirm-destructive.ts` | Confirms before destructive session actions (clear, switch, fork) |
| `dirty-repo-guard.ts` | Prevents session changes with uncommitted git changes |
| `sandbox/` | OS-level sandboxing using `@anthropic-ai/sandbox-runtime` with per-project config |
| `gondolin/` | Route built-in tools and `!` commands into a Gondolin micro-VM |
For isolation setup and limitations, see [Run Pi in an isolated environment](../../docs/containerization.md).
### Custom Tools
| Extension | Description |

View file

@ -1 +0,0 @@
node_modules

View file

@ -1,321 +0,0 @@
/**
* Sandbox Extension - OS-level sandboxing for bash commands
*
* Uses @anthropic-ai/sandbox-runtime to enforce filesystem and network
* restrictions on bash commands at the OS level (sandbox-exec on macOS,
* bubblewrap on Linux).
*
* Note: this example intentionally overrides the built-in `bash` tool to show
* how built-in tools can be replaced. Alternatively, you could sandbox `bash`
* via `tool_call` input mutation without replacing the tool.
*
* Config files (merged, project takes precedence):
* - ~/.pi/agent/extensions/sandbox.json (global)
* - <cwd>/.pi/sandbox.json (project-local)
*
* Example .pi/sandbox.json:
* ```json
* {
* "enabled": true,
* "network": {
* "allowedDomains": ["github.com", "*.github.com"],
* "deniedDomains": []
* },
* "filesystem": {
* "denyRead": ["~/.ssh", "~/.aws"],
* "allowWrite": [".", "/tmp"],
* "denyWrite": [".env"]
* }
* }
* ```
*
* Usage:
* - `pi -e ./sandbox` - sandbox enabled with default/config settings
* - `pi -e ./sandbox --no-sandbox` - disable sandboxing
* - `/sandbox` - show current sandbox configuration
*
* Setup:
* 1. Copy sandbox/ directory to ~/.pi/agent/extensions/
* 2. Run `npm install` in ~/.pi/agent/extensions/sandbox/
*
* Linux also requires: bubblewrap, socat, ripgrep
*/
import { spawn } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { SandboxManager, type SandboxRuntimeConfig } from "@anthropic-ai/sandbox-runtime";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { type BashOperations, CONFIG_DIR_NAME, createBashTool, getAgentDir } from "@earendil-works/pi-coding-agent";
interface SandboxConfig extends SandboxRuntimeConfig {
enabled?: boolean;
}
const DEFAULT_CONFIG: SandboxConfig = {
enabled: true,
network: {
allowedDomains: [
"npmjs.org",
"*.npmjs.org",
"registry.npmjs.org",
"registry.yarnpkg.com",
"pypi.org",
"*.pypi.org",
"github.com",
"*.github.com",
"api.github.com",
"raw.githubusercontent.com",
],
deniedDomains: [],
},
filesystem: {
denyRead: ["~/.ssh", "~/.aws", "~/.gnupg"],
allowWrite: [".", "/tmp"],
denyWrite: [".env", ".env.*", "*.pem", "*.key"],
},
};
function loadConfig(cwd: string): SandboxConfig {
const projectConfigPath = join(cwd, CONFIG_DIR_NAME, "sandbox.json");
const globalConfigPath = join(getAgentDir(), "extensions", "sandbox.json");
let globalConfig: Partial<SandboxConfig> = {};
let projectConfig: Partial<SandboxConfig> = {};
if (existsSync(globalConfigPath)) {
try {
globalConfig = JSON.parse(readFileSync(globalConfigPath, "utf-8"));
} catch (e) {
console.error(`Warning: Could not parse ${globalConfigPath}: ${e}`);
}
}
if (existsSync(projectConfigPath)) {
try {
projectConfig = JSON.parse(readFileSync(projectConfigPath, "utf-8"));
} catch (e) {
console.error(`Warning: Could not parse ${projectConfigPath}: ${e}`);
}
}
return deepMerge(deepMerge(DEFAULT_CONFIG, globalConfig), projectConfig);
}
function deepMerge(base: SandboxConfig, overrides: Partial<SandboxConfig>): SandboxConfig {
const result: SandboxConfig = { ...base };
if (overrides.enabled !== undefined) result.enabled = overrides.enabled;
if (overrides.network) {
result.network = { ...base.network, ...overrides.network };
}
if (overrides.filesystem) {
result.filesystem = { ...base.filesystem, ...overrides.filesystem };
}
const extOverrides = overrides as {
ignoreViolations?: Record<string, string[]>;
enableWeakerNestedSandbox?: boolean;
};
const extResult = result as { ignoreViolations?: Record<string, string[]>; enableWeakerNestedSandbox?: boolean };
if (extOverrides.ignoreViolations) {
extResult.ignoreViolations = extOverrides.ignoreViolations;
}
if (extOverrides.enableWeakerNestedSandbox !== undefined) {
extResult.enableWeakerNestedSandbox = extOverrides.enableWeakerNestedSandbox;
}
return result;
}
function createSandboxedBashOps(): BashOperations {
return {
async exec(command, cwd, { onData, signal, timeout }) {
if (!existsSync(cwd)) {
throw new Error(`Working directory does not exist: ${cwd}`);
}
const wrappedCommand = await SandboxManager.wrapWithSandbox(command);
return new Promise((resolve, reject) => {
const child = spawn("bash", ["-c", wrappedCommand], {
cwd,
detached: true,
stdio: ["ignore", "pipe", "pipe"],
});
let timedOut = false;
let timeoutHandle: NodeJS.Timeout | undefined;
if (timeout !== undefined && timeout > 0) {
timeoutHandle = setTimeout(() => {
timedOut = true;
if (child.pid) {
try {
process.kill(-child.pid, "SIGKILL");
} catch {
child.kill("SIGKILL");
}
}
}, timeout * 1000);
}
child.stdout?.on("data", onData);
child.stderr?.on("data", onData);
child.on("error", (err) => {
if (timeoutHandle) clearTimeout(timeoutHandle);
reject(err);
});
const onAbort = () => {
if (child.pid) {
try {
process.kill(-child.pid, "SIGKILL");
} catch {
child.kill("SIGKILL");
}
}
};
signal?.addEventListener("abort", onAbort, { once: true });
child.on("close", (code) => {
if (timeoutHandle) clearTimeout(timeoutHandle);
signal?.removeEventListener("abort", onAbort);
if (signal?.aborted) {
reject(new Error("aborted"));
} else if (timedOut) {
reject(new Error(`timeout:${timeout}`));
} else {
resolve({ exitCode: code });
}
});
});
},
};
}
export default function (pi: ExtensionAPI) {
pi.registerFlag("no-sandbox", {
description: "Disable OS-level sandboxing for bash commands",
type: "boolean",
default: false,
});
const localCwd = process.cwd();
const localBash = createBashTool(localCwd);
let sandboxEnabled = false;
let sandboxInitialized = false;
pi.registerTool({
...localBash,
label: "bash (sandboxed)",
async execute(id, params, signal, onUpdate, _ctx) {
if (!sandboxEnabled || !sandboxInitialized) {
return localBash.execute(id, params, signal, onUpdate);
}
const sandboxedBash = createBashTool(localCwd, {
operations: createSandboxedBashOps(),
});
return sandboxedBash.execute(id, params, signal, onUpdate);
},
});
pi.on("user_bash", () => {
if (!sandboxEnabled || !sandboxInitialized) return;
return { operations: createSandboxedBashOps() };
});
pi.on("session_start", async (_event, ctx) => {
const noSandbox = pi.getFlag("no-sandbox") as boolean;
if (noSandbox) {
sandboxEnabled = false;
ctx.ui.notify("Sandbox disabled via --no-sandbox", "warning");
return;
}
const config = loadConfig(ctx.cwd);
if (!config.enabled) {
sandboxEnabled = false;
ctx.ui.notify("Sandbox disabled via config", "info");
return;
}
const platform = process.platform;
if (platform !== "darwin" && platform !== "linux") {
sandboxEnabled = false;
ctx.ui.notify(`Sandbox not supported on ${platform}`, "warning");
return;
}
try {
const configExt = config as unknown as {
ignoreViolations?: Record<string, string[]>;
enableWeakerNestedSandbox?: boolean;
};
await SandboxManager.initialize({
network: config.network,
filesystem: config.filesystem,
ignoreViolations: configExt.ignoreViolations,
enableWeakerNestedSandbox: configExt.enableWeakerNestedSandbox,
});
sandboxEnabled = true;
sandboxInitialized = true;
const networkCount = config.network?.allowedDomains?.length ?? 0;
const writeCount = config.filesystem?.allowWrite?.length ?? 0;
ctx.ui.setStatus(
"sandbox",
ctx.ui.theme.fg("accent", `🔒 Sandbox: ${networkCount} domains, ${writeCount} write paths`),
);
ctx.ui.notify("Sandbox initialized", "info");
} catch (err) {
sandboxEnabled = false;
ctx.ui.notify(`Sandbox initialization failed: ${err instanceof Error ? err.message : err}`, "error");
}
});
pi.on("session_shutdown", async () => {
if (sandboxInitialized) {
try {
await SandboxManager.reset();
} catch {
// Ignore cleanup errors
}
}
});
pi.registerCommand("sandbox", {
description: "Show sandbox configuration",
handler: async (_args, ctx) => {
if (!sandboxEnabled) {
ctx.ui.notify("Sandbox is disabled", "info");
return;
}
const config = loadConfig(ctx.cwd);
const lines = [
"Sandbox Configuration:",
"",
"Network:",
` Allowed: ${config.network?.allowedDomains?.join(", ") || "(none)"}`,
` Denied: ${config.network?.deniedDomains?.join(", ") || "(none)"}`,
"",
"Filesystem:",
` Deny Read: ${config.filesystem?.denyRead?.join(", ") || "(none)"}`,
` Allow Write: ${config.filesystem?.allowWrite?.join(", ") || "(none)"}`,
` Deny Write: ${config.filesystem?.denyWrite?.join(", ") || "(none)"}`,
];
ctx.ui.notify(lines.join("\n"), "info");
},
});
}

View file

@ -1,92 +0,0 @@
{
"name": "pi-extension-sandbox",
"version": "1.4.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pi-extension-sandbox",
"version": "1.4.0",
"dependencies": {
"@anthropic-ai/sandbox-runtime": "^0.0.26"
}
},
"node_modules/@anthropic-ai/sandbox-runtime": {
"version": "0.0.26",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sandbox-runtime/-/sandbox-runtime-0.0.26.tgz",
"integrity": "sha512-DYV5LSsVMnzq0lbfaYMSpxZPUMAx4+hy343dRss+pVCLIfF62qOhxpYfZ5TmOk1GTDQm5f9wPprMNSStmnsV4w==",
"license": "Apache-2.0",
"dependencies": {
"@pondwader/socks5-server": "^1.0.10",
"@types/lodash-es": "^4.17.12",
"commander": "^12.1.0",
"lodash-es": "^4.17.21",
"shell-quote": "^1.8.3",
"zod": "^3.24.1"
},
"bin": {
"srt": "dist/cli.js"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@pondwader/socks5-server": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/@pondwader/socks5-server/-/socks5-server-1.0.10.tgz",
"integrity": "sha512-bQY06wzzR8D2+vVCUoBsr5QS2U6UgPUQRmErNwtsuI6vLcyRKkafjkr3KxbtGFf9aBBIV2mcvlsKD1UYaIV+sg==",
"license": "MIT"
},
"node_modules/@types/lodash": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==",
"license": "MIT"
},
"node_modules/@types/lodash-es": {
"version": "4.17.12",
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
"license": "MIT",
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/commander": {
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/lodash-es": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
"integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
"license": "MIT"
},
"node_modules/shell-quote": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
"integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}

View file

@ -1,19 +0,0 @@
{
"name": "pi-extension-sandbox",
"private": true,
"version": "1.4.0",
"type": "module",
"scripts": {
"clean": "echo 'nothing to clean'",
"build": "echo 'nothing to build'",
"check": "echo 'nothing to check'"
},
"pi": {
"extensions": [
"./index.ts"
]
},
"dependencies": {
"@anthropic-ai/sandbox-runtime": "0.0.26"
}
}

View file

@ -511,7 +511,7 @@
"hosted-git-info": "9.0.3",
"ignore": "7.0.8",
"jiti": "2.7.0",
"minimatch": "10.2.6",
"minimatch": "11.0.3",
"proper-lockfile": "4.1.2",
"quickjs-wasi": "3.6.2",
"semver": "7.8.5",
@ -582,6 +582,12 @@
},
"hasInstallScript": true
},
"node_modules/@isaacs/nonrecursive-glob-match": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@isaacs/nonrecursive-glob-match/-/nonrecursive-glob-match-1.0.3.tgz",
"integrity": "sha512-SI7kyKHq4XXDRozw+r9GlRZkia1ZkFrpLW98r2IC2Bpr8YkFGE3fvjzmxCG0x78A++69HmDNrDvSMYBZmHGpKA==",
"license": "BlueOak-1.0.0"
},
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
@ -1174,12 +1180,13 @@
}
},
"node_modules/minimatch": {
"version": "10.2.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
"integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-11.0.3.tgz",
"integrity": "sha512-iny/40PEGkNJQ5/Pv6z3nm6FbRAby32GeZRjCUJk2WnNDbgPSTcdI+DDvLqsgBdD4cD5sMtMheqPPnehCCa8Nw==",
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^5.0.8"
"@isaacs/nonrecursive-glob-match": "^1.0.3",
"brace-expansion": "^5.0.12"
},
"engines": {
"node": "18 || 20 || >=22"

View file

@ -29,12 +29,12 @@
"CHANGELOG.md"
],
"scripts": {
"clean": "shx rm -rf dist",
"clean": "node ../../scripts/package-files.mjs clean dist",
"build": "npm run build:unbundled && node ../../scripts/build-coding-agent-bundle.mjs",
"build:unbundled": "tsc -p tsconfig.build.json && shx chmod +x dist/cli.js dist/rpc-entry.js && npm run copy-assets",
"build:unbundled": "tsc -p tsconfig.build.json && node ../../scripts/package-files.mjs executable dist/cli.js dist/rpc-entry.js && npm run copy-assets",
"build:binary": "npm --prefix ../tui run build && npm --prefix ../telemetry run build && npm --prefix ../ai run build && npm --prefix ../agent run build && npm run build && bun build --compile --no-compile-autoload-bunfig --no-compile-autoload-dotenv ./dist/bun/cli.js ./src/utils/image-resize-worker.ts ./src/extensions/codemode/worker.ts --outfile dist/pi && npm run copy-binary-assets",
"copy-assets": "shx mkdir -p dist/modes/interactive/theme && shx cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && shx mkdir -p dist/modes/interactive/assets && shx cp src/modes/interactive/assets/*.png dist/modes/interactive/assets/ && shx mkdir -p dist/core/export-html/vendor && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/ && shx cp src/core/export-html/vendor/*.js dist/core/export-html/vendor/",
"copy-binary-assets": "shx cp package.json dist/ && shx cp README.md dist/ && shx cp CHANGELOG.md dist/ && shx mkdir -p dist/theme && shx cp src/modes/interactive/theme/*.json dist/theme/ && shx mkdir -p dist/assets && shx cp src/modes/interactive/assets/*.png dist/assets/ && shx mkdir -p dist/export-html/vendor && shx cp src/core/export-html/template.html dist/export-html/ && shx cp src/core/export-html/vendor/*.js dist/export-html/vendor/ && shx cp -r docs dist/ && shx cp -r examples dist/ && shx cp ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm dist/",
"copy-assets": "node ../../scripts/package-files.mjs copy \"src/modes/interactive/theme/*.json\" dist/modes/interactive/theme && node ../../scripts/package-files.mjs copy \"src/modes/interactive/assets/*.png\" dist/modes/interactive/assets && node ../../scripts/package-files.mjs copy src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html && node ../../scripts/package-files.mjs copy \"src/core/export-html/vendor/*.js\" dist/core/export-html/vendor",
"copy-binary-assets": "node ../../scripts/package-files.mjs copy package.json README.md CHANGELOG.md docs examples ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm dist && node ../../scripts/package-files.mjs copy \"src/modes/interactive/theme/*.json\" dist/theme && node ../../scripts/package-files.mjs copy \"src/modes/interactive/assets/*.png\" dist/assets && node ../../scripts/package-files.mjs copy src/core/export-html/template.html dist/export-html && node ../../scripts/package-files.mjs copy \"src/core/export-html/vendor/*.js\" dist/export-html/vendor",
"test": "vitest --run",
"generate:schemas": "node --import ./src/experimental/source-resolver.ts scripts/generate-schemas.ts",
"generate:references": "node --import ./src/experimental/source-resolver.ts scripts/generate-references.ts",
@ -56,7 +56,7 @@
"hosted-git-info": "9.0.3",
"ignore": "7.0.8",
"jiti": "2.7.0",
"minimatch": "10.2.6",
"minimatch": "11.0.3",
"proper-lockfile": "4.1.2",
"quickjs-wasi": "3.6.2",
"semver": "7.8.5",
@ -74,7 +74,6 @@
"@types/proper-lockfile": "4.1.4",
"@types/semver": "7.7.1",
"marked": "18.0.11",
"shx": "0.3.4",
"vitest": "4.1.12"
},
"keywords": [

View file

@ -123,7 +123,7 @@
"CHANGELOG.md"
],
"scripts": {
"clean": "shx rm -rf dist",
"clean": "node ../../scripts/package-files.mjs clean dist",
"build": "tsc -p tsconfig.build.json",
"test": "vitest --run && PI_DURABLE_DOCUMENT_RETENTION_MS=0 vitest --run && npm run test:store && npm run test:workers",
"test:store": "vitest --run --config vitest.store.config.ts",
@ -167,9 +167,8 @@
"typebox": "1.3.27"
},
"devDependencies": {
"@cloudflare/vitest-plugin": "1.4.4",
"@cloudflare/vitest-plugin": "1.4.10",
"fake-indexeddb": "6.2.6",
"shx": "0.3.4",
"vitest": "4.1.12"
}
}

View file

@ -22,7 +22,7 @@
"CHANGELOG.md"
],
"scripts": {
"clean": "shx rm -rf dist",
"clean": "node ../../scripts/package-files.mjs clean dist",
"build": "tsc -p tsconfig.build.json",
"build:daemon": "cargo build --manifest-path daemon/Cargo.toml --locked",
"test": "vitest --run",
@ -49,7 +49,6 @@
"@earendil-works/pi-durable": "1.4.0"
},
"devDependencies": {
"shx": "0.3.4",
"vitest": "4.1.12"
}
}

View file

@ -4,7 +4,7 @@
"private": true,
"type": "module",
"scripts": {
"clean": "shx rm -rf .eval",
"clean": "node ../../scripts/package-files.mjs clean .eval",
"eval": "npm run eval:host && npm run eval:docs --",
"eval:host": "vitest run --config vitest.evals.config.ts --project host",
"eval:audit": "vitest run --config vitest.evals.config.ts --project audit",
@ -16,7 +16,6 @@
"@earendil-works/pi-coding-agent": "1.4.0",
"@types/node": "22.19.19",
"@vitest-evals/core": "0.15.0",
"shx": "0.3.4",
"vitest": "4.1.12",
"vitest-evals": "0.15.0"
}

View file

@ -30,7 +30,7 @@
"LICENSES"
],
"scripts": {
"clean": "shx rm -rf dist",
"clean": "node ../../scripts/package-files.mjs clean dist",
"build": "tsc -p tsconfig.build.json",
"test": "vitest --run",
"prepublishOnly": "npm run clean && npm run build"
@ -55,7 +55,6 @@
},
"devDependencies": {
"@types/cross-spawn": "6.0.6",
"shx": "0.3.4",
"vitest": "4.1.12"
}
}

View file

@ -27,7 +27,7 @@
"README.md"
],
"scripts": {
"clean": "shx rm -rf dist",
"clean": "node ../../scripts/package-files.mjs clean dist",
"build": "tsc -p tsconfig.build.json",
"test": "vitest --run",
"prepublishOnly": "npm run clean && npm run build"

View file

@ -5,7 +5,7 @@
"type": "module",
"main": "dist/index.js",
"scripts": {
"clean": "shx rm -rf dist",
"clean": "node ../../scripts/package-files.mjs clean dist",
"build": "tsc -p tsconfig.build.json",
"build:native:darwin": "native/darwin/build.sh",
"build:native:linux": "native/linux/build.sh",

View file

@ -10,15 +10,7 @@ const auditLevel = "moderate";
const severities = ["info", "low", "moderate", "high", "critical"];
/** @type {Record<string, { package: string; reason: string }>} */
const acceptedAdvisories = {
"GHSA-86w9-cpqp-85rv": {
package: "node-forge",
reason:
"No fixed node-forge release exists. It is only reachable through @earendil-works/gondolin in the private, " +
"unpublished gondolin example extension. Gondolin only verifies leaf certificates against its own locally " +
"generated CA (public exponent 65537), so the low-exponent signature forgery does not apply.",
},
};
const acceptedAdvisories = {};
const result = spawnSync("npm", ["audit", "--omit=dev", "--json"], { encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
if (result.error) {

30
scripts/package-files.mjs Normal file
View file

@ -0,0 +1,30 @@
// Cross-platform file operations for package build scripts, using only Node built-ins.
import { chmodSync, cpSync, globSync, mkdirSync, rmSync, statSync } from "node:fs";
import { basename, join } from "node:path";
const [command, ...paths] = process.argv.slice(2);
const usage = "Usage: package-files.mjs clean <paths...> | copy <sources...> <directory> | executable <files...>";
if (paths.length === 0) throw new Error(usage);
switch (command) {
case "clean":
for (const path of paths) rmSync(path, { recursive: true, force: true });
break;
case "copy": {
const destination = paths.pop();
if (!destination || paths.length === 0) throw new Error(usage);
mkdirSync(destination, { recursive: true });
for (const pattern of paths) {
const sources = globSync(pattern);
if (sources.length === 0) throw new Error(`No files match ${pattern}`);
for (const source of sources) cpSync(source, join(destination, basename(source)), { recursive: true });
}
break;
}
case "executable":
for (const path of paths) chmodSync(path, statSync(path).mode | 0o111);
break;
default:
throw new Error(usage);
}

View file

@ -0,0 +1,82 @@
// Exercises the file operations used by package build scripts without touching build output.
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { test } from "node:test";
import { fileURLToPath } from "node:url";
const script = fileURLToPath(new URL("./package-files.mjs", import.meta.url));
function workspace(t) {
const directory = mkdtempSync(join(tmpdir(), "pi-package-files-"));
t.after(() => rmSync(directory, { recursive: true, force: true }));
return directory;
}
function run(directory, ...args) {
return spawnSync(process.execPath, [script, ...args], { cwd: directory, encoding: "utf8" });
}
test("clean removes directories and files and ignores missing paths", (t) => {
const directory = workspace(t);
mkdirSync(join(directory, "dist", "nested"), { recursive: true });
writeFileSync(join(directory, "dist", "nested", "file.js"), "output");
writeFileSync(join(directory, "output.txt"), "output");
assert.equal(run(directory, "clean", "dist", "output.txt", "missing").status, 0);
assert.equal(existsSync(join(directory, "dist")), false);
assert.equal(existsSync(join(directory, "output.txt")), false);
});
test("copy expands patterns, creates directories, and overwrites existing assets", (t) => {
const directory = workspace(t);
mkdirSync(join(directory, "source assets"));
writeFileSync(join(directory, "source assets", "dark.json"), "dark");
writeFileSync(join(directory, "source assets", "light.json"), "light");
writeFileSync(join(directory, "source assets", "ignored.txt"), "ignored");
const args = ["copy", "source assets/*.json", "output/assets"];
assert.equal(run(directory, ...args).status, 0);
assert.equal(readFileSync(join(directory, "output/assets/dark.json"), "utf8"), "dark");
assert.equal(readFileSync(join(directory, "output/assets/light.json"), "utf8"), "light");
assert.equal(existsSync(join(directory, "output/assets/ignored.txt")), false);
writeFileSync(join(directory, "source assets", "dark.json"), "updated");
assert.equal(run(directory, ...args).status, 0);
assert.equal(readFileSync(join(directory, "output/assets/dark.json"), "utf8"), "updated");
});
test("copy accepts multiple files and preserves directory names and nested contents", (t) => {
const directory = workspace(t);
mkdirSync(join(directory, "docs", "nested"), { recursive: true });
writeFileSync(join(directory, "docs", "nested", "guide.md"), "guide");
writeFileSync(join(directory, "README.md"), "readme");
assert.equal(run(directory, "copy", "docs", "README.md", "dist").status, 0);
assert.equal(readFileSync(join(directory, "dist", "docs", "nested", "guide.md"), "utf8"), "guide");
assert.equal(readFileSync(join(directory, "dist", "README.md"), "utf8"), "readme");
});
test("copy fails when an asset pattern matches nothing", (t) => {
const result = run(workspace(t), "copy", "missing/*.json", "dist");
assert.notEqual(result.status, 0);
assert.match(result.stderr, /No files match missing/);
});
test("executable adds executable bits without removing existing permissions", (t) => {
const directory = workspace(t);
for (const name of ["cli.js", "rpc.js"]) writeFileSync(join(directory, name), "entry", { mode: 0o640 });
const mode = statSync(join(directory, "cli.js")).mode;
assert.equal(run(directory, "executable", "cli.js", "rpc.js").status, 0);
if (process.platform !== "win32") {
assert.equal(statSync(join(directory, "cli.js")).mode & 0o777, (mode | 0o111) & 0o777);
assert.equal(statSync(join(directory, "rpc.js")).mode & 0o111, 0o111);
}
});
test("invalid commands and missing arguments fail", (t) => {
const directory = workspace(t);
for (const args of [[], ["unknown", "dist"], ["clean"], ["copy", "dist"], ["executable"]]) {
const result = run(directory, ...args);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /Usage: package-files/);
}
});