codebase-cli 2.0.0-pre.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 (172) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +266 -0
  3. package/bin/codebase +2 -0
  4. package/dist/agent/agent.js +198 -0
  5. package/dist/agent/agent.js.map +1 -0
  6. package/dist/agent/config.js +117 -0
  7. package/dist/agent/config.js.map +1 -0
  8. package/dist/agent/events.js +153 -0
  9. package/dist/agent/events.js.map +1 -0
  10. package/dist/agent/router.js +35 -0
  11. package/dist/agent/router.js.map +1 -0
  12. package/dist/agent/system-prompt.js +21 -0
  13. package/dist/agent/system-prompt.js.map +1 -0
  14. package/dist/auth/cli.js +138 -0
  15. package/dist/auth/cli.js.map +1 -0
  16. package/dist/auth/credentials.js +105 -0
  17. package/dist/auth/credentials.js.map +1 -0
  18. package/dist/auth/flow.js +222 -0
  19. package/dist/auth/flow.js.map +1 -0
  20. package/dist/auth/pkce.js +46 -0
  21. package/dist/auth/pkce.js.map +1 -0
  22. package/dist/cli.js +69 -0
  23. package/dist/cli.js.map +1 -0
  24. package/dist/clipboard/copy.js +106 -0
  25. package/dist/clipboard/copy.js.map +1 -0
  26. package/dist/commands/builtins.js +203 -0
  27. package/dist/commands/builtins.js.map +1 -0
  28. package/dist/commands/registry.js +65 -0
  29. package/dist/commands/registry.js.map +1 -0
  30. package/dist/commands/types.js +2 -0
  31. package/dist/commands/types.js.map +1 -0
  32. package/dist/compaction/engine.js +209 -0
  33. package/dist/compaction/engine.js.map +1 -0
  34. package/dist/compaction/tokens.js +79 -0
  35. package/dist/compaction/tokens.js.map +1 -0
  36. package/dist/compaction/types.js +2 -0
  37. package/dist/compaction/types.js.map +1 -0
  38. package/dist/diagnostics/checkers.js +211 -0
  39. package/dist/diagnostics/checkers.js.map +1 -0
  40. package/dist/diagnostics/engine.js +71 -0
  41. package/dist/diagnostics/engine.js.map +1 -0
  42. package/dist/diagnostics/types.js +2 -0
  43. package/dist/diagnostics/types.js.map +1 -0
  44. package/dist/dotenv/loader.js +115 -0
  45. package/dist/dotenv/loader.js.map +1 -0
  46. package/dist/glue/client.js +47 -0
  47. package/dist/glue/client.js.map +1 -0
  48. package/dist/glue/intent.js +78 -0
  49. package/dist/glue/intent.js.map +1 -0
  50. package/dist/glue/narration.js +55 -0
  51. package/dist/glue/narration.js.map +1 -0
  52. package/dist/headless/run.js +89 -0
  53. package/dist/headless/run.js.map +1 -0
  54. package/dist/hooks/manager.js +158 -0
  55. package/dist/hooks/manager.js.map +1 -0
  56. package/dist/hooks/runner.js +70 -0
  57. package/dist/hooks/runner.js.map +1 -0
  58. package/dist/hooks/types.js +2 -0
  59. package/dist/hooks/types.js.map +1 -0
  60. package/dist/memory/inject.js +12 -0
  61. package/dist/memory/inject.js.map +1 -0
  62. package/dist/memory/store.js +178 -0
  63. package/dist/memory/store.js.map +1 -0
  64. package/dist/memory/types.js +10 -0
  65. package/dist/memory/types.js.map +1 -0
  66. package/dist/permissions/store.js +172 -0
  67. package/dist/permissions/store.js.map +1 -0
  68. package/dist/plan/flow.js +214 -0
  69. package/dist/plan/flow.js.map +1 -0
  70. package/dist/plan/prompts.js +69 -0
  71. package/dist/plan/prompts.js.map +1 -0
  72. package/dist/plan/store.js +37 -0
  73. package/dist/plan/store.js.map +1 -0
  74. package/dist/plan/types.js +3 -0
  75. package/dist/plan/types.js.map +1 -0
  76. package/dist/sessions/store.js +105 -0
  77. package/dist/sessions/store.js.map +1 -0
  78. package/dist/skills/loader.js +41 -0
  79. package/dist/skills/loader.js.map +1 -0
  80. package/dist/skills/platform-loader.js +63 -0
  81. package/dist/skills/platform-loader.js.map +1 -0
  82. package/dist/skills/types.js +21 -0
  83. package/dist/skills/types.js.map +1 -0
  84. package/dist/tools/ask-user.js +61 -0
  85. package/dist/tools/ask-user.js.map +1 -0
  86. package/dist/tools/dispatch-agent.js +178 -0
  87. package/dist/tools/dispatch-agent.js.map +1 -0
  88. package/dist/tools/edit-file.js +80 -0
  89. package/dist/tools/edit-file.js.map +1 -0
  90. package/dist/tools/errors.js +89 -0
  91. package/dist/tools/errors.js.map +1 -0
  92. package/dist/tools/file-ops.js +136 -0
  93. package/dist/tools/file-ops.js.map +1 -0
  94. package/dist/tools/file-state-cache.js +92 -0
  95. package/dist/tools/file-state-cache.js.map +1 -0
  96. package/dist/tools/git/branch.js +84 -0
  97. package/dist/tools/git/branch.js.map +1 -0
  98. package/dist/tools/git/commit.js +83 -0
  99. package/dist/tools/git/commit.js.map +1 -0
  100. package/dist/tools/git/diff.js +72 -0
  101. package/dist/tools/git/diff.js.map +1 -0
  102. package/dist/tools/git/git-helpers.js +58 -0
  103. package/dist/tools/git/git-helpers.js.map +1 -0
  104. package/dist/tools/git/log.js +70 -0
  105. package/dist/tools/git/log.js.map +1 -0
  106. package/dist/tools/git/status.js +97 -0
  107. package/dist/tools/git/status.js.map +1 -0
  108. package/dist/tools/git/worktree.js +132 -0
  109. package/dist/tools/git/worktree.js.map +1 -0
  110. package/dist/tools/glob.js +128 -0
  111. package/dist/tools/glob.js.map +1 -0
  112. package/dist/tools/grep.js +199 -0
  113. package/dist/tools/grep.js.map +1 -0
  114. package/dist/tools/list-files.js +120 -0
  115. package/dist/tools/list-files.js.map +1 -0
  116. package/dist/tools/memory-tools.js +127 -0
  117. package/dist/tools/memory-tools.js.map +1 -0
  118. package/dist/tools/multi-edit.js +87 -0
  119. package/dist/tools/multi-edit.js.map +1 -0
  120. package/dist/tools/notebook-edit.js +147 -0
  121. package/dist/tools/notebook-edit.js.map +1 -0
  122. package/dist/tools/permission.js +168 -0
  123. package/dist/tools/permission.js.map +1 -0
  124. package/dist/tools/plan-mode.js +76 -0
  125. package/dist/tools/plan-mode.js.map +1 -0
  126. package/dist/tools/read-file.js +135 -0
  127. package/dist/tools/read-file.js.map +1 -0
  128. package/dist/tools/registry.js +52 -0
  129. package/dist/tools/registry.js.map +1 -0
  130. package/dist/tools/shell.js +216 -0
  131. package/dist/tools/shell.js.map +1 -0
  132. package/dist/tools/task-store.js +70 -0
  133. package/dist/tools/task-store.js.map +1 -0
  134. package/dist/tools/tasks.js +131 -0
  135. package/dist/tools/tasks.js.map +1 -0
  136. package/dist/tools/types.js +2 -0
  137. package/dist/tools/types.js.map +1 -0
  138. package/dist/tools/web-fetch.js +152 -0
  139. package/dist/tools/web-fetch.js.map +1 -0
  140. package/dist/tools/web-search.js +169 -0
  141. package/dist/tools/web-search.js.map +1 -0
  142. package/dist/tools/write-file.js +70 -0
  143. package/dist/tools/write-file.js.map +1 -0
  144. package/dist/types.js +9 -0
  145. package/dist/types.js.map +1 -0
  146. package/dist/ui/App.js +216 -0
  147. package/dist/ui/App.js.map +1 -0
  148. package/dist/ui/Input.js +90 -0
  149. package/dist/ui/Input.js.map +1 -0
  150. package/dist/ui/Message.js +89 -0
  151. package/dist/ui/Message.js.map +1 -0
  152. package/dist/ui/MessageList.js +35 -0
  153. package/dist/ui/MessageList.js.map +1 -0
  154. package/dist/ui/Permission.js +39 -0
  155. package/dist/ui/Permission.js.map +1 -0
  156. package/dist/ui/Status.js +34 -0
  157. package/dist/ui/Status.js.map +1 -0
  158. package/dist/ui/TaskPanel.js +43 -0
  159. package/dist/ui/TaskPanel.js.map +1 -0
  160. package/dist/ui/Throbber.js +20 -0
  161. package/dist/ui/Throbber.js.map +1 -0
  162. package/dist/ui/ToolPanel.js +83 -0
  163. package/dist/ui/ToolPanel.js.map +1 -0
  164. package/dist/ui/UserQuery.js +38 -0
  165. package/dist/ui/UserQuery.js.map +1 -0
  166. package/dist/ui/input-state.js +210 -0
  167. package/dist/ui/input-state.js.map +1 -0
  168. package/dist/ui/wrap.js +30 -0
  169. package/dist/ui/wrap.js.map +1 -0
  170. package/dist/user-queries/store.js +60 -0
  171. package/dist/user-queries/store.js.map +1 -0
  172. package/package.json +76 -0
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Read-only shell command allowlist. Direct port from
3
+ * permission.go:65-90 on origin/anthropic-support. Adding a prefix
4
+ * requires a code-review note: why is this safe?
5
+ *
6
+ * Matching rules:
7
+ * - exact match against the first piped/conditional segment
8
+ * - prefix-with-space match (so `git log` matches `git log --oneline`)
9
+ *
10
+ * Anything that survives the dangerous-pattern check AND matches a
11
+ * read-only prefix bypasses the permission gate. Everything else prompts.
12
+ */
13
+ export const READ_ONLY_SHELL_PREFIXES = [
14
+ // Listing + viewing
15
+ "ls",
16
+ "cat",
17
+ "head",
18
+ "tail",
19
+ "wc",
20
+ "file",
21
+ "stat",
22
+ "tree",
23
+ "basename",
24
+ "dirname",
25
+ "realpath",
26
+ // Search
27
+ "grep",
28
+ "egrep",
29
+ "fgrep",
30
+ "rg",
31
+ "find",
32
+ "fd",
33
+ "ag",
34
+ "locate",
35
+ // Git read
36
+ "git status",
37
+ "git log",
38
+ "git diff",
39
+ "git show",
40
+ "git branch",
41
+ "git remote",
42
+ "git tag",
43
+ "git stash list",
44
+ "git config --get",
45
+ "git rev-parse",
46
+ "git ls-files",
47
+ "git blame",
48
+ "git describe",
49
+ "git shortlog",
50
+ "git reflog",
51
+ // Build / test (run the same command repeatedly is fine)
52
+ "go vet",
53
+ "go build",
54
+ "go test",
55
+ "go fmt",
56
+ "gofmt -l",
57
+ "npm test",
58
+ "npm run",
59
+ "npm ls",
60
+ "npm view",
61
+ "npm outdated",
62
+ "pnpm test",
63
+ "pnpm run",
64
+ "yarn test",
65
+ "yarn run",
66
+ "pytest",
67
+ "python -c",
68
+ "python3 -c",
69
+ "ruby -e",
70
+ "node -e",
71
+ "node --check",
72
+ "deno check",
73
+ "cargo check",
74
+ "cargo build",
75
+ "cargo test",
76
+ "cargo clippy",
77
+ "make",
78
+ "mvn test",
79
+ "gradle test",
80
+ // System inspection
81
+ "du",
82
+ "df",
83
+ "uname",
84
+ "whoami",
85
+ "pwd",
86
+ "env",
87
+ "printenv",
88
+ "hostname",
89
+ "uptime",
90
+ "id",
91
+ "groups",
92
+ "ps",
93
+ "who",
94
+ // Pipe-friendly text utilities
95
+ "jq",
96
+ "sort",
97
+ "uniq",
98
+ "tr",
99
+ "cut",
100
+ "awk",
101
+ "sed",
102
+ "echo",
103
+ "printf",
104
+ "date",
105
+ "which",
106
+ "type",
107
+ "command -v",
108
+ "diff",
109
+ "cmp",
110
+ "yes",
111
+ ];
112
+ /**
113
+ * Patterns that always force a permission prompt, even if the leading
114
+ * command looks read-only. These are the "if you do this without
115
+ * approval and it goes wrong, the user loses data" patterns.
116
+ */
117
+ export const DANGEROUS_PATTERNS = [
118
+ /\brm\s+-r?-?f\s+\//,
119
+ /\brm\s+-r?-?f\s+~/,
120
+ /:\s*\(\s*\)\s*\{[^}]*:\s*\|\s*:[^}]*\}\s*;/, // fork bomb
121
+ /\bdd\s+(?:[a-z]+=\S+\s+)*of=\/dev\//,
122
+ /\bmkfs(?:\.\w+)?\b/,
123
+ /\bshutdown\b/,
124
+ /\breboot\b/,
125
+ />\s*\/dev\/sd[a-z]/,
126
+ /\bchmod\s+(?:-R\s+)?(?:777|0?000)\s+\//,
127
+ ];
128
+ /** True if the shell command should require user approval before running. */
129
+ export function shellNeedsPermission(rawCommand) {
130
+ const cmd = rawCommand.trim();
131
+ if (!cmd)
132
+ return true;
133
+ if (DANGEROUS_PATTERNS.some((re) => re.test(cmd)))
134
+ return true;
135
+ const firstSegment = splitFirstSegment(cmd);
136
+ return !isReadOnlyPrefix(firstSegment);
137
+ }
138
+ /** True if the leading token of a chained shell expression is on the allowlist. */
139
+ function isReadOnlyPrefix(segment) {
140
+ const trimmed = segment.trim();
141
+ for (const prefix of READ_ONLY_SHELL_PREFIXES) {
142
+ if (trimmed === prefix || trimmed.startsWith(`${prefix} `)) {
143
+ return true;
144
+ }
145
+ }
146
+ return false;
147
+ }
148
+ /**
149
+ * Extract the first command in a chain. Splits on shell separators while
150
+ * ignoring those inside single or double quotes — we don't want
151
+ * `echo "rm -rf /"` to be misclassified.
152
+ */
153
+ function splitFirstSegment(cmd) {
154
+ let inSingle = false;
155
+ let inDouble = false;
156
+ for (let i = 0; i < cmd.length; i++) {
157
+ const ch = cmd[i];
158
+ if (ch === "'" && !inDouble)
159
+ inSingle = !inSingle;
160
+ else if (ch === '"' && !inSingle)
161
+ inDouble = !inDouble;
162
+ else if (!inSingle && !inDouble && (ch === "|" || ch === ";" || ch === "&")) {
163
+ return cmd.slice(0, i);
164
+ }
165
+ }
166
+ return cmd;
167
+ }
168
+ //# sourceMappingURL=permission.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission.js","sourceRoot":"","sources":["../../src/tools/permission.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ import { Type } from "typebox";
2
+ const EnterParams = Type.Object({
3
+ reason: Type.Optional(Type.String({
4
+ description: "Why you're entering plan mode. Optional, shown in the result for the user.",
5
+ })),
6
+ });
7
+ const ENTER_DESCRIPTION = `Enter plan mode. While active, destructive tools (write_file, edit_file, multi_edit, notebook_edit, shell, git_commit, git_branch, enter_worktree, exit_worktree) will be BLOCKED at the permission gate — only read tools work.
8
+
9
+ Use this when the user asks for a complex multi-step change. The flow is:
10
+ 1. enter_plan_mode (no args, optionally a reason)
11
+ 2. Investigate freely with read_file, list_files, glob, grep, etc.
12
+ 3. Produce a markdown plan as a regular assistant message.
13
+ 4. Call exit_plan_mode with the plan as the argument. The user sees the plan in the result.
14
+ 5. After exit, you have full tool access again to execute the plan.
15
+
16
+ Don't enter plan mode for trivial single-step changes — the gate just adds friction there. Don't try to write files while in plan mode; the gate will block and you'll have to retry without progress.`;
17
+ export function createEnterPlanMode(ctx) {
18
+ return {
19
+ name: "enter_plan_mode",
20
+ label: "Plan: enter",
21
+ description: ENTER_DESCRIPTION,
22
+ parameters: EnterParams,
23
+ executionMode: "sequential",
24
+ execute: async (_id, params) => {
25
+ ctx.planMode.enter();
26
+ const reason = params.reason?.trim() || null;
27
+ return {
28
+ content: [
29
+ {
30
+ type: "text",
31
+ text: reason
32
+ ? `Entered plan mode (${reason}). Write/edit/shell tools are blocked until exit_plan_mode.`
33
+ : "Entered plan mode. Write/edit/shell tools are blocked until exit_plan_mode.",
34
+ },
35
+ ],
36
+ details: { active: true, reason },
37
+ };
38
+ },
39
+ };
40
+ }
41
+ const ExitParams = Type.Object({
42
+ plan: Type.String({
43
+ minLength: 1,
44
+ description: "The markdown plan you've produced. The user sees this verbatim and can choose whether to continue.",
45
+ }),
46
+ });
47
+ const EXIT_DESCRIPTION = `Exit plan mode and present your plan to the user.
48
+
49
+ Pass the markdown plan as 'plan'. The user sees it in the result and can either let you continue executing the plan, or interrupt with corrections. After this returns you have full tool access again.
50
+
51
+ Don't exit without a plan. Don't exit if you're still investigating — keep using read tools first.`;
52
+ export function createExitPlanMode(ctx) {
53
+ return {
54
+ name: "exit_plan_mode",
55
+ label: "Plan: exit",
56
+ description: EXIT_DESCRIPTION,
57
+ parameters: ExitParams,
58
+ executionMode: "sequential",
59
+ execute: async (_id, params) => {
60
+ ctx.planMode.exit();
61
+ return {
62
+ content: [
63
+ {
64
+ type: "text",
65
+ text: `Exited plan mode. Plan presented:\n\n${params.plan}\n\nFull tool access restored. Execute the plan in order; stop or revise if the user pushes back.`,
66
+ },
67
+ ],
68
+ details: { active: false, plan: params.plan },
69
+ };
70
+ },
71
+ };
72
+ }
73
+ export function createPlanModeTools(ctx) {
74
+ return [createEnterPlanMode(ctx), createExitPlanMode(ctx)];
75
+ }
76
+ //# sourceMappingURL=plan-mode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-mode.js","sourceRoot":"","sources":["../../src/tools/plan-mode.ts"],"names":[],"mappings":"AACA,OAAO,EAA6B,IAAI,EAAE,MAAM,SAAS,CAAC;AAG1D,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC;IAC/B,MAAM,EAAE,IAAI,CAAC,QAAQ,CACpB,IAAI,CAAC,MAAM,CAAC;QACX,WAAW,EAAE,4EAA4E;KACzF,CAAC,CACF;CACD,CAAC,CAAC;AASH,MAAM,iBAAiB,GAAG;;;;;;;;;uMAS6K,CAAC;AAExM,MAAM,UAAU,mBAAmB,CAAC,GAAgB;IACnD,OAAO;QACN,IAAI,EAAE,iBAAiB;QACvB,KAAK,EAAE,aAAa;QACpB,WAAW,EAAE,iBAAiB;QAC9B,UAAU,EAAE,WAAW;QACvB,aAAa,EAAE,YAAY;QAC3B,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE;YAC9B,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YACrB,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC;YAC7C,OAAO;gBACN,OAAO,EAAE;oBACR;wBACC,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,MAAM;4BACX,CAAC,CAAC,sBAAsB,MAAM,6DAA6D;4BAC3F,CAAC,CAAC,6EAA6E;qBAChF;iBACD;gBACD,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE;aACjC,CAAC;QACH,CAAC;KACD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC;IAC9B,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC;QACjB,SAAS,EAAE,CAAC;QACZ,WAAW,EAAE,oGAAoG;KACjH,CAAC;CACF,CAAC,CAAC;AASH,MAAM,gBAAgB,GAAG;;;;mGAI0E,CAAC;AAEpG,MAAM,UAAU,kBAAkB,CAAC,GAAgB;IAClD,OAAO;QACN,IAAI,EAAE,gBAAgB;QACtB,KAAK,EAAE,YAAY;QACnB,WAAW,EAAE,gBAAgB;QAC7B,UAAU,EAAE,UAAU;QACtB,aAAa,EAAE,YAAY;QAC3B,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE;YAC9B,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;YACpB,OAAO;gBACN,OAAO,EAAE;oBACR;wBACC,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,wCAAwC,MAAM,CAAC,IAAI,mGAAmG;qBAC5J;iBACD;gBACD,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE;aAC7C,CAAC;QACH,CAAC;KACD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,GAAgB;IACnD,OAAO,CAAC,mBAAmB,CAAC,GAAG,CAAC,EAAE,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,135 @@
1
+ import { readFileSync, statSync } from "node:fs";
2
+ import { extname } from "node:path";
3
+ import { Type } from "typebox";
4
+ import { BinaryFileError, FileTooLargeError } from "./errors.js";
5
+ import { detectEol, isLikelyBinary, resolveInsideCwd, stripBOM } from "./file-ops.js";
6
+ const Params = Type.Object({
7
+ path: Type.String({
8
+ description: "File path (absolute or relative to the project root).",
9
+ }),
10
+ offset: Type.Optional(Type.Integer({
11
+ minimum: 1,
12
+ description: "1-based starting line. Defaults to 1.",
13
+ })),
14
+ limit: Type.Optional(Type.Integer({
15
+ minimum: 1,
16
+ maximum: 2000,
17
+ description: "Max lines to return. Defaults to 2000.",
18
+ })),
19
+ });
20
+ const DEFAULT_LIMIT = 2000;
21
+ const MAX_FILE_BYTES = 5 * 1024 * 1024;
22
+ const IMAGE_MIME = {
23
+ ".png": "image/png",
24
+ ".jpg": "image/jpeg",
25
+ ".jpeg": "image/jpeg",
26
+ ".gif": "image/gif",
27
+ ".webp": "image/webp",
28
+ ".bmp": "image/bmp",
29
+ };
30
+ const DESCRIPTION = `Read the contents of a text or image file.
31
+
32
+ Arguments:
33
+ - path: file path. Absolute or relative to the project root. Symlinks are followed but resolved targets must remain inside the project root.
34
+ - offset (optional): 1-based first line to return. Default 1.
35
+ - limit (optional): max lines to return. Default 2000, max 2000.
36
+
37
+ Behavior:
38
+ - Text files: returned line-numbered (six-column gutter, tab, content), suitable for the model to quote back into edit_file.
39
+ - Image files (.png, .jpg, .jpeg, .gif, .webp, .bmp): returned as a vision payload — works only with image-capable models.
40
+ - Files > 5 MB are rejected; use shell head/tail/sed for huge files.
41
+ - Binary text files are rejected.
42
+ - Reading a file records a snapshot for read-before-edit. If the file changes on disk before edit_file fires, the edit is rejected with a clear error.`;
43
+ export function createReadFile(ctx) {
44
+ return {
45
+ name: "read_file",
46
+ label: "Read",
47
+ description: DESCRIPTION,
48
+ parameters: Params,
49
+ executionMode: "parallel",
50
+ execute: async (_toolCallId, params) => {
51
+ const absPath = resolveInsideCwd(ctx.cwd, params.path);
52
+ let stat;
53
+ try {
54
+ stat = statSync(absPath);
55
+ }
56
+ catch (err) {
57
+ const reason = err instanceof Error ? err.message : String(err);
58
+ throw new Error(`Cannot read ${params.path}: ${reason}`);
59
+ }
60
+ if (stat.isDirectory()) {
61
+ throw new Error(`${params.path} is a directory; use list_files to enumerate its contents.`);
62
+ }
63
+ // Image branch
64
+ const ext = extname(absPath).toLowerCase();
65
+ const mime = IMAGE_MIME[ext];
66
+ if (mime) {
67
+ if (stat.size > MAX_FILE_BYTES) {
68
+ throw new FileTooLargeError(params.path, stat.size, MAX_FILE_BYTES);
69
+ }
70
+ const data = readFileSync(absPath).toString("base64");
71
+ return {
72
+ content: [{ type: "image", data, mimeType: mime }],
73
+ details: {
74
+ path: absPath,
75
+ bytes: stat.size,
76
+ totalLines: 0,
77
+ returnedLines: 0,
78
+ hasBOM: false,
79
+ eol: "",
80
+ isPartialView: false,
81
+ isImage: true,
82
+ },
83
+ };
84
+ }
85
+ if (stat.size > MAX_FILE_BYTES) {
86
+ throw new FileTooLargeError(params.path, stat.size, MAX_FILE_BYTES);
87
+ }
88
+ const raw = readFileSync(absPath);
89
+ if (isLikelyBinary(raw)) {
90
+ throw new BinaryFileError(params.path);
91
+ }
92
+ const { content, hasBOM } = stripBOM(raw);
93
+ const eol = detectEol(content);
94
+ const allLines = content.split(/\r\n|\n/);
95
+ // Trailing-newline files split into an extra empty trailing element; trim it for line counts.
96
+ const totalLines = allLines.length > 0 && allLines[allLines.length - 1] === "" ? allLines.length - 1 : allLines.length;
97
+ const offset = params.offset ?? 1;
98
+ const limit = params.limit ?? DEFAULT_LIMIT;
99
+ const startIdx = Math.max(0, offset - 1);
100
+ const endIdx = Math.min(startIdx + limit, totalLines);
101
+ const isPartialView = startIdx > 0 || endIdx < totalLines;
102
+ const slice = allLines.slice(startIdx, endIdx);
103
+ const formatted = slice.map((line, i) => `${pad6(startIdx + i + 1)}\t${line}`).join("\n");
104
+ const tail = isPartialView ? `\n... (showing lines ${startIdx + 1}-${endIdx} of ${totalLines})` : "";
105
+ ctx.fileStateCache.record({
106
+ path: absPath,
107
+ content,
108
+ mtimeMs: stat.mtimeMs,
109
+ size: stat.size,
110
+ hasBOM,
111
+ eol,
112
+ isPartialView,
113
+ range: isPartialView ? { startLine: startIdx + 1, endLine: endIdx } : undefined,
114
+ storedAt: Date.now(),
115
+ });
116
+ return {
117
+ content: [{ type: "text", text: formatted + tail }],
118
+ details: {
119
+ path: absPath,
120
+ bytes: stat.size,
121
+ totalLines,
122
+ returnedLines: slice.length,
123
+ hasBOM,
124
+ eol,
125
+ isPartialView,
126
+ isImage: false,
127
+ },
128
+ };
129
+ },
130
+ };
131
+ }
132
+ function pad6(n) {
133
+ return String(n).padStart(6, " ");
134
+ }
135
+ //# sourceMappingURL=read-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-file.js","sourceRoot":"","sources":["../../src/tools/read-file.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import { createAskUser } from "./ask-user.js";
2
+ import { createDispatchAgent } from "./dispatch-agent.js";
3
+ import { createEditFile } from "./edit-file.js";
4
+ import { createGitBranch } from "./git/branch.js";
5
+ import { createGitCommit } from "./git/commit.js";
6
+ import { createGitDiff } from "./git/diff.js";
7
+ import { createGitLog } from "./git/log.js";
8
+ import { createGitStatus } from "./git/status.js";
9
+ import { createEnterWorktree, createExitWorktree } from "./git/worktree.js";
10
+ import { createGlob } from "./glob.js";
11
+ import { createGrep } from "./grep.js";
12
+ import { createListFiles } from "./list-files.js";
13
+ import { createMultiEdit } from "./multi-edit.js";
14
+ import { createNotebookEdit } from "./notebook-edit.js";
15
+ import { createPlanModeTools } from "./plan-mode.js";
16
+ import { createReadFile } from "./read-file.js";
17
+ import { createShell } from "./shell.js";
18
+ import { createTaskTools } from "./tasks.js";
19
+ import { createWebFetch } from "./web-fetch.js";
20
+ import { createWebSearch } from "./web-search.js";
21
+ import { createWriteFile } from "./write-file.js";
22
+ /**
23
+ * Returns every built-in tool, configured against the given context.
24
+ * Phase 2 commits append factories to this list one by one.
25
+ */
26
+ export function buildTools(ctx) {
27
+ return [
28
+ createReadFile(ctx),
29
+ createEditFile(ctx),
30
+ createMultiEdit(ctx),
31
+ createNotebookEdit(ctx),
32
+ createWriteFile(ctx),
33
+ createShell(ctx),
34
+ createListFiles(ctx),
35
+ createGlob(ctx),
36
+ createGrep(ctx),
37
+ createGitStatus(ctx),
38
+ createGitDiff(ctx),
39
+ createGitLog(ctx),
40
+ createGitCommit(ctx),
41
+ createGitBranch(ctx),
42
+ createEnterWorktree(ctx),
43
+ createExitWorktree(ctx),
44
+ createAskUser(ctx),
45
+ ...createPlanModeTools(ctx),
46
+ createWebFetch(ctx),
47
+ createWebSearch(ctx),
48
+ createDispatchAgent(ctx),
49
+ ...createTaskTools(ctx),
50
+ ];
51
+ }
52
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/tools/registry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,GAAgB;IAC1C,OAAO;QACN,cAAc,CAAC,GAAG,CAAC;QACnB,cAAc,CAAC,GAAG,CAAC;QACnB,eAAe,CAAC,GAAG,CAAC;QACpB,kBAAkB,CAAC,GAAG,CAAC;QACvB,eAAe,CAAC,GAAG,CAAC;QACpB,WAAW,CAAC,GAAG,CAAC;QAChB,eAAe,CAAC,GAAG,CAAC;QACpB,UAAU,CAAC,GAAG,CAAC;QACf,UAAU,CAAC,GAAG,CAAC;QACf,eAAe,CAAC,GAAG,CAAC;QACpB,aAAa,CAAC,GAAG,CAAC;QAClB,YAAY,CAAC,GAAG,CAAC;QACjB,eAAe,CAAC,GAAG,CAAC;QACpB,eAAe,CAAC,GAAG,CAAC;QACpB,mBAAmB,CAAC,GAAG,CAAC;QACxB,kBAAkB,CAAC,GAAG,CAAC;QACvB,aAAa,CAAC,GAAG,CAAC;QAClB,GAAG,mBAAmB,CAAC,GAAG,CAAC;QAC3B,cAAc,CAAC,GAAG,CAAC;QACnB,eAAe,CAAC,GAAG,CAAC;QACpB,mBAAmB,CAAC,GAAG,CAAC;QACxB,GAAG,eAAe,CAAC,GAAG,CAAC;KACvB,CAAC;AACH,CAAC"}
@@ -0,0 +1,216 @@
1
+ import { spawn } from "node:child_process";
2
+ import { randomBytes } from "node:crypto";
3
+ import { writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join, resolve } from "node:path";
6
+ import { Type } from "typebox";
7
+ import { TimeoutError } from "./errors.js";
8
+ const Params = Type.Object({
9
+ command: Type.String({
10
+ description: "Shell command to run via /bin/sh -c (Unix) or cmd.exe /c (Windows). Pipes and redirection allowed.",
11
+ }),
12
+ cwd: Type.Optional(Type.String({
13
+ description: "Working directory, relative to the project root. Defaults to the project root. Must remain inside the project root.",
14
+ })),
15
+ timeout_ms: Type.Optional(Type.Integer({
16
+ minimum: 100,
17
+ maximum: 600_000,
18
+ description: "Kill the command after this many ms. Default 30000. Max 600000 (10 min).",
19
+ })),
20
+ });
21
+ const DEFAULT_TIMEOUT_MS = 30_000;
22
+ const VISIBLE_CAP_BYTES = 30_000;
23
+ const HEAD_RATIO = 0.6;
24
+ const DESCRIPTION = `Run a shell command. Output is streamed back as it arrives so long-running commands surface progress immediately.
25
+
26
+ Behavior:
27
+ - Runs through the platform shell (sh -c on Unix, cmd /c on Windows) so pipes and redirection work.
28
+ - Output is captured combined (stdout + stderr in source order). Up to ~30 KB is shown to you; anything beyond is written to a temp file whose path is reported in the result so you can read selectively.
29
+ - Default timeout is 30 seconds; raise via timeout_ms (max 10 minutes). On timeout you'll see "command timed out".
30
+ - The cwd defaults to the project root. Set cwd if the command needs to run elsewhere within the project.
31
+ - Aborting the agent (Ctrl-C) propagates to the running command via SIGTERM.
32
+
33
+ Permission gating: write/destructive shell commands are gated by a hook before reaching this tool. Read-only commands (ls, cat, grep, rg, git log, npm test, etc.) bypass the gate.`;
34
+ export function createShell(ctx) {
35
+ return {
36
+ name: "shell",
37
+ label: "Shell",
38
+ description: DESCRIPTION,
39
+ parameters: Params,
40
+ executionMode: "sequential",
41
+ execute: async (toolCallId, params, signal, onUpdate) => {
42
+ const cwd = resolveSubCwd(ctx.cwd, params.cwd);
43
+ const timeoutMs = params.timeout_ms ?? DEFAULT_TIMEOUT_MS;
44
+ const startedAt = Date.now();
45
+ const acc = new OutputAccumulator();
46
+ let timedOut = false;
47
+ let aborted = false;
48
+ let updateTimer;
49
+ let lastUpdateAt = 0;
50
+ const child = spawn(params.command, {
51
+ shell: true,
52
+ cwd,
53
+ env: process.env,
54
+ stdio: ["ignore", "pipe", "pipe"],
55
+ detached: process.platform !== "win32",
56
+ });
57
+ const flushUpdate = () => {
58
+ if (!onUpdate)
59
+ return;
60
+ const now = Date.now();
61
+ if (now - lastUpdateAt < 100)
62
+ return;
63
+ lastUpdateAt = now;
64
+ const visible = acc.visible(VISIBLE_CAP_BYTES);
65
+ onUpdate(buildPartial(params.command, visible.text, acc.size()));
66
+ };
67
+ const stop = (kind) => {
68
+ if (kind === "timeout")
69
+ timedOut = true;
70
+ if (kind === "abort")
71
+ aborted = true;
72
+ killProcess(child);
73
+ };
74
+ child.stdout?.on("data", (chunk) => {
75
+ acc.add(chunk);
76
+ scheduleUpdate();
77
+ });
78
+ child.stderr?.on("data", (chunk) => {
79
+ acc.add(chunk);
80
+ scheduleUpdate();
81
+ });
82
+ function scheduleUpdate() {
83
+ if (!onUpdate || updateTimer)
84
+ return;
85
+ updateTimer = setTimeout(() => {
86
+ updateTimer = undefined;
87
+ flushUpdate();
88
+ }, 100);
89
+ }
90
+ const abortHandler = () => stop("abort");
91
+ signal?.addEventListener("abort", abortHandler);
92
+ const timeoutTimer = setTimeout(() => stop("timeout"), timeoutMs);
93
+ const exit = await new Promise((resolveExit) => {
94
+ child.on("error", (err) => {
95
+ acc.add(Buffer.from(`\n[shell error: ${err.message}]\n`, "utf8"));
96
+ resolveExit({ code: 1, signal: null });
97
+ });
98
+ child.on("close", (code, sig) => resolveExit({ code, signal: sig }));
99
+ });
100
+ signal?.removeEventListener("abort", abortHandler);
101
+ clearTimeout(timeoutTimer);
102
+ if (updateTimer)
103
+ clearTimeout(updateTimer);
104
+ const durationMs = Date.now() - startedAt;
105
+ const visible = acc.visible(VISIBLE_CAP_BYTES);
106
+ const spillPath = visible.truncated ? spillToFile(toolCallId, acc.full()) : null;
107
+ if (timedOut) {
108
+ throw new TimeoutError(Math.round(timeoutMs / 1000), "shell");
109
+ }
110
+ const summary = formatSummary(visible.text, exit, durationMs, acc.size(), spillPath, aborted);
111
+ return {
112
+ content: [{ type: "text", text: summary }],
113
+ details: {
114
+ command: params.command,
115
+ exitCode: exit.code,
116
+ signal: exit.signal,
117
+ durationMs,
118
+ bytesTotal: acc.size(),
119
+ truncated: visible.truncated,
120
+ spillPath,
121
+ timedOut,
122
+ aborted,
123
+ },
124
+ };
125
+ },
126
+ };
127
+ }
128
+ function resolveSubCwd(projectCwd, requested) {
129
+ if (!requested)
130
+ return resolve(projectCwd);
131
+ const abs = resolve(projectCwd, requested);
132
+ if (abs !== resolve(projectCwd) && !abs.startsWith(`${resolve(projectCwd)}/`)) {
133
+ throw new Error(`cwd ${requested} is outside the project root.`);
134
+ }
135
+ return abs;
136
+ }
137
+ function killProcess(child) {
138
+ if (!child.pid)
139
+ return;
140
+ try {
141
+ if (process.platform !== "win32") {
142
+ // Negative pid kills the entire process group spawned via detached:true.
143
+ process.kill(-child.pid, "SIGTERM");
144
+ }
145
+ else {
146
+ child.kill("SIGTERM");
147
+ }
148
+ }
149
+ catch {
150
+ // already exited
151
+ }
152
+ }
153
+ function spillToFile(toolCallId, full) {
154
+ const safeId = toolCallId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 32) || randomBytes(4).toString("hex");
155
+ const path = join(tmpdir(), `codebase-shell-${safeId}-${Date.now()}.log`);
156
+ writeFileSync(path, full);
157
+ return path;
158
+ }
159
+ function formatSummary(visible, exit, durationMs, bytesTotal, spillPath, aborted) {
160
+ const lines = [];
161
+ lines.push(visible);
162
+ lines.push("");
163
+ const exitLabel = aborted ? "aborted" : exit.signal ? `killed by ${exit.signal}` : `exit ${exit.code ?? "?"}`;
164
+ lines.push(`[${exitLabel} in ${durationMs}ms; ${bytesTotal} bytes total]`);
165
+ if (spillPath) {
166
+ lines.push(`[full output spilled to ${spillPath}]`);
167
+ }
168
+ return lines.join("\n");
169
+ }
170
+ function buildPartial(command, visible, bytesTotal) {
171
+ return {
172
+ content: [{ type: "text", text: visible }],
173
+ details: {
174
+ command,
175
+ exitCode: null,
176
+ signal: null,
177
+ durationMs: 0,
178
+ bytesTotal,
179
+ truncated: false,
180
+ spillPath: null,
181
+ timedOut: false,
182
+ aborted: false,
183
+ },
184
+ };
185
+ }
186
+ /**
187
+ * Buffers stdout/stderr chunks. Tracks total bytes so we can decide when to
188
+ * spill, and produces a head+tail visible slice when over budget.
189
+ */
190
+ export class OutputAccumulator {
191
+ chunks = [];
192
+ totalBytes = 0;
193
+ add(chunk) {
194
+ this.chunks.push(chunk);
195
+ this.totalBytes += chunk.length;
196
+ }
197
+ size() {
198
+ return this.totalBytes;
199
+ }
200
+ full() {
201
+ return Buffer.concat(this.chunks);
202
+ }
203
+ visible(cap) {
204
+ const all = this.full();
205
+ if (all.length <= cap) {
206
+ return { text: all.toString("utf8"), truncated: false };
207
+ }
208
+ const headSize = Math.floor(cap * HEAD_RATIO);
209
+ const noticeStart = `\n…[${all.length - cap} bytes truncated]…\n`;
210
+ const tailSize = cap - headSize - Buffer.byteLength(noticeStart, "utf8");
211
+ const head = all.subarray(0, headSize).toString("utf8");
212
+ const tail = tailSize > 0 ? all.subarray(all.length - tailSize).toString("utf8") : "";
213
+ return { text: head + noticeStart + tail, truncated: true };
214
+ }
215
+ }
216
+ //# sourceMappingURL=shell.js.map