@epoch-ai/cli 2.2.4 → 2.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
- "version": "2.2.4",
3
+ "version": "2.2.6",
4
4
  "name": "@epoch-ai/cli",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -23,7 +23,7 @@
23
23
  "db": "bun drizzle-kit"
24
24
  },
25
25
  "bin": {
26
- "epochcli": "bin/epochcli"
26
+ "epochcli": "bin/epochcli.cjs"
27
27
  },
28
28
  "randomField": "this-is-a-random-value-12345",
29
29
  "exports": {
package/script/publish.ts CHANGED
@@ -27,7 +27,7 @@ await Bun.file(`./dist/${pkg.name}/package.json`).write(
27
27
  {
28
28
  name: pkg.name,
29
29
  bin: {
30
- epochcli: `./bin/epochcli`,
30
+ epochcli: `./bin/epochcli.cjs`,
31
31
  },
32
32
  scripts: {
33
33
  postinstall: "node ./postinstall.mjs",
@@ -41,16 +41,14 @@ await Bun.file(`./dist/${pkg.name}/package.json`).write(
41
41
  ),
42
42
  )
43
43
 
44
- const tasks = Object.entries(binaries).map(async ([name]) => {
44
+ for (const [name] of Object.entries(binaries)) {
45
45
  const pkgDir = `./dist/${name}`
46
46
  if (process.platform !== "win32") {
47
47
  await $`chmod -R 755 .`.cwd(pkgDir)
48
48
  }
49
- await $`bun pm pack`.cwd(pkgDir)
50
- await $`npm publish *.tgz --access public --tag latest`.cwd(pkgDir).nothrow()
51
- })
52
- await Promise.all(tasks)
53
- await $`cd ./dist/${pkg.name} && bun pm pack && npm publish *.tgz --access public --tag latest`.nothrow()
49
+ await $`npm publish --access public --tag latest`.cwd(pkgDir).nothrow()
50
+ }
51
+ await $`cd ./dist/${pkg.name} && npm publish --access public --tag latest`.nothrow()
54
52
 
55
53
  // const image = "ghcr.io/benjamesmurray/epoch-cli"
56
54
  // const platforms = "linux/amd64,linux/arm64"
@@ -1219,7 +1219,19 @@ export function Prompt(props: PromptProps) {
1219
1219
  />
1220
1220
  </box>
1221
1221
  <box flexDirection="row" justifyContent="space-between">
1222
- <Show when={status().type !== "idle"} fallback={props.hint ?? <text />}>
1222
+ <Show
1223
+ when={status().type !== "idle"}
1224
+ fallback={
1225
+ <box flexDirection="row" gap={2}>
1226
+ {props.hint ?? <text />}
1227
+ <Show when={store.mode === "normal"}>
1228
+ <text fg={theme.text}>
1229
+ {keybind.print("yolo_toggle")} <span style={{ fg: theme.textMuted }}>mode</span>
1230
+ </text>
1231
+ </Show>
1232
+ </box>
1233
+ }
1234
+ >
1223
1235
  <box
1224
1236
  flexDirection="row"
1225
1237
  gap={1}
@@ -1307,7 +1319,7 @@ export function Prompt(props: PromptProps) {
1307
1319
  <Match when={usage()}>
1308
1320
  {(item) => (
1309
1321
  <text fg={theme.textMuted} wrapMode="none">
1310
- {[item().context, item().cost].filter(Boolean).join(" · ")}
1322
+ {item().context}
1311
1323
  </text>
1312
1324
  )}
1313
1325
  </Match>
@@ -1,34 +1,46 @@
1
1
  import type { AssistantMessage } from "@epoch-ai/sdk/v2"
2
2
  import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from "@epoch-ai/plugin/tui"
3
- import { createMemo } from "solid-js"
3
+ import { createMemo, Show } from "solid-js"
4
4
 
5
5
  const id = "internal:sidebar-context"
6
6
 
7
- const money = new Intl.NumberFormat("en-US", {
8
- style: "currency",
9
- currency: "USD",
10
- })
11
-
12
7
  function View(props: { api: TuiPluginApi; session_id: string }) {
13
8
  const theme = () => props.api.theme.current
14
9
  const msg = createMemo(() => props.api.state.session.messages(props.session_id))
15
- const cost = createMemo(() => msg().reduce((sum, item) => sum + (item.role === "assistant" ? item.cost : 0), 0))
16
10
 
17
11
  const state = createMemo(() => {
18
12
  const last = msg().findLast((item): item is AssistantMessage => item.role === "assistant" && item.tokens.output > 0)
19
13
  if (!last) {
20
14
  return {
21
15
  tokens: 0,
22
- percent: null,
16
+ usable: null,
17
+ limit: null,
18
+ margin: null,
23
19
  }
24
20
  }
25
21
 
26
22
  const tokens =
27
23
  last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write
28
24
  const model = props.api.state.provider.find((item) => item.id === last.providerID)?.models[last.modelID]
25
+
26
+ let limit = null
27
+ let usable = null
28
+ let margin = null
29
+
30
+ if (model) {
31
+ limit = model.limit.context
32
+ if (limit > 0) {
33
+ const maxOutput = Math.min(model.limit.output || 32000, 32000)
34
+ margin = Math.min(1024, Math.max(500, maxOutput))
35
+ usable = model.limit.input ? model.limit.input : limit - margin
36
+ }
37
+ }
38
+
29
39
  return {
30
40
  tokens,
31
- percent: model?.limit.context ? Math.round((tokens / model.limit.context) * 100) : null,
41
+ usable,
42
+ limit,
43
+ margin,
32
44
  }
33
45
  })
34
46
 
@@ -38,8 +50,15 @@ function View(props: { api: TuiPluginApi; session_id: string }) {
38
50
  <b>Context</b>
39
51
  </text>
40
52
  <text fg={theme().textMuted}>{state().tokens.toLocaleString()} tokens</text>
41
- <text fg={theme().textMuted}>{state().percent ?? 0}% used</text>
42
- <text fg={theme().textMuted}>{money.format(cost())} spent</text>
53
+ <Show when={state().usable !== null}>
54
+ <text fg={theme().textMuted}>{state().usable!.toLocaleString()} usable</text>
55
+ </Show>
56
+ <Show when={state().limit !== null}>
57
+ <text fg={theme().textMuted}>{state().limit!.toLocaleString()} limit</text>
58
+ </Show>
59
+ <Show when={state().margin !== null}>
60
+ <text fg={theme().textMuted}>{state().margin!.toLocaleString()} margin</text>
61
+ </Show>
43
62
  </box>
44
63
  )
45
64
  }
@@ -64,9 +64,9 @@ function View(props: { api: TuiPluginApi }) {
64
64
  <span style={{ fg: theme().text }}>{path().name}</span>
65
65
  </text>
66
66
  <text fg={theme().textMuted}>
67
- <span style={{ fg: theme().success }}>•</span> <b>Open</b>
67
+ <span style={{ fg: theme().success }}>•</span> <b>Epoch</b>
68
68
  <span style={{ fg: theme().text }}>
69
- <b>Code</b>
69
+ <b> CLI</b>
70
70
  </span>{" "}
71
71
  <span>{props.api.app.version}</span>
72
72
  </text>
@@ -8,11 +8,21 @@ import { useRouteData } from "@tui/context/route"
8
8
  import { usePromptRef } from "../context/prompt"
9
9
  import { useLocal } from "../context/local"
10
10
  import { TuiPluginRuntime } from "../plugin"
11
+ import { execSync } from "child_process"
11
12
 
12
13
  // TODO: what is the best way to do this?
13
14
  let once = false
15
+
16
+ let userName = "there"
17
+ try {
18
+ const output = execSync("git config --global user.name", { stdio: "pipe" }).toString().trim()
19
+ if (output) userName = output
20
+ } catch {
21
+ // Ignore errors (e.g., git not found, config not set)
22
+ }
23
+
14
24
  const placeholder = {
15
- normal: ["Fix a TODO in the codebase", "What is the tech stack of this project?", "Fix broken tests"],
25
+ normal: [`Hi ${userName}, let's go!`],
16
26
  shell: ["ls -la", "git status", "pwd"],
17
27
  }
18
28
 
@@ -56,9 +56,9 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
56
56
  <box flexShrink={0} gap={1} paddingTop={1}>
57
57
  <TuiPluginRuntime.Slot name="sidebar_footer" mode="single_winner" session_id={props.sessionID}>
58
58
  <text fg={theme.textMuted}>
59
- <span style={{ fg: theme.success }}>•</span> <b>Open</b>
59
+ <span style={{ fg: theme.success }}>•</span> <b>Epoch</b>
60
60
  <span style={{ fg: theme.text }}>
61
- <b>Code</b>
61
+ <b> CLI</b>
62
62
  </span>{" "}
63
63
  <span>{Installation.VERSION}</span>
64
64
  </text>
@@ -10,9 +10,15 @@ import { Bus } from "../bus"
10
10
  import { Command } from "../command"
11
11
  import { Instance } from "./instance"
12
12
  import { Log } from "@/util/log"
13
+ import { initProjectFiles } from "./init-files"
13
14
 
14
15
  export async function InstanceBootstrap() {
15
16
  Log.Default.info("bootstrapping", { directory: Instance.directory })
17
+
18
+ if (Instance.project.vcs === "git" && Instance.worktree !== "/") {
19
+ await initProjectFiles(Instance.directory, Instance.worktree)
20
+ }
21
+
16
22
  await Plugin.init()
17
23
  Format.init()
18
24
  await LSP.init()
@@ -0,0 +1,328 @@
1
+ import fs from "fs/promises"
2
+ import path from "path"
3
+ import { zodToJsonSchema } from "zod-to-json-schema"
4
+ import { Config } from "../config/config"
5
+ import { Log } from "../util/log"
6
+
7
+ const log = Log.create({ service: "project-init" })
8
+
9
+ const AGENTS_MD_CONTENT = `# Epoch CLI Agent Guidelines
10
+
11
+ ## 1. Mandatory Workflows
12
+ - **\`mcpx\` (Unified MCP Interface)**: Use for ALL MCP interactions (spec, map, ground, github).
13
+ - **Adding Servers**: When asked to install or configure a new MCP server, you MUST follow the SOP defined in \`docs/MCP_config_guide.md\`.
14
+ - **Syntax**: Pass arguments via standard flags, \`key=value\` strings, or structured JSON.
15
+ - **Self-Discovery**: Use \`tool="--help"\` or \`args=["--help"]\` to inspect servers/tools.
16
+ - **\`spec\` (State Management)**: Maintain project state via \`mcpx\` server="spec".
17
+ - **Linear Progression**: \`sc_init\` -> \`write Specification.md\` -> \`sc_approve\` -> \`write Tasks.json\` -> \`sc_approve\` -> \`Build\`.
18
+ - **Arguments**: \`sc_init\` MANDATES the \`name\` parameter (e.g., \`flags: {"name": "my-feature"}\`). \`sc_plan\` and \`sc_approve\` take NO arguments. \`sc_todo_*\` requires \`--id\`.
19
+ - **\`map\` (Architectural Discovery)**: Use \`mcpx\` server="map" for navigation. Prefer \`pm_status\` and \`pm_query\` over manual \`ls\` or \`glob\`.
20
+
21
+ ## 2. Enforcement
22
+ - **Drafting Wall**: All template tags in \`Specification.md\` must be cleared and \`template_tags_present\` set to \`false\` before implementation.
23
+ - **Orientation**: Trust the provided context. If \`ORIENTATION_REQUIREMENTS: SATISFIED\` is present, do not repeat discovery tools.
24
+ - **YOLO Mode**: Proceed autonomously until \`task_complete\` is called.
25
+
26
+ ## 3. Subagent Guardrails
27
+ - **No State Mutation**: Subagents (e.g., \`@explore\`, \`@general\`) are STRICTLY FORBIDDEN from calling \`sc_init\` or \`sc_approve\`. Only the primary \`plan\` or \`build\` agent may mutate the global project lifecycle state.
28
+ - **Architectural Discovery**: Subagents MUST prioritize \`mcpx map\` tools (\`pm_query\` for context, \`pm_fetch_symbol\` for precise code extraction) to navigate the codebase efficiently. Avoid wide \`glob\` or \`read\` loops on large files.
29
+ `
30
+
31
+ const MCP_CONFIG_GUIDE_CONTENT = `# Guide: Setting Up MCP Servers with \`mcpx-rust\`
32
+
33
+ This guide explains how to configure Model Context Protocol (MCP) servers within this project using the \`mcpx-rust\` CLI utility. By using \`mcpx\`, we reduce prompt bloat by replacing massive JSON schemas with a single, discoverable CLI interface.
34
+
35
+ ## 1. Installation
36
+
37
+ \`mcpx-rust\` is the Rust-based binary used in this project to turn MCP servers into composable shell commands.
38
+
39
+ \`\`\`bash
40
+ # Via Cargo
41
+ cargo install mcpx-rust
42
+ \`\`\`
43
+
44
+ ## 2. Registering Servers
45
+
46
+ \`mcpx-rust\` stores its configuration in \`~/.config/mcpx/config.toml\`. You must edit this file manually to add or modify servers.
47
+
48
+ ### Manual Configuration
49
+ Add entries to the \`[mcp_servers]\` section in \`~/.config/mcpx/config.toml\`:
50
+
51
+ \`\`\`toml
52
+ [mcp_servers.map]
53
+ command = "project-map-cli-rust"
54
+ args = ["mcp"]
55
+
56
+ [mcp_servers.spec]
57
+ command = "deliver-cli"
58
+ args = ["mcp"]
59
+
60
+ [mcp_servers.github]
61
+ command = "npx"
62
+ args = ["-y", "@modelcontextprotocol/server-github"]
63
+ env = { GITHUB_TOKEN = "\${GITHUB_TOKEN}" }
64
+ \`\`\`
65
+
66
+ ## 3. \`epochcli\` / \`gemini-cli\` Integration
67
+
68
+ To enable \`mcpx\` integration, update your configuration file (e.g., \`.epochcli/epochcli.jsonc\` or \`.gemini/settings.json\`):
69
+
70
+ \`\`\`jsonc
71
+ {
72
+ "mcpx": {
73
+ "enabled": true,
74
+ "binaryPath": "mcpx-rust" // Optional: defaults to 'mcpx-rust'
75
+ },
76
+ "mcp": {} // Leave empty to disable standard schema-based MCP tools
77
+ }
78
+ \`\`\`
79
+
80
+ When enabled, the CLI will only expose a single \`mcpx\` tool to the LLM. The agent will discover capabilities dynamically by running \`mcpx <server> --help\`.
81
+
82
+ ## 4. Usage and Composition
83
+
84
+ Once configured, tools can be called using standard shell composition:
85
+
86
+ \`\`\`bash
87
+ # List all servers
88
+ mcpx-rust list
89
+
90
+ # List tools for a server
91
+ mcpx-rust github
92
+
93
+ # Inspect a specific tool's schema
94
+ mcpx-rust github search-repositories --help
95
+
96
+ # Call a tool and pipe to jq
97
+ mcpx-rust github search-repositories query=mcp --json | jq -r '.content[0].text'
98
+ \`\`\`
99
+
100
+ Note: \`mcpx-rust\` uses \`key=value\` syntax for positional arguments or standard \`--flag value\` syntax depending on the tool's implementation.
101
+
102
+ ## 5. Project Servers
103
+
104
+ The following project-specific servers are pre-configured. Agents should use the unified \`mcpx\` tool for all operations:
105
+
106
+ - **\`spec\`**: Management of specification-driven development.
107
+ - \`mcpx spec sc_status\`: View project health and next steps.
108
+ - \`mcpx spec sc_todo_start\`: Mark a task as active.
109
+ - **\`map\`**: Architectural mapping and symbol analysis.
110
+ - \`mcpx map pm_query\`: Search for symbols or get file context.
111
+ - \`mcpx map pm_plan\`: Analyze the architectural impact of a change.
112
+ - **\`ground\`**: Synthesis of behavioral rules and operational facts.
113
+ - \`mcpx ground gt_status\`: Check current project rules.
114
+ - \`mcpx ground gt_refresh\`: Force a refresh of the project constitution.
115
+
116
+ ## 6. Agent SOP: Adding a New Server
117
+
118
+ When an agent is instructed to add or install a new MCP server, it MUST follow this sequence to ensure the server is properly registered and discoverable:
119
+
120
+ 1. **Identify the Server**: Determine the correct command (e.g., \`npx -y package-name\`) or binary path for the requested MCP server.
121
+ 2. **Register the Server**: Update the configuration in \`~/.config/mcpx/config.toml\`.
122
+ - *Note*: Agents must use \`echo\` or \`cat <<EOF\` via \`run_shell_command\` to modify this file, as it lives outside the standard workspace directory.
123
+ 3. **Verify Installation**: Run \`mcpx-rust list\` and \`mcpx-rust <new_server> --help\` to ensure the routing engine recognizes the new server and the tool schema is accessible.
124
+ 4. **Update Project Knowledge**: Update \`AGENTS.md\` (or the relevant instruction file) with a brief summary of the new server's capabilities and its \`mcpx\` syntax so that future agent turns can utilize the new tools.
125
+ `
126
+
127
+ const MODEL_CONFIG_GUIDE_CONTENT = `# Model Configuration Guide
128
+
129
+ Epoch CLI utilizes a **Dual-Model Architecture** optimized for high-performance coding and reliable background supervision. This environment uses a unified proxy architecture (**llama-swap**) to manage model transitions efficiently.
130
+
131
+ ## 1. Unified Architecture (llama-swap)
132
+
133
+ Instead of managing separate URLs and ports, all models are served through a single transparent proxy on one port. This allows the system to dynamically swap models in VRAM as needed while maintaining a consistent client configuration.
134
+
135
+ * **Unified API Endpoint:** \`http://localhost:8085/v1\`
136
+ * **Protocol:** OpenAI Compatible
137
+ * **Authentication:** Shared API Key (e.g., \`2250\`)
138
+
139
+ ## 2. Configuration (\`epochcli.jsonc\`)
140
+
141
+ You can configure Epoch CLI to use either a single unified endpoint for both main and side roles, or a dual-model configuration that leverages the \`llama-swap\` proxy.
142
+
143
+ ### Option A: Single Endpoint Configuration (Recommended for High Context)
144
+
145
+ This setup uses one model for both roles. It is ideal for maximizing the context window (e.g., 64k) and eliminating the 15-20 second "cold start" delay associated with swapping models.
146
+
147
+ \`\`\`jsonc
148
+ {
149
+ "model": "local-unified/qwen-unified",
150
+ "side_model": "local-unified/qwen-unified",
151
+ "provider": {
152
+ "local-unified": {
153
+ "npm": "@ai-sdk/openai-compatible",
154
+ "name": "Local Unified (Qwen 64k)",
155
+ "options": {
156
+ "baseURL": "http://localhost:8085/v1",
157
+ "apiKey": "2250"
158
+ },
159
+ "models": {
160
+ "qwen-unified": {
161
+ "name": "Qwen Unified 64k",
162
+ "limit": { "context": 64000, "output": 4096 }
163
+ }
164
+ }
165
+ }
166
+ }
167
+ }
168
+ \`\`\`
169
+
170
+ ### Option B: Dual-Model Configuration (llama-swap)
171
+
172
+ This setup defines separate providers for main and side roles. The proxy handles routing based on the model ID. This is useful when you want a dedicated smaller model for clerk duties to save compute or when specific roles require different model capabilities.
173
+
174
+ \`\`\`jsonc
175
+ {
176
+ "model": "local-main/qwen3.6-35b-a3b-coding",
177
+ "side_model": "local-side/nemotron-3-nano",
178
+ "provider": {
179
+ "local-main": {
180
+ "npm": "@ai-sdk/openai-compatible",
181
+ "name": "Local Main (Coding)",
182
+ "options": {
183
+ "baseURL": "http://localhost:8085/v1",
184
+ "apiKey": "2250"
185
+ },
186
+ "models": {
187
+ "qwen3.6-35b-a3b-coding": {
188
+ "name": "Qwen 3.6 35B Coding",
189
+ "limit": { "context": 64000, "output": 4096 }
190
+ }
191
+ }
192
+ },
193
+ "local-side": {
194
+ "npm": "@ai-sdk/openai-compatible",
195
+ "name": "Local Side (Clerk)",
196
+ "options": {
197
+ "baseURL": "http://localhost:8085/v1",
198
+ "apiKey": "2250"
199
+ },
200
+ "models": {
201
+ "nemotron-3-nano": {
202
+ "name": "Nemotron 3 Nano",
203
+ "limit": { "context": 32000, "output": 2048 }
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
209
+ \`\`\`
210
+
211
+ ### Switching Between Modes
212
+
213
+ To switch modes, simply update the \`model\`, \`side_model\`, and \`provider\` fields in your \`.epochcli/epochcli.jsonc\` file to match the desired configuration block. The CLI will automatically pick up the changes on the next execution.
214
+
215
+ ## 3. How Epoch CLI Identifies Models
216
+
217
+ Epoch CLI uses **keyword matching** on the Model ID string to apply specific architectural optimizations. You do not need to manually configure prompts or behaviors for different models as long as the ID is named correctly:
218
+
219
+ * **Qwen Optimized:** If the ID contains \`"qwen"\` (e.g., \`qwen3.6-35b-a3b-coding\`), the CLI automatically sets the temperature to \`0.55\` and Top-P to \`1.0\`.
220
+ * **Gemma Optimized:** If the ID contains \`"gemma-4"\`, the CLI enables reasoning token injection (\`<|think|>\`), specialized system prompts, and the Three-Stage Sanitizer to repair potential JSON errors.
221
+
222
+ ## 4. Operational Considerations
223
+
224
+ ### The "Cold Start" (Model Swapping)
225
+ The environment uses a **SWAP approach** to maximize VRAM for high-context models. Only one model is active in memory at a time.
226
+ * **Instant Response:** If you request the model that is already "hot" in memory.
227
+ * **Swap Delay:** If you request a model that is currently swapped out, the proxy will load it automatically. This adds a **15–20 second delay** to the first request.
228
+
229
+ ### Context Persistence & TTL
230
+ Models stay active in VRAM for **60 minutes** of inactivity before being automatically unloaded. This ensures subsequent requests within the same hour are nearly instantaneous.
231
+
232
+ ## 5. Monitoring & Maintenance
233
+
234
+ * **Dashboard:** You can monitor which model is currently active and view proxy status at \`http://localhost:8085/ui\`.
235
+ * **Restart Stack:** If you need to restart the entire dual-model stack, use the provided launch script:
236
+ \`\`\`bash
237
+ /home/llm/utils/launch/launch-dual.sh
238
+ \`\`\`
239
+ `
240
+
241
+ const DEFAULT_EPOCHCLI_JSONC = `{
242
+ "$schema": "./config.json",
243
+ "model": "local-unified/qwen-unified",
244
+ "side_model": "local-unified/qwen-unified",
245
+ "provider": {
246
+ "local-unified": {
247
+ "npm": "@ai-sdk/openai-compatible",
248
+ "name": "Local Unified (Qwen 64k)",
249
+ "options": {
250
+ "baseURL": "http://localhost:8085/v1",
251
+ "apiKey": "2250"
252
+ },
253
+ "models": {
254
+ "qwen-unified": {
255
+ "name": "Qwen Unified 64k",
256
+ "limit": {
257
+ "context": 64000,
258
+ "output": 4096
259
+ }
260
+ }
261
+ }
262
+ }
263
+ },
264
+ "permission": {
265
+ "edit": {
266
+ "packages/opencode/migration/*": "deny"
267
+ }
268
+ },
269
+ "mcpx": {
270
+ "enabled": true
271
+ }
272
+ }
273
+ `
274
+
275
+ export async function initProjectFiles(directory: string, worktree: string) {
276
+ try {
277
+ const epochcliDir = path.join(worktree, ".epochcli")
278
+
279
+ try {
280
+ await fs.access(epochcliDir)
281
+ return // Already initialized
282
+ } catch {
283
+ // Doesn't exist, proceed with initialization
284
+ }
285
+
286
+ log.info("Initializing new project files", { worktree })
287
+
288
+ // Create .epochcli directory
289
+ await fs.mkdir(epochcliDir, { recursive: true })
290
+
291
+ // Generate JSON Schema
292
+ const schema = zodToJsonSchema(Config.Info, {
293
+ name: "Config",
294
+ $refStrategy: "none",
295
+ })
296
+
297
+ // Write config.json (schema)
298
+ await fs.writeFile(
299
+ path.join(epochcliDir, "config.json"),
300
+ JSON.stringify(schema, null, 2)
301
+ )
302
+
303
+ // Write default epochcli.jsonc
304
+ await fs.writeFile(
305
+ path.join(epochcliDir, "epochcli.jsonc"),
306
+ DEFAULT_EPOCHCLI_JSONC
307
+ )
308
+
309
+ // Setup docs
310
+ const docsDir = path.join(epochcliDir, "docs")
311
+ await fs.mkdir(docsDir, { recursive: true })
312
+ await fs.writeFile(path.join(docsDir, "MCP_config_guide.md"), MCP_CONFIG_GUIDE_CONTENT)
313
+ await fs.writeFile(path.join(docsDir, "model_config.md"), MODEL_CONFIG_GUIDE_CONTENT)
314
+
315
+ // Setup AGENTS.md in the current directory if it doesn't exist
316
+ const agentsFile = path.join(directory, "AGENTS.md")
317
+ try {
318
+ await fs.access(agentsFile)
319
+ } catch {
320
+ await fs.writeFile(agentsFile, AGENTS_MD_CONTENT)
321
+ log.info("Created AGENTS.md", { path: agentsFile })
322
+ }
323
+
324
+ log.info("Project initialized successfully", { dir: epochcliDir })
325
+ } catch (error) {
326
+ log.error("Failed to initialize project files", { error })
327
+ }
328
+ }
@@ -30,6 +30,5 @@ export function isOverflow(input: {
30
30
  (input.tokens.reasoning ?? 0) +
31
31
  ((input.tokens.cache?.read ?? 0) + (input.tokens.cache?.write ?? 0)))
32
32
 
33
- console.log(`[OverflowCheck] tokens=${count} usable=${usable} (limit=${context} margin=${safetyMargin})`)
34
33
  return count >= usable
35
34
  }
File without changes