anyclaude-sdk 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 (195) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +295 -0
  3. package/dist/agent.d.ts +110 -0
  4. package/dist/agent.js +897 -0
  5. package/dist/background/index.d.ts +3 -0
  6. package/dist/background/index.js +9 -0
  7. package/dist/background/manager.d.ts +32 -0
  8. package/dist/background/manager.js +108 -0
  9. package/dist/background/tools.d.ts +5 -0
  10. package/dist/background/tools.js +98 -0
  11. package/dist/background/worker.d.ts +19 -0
  12. package/dist/background/worker.js +30 -0
  13. package/dist/commands/builtins.d.ts +2 -0
  14. package/dist/commands/builtins.js +306 -0
  15. package/dist/commands/index.d.ts +21 -0
  16. package/dist/commands/index.js +56 -0
  17. package/dist/commands/types.d.ts +110 -0
  18. package/dist/commands/types.js +5 -0
  19. package/dist/compact.d.ts +22 -0
  20. package/dist/compact.js +67 -0
  21. package/dist/fs/dexie.d.ts +57 -0
  22. package/dist/fs/dexie.js +243 -0
  23. package/dist/fs/index.d.ts +4 -0
  24. package/dist/fs/index.js +13 -0
  25. package/dist/fs/linuxTree.d.ts +11 -0
  26. package/dist/fs/linuxTree.js +43 -0
  27. package/dist/fs/opfs.d.ts +23 -0
  28. package/dist/fs/opfs.js +112 -0
  29. package/dist/index.d.ts +26 -0
  30. package/dist/index.js +29 -0
  31. package/dist/llm/anthropic.d.ts +24 -0
  32. package/dist/llm/anthropic.js +280 -0
  33. package/dist/llm/index.d.ts +3 -0
  34. package/dist/llm/index.js +3 -0
  35. package/dist/llm/inlineTools.d.ts +11 -0
  36. package/dist/llm/inlineTools.js +72 -0
  37. package/dist/llm/openai.d.ts +29 -0
  38. package/dist/llm/openai.js +224 -0
  39. package/dist/llm/responses.d.ts +18 -0
  40. package/dist/llm/responses.js +256 -0
  41. package/dist/mcp/client.d.ts +20 -0
  42. package/dist/mcp/client.js +156 -0
  43. package/dist/mcp/index.d.ts +24 -0
  44. package/dist/mcp/index.js +157 -0
  45. package/dist/mcp/proxy.d.ts +3 -0
  46. package/dist/mcp/proxy.js +25 -0
  47. package/dist/mcp/sdkServer.d.ts +21 -0
  48. package/dist/mcp/sdkServer.js +28 -0
  49. package/dist/mcp/types.d.ts +92 -0
  50. package/dist/mcp/types.js +5 -0
  51. package/dist/memory/index.d.ts +4 -0
  52. package/dist/memory/index.js +5 -0
  53. package/dist/memory/render.d.ts +7 -0
  54. package/dist/memory/render.js +46 -0
  55. package/dist/memory/store.d.ts +20 -0
  56. package/dist/memory/store.js +79 -0
  57. package/dist/memory/tools.d.ts +5 -0
  58. package/dist/memory/tools.js +95 -0
  59. package/dist/memory/types.d.ts +15 -0
  60. package/dist/memory/types.js +4 -0
  61. package/dist/permissions/dangerous.d.ts +4 -0
  62. package/dist/permissions/dangerous.js +24 -0
  63. package/dist/permissions/gate.d.ts +21 -0
  64. package/dist/permissions/gate.js +66 -0
  65. package/dist/permissions/index.d.ts +5 -0
  66. package/dist/permissions/index.js +6 -0
  67. package/dist/permissions/match.d.ts +19 -0
  68. package/dist/permissions/match.js +104 -0
  69. package/dist/permissions/planMode.d.ts +3 -0
  70. package/dist/permissions/planMode.js +33 -0
  71. package/dist/permissions/types.d.ts +19 -0
  72. package/dist/permissions/types.js +2 -0
  73. package/dist/persist.d.ts +15 -0
  74. package/dist/persist.js +58 -0
  75. package/dist/prompt.d.ts +6 -0
  76. package/dist/prompt.js +34 -0
  77. package/dist/query.d.ts +105 -0
  78. package/dist/query.js +115 -0
  79. package/dist/queue.d.ts +23 -0
  80. package/dist/queue.js +43 -0
  81. package/dist/sandbox/cloudflare.d.ts +48 -0
  82. package/dist/sandbox/cloudflare.js +124 -0
  83. package/dist/sandbox/daytona.d.ts +48 -0
  84. package/dist/sandbox/daytona.js +79 -0
  85. package/dist/sandbox/e2b.d.ts +54 -0
  86. package/dist/sandbox/e2b.js +87 -0
  87. package/dist/sandbox/index.d.ts +8 -0
  88. package/dist/sandbox/index.js +19 -0
  89. package/dist/sandbox/local.d.ts +51 -0
  90. package/dist/sandbox/local.js +155 -0
  91. package/dist/sandbox/types.d.ts +18 -0
  92. package/dist/sandbox/types.js +27 -0
  93. package/dist/sandbox/util.d.ts +15 -0
  94. package/dist/sandbox/util.js +100 -0
  95. package/dist/sandbox/vercel.d.ts +48 -0
  96. package/dist/sandbox/vercel.js +130 -0
  97. package/dist/session/index.d.ts +2 -0
  98. package/dist/session/index.js +6 -0
  99. package/dist/session/store.d.ts +28 -0
  100. package/dist/session/store.js +122 -0
  101. package/dist/session/types.d.ts +22 -0
  102. package/dist/session/types.js +2 -0
  103. package/dist/settings/index.d.ts +3 -0
  104. package/dist/settings/index.js +3 -0
  105. package/dist/settings/load.d.ts +20 -0
  106. package/dist/settings/load.js +36 -0
  107. package/dist/settings/merge.d.ts +13 -0
  108. package/dist/settings/merge.js +65 -0
  109. package/dist/settings/types.d.ts +17 -0
  110. package/dist/settings/types.js +3 -0
  111. package/dist/skills/index.d.ts +4 -0
  112. package/dist/skills/index.js +5 -0
  113. package/dist/skills/load.d.ts +23 -0
  114. package/dist/skills/load.js +54 -0
  115. package/dist/skills/parse.d.ts +7 -0
  116. package/dist/skills/parse.js +40 -0
  117. package/dist/skills/tool.d.ts +2 -0
  118. package/dist/skills/tool.js +39 -0
  119. package/dist/skills/types.d.ts +10 -0
  120. package/dist/skills/types.js +4 -0
  121. package/dist/team/dispatch.d.ts +2 -0
  122. package/dist/team/dispatch.js +41 -0
  123. package/dist/team/index.d.ts +9 -0
  124. package/dist/team/index.js +11 -0
  125. package/dist/team/mailbox.d.ts +24 -0
  126. package/dist/team/mailbox.js +33 -0
  127. package/dist/team/prompt.d.ts +1 -0
  128. package/dist/team/prompt.js +12 -0
  129. package/dist/team/runner.d.ts +20 -0
  130. package/dist/team/runner.js +45 -0
  131. package/dist/team/taskBoard.d.ts +41 -0
  132. package/dist/team/taskBoard.js +73 -0
  133. package/dist/team/tools.d.ts +7 -0
  134. package/dist/team/tools.js +190 -0
  135. package/dist/tools/bash.d.ts +2 -0
  136. package/dist/tools/bash.js +45 -0
  137. package/dist/tools/config.d.ts +2 -0
  138. package/dist/tools/config.js +44 -0
  139. package/dist/tools/define.d.ts +18 -0
  140. package/dist/tools/define.js +21 -0
  141. package/dist/tools/delete_file.d.ts +2 -0
  142. package/dist/tools/delete_file.js +33 -0
  143. package/dist/tools/edit_file.d.ts +2 -0
  144. package/dist/tools/edit_file.js +93 -0
  145. package/dist/tools/fileTypes.d.ts +32 -0
  146. package/dist/tools/fileTypes.js +166 -0
  147. package/dist/tools/glob.d.ts +2 -0
  148. package/dist/tools/glob.js +53 -0
  149. package/dist/tools/grep.d.ts +2 -0
  150. package/dist/tools/grep.js +110 -0
  151. package/dist/tools/imageProcessor.d.ts +15 -0
  152. package/dist/tools/imageProcessor.js +83 -0
  153. package/dist/tools/index.d.ts +28 -0
  154. package/dist/tools/index.js +45 -0
  155. package/dist/tools/list_files.d.ts +2 -0
  156. package/dist/tools/list_files.js +42 -0
  157. package/dist/tools/multi_edit.d.ts +2 -0
  158. package/dist/tools/multi_edit.js +112 -0
  159. package/dist/tools/notebook_edit.d.ts +2 -0
  160. package/dist/tools/notebook_edit.js +118 -0
  161. package/dist/tools/plan_mode.d.ts +4 -0
  162. package/dist/tools/plan_mode.js +44 -0
  163. package/dist/tools/read_file.d.ts +2 -0
  164. package/dist/tools/read_file.js +193 -0
  165. package/dist/tools/task.d.ts +2 -0
  166. package/dist/tools/task.js +77 -0
  167. package/dist/tools/todo_write.d.ts +2 -0
  168. package/dist/tools/todo_write.js +104 -0
  169. package/dist/tools/tool_search.d.ts +2 -0
  170. package/dist/tools/tool_search.js +49 -0
  171. package/dist/tools/types.d.ts +82 -0
  172. package/dist/tools/types.js +1 -0
  173. package/dist/tools/walk.d.ts +29 -0
  174. package/dist/tools/walk.js +82 -0
  175. package/dist/tools/web_fetch.d.ts +2 -0
  176. package/dist/tools/web_fetch.js +76 -0
  177. package/dist/tools/web_search.d.ts +22 -0
  178. package/dist/tools/web_search.js +195 -0
  179. package/dist/tools/write_file.d.ts +2 -0
  180. package/dist/tools/write_file.js +39 -0
  181. package/dist/types/index.d.ts +363 -0
  182. package/dist/types/index.js +9 -0
  183. package/dist/util/ids.d.ts +3 -0
  184. package/dist/util/ids.js +22 -0
  185. package/dist/util/paths.d.ts +16 -0
  186. package/dist/util/paths.js +72 -0
  187. package/dist/util/pricing.d.ts +15 -0
  188. package/dist/util/pricing.js +81 -0
  189. package/dist/workspace/index.d.ts +2 -0
  190. package/dist/workspace/index.js +2 -0
  191. package/dist/workspace/memory.d.ts +28 -0
  192. package/dist/workspace/memory.js +97 -0
  193. package/dist/workspace/webcontainer.d.ts +65 -0
  194. package/dist/workspace/webcontainer.js +156 -0
  195. package/package.json +78 -0
@@ -0,0 +1,56 @@
1
+ // Slash-command entry point. The agent loop calls runSlashCommand() when a user
2
+ // message is a string beginning with '/'. Returns null when the text isn't a
3
+ // recognized command (the loop then treats it as a normal prompt).
4
+ import { BUILTIN_COMMANDS } from './builtins.js';
5
+ export * from './types.js';
6
+ export { BUILTIN_COMMANDS } from './builtins.js';
7
+ /** Parse `/name args...` → { name (lowercased, no slash), args }. Null if not a command. */
8
+ export function parseSlashCommand(text) {
9
+ const t = text.trimStart();
10
+ if (!t.startsWith('/'))
11
+ return null;
12
+ const body = t.slice(1);
13
+ const sp = body.search(/\s/);
14
+ if (sp === -1)
15
+ return { name: body.toLowerCase(), args: '' };
16
+ return { name: body.slice(0, sp).toLowerCase(), args: body.slice(sp + 1).trim() };
17
+ }
18
+ /**
19
+ * Resolve and execute a slash command. Returns the command's SlashOutcome, or
20
+ * null when the text isn't a slash command or names an unknown command (so the
21
+ * caller can fall back to treating it as a normal prompt).
22
+ */
23
+ export async function runSlashCommand(text, ctx) {
24
+ const parsed = parseSlashCommand(text);
25
+ if (!parsed)
26
+ return null;
27
+ // User commands override built-ins by name.
28
+ const merged = mergeCommands(BUILTIN_COMMANDS, ctx.commands ?? []);
29
+ const command = merged.find((c) => c.name === parsed.name);
30
+ if (!command)
31
+ return null;
32
+ const fullCtx = { ...ctx, commands: merged };
33
+ return await command.run(parsed.args, fullCtx);
34
+ }
35
+ function mergeCommands(builtins, user) {
36
+ const byName = new Map();
37
+ for (const c of builtins)
38
+ byName.set(c.name, c);
39
+ for (const c of user)
40
+ byName.set(c.name, c); // user overrides
41
+ return [...byName.values()];
42
+ }
43
+ /**
44
+ * Define a custom prompt-template command (like a Claude Code markdown command).
45
+ * `$ARGUMENTS` in the template is replaced with the invocation args.
46
+ */
47
+ export function promptCommand(name, description, prompt, argumentHint) {
48
+ return {
49
+ name,
50
+ description,
51
+ argumentHint,
52
+ run(args) {
53
+ return { expandedPrompt: prompt.replace(/\$ARGUMENTS/g, args) };
54
+ },
55
+ };
56
+ }
@@ -0,0 +1,110 @@
1
+ import type { ChatMsg, Usage } from '../types/index.js';
2
+ /** Minimal structural view of the LLM client a command may use (e.g. /compact). */
3
+ export interface CommandLLM {
4
+ streamChat(messages: ChatMsg[], opts: {
5
+ model?: string;
6
+ signal?: AbortSignal;
7
+ onToken: (delta: string) => void;
8
+ }): Promise<{
9
+ text: string;
10
+ }>;
11
+ }
12
+ export interface SlashCommandContext {
13
+ /** Live reference to the conversation history (history[0] is the system msg). Read-only intent. */
14
+ history: ChatMsg[];
15
+ /** Tools currently available to the agent. */
16
+ tools: Array<{
17
+ name: string;
18
+ description: string;
19
+ }>;
20
+ /** Active model id, if known. */
21
+ model?: string;
22
+ /** Working directory. */
23
+ cwd: string;
24
+ /** Accumulated token usage so far, if tracked. */
25
+ usage?: Usage;
26
+ /** Mutable session state. */
27
+ store?: {
28
+ todos: unknown[];
29
+ };
30
+ /** Abort signal for long-running commands (e.g. /compact). */
31
+ signal?: AbortSignal;
32
+ /** LLM client, when available (required by /compact). */
33
+ llm?: CommandLLM;
34
+ /** The full merged command registry (built-ins + user commands), for /help. */
35
+ commands: SlashCommand[];
36
+ /** Current session id. */
37
+ sessionId?: string;
38
+ /** Session store, for /sessions, /resume, /rename. */
39
+ sessionStore?: {
40
+ list(): Promise<Array<{
41
+ sessionId: string;
42
+ title?: string;
43
+ updatedAt: number;
44
+ messageCount: number;
45
+ }>>;
46
+ load(id: string): Promise<ChatMsg[] | null>;
47
+ rename(id: string, title: string): Promise<void>;
48
+ remove(id: string): Promise<void>;
49
+ };
50
+ /** Paths the model has read this session, for /files. */
51
+ readFiles?: Set<string>;
52
+ /** Configured sub-agents, for /agents. */
53
+ agents?: Record<string, {
54
+ description: string;
55
+ model?: string;
56
+ }>;
57
+ /** MCP server statuses, for /mcp. */
58
+ mcpServers?: Array<{
59
+ name: string;
60
+ status: string;
61
+ }>;
62
+ /** Active permission mode, for /permissions. */
63
+ permissionMode?: string;
64
+ /** Background tasks, for /tasks. */
65
+ background?: {
66
+ list(): Array<{
67
+ id: string;
68
+ status: string;
69
+ description: string;
70
+ }>;
71
+ };
72
+ /** Team task board, for /board. */
73
+ board?: {
74
+ list(): Array<{
75
+ id: string;
76
+ status: string;
77
+ owner?: string;
78
+ subject: string;
79
+ }>;
80
+ };
81
+ /** Shell executor, for /diff. */
82
+ exec?: (command: string) => Promise<{
83
+ output: string;
84
+ exitCode: number;
85
+ }>;
86
+ /** Filesystem, for /memory. */
87
+ fs?: {
88
+ readFile(path: string): Promise<string | null>;
89
+ };
90
+ /** Persistent memory store, for /memory. */
91
+ memory?: {
92
+ render(): Promise<string>;
93
+ };
94
+ }
95
+ export interface SlashOutcome {
96
+ /** Text surfaced to the user as a system `local_command_output` message. */
97
+ systemText?: string;
98
+ /** Replace the user's turn with this prompt and run the LLM normally. */
99
+ expandedPrompt?: string;
100
+ /** Replace the entire conversation history verbatim (include the system message). */
101
+ newHistory?: ChatMsg[];
102
+ /** Signals a compaction happened (loop emits a compact_boundary message). */
103
+ compacted?: boolean;
104
+ }
105
+ export interface SlashCommand {
106
+ name: string;
107
+ description: string;
108
+ argumentHint?: string;
109
+ run(args: string, ctx: SlashCommandContext): Promise<SlashOutcome> | SlashOutcome;
110
+ }
@@ -0,0 +1,5 @@
1
+ // Slash-command system types. Ports Claude Code's /compact, /clear, /help, etc.
2
+ // The agent loop invokes runSlashCommand() when a user message is a string that
3
+ // starts with '/'. A command returns a SlashOutcome describing what the loop
4
+ // should do (surface text, expand into a prompt, and/or rewrite history).
5
+ export {};
@@ -0,0 +1,22 @@
1
+ import type { ChatMsg } from './types/index.js';
2
+ /** Rough token estimate for a transcript (≈4 chars/token). */
3
+ export declare function estimateTokens(history: ChatMsg[]): number;
4
+ interface SummarizerLLM {
5
+ streamChat(messages: ChatMsg[], opts: {
6
+ model?: string;
7
+ signal?: AbortSignal;
8
+ onToken: (d: string) => void;
9
+ }): Promise<{
10
+ text: string;
11
+ }>;
12
+ }
13
+ /**
14
+ * Summarize `history` into a new transcript `[systemMsg, { user: summary }]`.
15
+ * Returns null if there's nothing to compact or the LLM produced no summary.
16
+ */
17
+ export declare function summarizeHistory(history: ChatMsg[], llm: SummarizerLLM, opts?: {
18
+ model?: string;
19
+ signal?: AbortSignal;
20
+ focus?: string;
21
+ }): Promise<ChatMsg[] | null>;
22
+ export {};
@@ -0,0 +1,67 @@
1
+ // Conversation compaction: summarize a long transcript into a compact form so
2
+ // the context window doesn't overflow. Used by auto-compaction in the agent
3
+ // loop (and shareable with the /compact slash command).
4
+ /** Rough token estimate for a transcript (≈4 chars/token). */
5
+ export function estimateTokens(history) {
6
+ let chars = 0;
7
+ for (const m of history) {
8
+ chars += typeof m.content === 'string' ? m.content.length : JSON.stringify(m.content).length;
9
+ if (m.tool_calls)
10
+ chars += JSON.stringify(m.tool_calls).length;
11
+ }
12
+ return Math.round(chars / 4);
13
+ }
14
+ /** Render a transcript (excluding the system message) as plain text. */
15
+ function transcript(history) {
16
+ return history
17
+ .slice(1)
18
+ .map((m) => {
19
+ const body = typeof m.content === 'string'
20
+ ? m.content
21
+ : m.content
22
+ .map((b) => b.type === 'text'
23
+ ? b.text
24
+ : b.type === 'tool_use'
25
+ ? `[tool_use ${b.name} ${JSON.stringify(b.input)}]`
26
+ : b.type === 'tool_result'
27
+ ? `[tool_result ${typeof b.content === 'string' ? b.content : '...'}]`
28
+ : `[${b.type}]`)
29
+ .join('\n');
30
+ const calls = m.tool_calls?.length
31
+ ? ' ' + m.tool_calls.map((c) => `[call ${c.function.name}(${c.function.arguments})]`).join(' ')
32
+ : '';
33
+ return `${m.role.toUpperCase()}: ${body}${calls}`;
34
+ })
35
+ .join('\n\n');
36
+ }
37
+ /**
38
+ * Summarize `history` into a new transcript `[systemMsg, { user: summary }]`.
39
+ * Returns null if there's nothing to compact or the LLM produced no summary.
40
+ */
41
+ export async function summarizeHistory(history, llm, opts = {}) {
42
+ if (history.length <= 2)
43
+ return null;
44
+ const instruction = 'Summarize the following conversation transcript concisely but completely. ' +
45
+ 'Preserve: the user’s goals, key decisions, files created/edited (with paths), ' +
46
+ 'important findings, and any unfinished work or next steps. Use short sections.' +
47
+ (opts.focus ? `\nPay special attention to: ${opts.focus}` : '');
48
+ const messages = [
49
+ { role: 'system', content: 'You are a precise conversation summarizer.' },
50
+ { role: 'user', content: `${instruction}\n\n---\n${transcript(history)}` },
51
+ ];
52
+ let summary = '';
53
+ try {
54
+ const res = await llm.streamChat(messages, {
55
+ model: opts.model,
56
+ signal: opts.signal,
57
+ onToken: () => { },
58
+ });
59
+ summary = res.text?.trim() ?? '';
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ if (!summary)
65
+ return null;
66
+ return [history[0], { role: 'user', content: `Summary of the conversation so far:\n${summary}` }];
67
+ }
@@ -0,0 +1,57 @@
1
+ import type { FileSystem } from '../types/index.js';
2
+ /** A stored filesystem node (file or directory). */
3
+ export interface FsNode {
4
+ /** Normalized absolute path (primary key). */
5
+ path: string;
6
+ /** Parent directory path (indexed for readdir). '' for the root. */
7
+ parent: string;
8
+ /** Final path segment. */
9
+ name: string;
10
+ kind: 'file' | 'dir';
11
+ size: number;
12
+ /** Last-modified time, epoch ms. */
13
+ mtime: number;
14
+ /** POSIX-style mode bits. */
15
+ mode: number;
16
+ /** File contents (files only). */
17
+ data?: Uint8Array;
18
+ /** Symlink target (symlinks only). */
19
+ symlink?: string;
20
+ }
21
+ export interface DexieFileSystemOptions {
22
+ cwd?: string;
23
+ /** Wipe all nodes on first open (fresh filesystem). */
24
+ resetOnInit?: boolean;
25
+ }
26
+ export declare class DexieFileSystem implements FileSystem {
27
+ readonly cwd: string;
28
+ private readonly dbName;
29
+ private readonly resetOnInit;
30
+ private db;
31
+ private opening;
32
+ constructor(dbName?: string, options?: DexieFileSystemOptions);
33
+ private open;
34
+ private resolve;
35
+ /** Create every ancestor directory of `path` (and optionally `path` itself). */
36
+ private ensureDirs;
37
+ readFile(path: string): Promise<string | null>;
38
+ readBinary(path: string): Promise<Uint8Array | null>;
39
+ writeFile(path: string, contents: string): Promise<void>;
40
+ writeBinary(path: string, data: Uint8Array): Promise<void>;
41
+ deleteFile(path: string): Promise<void>;
42
+ readdir(path: string): Promise<Array<{
43
+ name: string;
44
+ isDir: boolean;
45
+ }> | null>;
46
+ mkdir(path: string): Promise<void>;
47
+ /** Return node metadata (without the data blob), or null if missing. */
48
+ stat(path: string): Promise<Omit<FsNode, 'data'> | null>;
49
+ exists(path: string): Promise<boolean>;
50
+ chmod(path: string, mode: number): Promise<void>;
51
+ /** Create a symlink node at `linkPath` pointing at `target`. */
52
+ symlink(target: string, linkPath: string): Promise<void>;
53
+ /** Move/rename a node (and its descendants, for directories). */
54
+ rename(from: string, to: string): Promise<void>;
55
+ /** Wipe the entire filesystem (then recreate the root). */
56
+ clear(): Promise<void>;
57
+ }
@@ -0,0 +1,243 @@
1
+ // DexieFileSystem — a durable, full Linux-style filesystem backed by IndexedDB
2
+ // via Dexie. Implements the SDK's FileSystem interface plus power-user methods
3
+ // (stat/chmod/symlink/rename/exists/clear).
4
+ //
5
+ // Model: an inode-per-path table keyed by the normalized absolute path, with a
6
+ // `parent` index for O(children) readdir. Persistent across reloads, queryable,
7
+ // available in every browser that has IndexedDB.
8
+ //
9
+ // `dexie` is an OPTIONAL peer dependency — imported dynamically so this module
10
+ // loads even when Dexie isn't installed (the error surfaces only on first use).
11
+ import { ancestors, basename, dirname, resolvePath } from '../util/paths.js';
12
+ const encoder = new TextEncoder();
13
+ const decoder = new TextDecoder();
14
+ const DEFAULT_FILE_MODE = 0o644;
15
+ const DEFAULT_DIR_MODE = 0o755;
16
+ export class DexieFileSystem {
17
+ constructor(dbName = 'bcs-fs', options = {}) {
18
+ this.db = null;
19
+ this.opening = null;
20
+ this.dbName = dbName;
21
+ this.cwd = options.cwd ?? '/';
22
+ this.resetOnInit = options.resetOnInit ?? false;
23
+ }
24
+ // ---- lifecycle ----
25
+ async open() {
26
+ if (this.db)
27
+ return this.db;
28
+ if (this.opening)
29
+ return this.opening;
30
+ this.opening = (async () => {
31
+ // @ts-ignore optional peer dependency, resolved at runtime
32
+ const mod = await import('dexie');
33
+ const Dexie = mod.default ?? mod;
34
+ const db = new Dexie(this.dbName);
35
+ db.version(1).stores({
36
+ // primary key `path`, secondary index `parent`
37
+ nodes: 'path, parent',
38
+ });
39
+ this.db = db;
40
+ if (this.resetOnInit)
41
+ await db.nodes.clear();
42
+ // Ensure the root directory exists.
43
+ const root = await db.nodes.get('/');
44
+ if (!root) {
45
+ await db.nodes.put({
46
+ path: '/',
47
+ parent: '',
48
+ name: '',
49
+ kind: 'dir',
50
+ size: 0,
51
+ mtime: Date.now(),
52
+ mode: DEFAULT_DIR_MODE,
53
+ });
54
+ }
55
+ return db;
56
+ })();
57
+ return this.opening;
58
+ }
59
+ resolve(p) {
60
+ return resolvePath(this.cwd, p);
61
+ }
62
+ /** Create every ancestor directory of `path` (and optionally `path` itself). */
63
+ async ensureDirs(db, path, includeSelf) {
64
+ const dirs = ancestors(path);
65
+ if (includeSelf)
66
+ dirs.push(path);
67
+ for (const dir of dirs) {
68
+ if (dir === '/' || dir === '')
69
+ continue;
70
+ const existing = await db.nodes.get(dir);
71
+ if (existing) {
72
+ if (existing.kind !== 'dir') {
73
+ throw new Error(`ENOTDIR: '${dir}' exists and is not a directory`);
74
+ }
75
+ continue;
76
+ }
77
+ await db.nodes.put({
78
+ path: dir,
79
+ parent: dirname(dir),
80
+ name: basename(dir),
81
+ kind: 'dir',
82
+ size: 0,
83
+ mtime: Date.now(),
84
+ mode: DEFAULT_DIR_MODE,
85
+ });
86
+ }
87
+ }
88
+ // ---- FileSystem interface ----
89
+ async readFile(path) {
90
+ const bytes = await this.readBinary(path);
91
+ return bytes ? decoder.decode(bytes) : null;
92
+ }
93
+ async readBinary(path) {
94
+ try {
95
+ const db = await this.open();
96
+ const node = await db.nodes.get(this.resolve(path));
97
+ if (!node || node.kind !== 'file')
98
+ return null;
99
+ return node.data ?? new Uint8Array(0);
100
+ }
101
+ catch {
102
+ return null;
103
+ }
104
+ }
105
+ async writeFile(path, contents) {
106
+ await this.writeBinary(path, encoder.encode(contents));
107
+ }
108
+ async writeBinary(path, data) {
109
+ const db = await this.open();
110
+ const abs = this.resolve(path);
111
+ await this.ensureDirs(db, abs, false);
112
+ const prev = await db.nodes.get(abs);
113
+ await db.nodes.put({
114
+ path: abs,
115
+ parent: dirname(abs),
116
+ name: basename(abs),
117
+ kind: 'file',
118
+ size: data.byteLength,
119
+ mtime: Date.now(),
120
+ mode: prev?.mode ?? DEFAULT_FILE_MODE,
121
+ data,
122
+ });
123
+ }
124
+ async deleteFile(path) {
125
+ const db = await this.open();
126
+ const abs = this.resolve(path);
127
+ if (abs === '/') {
128
+ await db.nodes.clear();
129
+ await this.open(); // recreate root
130
+ return;
131
+ }
132
+ const node = await db.nodes.get(abs);
133
+ await db.nodes.delete(abs);
134
+ if (node?.kind === 'dir') {
135
+ // rm -rf: remove all descendants (path prefix).
136
+ const descendants = await db.nodes.where('path').startsWith(abs + '/').primaryKeys();
137
+ for (const key of descendants)
138
+ await db.nodes.delete(key);
139
+ }
140
+ }
141
+ async readdir(path) {
142
+ try {
143
+ const db = await this.open();
144
+ const abs = this.resolve(path);
145
+ const node = await db.nodes.get(abs);
146
+ if (!node || node.kind !== 'dir')
147
+ return null;
148
+ const children = await db.nodes.where('parent').equals(abs).toArray();
149
+ return children.map((c) => ({ name: c.name, isDir: c.kind === 'dir' }));
150
+ }
151
+ catch {
152
+ return null;
153
+ }
154
+ }
155
+ async mkdir(path) {
156
+ const db = await this.open();
157
+ await this.ensureDirs(db, this.resolve(path), true);
158
+ }
159
+ // ---- power-user extensions ----
160
+ /** Return node metadata (without the data blob), or null if missing. */
161
+ async stat(path) {
162
+ try {
163
+ const db = await this.open();
164
+ const node = await db.nodes.get(this.resolve(path));
165
+ if (!node)
166
+ return null;
167
+ const { data: _data, ...meta } = node;
168
+ return meta;
169
+ }
170
+ catch {
171
+ return null;
172
+ }
173
+ }
174
+ async exists(path) {
175
+ const db = await this.open();
176
+ return (await db.nodes.get(this.resolve(path))) != null;
177
+ }
178
+ async chmod(path, mode) {
179
+ const db = await this.open();
180
+ const abs = this.resolve(path);
181
+ const node = await db.nodes.get(abs);
182
+ if (!node)
183
+ throw new Error(`ENOENT: no such file '${abs}'`);
184
+ node.mode = mode;
185
+ node.mtime = Date.now();
186
+ await db.nodes.put(node);
187
+ }
188
+ /** Create a symlink node at `linkPath` pointing at `target`. */
189
+ async symlink(target, linkPath) {
190
+ const db = await this.open();
191
+ const abs = this.resolve(linkPath);
192
+ await this.ensureDirs(db, abs, false);
193
+ await db.nodes.put({
194
+ path: abs,
195
+ parent: dirname(abs),
196
+ name: basename(abs),
197
+ kind: 'file',
198
+ size: target.length,
199
+ mtime: Date.now(),
200
+ mode: 0o777,
201
+ symlink: target,
202
+ });
203
+ }
204
+ /** Move/rename a node (and its descendants, for directories). */
205
+ async rename(from, to) {
206
+ const db = await this.open();
207
+ const src = this.resolve(from);
208
+ const dst = this.resolve(to);
209
+ const node = await db.nodes.get(src);
210
+ if (!node)
211
+ throw new Error(`ENOENT: no such file '${src}'`);
212
+ await this.ensureDirs(db, dst, false);
213
+ const move = (n, newPath) => ({
214
+ ...n,
215
+ path: newPath,
216
+ parent: dirname(newPath),
217
+ name: basename(newPath),
218
+ mtime: Date.now(),
219
+ });
220
+ if (node.kind === 'dir') {
221
+ const descendants = await db.nodes.where('path').startsWith(src + '/').toArray();
222
+ const moved = [move(node, dst)];
223
+ for (const d of descendants)
224
+ moved.push(move(d, dst + d.path.slice(src.length)));
225
+ await db.nodes.bulkPut(moved);
226
+ await db.nodes.delete(src);
227
+ for (const d of descendants)
228
+ await db.nodes.delete(d.path);
229
+ }
230
+ else {
231
+ await db.nodes.put(move(node, dst));
232
+ await db.nodes.delete(src);
233
+ }
234
+ }
235
+ /** Wipe the entire filesystem (then recreate the root). */
236
+ async clear() {
237
+ const db = await this.open();
238
+ await db.nodes.clear();
239
+ this.db = null;
240
+ this.opening = null;
241
+ await this.open();
242
+ }
243
+ }
@@ -0,0 +1,4 @@
1
+ export { DexieFileSystem, type FsNode, type DexieFileSystemOptions, } from './dexie.js';
2
+ export { OpfsFileSystem, type OpfsFileSystemOptions } from './opfs.js';
3
+ export { seedLinuxTree, LINUX_DIRS, DEFAULT_HOME } from './linuxTree.js';
4
+ export { MemoryFileSystem } from '../workspace/memory.js';
@@ -0,0 +1,13 @@
1
+ // Persistent browser filesystems for browser-claude-sdk.
2
+ //
3
+ // - DexieFileSystem (recommended): IndexedDB-backed, durable, queryable, with
4
+ // metadata + power-user methods. Optional peer dependency `dexie`.
5
+ // - OpfsFileSystem: Origin Private File System; native hierarchical handles,
6
+ // best for large binary files.
7
+ // - seedLinuxTree: lay down a standard Linux FHS skeleton.
8
+ export { DexieFileSystem, } from './dexie.js';
9
+ export { OpfsFileSystem } from './opfs.js';
10
+ export { seedLinuxTree, LINUX_DIRS, DEFAULT_HOME } from './linuxTree.js';
11
+ // In-memory FileSystem (great for tests) — surfaced here alongside the
12
+ // persistent backends for discoverability; it physically lives in workspace/.
13
+ export { MemoryFileSystem } from '../workspace/memory.js';
@@ -0,0 +1,11 @@
1
+ import type { FileSystem } from '../types/index.js';
2
+ export declare const DEFAULT_HOME = "/home/user";
3
+ /** Standard Filesystem Hierarchy Standard directories we create. */
4
+ export declare const LINUX_DIRS: string[];
5
+ /**
6
+ * Idempotently create the Linux directory skeleton plus a few token files.
7
+ * Safe to call repeatedly (mkdir is recursive; writes overwrite identical content).
8
+ */
9
+ export declare function seedLinuxTree(fs: FileSystem, opts?: {
10
+ home?: string;
11
+ }): Promise<void>;
@@ -0,0 +1,43 @@
1
+ // seedLinuxTree — populate a FileSystem with a standard FHS directory skeleton
2
+ // so the agent sees a familiar Linux layout to work in.
3
+ export const DEFAULT_HOME = '/home/user';
4
+ /** Standard Filesystem Hierarchy Standard directories we create. */
5
+ export const LINUX_DIRS = [
6
+ '/bin',
7
+ '/sbin',
8
+ '/etc',
9
+ '/home',
10
+ '/root',
11
+ '/tmp',
12
+ '/var',
13
+ '/var/log',
14
+ '/usr',
15
+ '/usr/bin',
16
+ '/usr/lib',
17
+ '/usr/local',
18
+ '/opt',
19
+ '/mnt',
20
+ '/proc',
21
+ '/sys',
22
+ '/dev',
23
+ ];
24
+ const OS_RELEASE = `PRETTY_NAME="browser-claude-sdk sandbox"
25
+ NAME="bcs-linux"
26
+ ID=bcs
27
+ VERSION_ID="1.0"
28
+ HOME_URL="https://webcontainers.io"
29
+ `;
30
+ /**
31
+ * Idempotently create the Linux directory skeleton plus a few token files.
32
+ * Safe to call repeatedly (mkdir is recursive; writes overwrite identical content).
33
+ */
34
+ export async function seedLinuxTree(fs, opts = {}) {
35
+ const home = opts.home ?? DEFAULT_HOME;
36
+ for (const dir of LINUX_DIRS) {
37
+ await fs.mkdir(dir);
38
+ }
39
+ await fs.mkdir(home);
40
+ await fs.writeFile('/etc/hostname', 'sandbox\n');
41
+ await fs.writeFile('/etc/os-release', OS_RELEASE);
42
+ await fs.writeFile(`${home}/.bashrc`, '');
43
+ }
@@ -0,0 +1,23 @@
1
+ import type { FileSystem } from '../types/index.js';
2
+ export interface OpfsFileSystemOptions {
3
+ cwd?: string;
4
+ }
5
+ export declare class OpfsFileSystem implements FileSystem {
6
+ readonly cwd: string;
7
+ constructor(options?: OpfsFileSystemOptions);
8
+ static isSupported(): boolean;
9
+ private root;
10
+ /** Walk to the directory handle for `dirSegments`, optionally creating it. */
11
+ private dirHandle;
12
+ private split;
13
+ readBinary(path: string): Promise<Uint8Array | null>;
14
+ readFile(path: string): Promise<string | null>;
15
+ writeBinary(path: string, data: Uint8Array): Promise<void>;
16
+ writeFile(path: string, contents: string): Promise<void>;
17
+ deleteFile(path: string): Promise<void>;
18
+ readdir(path: string): Promise<Array<{
19
+ name: string;
20
+ isDir: boolean;
21
+ }> | null>;
22
+ mkdir(path: string): Promise<void>;
23
+ }