nebula-ai-core 0.1.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.
Files changed (109) hide show
  1. package/README.md +24 -0
  2. package/package.json +69 -0
  3. package/src/brain/compaction.ts +131 -0
  4. package/src/brain/frozen-prefix.ts +320 -0
  5. package/src/brain/history-persist.ts +154 -0
  6. package/src/brain/index.ts +43 -0
  7. package/src/brain/openai-brain.ts +533 -0
  8. package/src/brain/sanitize.ts +23 -0
  9. package/src/brain/stub.ts +20 -0
  10. package/src/brain/types.ts +129 -0
  11. package/src/chain.ts +75 -0
  12. package/src/claude-plugins/discovery.ts +152 -0
  13. package/src/claude-plugins/index.ts +6 -0
  14. package/src/claude-plugins/types.ts +38 -0
  15. package/src/commands/index.ts +16 -0
  16. package/src/commands/registry.ts +255 -0
  17. package/src/config.ts +213 -0
  18. package/src/economy/index.ts +6 -0
  19. package/src/events/index.ts +4 -0
  20. package/src/events/listeners.ts +37 -0
  21. package/src/events/queue.ts +63 -0
  22. package/src/events/router.ts +42 -0
  23. package/src/events/types.ts +28 -0
  24. package/src/format.ts +12 -0
  25. package/src/identity/agent-card.ts +110 -0
  26. package/src/identity/deployments.ts +20 -0
  27. package/src/identity/erc8004.ts +161 -0
  28. package/src/identity/index.ts +29 -0
  29. package/src/identity/keystore-blob.ts +60 -0
  30. package/src/identity/receipt.ts +27 -0
  31. package/src/identity/stub.ts +29 -0
  32. package/src/identity/types.ts +20 -0
  33. package/src/index.ts +372 -0
  34. package/src/locks.ts +233 -0
  35. package/src/mcp/discovery.ts +150 -0
  36. package/src/mcp/index.ts +10 -0
  37. package/src/mcp/manager.ts +110 -0
  38. package/src/mcp/stdio-client.ts +154 -0
  39. package/src/mcp/types.ts +44 -0
  40. package/src/memory/edit.ts +53 -0
  41. package/src/memory/encryption.ts +88 -0
  42. package/src/memory/fs-util.ts +15 -0
  43. package/src/memory/index-file.ts +74 -0
  44. package/src/memory/index-sync.ts +99 -0
  45. package/src/memory/index.ts +58 -0
  46. package/src/memory/list-tool.ts +105 -0
  47. package/src/memory/pack-blob.ts +120 -0
  48. package/src/memory/pack-gather.ts +112 -0
  49. package/src/memory/parser.ts +20 -0
  50. package/src/memory/read-tool.ts +198 -0
  51. package/src/memory/save-tool.ts +189 -0
  52. package/src/memory/scan.ts +63 -0
  53. package/src/memory/topic.ts +32 -0
  54. package/src/memory/types.ts +49 -0
  55. package/src/migration/index.ts +6 -0
  56. package/src/migration/option3-crypto.ts +127 -0
  57. package/src/operator/index.ts +9 -0
  58. package/src/operator/keychain.ts +53 -0
  59. package/src/operator/keystore-file.ts +33 -0
  60. package/src/operator/privkey-base.ts +60 -0
  61. package/src/operator/raw-privkey.ts +39 -0
  62. package/src/operator/signer.ts +46 -0
  63. package/src/operator/walletconnect.ts +454 -0
  64. package/src/pairing.ts +285 -0
  65. package/src/paths.ts +70 -0
  66. package/src/permission/dangerous.ts +108 -0
  67. package/src/permission/env-redact.ts +54 -0
  68. package/src/permission/index.ts +16 -0
  69. package/src/permission/path-guard.ts +114 -0
  70. package/src/permission/service.ts +191 -0
  71. package/src/plugins/context.ts +225 -0
  72. package/src/plugins/hooks.ts +81 -0
  73. package/src/plugins/index.ts +24 -0
  74. package/src/plugins/tool-search.ts +49 -0
  75. package/src/public/card.ts +67 -0
  76. package/src/runtime/activity.ts +29 -0
  77. package/src/runtime/index.ts +2 -0
  78. package/src/runtime/runtime.ts +113 -0
  79. package/src/sandbox/credentials.ts +25 -0
  80. package/src/sandbox/docker.ts +396 -0
  81. package/src/sandbox/factory.ts +99 -0
  82. package/src/sandbox/index.ts +15 -0
  83. package/src/sandbox/linux.ts +141 -0
  84. package/src/sandbox/local.ts +19 -0
  85. package/src/sandbox/macos.ts +71 -0
  86. package/src/sandbox/seatbelt-profile.ts +139 -0
  87. package/src/sandbox/types.ts +129 -0
  88. package/src/skills/index.ts +8 -0
  89. package/src/skills/scanner.ts +257 -0
  90. package/src/skills/triggers.ts +78 -0
  91. package/src/skills/types.ts +37 -0
  92. package/src/storage/encryption.ts +87 -0
  93. package/src/storage/factory.ts +31 -0
  94. package/src/storage/index.ts +11 -0
  95. package/src/storage/local-stub.ts +70 -0
  96. package/src/storage/sqlite.ts +95 -0
  97. package/src/storage/types.ts +21 -0
  98. package/src/tools/escalation.ts +200 -0
  99. package/src/tools/index.ts +11 -0
  100. package/src/tools/registry.ts +152 -0
  101. package/src/tools/types.ts +65 -0
  102. package/src/tools/zod-helpers.ts +36 -0
  103. package/src/tools/zod-schema.ts +99 -0
  104. package/src/wallet/drain.ts +79 -0
  105. package/src/wallet/eoa.ts +51 -0
  106. package/src/wallet/index.ts +47 -0
  107. package/src/wallet/keystore.ts +50 -0
  108. package/src/wallet/operator-keystore-crypto.ts +530 -0
  109. package/src/wallet/operator-session.ts +344 -0
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Linux bubblewrap (`bwrap`) backend. Mirrors the macOS sandbox-exec backend
3
+ * for Linux operators. Wraps every tool spawn in:
4
+ *
5
+ * bwrap <profile-args...> <orig-command> <orig-args...>
6
+ *
7
+ * `bwrap` is unprivileged user-namespace sandboxing — the same primitive
8
+ * Flatpak / Bubblewrap / chromium use. No setuid required (kernel must have
9
+ * unprivileged user namespaces enabled, which is the default on every modern
10
+ * distro: Ubuntu 22+, Fedora, Arch, Debian 11+).
11
+ *
12
+ * Profile policy mirrors macOS seatbelt:
13
+ * - read-only bind of / (so commands like `cat`, `ls`, `find` work)
14
+ * - writable bind of agentDir + workspaceRoot
15
+ * - writable bind of /tmp (nebula-* dirs land there)
16
+ * - tmpfs overlay of credential dirs (~/.ssh, ~/.aws, ~/Library/Keychains
17
+ * [doesn't exist on Linux but cheap to include for portability],
18
+ * ~/.config/gcloud) — reads return empty
19
+ * - --unshare-all --share-net keeps network reachable so nebula can still
20
+ * hit Mantle RPC, the indexer, etc.
21
+ * - --die-with-parent kills the child if nebula crashes, no zombies
22
+ * - --new-session puts the child in its own session (Ctrl-C from nebula
23
+ * doesn't propagate to the inner command)
24
+ */
25
+
26
+ import { existsSync } from 'node:fs'
27
+ import { credentialDirs } from './credentials'
28
+ import type {
29
+ SandboxBackend,
30
+ SandboxBackendOpts,
31
+ SandboxEnvHint,
32
+ SandboxSpawnRequest,
33
+ WrappedSpawn,
34
+ } from './types'
35
+
36
+ /**
37
+ * Probe order for bwrap binary. Most distros put it at /usr/bin/bwrap; some
38
+ * via pkg-managed systems at /usr/local/bin. First existing path wins.
39
+ */
40
+ const BWRAP_CANDIDATES: ReadonlyArray<string> = ['/usr/bin/bwrap', '/usr/local/bin/bwrap']
41
+
42
+ function findBwrap(): string | null {
43
+ for (const path of BWRAP_CANDIDATES) {
44
+ if (existsSync(path)) return path
45
+ }
46
+ return null
47
+ }
48
+
49
+ /**
50
+ * Build the bwrap argv prefix that wraps the user command. Returns a flat
51
+ * argv array; the actual command + args are appended after.
52
+ */
53
+ export function buildBwrapArgs(opts: SandboxBackendOpts): string[] {
54
+ const args: string[] = []
55
+
56
+ // Base filesystem: read-only bind of /. The container can read system tools
57
+ // (cat, ls, etc.) but cannot modify them. Override specific subdirs below.
58
+ args.push('--ro-bind', '/', '/')
59
+
60
+ // Writable subdirs: agentDir + workspaceRoot + /tmp (for nebula-* test dirs)
61
+ args.push('--bind', opts.agentDir, opts.agentDir)
62
+ args.push('--bind', opts.workspaceRoot, opts.workspaceRoot)
63
+ args.push('--bind', '/tmp', '/tmp')
64
+
65
+ // Optional extra-writable subpaths (test sandbox dirs, custom workspaces).
66
+ if (opts.extraWriteAllow) {
67
+ for (const path of opts.extraWriteAllow) {
68
+ args.push('--bind', path, path)
69
+ }
70
+ }
71
+
72
+ // Credential blackouts: empty tmpfs overlays so reads return ENOENT-equivalent.
73
+ // Shared list with seatbelt-profile.ts to prevent platform drift.
74
+ for (const dir of credentialDirs(opts.homedir)) {
75
+ args.push('--tmpfs', dir)
76
+ }
77
+
78
+ // Optional extra denies via tmpfs.
79
+ if (opts.extraWriteDeny) {
80
+ for (const path of opts.extraWriteDeny) {
81
+ args.push('--tmpfs', path)
82
+ }
83
+ }
84
+
85
+ // System pseudo-filesystems.
86
+ args.push('--proc', '/proc')
87
+ args.push('--dev', '/dev')
88
+
89
+ // Namespace isolation: unshare everything except network (nebula needs network
90
+ // for Mantle RPC, indexer, brain inference). PID namespace isolates process tree
91
+ // from host. UTS namespace gives the sandbox its own hostname.
92
+ args.push('--unshare-all', '--share-net')
93
+
94
+ // Lifetime + signal handling.
95
+ args.push('--die-with-parent') // child dies if nebula dies
96
+ args.push('--new-session') // Ctrl-C from nebula doesn't kill the child directly
97
+
98
+ return args
99
+ }
100
+
101
+ export class LinuxBubblewrapBackend implements SandboxBackend {
102
+ readonly mode = 'os' as const
103
+ readonly label = 'os:linux'
104
+ private readonly bwrapPath: string
105
+ private readonly bwrapArgs: string[]
106
+
107
+ constructor(opts: SandboxBackendOpts) {
108
+ const path = findBwrap()
109
+ if (!path) {
110
+ throw new Error(
111
+ `bwrap not found in ${BWRAP_CANDIDATES.join(', ')}. Linux sandbox backend requires bubblewrap (apt install bubblewrap / dnf install bubblewrap).`,
112
+ )
113
+ }
114
+ this.bwrapPath = path
115
+ this.bwrapArgs = buildBwrapArgs(opts)
116
+ }
117
+
118
+ /** Test-only accessor for the bwrap argv prefix. */
119
+ getBwrapArgs(): readonly string[] {
120
+ return this.bwrapArgs
121
+ }
122
+
123
+ envHint(): SandboxEnvHint {
124
+ return {
125
+ mode: 'os',
126
+ label: this.label,
127
+ innerOs: 'linux',
128
+ workspaceMount: null,
129
+ scope:
130
+ 'shell.run, code.execute, shell.process_start are wrapped in a bubblewrap profile; writes outside agentDir + cwd + /tmp/nebula-* are denied',
131
+ }
132
+ }
133
+
134
+ async wrapSpawn(req: SandboxSpawnRequest): Promise<WrappedSpawn> {
135
+ return {
136
+ command: this.bwrapPath,
137
+ args: [...this.bwrapArgs, '--', req.command, ...req.args],
138
+ options: req.options,
139
+ }
140
+ }
141
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Passthrough backend. Used when `sandbox.mode = 'none'` (today's default for
3
+ * back-compat) or when the platform doesn't support `os` mode.
4
+ */
5
+
6
+ import type { SandboxBackend, SandboxSpawnRequest, WrappedSpawn } from './types'
7
+
8
+ export class LocalBackend implements SandboxBackend {
9
+ readonly mode = 'none' as const
10
+ readonly label = 'none'
11
+
12
+ async wrapSpawn(req: SandboxSpawnRequest): Promise<WrappedSpawn> {
13
+ return {
14
+ command: req.command,
15
+ args: req.args,
16
+ options: req.options,
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * macOS sandbox-exec backend. Wraps every tool spawn in:
3
+ *
4
+ * sandbox-exec -p '<seatbelt-profile>' <orig-command> <orig-args...>
5
+ *
6
+ * `sandbox-exec` is at /usr/bin/sandbox-exec on every macOS install. The
7
+ * `man` page calls it "deprecated" in favour of the modern App Sandbox API,
8
+ * but it's still ships and is used by Apple internally; verified working on
9
+ * macOS 25.4.0. The deprecation is a recommendation that new GUI apps adopt
10
+ * App Sandbox, not a removal.
11
+ *
12
+ * The profile is built once at backend-init and reused for every spawn. The
13
+ * profile is passed inline via `-p` (no temp file management needed).
14
+ */
15
+
16
+ import { existsSync } from 'node:fs'
17
+ import { buildSeatbeltProfile } from './seatbelt-profile'
18
+ import type {
19
+ SandboxBackend,
20
+ SandboxBackendOpts,
21
+ SandboxEnvHint,
22
+ SandboxSpawnRequest,
23
+ WrappedSpawn,
24
+ } from './types'
25
+
26
+ const SANDBOX_EXEC_PATH = '/usr/bin/sandbox-exec'
27
+
28
+ export class MacOSSandboxExecBackend implements SandboxBackend {
29
+ readonly mode = 'os' as const
30
+ readonly label = 'os:darwin'
31
+ private readonly profile: string
32
+
33
+ constructor(opts: SandboxBackendOpts) {
34
+ if (!existsSync(SANDBOX_EXEC_PATH)) {
35
+ throw new Error(
36
+ `sandbox-exec not found at ${SANDBOX_EXEC_PATH}. macOS sandbox backend requires the system tool.`,
37
+ )
38
+ }
39
+ this.profile = buildSeatbeltProfile({
40
+ agentDir: opts.agentDir,
41
+ workspaceRoot: opts.workspaceRoot,
42
+ homedir: opts.homedir,
43
+ extraWriteAllow: opts.extraWriteAllow,
44
+ extraWriteDeny: opts.extraWriteDeny,
45
+ })
46
+ }
47
+
48
+ /** Test-only accessor for the rendered profile. */
49
+ getProfile(): string {
50
+ return this.profile
51
+ }
52
+
53
+ envHint(): SandboxEnvHint {
54
+ return {
55
+ mode: 'os',
56
+ label: this.label,
57
+ innerOs: 'darwin',
58
+ workspaceMount: null,
59
+ scope:
60
+ 'shell.run, code.execute, shell.process_start are wrapped in sandbox-exec; writes outside agentDir + cwd + /tmp/nebula-* are denied',
61
+ }
62
+ }
63
+
64
+ async wrapSpawn(req: SandboxSpawnRequest): Promise<WrappedSpawn> {
65
+ return {
66
+ command: SANDBOX_EXEC_PATH,
67
+ args: ['-p', this.profile, req.command, ...req.args],
68
+ options: req.options,
69
+ }
70
+ }
71
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * macOS seatbelt (SBPL) profile generator. Used by MacOSSandboxExecBackend to
3
+ * build the `-p` argument for `sandbox-exec`.
4
+ *
5
+ * Profile policy (deny-default + targeted allows):
6
+ *
7
+ * READS: broad (allow file-read*). Reading is fine; it's writes + network
8
+ * exfil + process-fork-into-system that need gating. The brain's job is to
9
+ * help with files; we don't want it crippled on read.
10
+ *
11
+ * WRITES: deny default, allow ONLY:
12
+ * - agentDir (nebula state)
13
+ * - workspaceRoot (the cwd nebula was launched from; fs.write authorized
14
+ * through the modal lands here)
15
+ * - /tmp/nebula-* (nebula's own temp scratch — code.execute snippets, etc.)
16
+ * - /private/tmp/nebula-* (macOS canonical /tmp)
17
+ * - /var/folders (macOS user temp dir, where mkdtemp() defaults land)
18
+ * - any extra subpaths in `extraWriteAllow`
19
+ *
20
+ * EXPLICIT WRITE DENY (overrides allows on overlap):
21
+ * - $HOME/.ssh
22
+ * - $HOME/.aws
23
+ * - $HOME/Library/Keychains
24
+ * - $HOME/.config/gcloud
25
+ * - $HOME/.nebula (the broader nebula state tree — only the agent's own
26
+ * agentDir is allowed; brain shouldn't rewrite ~/.nebula/config.ts)
27
+ *
28
+ * NETWORK: allow* (nebula legitimately needs Mantle RPC, indexer, compute,
29
+ * WC relay, plus user-asked-for HTTP). Future hardening: allowlist by host.
30
+ *
31
+ * PROCESS: allow process-fork + process-exec (tools spawn child binaries).
32
+ * IPC: allow mach-lookup, ipc-posix-shm, signal — needed by most CLI
33
+ * tooling, otherwise the simplest commands fail.
34
+ *
35
+ * The seatbelt syntax is Apple's internal SBPL (Scheme-like). It's
36
+ * undocumented officially but stable across macOS versions; deprecated in
37
+ * `man sandbox-exec` but still functional and used by Apple itself for many
38
+ * system services.
39
+ *
40
+ * NOTE on order: in seatbelt SBPL, deny rules placed AFTER allows take
41
+ * precedence on overlap. So we put the denylist after the allowlist for
42
+ * credentials.
43
+ */
44
+
45
+ import { credentialDirs } from './credentials'
46
+
47
+ export interface SeatbeltProfileOpts {
48
+ agentDir: string
49
+ workspaceRoot: string
50
+ homedir: string
51
+ extraWriteAllow?: string[]
52
+ extraWriteDeny?: string[]
53
+ }
54
+
55
+ /**
56
+ * Escape a path for safe inclusion in an SBPL string literal. Seatbelt strings
57
+ * are double-quoted; embedded backslashes and double quotes need escaping.
58
+ * Newlines also break the parser. Nebula paths come from process.cwd() and
59
+ * homedir() so they're well-formed Unix paths in practice, but we escape
60
+ * defensively.
61
+ */
62
+ function sbplEscape(p: string): string {
63
+ return p.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, ' ')
64
+ }
65
+
66
+ export function buildSeatbeltProfile(opts: SeatbeltProfileOpts): string {
67
+ const home = sbplEscape(opts.homedir)
68
+ const agent = sbplEscape(opts.agentDir)
69
+ const workspace = sbplEscape(opts.workspaceRoot)
70
+
71
+ const allowSubpaths = [
72
+ `(allow file-write* (subpath "${agent}"))`,
73
+ `(allow file-write* (subpath "${workspace}"))`,
74
+ `(allow file-write* (regex #"^/tmp/nebula-"))`,
75
+ `(allow file-write* (regex #"^/private/tmp/nebula-"))`,
76
+ `(allow file-write* (subpath "/var/folders"))`,
77
+ `(allow file-write* (subpath "/private/var/folders"))`,
78
+ ...(opts.extraWriteAllow ?? []).map(p => `(allow file-write* (subpath "${sbplEscape(p)}"))`),
79
+ ].join('\n ')
80
+
81
+ const credDirs = credentialDirs(opts.homedir).map(sbplEscape)
82
+ const denySubpaths = [
83
+ ...credDirs.map(p => `(deny file-write* (subpath "${p}"))`),
84
+ `(deny file-write* (subpath "${home}/.nebula"))`,
85
+ ...(opts.extraWriteDeny ?? []).map(p => `(deny file-write* (subpath "${sbplEscape(p)}"))`),
86
+ ].join('\n ')
87
+
88
+ // Read-side: allow broadly (the agent legitimately needs to read system
89
+ // binaries, libraries, project files, etc.) but EXPLICITLY deny credential
90
+ // dirs. This blocks `cat ~/.ssh/id_rsa` -- shell.run that bypasses
91
+ // PathGuard's tool-level checks. Network is broad; if exfil is a concern
92
+ // (read public file + POST somewhere), use Docker mode.
93
+ const denyReadSubpaths = credDirs.map(p => `(deny file-read* (subpath "${p}"))`).join('\n ')
94
+
95
+ // The agentDir is under ~/.nebula/agents/<id>/, and we deny ~/.nebula broadly,
96
+ // so we MUST re-allow agentDir AFTER the deny to keep nebula's own state
97
+ // writable. SBPL is order-sensitive: later rules win on overlap.
98
+ return `(version 1)
99
+ (deny default)
100
+
101
+ ;; Process management — tools spawn binaries.
102
+ (allow process-fork)
103
+ (allow process-exec)
104
+
105
+ ;; IPC + system bookkeeping that any non-trivial CLI needs.
106
+ (allow mach-lookup)
107
+ (allow mach-priv-host-port)
108
+ (allow mach-task-name)
109
+ (allow ipc-posix-shm)
110
+ (allow signal)
111
+ (allow sysctl-read)
112
+ (allow sysctl-write)
113
+ (allow system-fsctl)
114
+ (allow system-info)
115
+ (allow system-socket)
116
+ (allow iokit-open)
117
+
118
+ ;; Network — broad. Nebula needs Mantle RPC, indexer, compute, WC relay, plus
119
+ ;; arbitrary HTTP for browse + brain-driven fetches. Tighten via allowlist
120
+ ;; when we have explicit host policy.
121
+ (allow network*)
122
+
123
+ ;; Reads — broad by default so binaries/libraries/project files work.
124
+ (allow file-read*)
125
+
126
+ ;; Explicit credential read denies (override allow file-read*).
127
+ ${denyReadSubpaths}
128
+
129
+ ;; Writes — deny default, allowlist:
130
+ ${allowSubpaths}
131
+
132
+ ;; Explicit credential + state-tree denies (override allowlist on overlap).
133
+ ${denySubpaths}
134
+
135
+ ;; Re-allow agentDir AFTER the ~/.nebula broad deny so nebula's own state is
136
+ ;; writable. SBPL applies later rules first, so this comes last.
137
+ (allow file-write* (subpath "${agent}"))
138
+ `
139
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Sandbox abstraction for limb execution.
3
+ *
4
+ * Phase 9.5 (Apr 28 2026 incident response). Nebula's limbs run on the operator's
5
+ * host. Permission floors (PathGuard + dangerous-pattern modal + strict/prompt/yolo)
6
+ * caught the rm correctly during the v0.9.3 benchmark, but once the modal granted
7
+ * `s` (allow session), the command ran on the real host with full FS access. The
8
+ * cascade (tmux socket → daemon detach → orphan name-slot blocking) was severe.
9
+ *
10
+ * This module adds a structural layer BENEATH the permission floor: every
11
+ * spawn() call from a tool is routed through a `SandboxBackend` which can wrap
12
+ * the command in an OS sandbox before execution. Even if the permission floor
13
+ * is bypassed (yolo, allow-session, allow-once), the sandbox profile prevents
14
+ * writes outside an allowlist.
15
+ *
16
+ * Mirrors hermes-agent's TERMINAL_ENV pattern (local | docker | modal | daytona |
17
+ * singularity | ssh) but starts smaller: `none` (passthrough) and `os` (macOS
18
+ * sandbox-exec / future Linux bubblewrap). Docker mode is a separate followup
19
+ * bundle.
20
+ */
21
+
22
+ import type { SpawnOptions } from 'node:child_process'
23
+
24
+ /**
25
+ * Mode selector. Lives under `sandbox.mode` in `~/.nebula/config.ts`.
26
+ *
27
+ * - `none`: passthrough (today's behaviour). No sandboxing applied. Default
28
+ * for backward compatibility while Tier 2 stabilizes.
29
+ * - `os`: native OS sandbox. macOS uses sandbox-exec with a deny-default
30
+ * seatbelt profile. Linux uses bubblewrap (post-MVP). On unsupported
31
+ * platforms falls back to `none` with a startup warning.
32
+ * - `docker`: long-lived container per session, every spawn goes through
33
+ * `docker exec`. Future bundle.
34
+ */
35
+ export type SandboxMode = 'none' | 'os' | 'docker'
36
+
37
+ /**
38
+ * Inputs the factory needs to construct a backend.
39
+ * - `agentDir`: write-allowed (nebula writes activity log, mcp debug, etc.).
40
+ * - `workspaceRoot`: write-allowed (where the operator launched nebula from;
41
+ * fs.write/fs.patch authorized through the modal land here).
42
+ * - `homedir`: used by the seatbelt profile to deny secret-bearing subdirs
43
+ * (`~/.ssh`, `~/.aws`, `~/Library/Keychains`, `~/.config/gcloud`).
44
+ * - `extraWriteAllow`: optional extra subpaths to allow writes under (test
45
+ * sandbox dirs, custom workspaces).
46
+ * - `extraWriteDeny`: optional extra subpaths to explicitly block writes.
47
+ */
48
+ export interface SandboxBackendOpts {
49
+ agentDir: string
50
+ workspaceRoot: string
51
+ homedir: string
52
+ extraWriteAllow?: string[]
53
+ extraWriteDeny?: string[]
54
+ }
55
+
56
+ /**
57
+ * One spawn request, fully described before the backend wraps it. We pass
58
+ * `argv` rather than the legacy `(command, options)` form because backends
59
+ * that prepend `sandbox-exec -p ...` need to construct an explicit argv;
60
+ * mixing `shell: true` with a wrapper produces confused quoting.
61
+ */
62
+ export interface WrappedSpawn {
63
+ /** The binary that should actually be exec'd. May be the original, may be a wrapper. */
64
+ command: string
65
+ /** Args to pass. Wrapper backends prepend their own. */
66
+ args: string[]
67
+ /** SpawnOptions to pass through. `shell` is intentionally omitted because the wrapper builds the explicit argv. */
68
+ options: SpawnOptions
69
+ }
70
+
71
+ /**
72
+ * Inputs the tool layer hands the backend per spawn. The backend wraps the
73
+ * argv (e.g. prepend `sandbox-exec -p <profile>` or rewrite as `docker exec
74
+ * <containerId> ...`). For shell.run-style tools, the caller MUST pass an
75
+ * explicit argv (`command='/bin/sh', args=['-c', userCommand]`) — the backend
76
+ * cannot use `shell: true` because the wrapper builds the argv itself.
77
+ */
78
+ export interface SandboxSpawnRequest {
79
+ command: string
80
+ args: string[]
81
+ options: SpawnOptions
82
+ }
83
+
84
+ /**
85
+ * Environment hint surfaced to the brain via the frozen prefix's # Environment
86
+ * block. Lets the brain skip the "run pwd + ls / + uname to figure out where
87
+ * I am" empirical-discovery dance — saves wasted tool calls when the brain
88
+ * defaults to host-style commands inside a Linux container (BSD sed, fs.read
89
+ * /workspace ENOENT, etc.).
90
+ *
91
+ * Each non-passthrough backend implements `envHint()` to surface its specific
92
+ * shape. `LocalBackend` returns null (no sandbox, no hint).
93
+ */
94
+ export interface SandboxEnvHint {
95
+ mode: SandboxMode
96
+ label: string
97
+ innerOs?: 'linux' | 'darwin' | null
98
+ workspaceMount?: string | null
99
+ scope?: string | null
100
+ }
101
+
102
+ /**
103
+ * The backend interface. Implementations: LocalBackend (passthrough),
104
+ * MacOSSandboxExecBackend (sandbox-exec wrapper), LinuxBubblewrapBackend
105
+ * (bwrap wrapper), DockerBackend (per-session container).
106
+ *
107
+ * `wrapSpawn` is async to allow lifecycle work (e.g. DockerBackend lazy-starts
108
+ * the container on first call). Sync backends just `return Promise.resolve(...)`.
109
+ * Optional `dispose` lets backends clean up (DockerBackend kills its container).
110
+ * Optional `envHint` returns a brain-facing description of the sandbox shape.
111
+ */
112
+ export interface SandboxBackend {
113
+ /** Backend identifier surfaced in logs / debug output. */
114
+ readonly mode: SandboxMode
115
+ /** Backend label including platform detail (e.g. 'os:darwin', 'docker:oven/bun:1'). */
116
+ readonly label: string
117
+ /**
118
+ * Wrap a spawn request. Returns (a Promise of) the argv that should be
119
+ * passed to `spawn(command, args, options)`. For `none`, returns the request
120
+ * unchanged. For `os`, returns a sandbox-exec wrapper. For `docker`, returns
121
+ * `docker exec <containerId> <orig-command>`, awaiting container start on
122
+ * the first call.
123
+ */
124
+ wrapSpawn(req: SandboxSpawnRequest): Promise<WrappedSpawn>
125
+ /** Optional cleanup (kill long-lived containers, remove temp files). Called on nebula exit. */
126
+ dispose?(): Promise<void>
127
+ /** Optional brain-facing description of the sandbox shape. Null for passthrough. */
128
+ envHint?(): SandboxEnvHint | null
129
+ }
@@ -0,0 +1,8 @@
1
+ export type { SkillFrontmatter, SkillRef, SkillSource } from './types'
2
+ export { scanSkills, parseFrontmatter, type SkillScannerOptions } from './scanner'
3
+ export {
4
+ matchTriggers,
5
+ matchFilePattern,
6
+ matchBashPattern,
7
+ type SkillTriggerMatch,
8
+ } from './triggers'