windows-development-sandbox 1.11.6 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -6
- package/config.example.json +4 -3
- package/dist/app/features/apps/apps-rows.d.ts +4 -1
- package/dist/app/features/apps/apps-rows.js +7 -2
- package/dist/app/features/apps/apps.def.d.ts +19 -0
- package/dist/app/features/apps/apps.def.js +49 -12
- package/dist/app/features/apps/apps.run.d.ts +2 -0
- package/dist/app/features/apps/apps.run.js +1 -1
- package/dist/app/features/audit/audit.def.d.ts +341 -0
- package/dist/app/features/audit/audit.def.js +454 -0
- package/dist/app/features/audit/audit.run.d.ts +107 -0
- package/dist/app/features/audit/audit.run.js +133 -0
- package/dist/app/features/audit/index.d.ts +2 -0
- package/dist/app/features/audit/index.js +3 -0
- package/dist/app/features/defs.d.ts +194 -1
- package/dist/app/features/defs.js +4 -0
- package/dist/app/features/projects/projects.def.js +1 -1
- package/dist/app/features/settings/settings.run.d.ts +3 -0
- package/dist/app/features/settings/settings.run.js +16 -2
- package/dist/app/features/updates/update-lines.d.ts +1 -1
- package/dist/app/features/updates/update-lines.js +3 -7
- package/dist/app/features/updates/updates.def.js +1 -1
- package/dist/app/kernel/define.d.ts +12 -1
- package/dist/app/registry.js +2 -0
- package/dist/cli/args.d.ts +0 -3
- package/dist/cli/args.js +3 -4
- package/dist/cli/commands/index.d.ts +1 -1
- package/dist/cli/commands/index.js +1 -1
- package/dist/cli/commands/launch/prepare.js +1 -1
- package/dist/cli/commands/maintenance/index.d.ts +0 -1
- package/dist/cli/commands/maintenance/index.js +0 -1
- package/dist/cli/commands/registry.js +1 -2
- package/dist/cli/manifest.d.ts +0 -41
- package/dist/cli/manifest.js +0 -14
- package/dist/cli/menu/pages/apps-page.js +2 -15
- package/dist/cli/menu/pages/maintenance-page.d.ts +4 -0
- package/dist/cli/menu/pages/maintenance-page.js +51 -8
- package/dist/cli/menu/views.js +2 -0
- package/dist/core/config/defaults.js +1 -1
- package/dist/core/schema/apps.d.ts +9 -0
- package/dist/core/schema/apps.js +9 -0
- package/dist/core/schema/config.d.ts +1 -0
- package/dist/core/schema/config.js +3 -3
- package/dist/core/schema/sandbox.d.ts +1 -0
- package/dist/core/schema/sandbox.js +11 -3
- package/dist/guest/apps/app-installer.js +50 -4
- package/dist/guest/apps/index.d.ts +1 -1
- package/dist/guest/apps/index.js +1 -1
- package/dist/guest/apps/sources.js +1 -1
- package/dist/guest/audit/parse.d.ts +32 -2
- package/dist/guest/audit/parse.js +43 -5
- package/dist/guest/audit/process-audit.d.ts +3 -1
- package/dist/guest/audit/process-audit.js +9 -6
- package/dist/guest/boot/boot.js +12 -3
- package/dist/guest/boot/guide.d.ts +1 -1
- package/dist/guest/boot/guide.js +27 -3
- package/dist/guest/daemon/sessions.d.ts +7 -0
- package/dist/guest/daemon/sessions.js +27 -2
- package/dist/guest/entries/daemon.js +2 -1
- package/dist/guest/setup/addon-mcp.d.ts +2 -0
- package/dist/guest/setup/addon-mcp.js +49 -0
- package/dist/guest/setup/agent-addons.d.ts +9 -0
- package/dist/guest/setup/agent-addons.js +22 -0
- package/dist/guest/setup/headroom.d.ts +7 -0
- package/dist/guest/setup/headroom.js +60 -0
- package/dist/guest/setup/managed-policy.d.ts +2 -1
- package/dist/guest/setup/managed-policy.js +15 -6
- package/dist/guest/toolchains/plan.js +8 -3
- package/dist/host/audit/attribute.d.ts +46 -0
- package/dist/host/audit/attribute.js +76 -0
- package/dist/host/audit/collect.d.ts +29 -3
- package/dist/host/audit/collect.js +256 -61
- package/dist/host/audit/git-commits.d.ts +29 -0
- package/dist/host/audit/git-commits.js +157 -0
- package/dist/host/audit/index.d.ts +5 -0
- package/dist/host/audit/index.js +6 -0
- package/dist/host/audit/log.d.ts +44 -7
- package/dist/host/audit/log.js +148 -45
- package/dist/host/audit/redact.d.ts +2 -0
- package/dist/host/audit/redact.js +67 -0
- package/dist/host/update/decision.d.ts +3 -2
- package/dist/host/update/decision.js +2 -3
- package/dist/host/update/updater.d.ts +4 -1
- package/dist/host/update/updater.js +11 -6
- package/dist/runtime/agent.js +110 -15
- package/dist/runtime/daemon.js +569 -316
- package/dist/runtime/db.js +110 -15
- package/dist/runtime/main.js +1011 -672
- package/dist/runtime/netproxy.js +110 -15
- package/dist/runtime/view.js +110 -15
- package/dist/selftest/app.test.js +25 -1
- package/dist/selftest/audit.test.js +354 -47
- package/dist/selftest/hcs.test.js +35 -0
- package/dist/selftest/menu-screens.js +1 -1
- package/dist/selftest/menu.test.js +3 -3
- package/dist/selftest/support/engine-golden.js +9 -0
- package/dist/selftest/support/fixtures.d.ts +1 -0
- package/dist/selftest/support/web-preview.js +94 -0
- package/dist/selftest/toolchains.test.js +147 -2
- package/dist/selftest/update-service.test.js +0 -7
- package/dist/selftest/update.test.js +4 -4
- package/dist/shared/apps/agent-addons.d.ts +64 -0
- package/dist/shared/apps/agent-addons.js +95 -0
- package/dist/shared/apps/catalogue.d.ts +33 -2
- package/dist/shared/apps/catalogue.js +102 -11
- package/dist/shared/apps/index.d.ts +3 -2
- package/dist/shared/apps/index.js +3 -2
- package/dist/shared/apps/selection.d.ts +2 -0
- package/dist/shared/apps/selection.js +7 -0
- package/dist/sync/watcher.js +9 -1
- package/dist/web/index.html +2 -2
- package/dist/web/{main-RQFN4WBY.css → main-UAQPI5XD.css} +1 -1
- package/dist/web/{main-QL7KHCEE.js → main-UD4QCXZR.js} +66 -66
- package/native/csharp/HcsHost/AssemblyInfo.cs +5 -3
- package/native/csharp/HcsHost/Hcs.cs +9 -0
- package/native/csharp/HcsHost/Program.cs +26 -1
- package/native/csharp/HcsHost/Share.cs +173 -0
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/package.json +1 -1
- package/sandbox/addons/ccusage/package-lock.json +111 -0
- package/sandbox/addons/ccusage/package.json +7 -0
- package/sandbox/addons/context7/package-lock.json +1313 -0
- package/sandbox/addons/context7/package.json +7 -0
- package/sandbox/addons/headroom/requirements.in +1 -0
- package/sandbox/addons/headroom/requirements.txt +2922 -0
- package/sandbox/addons/playwright-mcp/package-lock.json +56 -0
- package/sandbox/addons/playwright-mcp/package.json +7 -0
- package/schemas/config.schema.json +13 -4
- package/schemas/sandbox-config.schema.json +2 -2
- package/dist/cli/commands/maintenance/audit.d.ts +0 -3
- package/dist/cli/commands/maintenance/audit.js +0 -58
|
@@ -17,6 +17,8 @@ import { showData } from '../../app/features/data/index.js';
|
|
|
17
17
|
import { updatesStatus } from '../../app/features/updates/index.js';
|
|
18
18
|
import { containedStatus, showStorage } from '../../app/features/storage/index.js';
|
|
19
19
|
import { listSessions } from '../../app/features/sessions/index.js';
|
|
20
|
+
import { auditListing, listAudit } from '../../app/features/audit/index.js';
|
|
21
|
+
import { AuditLog } from '../../host/audit/index.js';
|
|
20
22
|
import { cancelled } from '../../app/kernel/index.js';
|
|
21
23
|
import { runDefinition } from '../../app/registry.js';
|
|
22
24
|
import { webSession } from '../../cli/services/index.js';
|
|
@@ -509,10 +511,101 @@ function sampleLogTail(input) {
|
|
|
509
511
|
next: 0,
|
|
510
512
|
});
|
|
511
513
|
}
|
|
514
|
+
/** A plausible audit log: two agents' sessions, the sandbox's own setup, a commit each, a guard finding. */
|
|
515
|
+
function sampleAuditLog(dir) {
|
|
516
|
+
const log = new AuditLog(dir, { days: 7, maxMb: 100 });
|
|
517
|
+
const at = (minutesAgo) => new Date(Date.now() - minutesAgo * MINUTE).toISOString();
|
|
518
|
+
const claude = { s: SESSIONS[0].id, ag: 'claude', pj: 'windows-sandbox', sv: 'p' };
|
|
519
|
+
const codex = { s: 'mva7codex2k91x', ag: 'codex', pj: 'mw2-agent', sv: 'p' };
|
|
520
|
+
const git = 'C:\\AgentData\\tools\\git\\cmd\\git.exe';
|
|
521
|
+
const node = 'C:\\nvm\\nodejs\\node.exe';
|
|
522
|
+
const ws = 'F:\\AgentProjects\\windows-sandbox';
|
|
523
|
+
const mw = 'F:\\AgentProjects\\mw2-agent';
|
|
524
|
+
const proc = (m, p, c, who, n) => ({
|
|
525
|
+
t: at(m),
|
|
526
|
+
k: 'proc',
|
|
527
|
+
p,
|
|
528
|
+
c,
|
|
529
|
+
pp: node,
|
|
530
|
+
...who,
|
|
531
|
+
...(n ? { n } : {}),
|
|
532
|
+
});
|
|
533
|
+
const file = (m, p, w, who) => ({
|
|
534
|
+
t: at(m),
|
|
535
|
+
k: 'file',
|
|
536
|
+
a: 'change',
|
|
537
|
+
p,
|
|
538
|
+
w,
|
|
539
|
+
...who,
|
|
540
|
+
});
|
|
541
|
+
log.append([
|
|
542
|
+
{ t: at(95), k: 'note', p: 'sandbox started; audit: programs, files, commits, sessions' },
|
|
543
|
+
proc(94, 'C:\\AgentData\\tools\\uv\\uv.exe', 'uv tool install aider-chat', {}),
|
|
544
|
+
{ t: at(90), k: 'session', a: 'start', p: 'claude', w: SESSIONS[0].cwd, c: claude.s, ...claude, sv: undefined },
|
|
545
|
+
proc(88, git, 'git status --short', claude),
|
|
546
|
+
proc(87, node, 'node dist/bin/agent-sandbox.js self-test audit', claude, 2),
|
|
547
|
+
file(80, 'src/host/audit/collect.ts', ws, claude),
|
|
548
|
+
file(79, 'src/app/features/audit/audit.def.ts', ws, claude),
|
|
549
|
+
proc(70, 'C:\\nvm\\nodejs\\npm.cmd', 'npm run build', claude),
|
|
550
|
+
{
|
|
551
|
+
t: at(66),
|
|
552
|
+
k: 'session',
|
|
553
|
+
a: 'start',
|
|
554
|
+
p: 'codex',
|
|
555
|
+
w: 'C:\\Projects\\mw2-agent',
|
|
556
|
+
c: codex.s,
|
|
557
|
+
...codex,
|
|
558
|
+
sv: undefined,
|
|
559
|
+
},
|
|
560
|
+
proc(64, 'C:\\Users\\WDAGUtilityAccount\\.cargo\\bin\\cargo.exe', 'cargo build --release', codex, 3),
|
|
561
|
+
file(60, 'src/main.rs', mw, { ...codex, sv: 't' }),
|
|
562
|
+
proc(52, git, 'git commit -m "Audit: link programs to their session"', claude),
|
|
563
|
+
{
|
|
564
|
+
t: at(52),
|
|
565
|
+
k: 'commit',
|
|
566
|
+
a: 'commit',
|
|
567
|
+
p: 'Audit: link programs to their session',
|
|
568
|
+
h: 'a3f9c2e81b7d4c60a1e2f3b4c5d6e7f8a9b0c1d2',
|
|
569
|
+
au: 'Josh <josh@example.com>',
|
|
570
|
+
w: ws,
|
|
571
|
+
...claude,
|
|
572
|
+
},
|
|
573
|
+
proc(40, git, 'git push --set-upstream origin https://***@github.com/example/mw2-agent.git', codex),
|
|
574
|
+
{ t: at(38), k: 'guard', p: 'hook: a pre-push hook added inside the sandbox was neutralised' },
|
|
575
|
+
proc(30, 'C:\\Users\\WDAGUtilityAccount\\.cargo\\bin\\cargo.exe', 'cargo test', codex, 4),
|
|
576
|
+
{
|
|
577
|
+
t: at(28),
|
|
578
|
+
k: 'commit',
|
|
579
|
+
a: 'commit',
|
|
580
|
+
p: 'Fix the map loader',
|
|
581
|
+
h: '7be0d41c9a2f4e3b8c1d0e9f8a7b6c5d4e3f2a1b',
|
|
582
|
+
au: 'Josh <josh@example.com>',
|
|
583
|
+
w: mw,
|
|
584
|
+
...codex,
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
t: at(20),
|
|
588
|
+
k: 'session',
|
|
589
|
+
a: 'end',
|
|
590
|
+
p: 'codex',
|
|
591
|
+
w: 'C:\\Projects\\mw2-agent',
|
|
592
|
+
c: codex.s + ' exit 0',
|
|
593
|
+
...codex,
|
|
594
|
+
sv: undefined,
|
|
595
|
+
},
|
|
596
|
+
proc(5, node, 'node dist/bin/agent-sandbox.js self-test app', claude),
|
|
597
|
+
]);
|
|
598
|
+
return log;
|
|
599
|
+
}
|
|
512
600
|
/** The real features where they only touch the temp folder, sample data for the rest. */
|
|
513
601
|
function previewRpc(ctx, state) {
|
|
602
|
+
const auditDir = path.join(ctx.storageRoot, 'audit', 'preview');
|
|
603
|
+
const audit = sampleAuditLog(auditDir);
|
|
514
604
|
const sample = async (id, input, io) => {
|
|
515
605
|
switch (id) {
|
|
606
|
+
case 'audit.list':
|
|
607
|
+
// The real listing over a sample log in the temp folder.
|
|
608
|
+
return auditListing(audit, { enabled: true, days: 7, maxMb: 100 }, listAudit.input.parse(input ?? {}), auditDir);
|
|
516
609
|
case 'sandbox.status':
|
|
517
610
|
return sampleStatus(state.running);
|
|
518
611
|
case 'vpn.status':
|
|
@@ -792,6 +885,7 @@ function previewRpc(ctx, state) {
|
|
|
792
885
|
'logs.tail': 'query',
|
|
793
886
|
'engine.status': 'query',
|
|
794
887
|
'engine.leftovers': 'query',
|
|
888
|
+
'audit.list': 'query',
|
|
795
889
|
};
|
|
796
890
|
return {
|
|
797
891
|
kind: (id) => kinds[id],
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// Picking tool versions and release assets: Node/Go/Python/Java version rules, GitHub release pages, PowerShell 7,
|
|
2
|
-
// cargo-binstall, the small CLI tools (gh, zstd, openssl),
|
|
2
|
+
// cargo-binstall, the small CLI tools (gh, zstd, openssl), when PowerShell 7 is wanted, and the apps' agent add-ons.
|
|
3
|
+
import fs from 'node:fs';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { javaMajorFrom, pickGoRelease, pickNodeVersion, pickPackageVersion, pickSpreadVersions, projectUses, specFromVersionFile, } from '../core/detect/index.js';
|
|
5
6
|
import { parseReleasePage, parseReleaseTags } from '../shared/download/index.js';
|
|
6
7
|
import { decidePwsh, pcHasPwsh, PWSH_MAX_BYTES, readPwshScripts, usesPwsh } from '../core/detect/index.js';
|
|
7
8
|
import { binstallAsset, CLI_TOOLS, decideCli, pwshAsset } from '../guest/toolchains/catalogue.js';
|
|
8
9
|
import { missingExtensions, vscodeExtensions, vscodeSettings } from '../guest/toolchains/tools/vscode-profile.js';
|
|
9
|
-
import { APP_CATEGORIES, APPS, appsNeedMesa, appsToInstall, exeMatcher } from '../shared/apps/index.js';
|
|
10
|
+
import { addonGuideLine, addonMcpServer, APP_CATEGORIES, APPS, appsNeedMesa, appsNeedUv, appsToInstall, CCUSAGE_VERSION, claudeManagedSettings, CONTEXT7_MCP_VERSION, exeMatcher, HEADROOM_VERSION, headroomArgs, headroomEnv, headroomUrl, PLAYWRIGHT_MCP_VERSION, RTK_VERSION, withAddonMcpServers, } from '../shared/apps/index.js';
|
|
10
11
|
import { ConfigSchema, LocalOverridesSchema } from '../core/schema/index.js';
|
|
11
12
|
export const toolchainsSuite = {
|
|
12
13
|
name: 'toolchains',
|
|
@@ -236,5 +237,149 @@ export const toolchainsSuite = {
|
|
|
236
237
|
!custom({ name: 'E', github: 'o/r', asset: 'x', url: 'https://x.org', exe: 'e.exe' }), 'apps: your own program comes from a GitHub release or an https link; plain http, a broken pattern or both are refused');
|
|
237
238
|
t.check(!LocalOverridesSchema.safeParse({ apps: { custom: {} } }).success &&
|
|
238
239
|
LocalOverridesSchema.safeParse({ apps: { install: ['jq'] } }).success, 'apps: a project file may add catalogue apps but never programs of its own');
|
|
240
|
+
t.section('agent add-ons');
|
|
241
|
+
const rtk = APPS.find((a) => a.id === 'rtk');
|
|
242
|
+
const rtkSource = rtk?.source;
|
|
243
|
+
t.check(rtk?.category === 'agents' &&
|
|
244
|
+
rtk.cli === true &&
|
|
245
|
+
rtk.userOnly === true &&
|
|
246
|
+
rtkSource !== undefined &&
|
|
247
|
+
'url' in rtkSource &&
|
|
248
|
+
rtkSource.url.startsWith('https://github.com/rtk-ai/rtk/releases/download/v' + RTK_VERSION + '/') &&
|
|
249
|
+
rtkSource.url.endsWith('-x86_64-pc-windows-msvc.zip') &&
|
|
250
|
+
/^[0-9a-f]{64}$/.test(rtkSource.sha256 ?? ''), 'rtk: a pinned https release file of rtk-ai/rtk with its SHA-256, on PATH, in your own settings only');
|
|
251
|
+
t.check(APPS.every((a) => !('url' in a.source) || a.source.url.startsWith('https://')) &&
|
|
252
|
+
APPS.every((a) => !('url' in a.source) || a.source.sha256 === undefined || /^[0-9a-f]{64}$/.test(a.source.sha256)), 'apps: every direct link is https, and a pinned checksum is 64 hex characters');
|
|
253
|
+
const agentApps = APPS.filter((a) => a.category === 'agents').map((a) => a.id);
|
|
254
|
+
t.check(APP_CATEGORIES.some((c) => c.id === 'agents' && c.label === 'Agent add-ons') &&
|
|
255
|
+
agentApps.includes('rtk') &&
|
|
256
|
+
agentApps.includes('claude-mem') &&
|
|
257
|
+
APPS.find((a) => a.id === 'claude-mem')?.setting === 'claudeProfile.syncClaudeMem', 'apps: the Agent add-ons category holds rtk and claude-mem (claude-mem through its own setting)');
|
|
258
|
+
t.check(!LocalOverridesSchema.safeParse({ apps: { install: ['rtk'] } }).success &&
|
|
259
|
+
!LocalOverridesSchema.safeParse({ apps: { install: ['claude-mem'] } }).success &&
|
|
260
|
+
ConfigSchema.shape.apps.shape.install.safeParse(['rtk']).success, 'rtk: only your own settings turn it on; a project file cannot');
|
|
261
|
+
const exe = 'C:\\AgentData\\tools\\apps\\rtk\\rtk.exe';
|
|
262
|
+
const withRtk = claudeManagedSettings([], exe);
|
|
263
|
+
const both = claudeManagedSettings(['Bash(git push:*)'], exe);
|
|
264
|
+
const gitOnly = claudeManagedSettings(['Bash(git push:*)'], null);
|
|
265
|
+
t.equal(withRtk?.hooks?.PreToolUse, [{ matcher: 'Bash', hooks: [{ type: 'command', command: '"C:/AgentData/tools/apps/rtk/rtk.exe" hook claude' }] }], "rtk: its documented Bash PreToolUse hook goes into the sandbox's managed settings");
|
|
266
|
+
t.check(claudeManagedSettings([], null) === null &&
|
|
267
|
+
gitOnly?.hooks === undefined &&
|
|
268
|
+
gitOnly?.permissions?.deny.length === 1 &&
|
|
269
|
+
both?.permissions?.deny.length === 1 &&
|
|
270
|
+
both.hooks?.PreToolUse.length === 1 &&
|
|
271
|
+
withRtk?.permissions === undefined, 'rtk: no hook when it is off; the git deny rules stay beside it; nothing to write when neither');
|
|
272
|
+
t.section('agent add-ons: Headroom, ccusage, Playwright MCP, Context7 MCP');
|
|
273
|
+
const pinned = {
|
|
274
|
+
headroom: ['uv', 'headroom-ai', HEADROOM_VERSION],
|
|
275
|
+
ccusage: ['npm', 'ccusage', CCUSAGE_VERSION],
|
|
276
|
+
'playwright-mcp': ['npm', '@playwright/mcp', PLAYWRIGHT_MCP_VERSION],
|
|
277
|
+
context7: ['npm', '@upstash/context7-mcp', CONTEXT7_MCP_VERSION],
|
|
278
|
+
};
|
|
279
|
+
for (const [id, [kind, pkg, version]] of Object.entries(pinned)) {
|
|
280
|
+
const entry = APPS.find((a) => a.id === id);
|
|
281
|
+
const source = entry?.source;
|
|
282
|
+
t.check(entry?.category === 'agents' &&
|
|
283
|
+
entry.userOnly === true &&
|
|
284
|
+
source !== undefined &&
|
|
285
|
+
'locked' in source &&
|
|
286
|
+
source.locked === kind &&
|
|
287
|
+
source.package === pkg &&
|
|
288
|
+
source.version === version &&
|
|
289
|
+
/^\d+\.\d+\.\d+$/.test(version) &&
|
|
290
|
+
!LocalOverridesSchema.safeParse({ apps: { install: [id] } }).success &&
|
|
291
|
+
ConfigSchema.shape.apps.shape.install.safeParse([id]).success, id + ': in Agent add-ons, a pinned ' + kind + ' package (' + pkg + ' ' + version + '), your own settings only');
|
|
292
|
+
const lockDir = path.join(t.packageRoot, 'sandbox', 'addons', id);
|
|
293
|
+
if (kind === 'npm') {
|
|
294
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(lockDir, 'package.json'), 'utf8'));
|
|
295
|
+
const lock = JSON.parse(fs.readFileSync(path.join(lockDir, 'package-lock.json'), 'utf8'));
|
|
296
|
+
const deps = Object.entries(lock.packages).filter(([key]) => key !== '');
|
|
297
|
+
t.check(manifest.dependencies[pkg] === version &&
|
|
298
|
+
Object.keys(manifest.dependencies).length === 1 &&
|
|
299
|
+
lock.lockfileVersion === 3 &&
|
|
300
|
+
lock.packages['node_modules/' + pkg]?.version === version &&
|
|
301
|
+
deps.length > 0 &&
|
|
302
|
+
deps.every(([, p]) => !p.link &&
|
|
303
|
+
(p.resolved ?? '').startsWith('https://registry.npmjs.org/') &&
|
|
304
|
+
/^sha512-[A-Za-z0-9+/]+=*$/.test(p.integrity ?? '')), id + ': its lock file pins ' + pkg + ' ' + version + '; every package comes from npm with a sha512 integrity');
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
const lines = fs.readFileSync(path.join(lockDir, 'requirements.txt'), 'utf8').split(/\r?\n/);
|
|
308
|
+
const requirements = lines.filter((l) => /^[A-Za-z0-9]/.test(l));
|
|
309
|
+
const blocks = lines
|
|
310
|
+
.join('\n')
|
|
311
|
+
.split(/\n(?=[A-Za-z0-9])/)
|
|
312
|
+
.slice(1);
|
|
313
|
+
t.check(requirements.includes(pkg + '==' + version + ' \\') &&
|
|
314
|
+
requirements.length > 10 &&
|
|
315
|
+
requirements.every((l) => /^[A-Za-z0-9._-]+(\[[^\]]+\])?==[^\s]+ \\$/.test(l)) &&
|
|
316
|
+
blocks.every((b) => /--hash=sha256:[0-9a-f]{64}/.test(b)) &&
|
|
317
|
+
!lines.some((l) => /^\s*(-e|--index-url|--extra-index-url|-f|--find-links|git\+|https?:)/.test(l.trim())), id + ': its requirements.txt pins every package to one version with SHA-256 hashes, from PyPI only');
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
t.check(appsNeedUv({ install: ['headroom'], custom: {} }) &&
|
|
321
|
+
!appsNeedUv({ install: ['ccusage', 'rtk'], custom: {} }) &&
|
|
322
|
+
!appsNeedUv(undefined), 'headroom: choosing it brings the uv step (it installs with uv)');
|
|
323
|
+
const services = APPS.filter((a) => a.service).map((a) => a.id);
|
|
324
|
+
t.check(['headroom', 'playwright-mcp', 'context7'].every((id) => services.includes(id)) &&
|
|
325
|
+
APPS.find((a) => a.id === 'ccusage')?.cli === true &&
|
|
326
|
+
!services.includes('ccusage'), 'add-ons: the proxy and MCP servers get no shortcut and no PATH entry; ccusage is a command on PATH');
|
|
327
|
+
// Headroom: loopback only, Anthropic only, no telemetry, and none of the agents' secrets in its environment.
|
|
328
|
+
const args = headroomArgs();
|
|
329
|
+
t.check(args[0] === 'proxy' &&
|
|
330
|
+
args[args.indexOf('--host') + 1] === '127.0.0.1' &&
|
|
331
|
+
args[args.indexOf('--port') + 1] === '8787' &&
|
|
332
|
+
args[args.indexOf('--anthropic-api-url') + 1] === 'https://api.anthropic.com' &&
|
|
333
|
+
args.includes('--no-telemetry') &&
|
|
334
|
+
headroomUrl() === 'http://127.0.0.1:8787', 'headroom: the proxy listens on 127.0.0.1 only and forwards only to api.anthropic.com, telemetry off');
|
|
335
|
+
const proxyEnv = headroomEnv({
|
|
336
|
+
PATH: 'C:\\Windows',
|
|
337
|
+
ANTHROPIC_API_KEY: 'sk-ant-x',
|
|
338
|
+
CLAUDE_CODE_OAUTH_TOKEN: 'oauth',
|
|
339
|
+
GITHUB_TOKEN: 'ghp',
|
|
340
|
+
OPENAI_API_KEY: 'sk',
|
|
341
|
+
DB_PASSWORD: 'pw',
|
|
342
|
+
HTTPS_PROXY: 'http://127.0.0.1:3128',
|
|
343
|
+
HEADROOM_OFFLINE: '0',
|
|
344
|
+
});
|
|
345
|
+
t.check(proxyEnv.PATH === 'C:\\Windows' &&
|
|
346
|
+
proxyEnv.HTTPS_PROXY === 'http://127.0.0.1:3128' &&
|
|
347
|
+
!Object.values(proxyEnv).some((v) => ['sk-ant-x', 'oauth', 'ghp', 'sk', 'pw'].includes(v)) &&
|
|
348
|
+
proxyEnv.HEADROOM_OFFLINE === '1' &&
|
|
349
|
+
proxyEnv.HEADROOM_TELEMETRY === 'off' &&
|
|
350
|
+
proxyEnv.HEADROOM_UPDATE_CHECK === 'off', 'headroom: starts without keys or tokens, offline apart from forwarding (no telemetry, updates or downloads)');
|
|
351
|
+
const routed = claudeManagedSettings([], null, headroomUrl());
|
|
352
|
+
t.check(routed?.env?.ANTHROPIC_BASE_URL === 'http://127.0.0.1:8787' &&
|
|
353
|
+
claudeManagedSettings([], exe)?.env === undefined &&
|
|
354
|
+
claudeManagedSettings(['Bash(git push:*)'], null)?.env === undefined &&
|
|
355
|
+
claudeManagedSettings(['Bash(git push:*)'], exe, headroomUrl())?.hooks?.PreToolUse.length === 1, "headroom: Claude Code is pointed at the proxy only while it runs (the sandbox's managed settings env)");
|
|
356
|
+
// MCP servers: set while on, removed when off, your own servers never touched.
|
|
357
|
+
const playwright = APPS.find((a) => a.id === 'playwright-mcp')?.mcp;
|
|
358
|
+
const context7 = APPS.find((a) => a.id === 'context7')?.mcp;
|
|
359
|
+
const known = APPS.flatMap((a) => (a.mcp ? [a.mcp.name] : []));
|
|
360
|
+
const pw = playwright ? addonMcpServer('C:\\AgentData\\tools\\apps\\playwright-mcp', playwright) : null;
|
|
361
|
+
t.equal(pw, {
|
|
362
|
+
type: 'stdio',
|
|
363
|
+
command: 'node',
|
|
364
|
+
args: [
|
|
365
|
+
'C:\\AgentData\\tools\\apps\\playwright-mcp\\node_modules\\@playwright\\mcp\\cli.js',
|
|
366
|
+
'--browser',
|
|
367
|
+
'msedge',
|
|
368
|
+
],
|
|
369
|
+
env: {},
|
|
370
|
+
}, 'playwright-mcp: a stdio MCP server run by node from its own folder, with Edge');
|
|
371
|
+
const mine = { playwright: { type: 'stdio', command: 'npx', args: ['@playwright/mcp'] } };
|
|
372
|
+
const on = withAddonMcpServers(mine, pw ? { 'sbx-playwright': pw } : {}, known);
|
|
373
|
+
const off = withAddonMcpServers({ ...on, 'sbx-context7': {} }, {}, known);
|
|
374
|
+
t.check(known.length === 2 &&
|
|
375
|
+
known.every((n) => n.startsWith('sbx-')) &&
|
|
376
|
+
context7?.name === 'sbx-context7' &&
|
|
377
|
+
'sbx-playwright' in on &&
|
|
378
|
+
'playwright' in on &&
|
|
379
|
+
Object.keys(off).join() === 'playwright' &&
|
|
380
|
+
withAddonMcpServers({ 'sbx-other': 1 }, {}, known)['sbx-other'] === 1, 'add-ons: their sbx-... MCP servers are set while on and removed when off; servers of your own stay');
|
|
381
|
+
t.check(addonGuideLine([], false).length === 0 &&
|
|
382
|
+
addonGuideLine(['sbx-playwright'], true).length === 2 &&
|
|
383
|
+
addonGuideLine(['sbx-playwright'], false)[0].includes('sbx-playwright'), 'add-ons: the agent guide names the MCP servers and ccusage only when they are on');
|
|
239
384
|
},
|
|
240
385
|
};
|
|
@@ -55,10 +55,6 @@ async function service(t) {
|
|
|
55
55
|
found.available &&
|
|
56
56
|
found.latest?.version === '1.0.1' &&
|
|
57
57
|
layout.activeVersion() === '1.0.0', 'a managed install reports its version, automatic updates off by default; check finds a newer version and changes nothing');
|
|
58
|
-
sandbox = true;
|
|
59
|
-
const blocked = await svc.run();
|
|
60
|
-
t.check(blocked?.kind === 'skipped' && blocked.reason === 'sandbox-running' && layout.activeVersion() === '1.0.0', 'a running sandbox stops the update');
|
|
61
|
-
sandbox = false;
|
|
62
58
|
const held = new UpdateLock(root, () => clock.at).acquire();
|
|
63
59
|
const whileHeld = held !== null && (await svc.run()) === null;
|
|
64
60
|
held?.();
|
|
@@ -239,10 +235,7 @@ async function command(t) {
|
|
|
239
235
|
publish(t, feed, '1.0.1', 'good');
|
|
240
236
|
lines.length = 0;
|
|
241
237
|
t.check((await runU(machine, { check: true })).includes('Version 1.0.1 is available'), '--check names the newer version and exits 0');
|
|
242
|
-
const waiting = { kind: 'skipped', reason: 'sandbox-running', current: '1.0.0' };
|
|
243
238
|
t.check(describeResult(null, false).code === 0 &&
|
|
244
|
-
describeResult(waiting, false).code === 1 &&
|
|
245
|
-
describeResult(waiting, true).code === 0 &&
|
|
246
239
|
describeResult({ kind: 'installed', version: '1.0.1', previous: '1.0.0' }, false).line.includes('from 1.0.0 to 1.0.1') &&
|
|
247
240
|
describeResult({ kind: 'failed', version: '1.0.1', reason: 'x', blocked: true, rolledBack: true, autoUpdateOff: true }, false).code === 1, 'exit codes: a running sandbox fails by hand but just waits unattended; a failed update exits 1');
|
|
248
241
|
}
|
|
@@ -86,13 +86,13 @@ export const updateSuite = {
|
|
|
86
86
|
skip('no-release'),
|
|
87
87
|
skip('up-to-date'),
|
|
88
88
|
skip('up-to-date'),
|
|
89
|
-
|
|
89
|
+
install('1.0.1'),
|
|
90
90
|
skip('too-soon'),
|
|
91
91
|
install('1.0.1'),
|
|
92
92
|
skip('failed-before'),
|
|
93
93
|
skip('failed-before'),
|
|
94
94
|
install('1.0.3'),
|
|
95
|
-
], 'only a newer release installs
|
|
95
|
+
], 'only a newer release installs, also while a sandbox runs; an hour after the last check is too soon unless forced; a failed version (and older) is not retried');
|
|
96
96
|
t.check(!isTooSoon({ lastCheck: 'not a date' }, now, 24), 'a damaged last-check time does not block updates');
|
|
97
97
|
t.section('updater: install, failure, rollback');
|
|
98
98
|
const clock = { at: new Date('2026-10-14T12:00:00Z') };
|
|
@@ -123,8 +123,8 @@ export const updateSuite = {
|
|
|
123
123
|
], [
|
|
124
124
|
{ kind: 'skipped', reason: 'too-soon', current: '1.0.1' },
|
|
125
125
|
{ kind: 'skipped', reason: 'failed-before', current: '1.0.1' },
|
|
126
|
-
{ kind: 'skipped', reason: '
|
|
127
|
-
], 'no second look within 24 hours, no retry of the failed version
|
|
126
|
+
{ kind: 'skipped', reason: 'failed-before', current: '1.0.1' },
|
|
127
|
+
], 'no second look within 24 hours, no retry of the failed version (a running sandbox is not a reason to skip)');
|
|
128
128
|
publish(t, feed, '1.0.3', 'good');
|
|
129
129
|
const third = await updater.run({ sandboxRunning: false });
|
|
130
130
|
t.check(JSON.stringify(third) === JSON.stringify({ kind: 'installed', version: '1.0.3', previous: '1.0.1' }) &&
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { AddonMcp } from './catalogue.js';
|
|
2
|
+
/** One hook command in Claude Code's settings. */
|
|
3
|
+
interface ClaudeHook {
|
|
4
|
+
readonly type: 'command';
|
|
5
|
+
readonly command: string;
|
|
6
|
+
}
|
|
7
|
+
/** Claude Code's managed-settings.json as the sandbox writes it. */
|
|
8
|
+
export interface ManagedSettings {
|
|
9
|
+
readonly permissions?: {
|
|
10
|
+
readonly deny: readonly string[];
|
|
11
|
+
};
|
|
12
|
+
readonly hooks?: {
|
|
13
|
+
readonly PreToolUse: readonly {
|
|
14
|
+
readonly matcher: string;
|
|
15
|
+
readonly hooks: readonly ClaudeHook[];
|
|
16
|
+
}[];
|
|
17
|
+
};
|
|
18
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
19
|
+
}
|
|
20
|
+
/** The command Claude Code runs for rtk's hook: the program by its full path (quoted, forward slashes for Git Bash). */
|
|
21
|
+
export declare function rtkHookCommand(exe: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* The managed settings for the git deny rules, rtk's Bash hook when rtk is installed (`rtkExe`) and the Headroom proxy
|
|
24
|
+
* when it runs (`headroomUrl`). Null when there is nothing to write (the file is removed then).
|
|
25
|
+
*/
|
|
26
|
+
export declare function claudeManagedSettings(deny: readonly string[], rtkExe: string | null, headroomUrl?: string | null): ManagedSettings | null;
|
|
27
|
+
/** The line every agent's sandbox guide gets while rtk is installed (Claude's commands go through it on their own). */
|
|
28
|
+
export declare const RTK_GUIDE_LINE = "- rtk is on PATH and compacts command output: Claude Code's Bash commands go through it on their own; in other agents prefix shell commands with `rtk` (`rtk git status`, `rtk cargo test`, `rtk ls`).";
|
|
29
|
+
/** Headroom listens here, on loopback only (its default port). */
|
|
30
|
+
export declare const HEADROOM_PORT = 8787;
|
|
31
|
+
/** The only upstream Headroom forwards to: Anthropic's API, set on its command line. */
|
|
32
|
+
export declare const HEADROOM_UPSTREAM = "https://api.anthropic.com";
|
|
33
|
+
/** Claude Code's ANTHROPIC_BASE_URL while the proxy runs. */
|
|
34
|
+
export declare function headroomUrl(port?: number): string;
|
|
35
|
+
/** `headroom proxy` on loopback, forwarding only to Anthropic's API, with no telemetry. */
|
|
36
|
+
export declare function headroomArgs(port?: number): string[];
|
|
37
|
+
/**
|
|
38
|
+
* Headroom's own switches: HEADROOM_OFFLINE refuses every connection Headroom would make on its own (telemetry, update
|
|
39
|
+
* checks, model downloads, usage polling) and keeps only the forwarding of the agent's requests.
|
|
40
|
+
*/
|
|
41
|
+
export declare const HEADROOM_ENV: Readonly<Record<string, string>>;
|
|
42
|
+
/** The environment Headroom starts with: the sandbox's, minus anything secret, plus its own switches. */
|
|
43
|
+
export declare function headroomEnv(base: Readonly<Record<string, string | undefined>>): Record<string, string>;
|
|
44
|
+
/** The guide line while the proxy runs. */
|
|
45
|
+
export declare function headroomGuideLine(logFile: string): string;
|
|
46
|
+
/** A user-scope MCP server in Claude Code's .claude.json. */
|
|
47
|
+
export interface McpServerConfig {
|
|
48
|
+
readonly type: 'stdio';
|
|
49
|
+
readonly command: string;
|
|
50
|
+
readonly args: readonly string[];
|
|
51
|
+
readonly env: Readonly<Record<string, string>>;
|
|
52
|
+
}
|
|
53
|
+
/** The name prefix of the sandbox's own MCP servers: names with it are the sandbox's to add and remove. */
|
|
54
|
+
export declare const ADDON_MCP_PREFIX = "sbx-";
|
|
55
|
+
/** The MCP server of an installed add-on: `node <folder>\<script> <args>` (Node is on PATH in every sandbox). */
|
|
56
|
+
export declare function addonMcpServer(folder: string, mcp: AddonMcp): McpServerConfig;
|
|
57
|
+
/**
|
|
58
|
+
* The user-scope MCP servers with the add-ons' servers set: every sbx-... server that `known` lists and that is not in
|
|
59
|
+
* `servers` is removed (an add-on turned off); servers of your own are never touched.
|
|
60
|
+
*/
|
|
61
|
+
export declare function withAddonMcpServers(current: Readonly<Record<string, unknown>>, servers: Readonly<Record<string, McpServerConfig>>, known: readonly string[]): Record<string, unknown>;
|
|
62
|
+
/** The guide line for the add-ons' MCP servers and tools (none: no line). */
|
|
63
|
+
export declare function addonGuideLine(mcpNames: readonly string[], ccusage: boolean): string[];
|
|
64
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/** The command Claude Code runs for rtk's hook: the program by its full path (quoted, forward slashes for Git Bash). */
|
|
2
|
+
export function rtkHookCommand(exe) {
|
|
3
|
+
return '"' + exe.replace(/\\/g, '/') + '" hook claude';
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* The managed settings for the git deny rules, rtk's Bash hook when rtk is installed (`rtkExe`) and the Headroom proxy
|
|
7
|
+
* when it runs (`headroomUrl`). Null when there is nothing to write (the file is removed then).
|
|
8
|
+
*/
|
|
9
|
+
export function claudeManagedSettings(deny, rtkExe, headroomUrl = null) {
|
|
10
|
+
if (!deny.length && !rtkExe && !headroomUrl)
|
|
11
|
+
return null;
|
|
12
|
+
return {
|
|
13
|
+
...(deny.length ? { permissions: { deny } } : {}),
|
|
14
|
+
...(rtkExe
|
|
15
|
+
? { hooks: { PreToolUse: [{ matcher: 'Bash', hooks: [{ type: 'command', command: rtkHookCommand(rtkExe) }] }] } }
|
|
16
|
+
: {}),
|
|
17
|
+
...(headroomUrl ? { env: { ANTHROPIC_BASE_URL: headroomUrl } } : {}),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** The line every agent's sandbox guide gets while rtk is installed (Claude's commands go through it on their own). */
|
|
21
|
+
export const RTK_GUIDE_LINE = "- rtk is on PATH and compacts command output: Claude Code's Bash commands go through it on their own; in other agents prefix shell commands with `rtk` (`rtk git status`, `rtk cargo test`, `rtk ls`).";
|
|
22
|
+
/** Headroom listens here, on loopback only (its default port). */
|
|
23
|
+
export const HEADROOM_PORT = 8787;
|
|
24
|
+
/** The only upstream Headroom forwards to: Anthropic's API, set on its command line. */
|
|
25
|
+
export const HEADROOM_UPSTREAM = 'https://api.anthropic.com';
|
|
26
|
+
/** Claude Code's ANTHROPIC_BASE_URL while the proxy runs. */
|
|
27
|
+
export function headroomUrl(port = HEADROOM_PORT) {
|
|
28
|
+
return 'http://127.0.0.1:' + port;
|
|
29
|
+
}
|
|
30
|
+
/** `headroom proxy` on loopback, forwarding only to Anthropic's API, with no telemetry. */
|
|
31
|
+
export function headroomArgs(port = HEADROOM_PORT) {
|
|
32
|
+
return [
|
|
33
|
+
'proxy',
|
|
34
|
+
'--host',
|
|
35
|
+
'127.0.0.1',
|
|
36
|
+
'--port',
|
|
37
|
+
String(port),
|
|
38
|
+
'--anthropic-api-url',
|
|
39
|
+
HEADROOM_UPSTREAM,
|
|
40
|
+
'--no-telemetry',
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Headroom's own switches: HEADROOM_OFFLINE refuses every connection Headroom would make on its own (telemetry, update
|
|
45
|
+
* checks, model downloads, usage polling) and keeps only the forwarding of the agent's requests.
|
|
46
|
+
*/
|
|
47
|
+
export const HEADROOM_ENV = {
|
|
48
|
+
HEADROOM_OFFLINE: '1',
|
|
49
|
+
HEADROOM_TELEMETRY: 'off',
|
|
50
|
+
HEADROOM_BEACON: 'off',
|
|
51
|
+
HEADROOM_UPDATE_CHECK: 'off',
|
|
52
|
+
DO_NOT_TRACK: '1',
|
|
53
|
+
HF_HUB_OFFLINE: '1',
|
|
54
|
+
PYTHONIOENCODING: 'utf-8',
|
|
55
|
+
};
|
|
56
|
+
/** Variables the proxy never gets: keys, tokens and passwords of the agents (it only forwards what Claude sends). */
|
|
57
|
+
const SECRET_NAME = /(KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)/i;
|
|
58
|
+
/** The environment Headroom starts with: the sandbox's, minus anything secret, plus its own switches. */
|
|
59
|
+
export function headroomEnv(base) {
|
|
60
|
+
const env = {};
|
|
61
|
+
for (const [name, value] of Object.entries(base))
|
|
62
|
+
if (value !== undefined && !SECRET_NAME.test(name) && !/^(ANTHROPIC|OPENAI|CLAUDE)_/i.test(name))
|
|
63
|
+
env[name] = value;
|
|
64
|
+
return { ...env, ...HEADROOM_ENV };
|
|
65
|
+
}
|
|
66
|
+
/** The guide line while the proxy runs. */
|
|
67
|
+
export function headroomGuideLine(logFile) {
|
|
68
|
+
return ("- Headroom compresses Claude Code's requests: Claude Code reaches the API through its proxy on " +
|
|
69
|
+
headroomUrl() +
|
|
70
|
+
' (log: `' +
|
|
71
|
+
logFile +
|
|
72
|
+
'`). If Claude Code cannot reach the API, the proxy stopped: it starts again with the next sandbox, or turn Headroom off in Apps.');
|
|
73
|
+
}
|
|
74
|
+
/** The name prefix of the sandbox's own MCP servers: names with it are the sandbox's to add and remove. */
|
|
75
|
+
export const ADDON_MCP_PREFIX = 'sbx-';
|
|
76
|
+
/** The MCP server of an installed add-on: `node <folder>\<script> <args>` (Node is on PATH in every sandbox). */
|
|
77
|
+
export function addonMcpServer(folder, mcp) {
|
|
78
|
+
const script = folder.replace(/[\\/]+$/, '') + '\\' + mcp.script.replace(/\//g, '\\');
|
|
79
|
+
return { type: 'stdio', command: 'node', args: [script, ...(mcp.args ?? [])], env: {} };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The user-scope MCP servers with the add-ons' servers set: every sbx-... server that `known` lists and that is not in
|
|
83
|
+
* `servers` is removed (an add-on turned off); servers of your own are never touched.
|
|
84
|
+
*/
|
|
85
|
+
export function withAddonMcpServers(current, servers, known) {
|
|
86
|
+
const kept = Object.fromEntries(Object.entries(current).filter(([name]) => !known.includes(name) || name in servers));
|
|
87
|
+
return { ...kept, ...servers };
|
|
88
|
+
}
|
|
89
|
+
/** The guide line for the add-ons' MCP servers and tools (none: no line). */
|
|
90
|
+
export function addonGuideLine(mcpNames, ccusage) {
|
|
91
|
+
return [
|
|
92
|
+
...(mcpNames.length ? ['- MCP servers in Claude Code from Apps: ' + mcpNames.join(', ') + '.'] : []),
|
|
93
|
+
...(ccusage ? ['- ccusage is on PATH: `ccusage daily` (or `monthly`, `session`) reports token use and cost.'] : []),
|
|
94
|
+
];
|
|
95
|
+
}
|
|
@@ -1,20 +1,36 @@
|
|
|
1
|
-
export type AppCategory = 'editors' | 'databases' | 'api' | 'git' | 'utilities' | 'browsers' | 'gamedev' | 'cloud' | 'diagrams' | 'network' | 'reversing' | 'cli';
|
|
1
|
+
export type AppCategory = 'agents' | 'editors' | 'databases' | 'api' | 'git' | 'utilities' | 'browsers' | 'gamedev' | 'cloud' | 'diagrams' | 'network' | 'reversing' | 'cli';
|
|
2
2
|
export declare const APP_CATEGORIES: {
|
|
3
3
|
id: AppCategory;
|
|
4
4
|
label: string;
|
|
5
5
|
description: string;
|
|
6
6
|
}[];
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Where a download comes from. Resolvers (sublime, kubectl ...) are code in guest/apps, named here by id. `locked`: a
|
|
9
|
+
* pinned package installed from the lock file in sandbox\addons\<id> (npm: package.json + package-lock.json, every
|
|
10
|
+
* tarball checked against its integrity; uv: requirements.txt, every wheel checked against its SHA-256).
|
|
11
|
+
*/
|
|
8
12
|
export type AppSource = {
|
|
9
13
|
github: string;
|
|
10
14
|
asset: string;
|
|
11
15
|
} | {
|
|
12
16
|
url: string;
|
|
17
|
+
sha256?: string;
|
|
13
18
|
} | {
|
|
14
19
|
resolve: string;
|
|
15
20
|
} | {
|
|
16
21
|
npm: string;
|
|
22
|
+
} | {
|
|
23
|
+
locked: 'npm' | 'uv';
|
|
24
|
+
package: string;
|
|
25
|
+
version: string;
|
|
17
26
|
};
|
|
27
|
+
/** An MCP server an add-on adds to the sandbox's Claude Code: `node <its folder>\<script> <args>`. */
|
|
28
|
+
export interface AddonMcp {
|
|
29
|
+
/** The server's name in Claude Code (sbx-...: the sandbox removes its own names again when the add-on is off). */
|
|
30
|
+
readonly name: string;
|
|
31
|
+
readonly script: string;
|
|
32
|
+
readonly args?: readonly string[];
|
|
33
|
+
}
|
|
18
34
|
export interface AppEntry {
|
|
19
35
|
id: string;
|
|
20
36
|
name: string;
|
|
@@ -39,6 +55,21 @@ export interface AppEntry {
|
|
|
39
55
|
setting?: string;
|
|
40
56
|
/** Only with full git access (it pushes on its own, past the sandbox's git guard). */
|
|
41
57
|
fullGitOnly?: boolean;
|
|
58
|
+
/** Which agents it plugs into, and how (agent add-ons). */
|
|
59
|
+
plugsInto?: string;
|
|
60
|
+
/** Only in your own settings: a project's .sandbox-config.json cannot turn it on (it changes what the agents run). */
|
|
61
|
+
userOnly?: boolean;
|
|
62
|
+
/** Started by the sandbox or the agent (a proxy, an MCP server): not on PATH and no shortcut. */
|
|
63
|
+
service?: boolean;
|
|
64
|
+
/** The MCP server it adds to the sandbox's Claude Code while it is on. */
|
|
65
|
+
mcp?: AddonMcp;
|
|
42
66
|
}
|
|
67
|
+
/** rtk (rtk-ai/rtk), pinned: the Apache-2.0 edition's Windows build. The checksum is the release's checksums.txt. */
|
|
68
|
+
export declare const RTK_VERSION = "1.0.1";
|
|
69
|
+
/** Pinned agent add-ons, installed from the lock files in sandbox\addons\<id> (renewing them: docs/toolchains.md). */
|
|
70
|
+
export declare const HEADROOM_VERSION = "0.41.0";
|
|
71
|
+
export declare const CCUSAGE_VERSION = "20.0.36";
|
|
72
|
+
export declare const PLAYWRIGHT_MCP_VERSION = "0.0.83";
|
|
73
|
+
export declare const CONTEXT7_MCP_VERSION = "4.4.2";
|
|
43
74
|
export declare const APPS: AppEntry[];
|
|
44
75
|
export declare function appById(id: string): AppEntry | undefined;
|