thincoder 0.12.67 → 0.12.69
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/CHANGELOG.md +32 -0
- package/README.md +21 -5
- package/bin/thincoder.mjs +41 -344
- package/package.json +2 -2
- package/src/acp/bridge.mjs +5 -2
- package/src/acp/handlers-session.mjs +12 -1
- package/src/cli/make-agent.mjs +44 -119
- package/src/cli/memory-command.mjs +45 -3
- package/src/command-interactive.mjs +196 -0
- package/src/command-table.mjs +185 -0
- package/src/completions.mjs +36 -12
- package/src/crash-reports.mjs +42 -20
- package/src/distill.mjs +3 -3
- package/src/heap-watch.mjs +5 -14
- package/src/tui/agent-turn.mjs +88 -50
- package/src/tui/clipboard.mjs +74 -30
- package/src/tui/cmd-advisor.mjs +39 -13
- package/src/tui/cmd-clear.mjs +2 -2
- package/src/tui/cmd-config.mjs +17 -1
- package/src/tui/cmd-eng.mjs +1 -1
- package/src/tui/cmd-mcp.mjs +1 -1
- package/src/tui/cmd-new.mjs +2 -2
- package/src/tui/cmd-reindex.mjs +1 -1
- package/src/tui/cmd-session.mjs +13 -5
- package/src/tui/cmd-think.mjs +35 -17
- package/src/tui/cmd-timers.mjs +33 -0
- package/src/tui/conversation-writer.mjs +2 -2
- package/src/tui/display-budget.mjs +9 -9
- package/src/tui/fold-block.mjs +1 -1
- package/src/tui/index.mjs +54 -18
- package/src/tui/interaction.mjs +4 -2
- package/src/tui/key-handler-ctrlc.mjs +1 -1
- package/src/tui/key-handler-search.mjs +1 -1
- package/src/tui/key-handler.mjs +1 -1
- package/src/tui/key-modes.mjs +1 -1
- package/src/tui/layout.mjs +5 -5
- package/src/tui/lifecycle-records.mjs +199 -0
- package/src/tui/model-picker.mjs +16 -199
- package/src/tui/mouse.mjs +3 -3
- package/src/tui/pickers.mjs +6 -2
- package/src/tui/provider-admin.mjs +213 -0
- package/src/tui/queued-merge.mjs +4 -60
- package/src/tui/queued-pickup.mjs +3 -3
- package/src/tui/render-conversation.mjs +13 -4
- package/src/tui/render-frame.mjs +43 -6
- package/src/tui/render-loop.mjs +2 -3
- package/src/tui/render-segments.mjs +2 -2
- package/src/tui/render.mjs +1 -1
- package/src/tui/slash-commands.mjs +3 -0
- package/src/tui/startup.mjs +28 -3
- package/src/tui/subagent-blocks.mjs +27 -19
- package/src/tui/subagent-children.mjs +5 -5
- package/src/tui/subagent-freeze.mjs +39 -37
- package/src/tui/subagent-panel.mjs +7 -7
- package/src/tui/suspension-drive.mjs +172 -259
- package/src/tui/timer-watch.mjs +64 -0
- package/src/tui/tool-args.mjs +10 -71
- package/src/tui/tool-display.mjs +6 -6
- package/src/tui/tool-events.mjs +33 -28
- package/src/tui/tui-lifecycle.mjs +13 -4
- package/src/tui/tui-state.mjs +3 -2
- package/src/tui/turn-face.mjs +4 -1
- package/src/tui/wizard.mjs +7 -2
- package/src/tui/wrapped-spawn.mjs +3 -3
package/src/cli/make-agent.mjs
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import {
|
|
1
|
+
/**
|
|
2
|
+
* make-agent.mjs — CLI 装配面(`assembleAgent` 出档;消费面 = `command-interactive.mjs` ∕ `acp.mjs` ∕
|
|
3
|
+
* `distill-command.mjs` ∕ `memory-command.mjs` ∕ `command-table.mjs`)。
|
|
4
|
+
* **装配序 = 核单源**(`thincoder-core/agent/assemble.mjs` —— B5 装配序上提批):本档 = 端壳 adapter,
|
|
5
|
+
* 留面 = cwd(`process.cwd()`)· MCP 合入 + `applyToolExclusions`(入核 `toolsFinalize` 缝 —— 位次保持:
|
|
6
|
+
* 工具装配后、`createAgent` 前)· `_mcpWarnings` · M1 装配钩子 `attachManifest` · 校验调用;装配序本体
|
|
7
|
+
* 与团队层取值随上提归核,本档四名**同名转口**(`teamConfig` ∕ `gitAuthor` ∕ `validateProvider` ∕
|
|
8
|
+
* `DEFAULT_DEPS`)——调用面 import 路径与名面零改。
|
|
9
|
+
*/
|
|
10
|
+
import { join } from "node:path"
|
|
11
|
+
// 装配序核单源(B5 装配序上提批):本档 = 核调用 + MCP 缝 + 后处理。
|
|
12
|
+
import { assembleAgent as coreAssemble, DEFAULT_DEPS, gitAuthor, teamConfig, validateProvider } from "@thincoder/core/agent/assemble.mjs"
|
|
11
13
|
import { resolveEngineeringManifest, projectView } from "@thincoder/core/manifest.mjs"
|
|
12
14
|
|
|
15
|
+
// 四名转口(核单源**恒等绑定**;调用面零改 —— 枚举见档头)。
|
|
16
|
+
export { DEFAULT_DEPS, gitAuthor, teamConfig, validateProvider }
|
|
17
|
+
|
|
13
18
|
/** 第 27 批 §12.3⑤(R-A1.1):装配期工具剔除——按 `name` 过滤的纯函数(机验锚)。
|
|
14
19
|
* 恒等语义:空列表 / 零命中 → 原数组原样返回(零意外剔除——T15)。
|
|
15
20
|
* ACP 通道经 `assembleAgent({ excludeTools })` 传入(acp.mjs `ACP_EXCLUDED_TOOLS`)。 */
|
|
@@ -20,49 +25,35 @@ export function applyToolExclusions(tools, excludeTools = []) {
|
|
|
20
25
|
return kept.length === tools.length ? tools : kept
|
|
21
26
|
}
|
|
22
27
|
|
|
23
|
-
/** Assemble an agent with memory, MCP tools, and code/doc indices attached (sync all layers, then return)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const providers = config.providersList
|
|
28
|
-
|
|
29
|
-
// Inject proxy URI into providers (double opt-in: provider.proxy + config.proxy.model)
|
|
30
|
-
const { injectProxy } = await import("@thincoder/core/proxy.mjs")
|
|
31
|
-
injectProxy(providers, config)
|
|
32
|
-
// config.provider 是 loadConfig 里的独立拷贝,同步注入结果
|
|
33
|
-
if (provider?.name) provider.proxyUri = providers.find((p) => p.name === provider.name)?.proxyUri
|
|
34
|
-
|
|
35
|
-
const memory = createMemory({ dbPath: config.memory.dbPath })
|
|
36
|
-
// Vector retrieval: enabled if embedding is configured (lazy vector generation, computed on first search)
|
|
37
|
-
if (config.embedding?.apiKey) {
|
|
38
|
-
const { createEmbedder } = await import("@thincoder/core/embedding.mjs")
|
|
39
|
-
memory.embedder = createEmbedder(config.embedding)
|
|
40
|
-
}
|
|
28
|
+
/** Assemble an agent with memory, MCP tools, and code/doc indices attached (sync all layers, then return)
|
|
29
|
+
* = 核装配序(`thincoder-core/agent/assemble.mjs`)+ 本端留面(MCP 合入 ∕ 剔除经 `toolsFinalize` 缝 ·
|
|
30
|
+
* `_mcpWarnings` · M1 装配钩子 · 校验调用)。`deps` 逐项可注入(缺省 = 核缺省)。 */
|
|
31
|
+
export async function assembleAgent({ excludeTools = [], slotData, deps = {} } = {}) {
|
|
41
32
|
const cwd = process.cwd()
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
config
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
await ensureClone(team)
|
|
62
|
-
await syncDir(memory, { layer: "team", dir: team.dir })
|
|
63
|
-
}
|
|
64
|
-
const baseTools = await assembleBuiltinTools({ memory, cwd, projectDir: config.memory.projectDir, author: gitAuthor(), team, model: provider?.model ?? null })
|
|
33
|
+
/** MCP 连接警告(缝体出参——装配后落 `agent._mcpWarnings`)。 */
|
|
34
|
+
const warnings = []
|
|
35
|
+
const agent = await coreAssemble({
|
|
36
|
+
cwd,
|
|
37
|
+
deps,
|
|
38
|
+
// 位次保持:工具装配后、createAgent 前(旧内联序 :64 → :66-110 → :112 逐位同)。
|
|
39
|
+
toolsFinalize: (baseTools, { config }) => finalizeTools(baseTools, { cwd, config, excludeTools, warnings }),
|
|
40
|
+
})
|
|
41
|
+
agent._mcpWarnings = warnings
|
|
42
|
+
// M1 装配钩子(会话起点①——装配期):判据 = 会话权威值(`slotData` 槽值优先 + config 回退);
|
|
43
|
+
// 附着 agent.manifest 供下游 M2–M9 读面(普通会话 = null);非 ok 态不抛(启动零拒绝)。
|
|
44
|
+
// 钩子后移的可观察后果(报明 / 建档晚于 memory sync / MCP 连接 / 工具装配——功能等价,仍在
|
|
45
|
+
// 进入正常循环之前)见 docs/core/design/MANIFEST.md §2.2。
|
|
46
|
+
attachManifest(agent, { cwd, slotData })
|
|
47
|
+
// SESSION.md §6.8 D-S1:assembleAgent 后唯一校验点(TUI/chat 两路径同源)——不抛错不退出,
|
|
48
|
+
// 标记由调用侧消费(TUI 弹重选 / headless 报错)。空 provider 由 TUI 路径在 startTUI 前清空。
|
|
49
|
+
validateProvider(agent, agent.config)
|
|
50
|
+
return agent
|
|
51
|
+
}
|
|
65
52
|
|
|
53
|
+
/** MCP 合入 + 工具剔除(核 `toolsFinalize` 缝体——CLI 专用;位次 = 工具装配后、`createAgent` 前)。
|
|
54
|
+
* 连接并发(死服务器不阻塞启动),失败收集为警告(TUI alt-buffer 下 stderr 不可见——经 `agent`
|
|
55
|
+
* 对象传递);`warnings` = 出参数组(调用侧装配 `agent._mcpWarnings`)。 */
|
|
56
|
+
async function finalizeTools(baseTools, { cwd, config, excludeTools, warnings }) {
|
|
66
57
|
// MCP servers: connect in parallel (a dead server won't block startup), collect failures as warnings (stderr invisible in TUI, passed via agent object)
|
|
67
58
|
const mcpServers = config.mcp?.servers ?? []
|
|
68
59
|
// Read project-level .mcp.json (standard MCP client convention) — merge into mcpServers
|
|
@@ -92,7 +83,6 @@ export async function assembleAgent({ excludeTools = [], slotData } = {}) {
|
|
|
92
83
|
console.error(`[mcp] Failed to read .mcp.json: ${e.message}`)
|
|
93
84
|
}
|
|
94
85
|
let mcpTools = []
|
|
95
|
-
const mcpWarnings = []
|
|
96
86
|
if (mcpServers.length) {
|
|
97
87
|
const { connectMcpServer } = await import("@thincoder/core/mcp.mjs")
|
|
98
88
|
const results = await Promise.allSettled(mcpServers.map((srv) => connectMcpServer(srv)))
|
|
@@ -104,37 +94,11 @@ export async function assembleAgent({ excludeTools = [], slotData } = {}) {
|
|
|
104
94
|
const srv = mcpServers[i]
|
|
105
95
|
const msg = `MCP server "${srv.name ?? srv.command}" failed to connect: ${r.reason?.message ?? r.reason}`
|
|
106
96
|
console.error(`[mcp] ${msg}`)
|
|
107
|
-
|
|
97
|
+
warnings.push(msg)
|
|
108
98
|
}
|
|
109
99
|
}
|
|
110
100
|
}
|
|
111
|
-
|
|
112
|
-
const agent = createAgent({
|
|
113
|
-
provider,
|
|
114
|
-
tools: applyToolExclusions([...baseTools, ...mcpTools], excludeTools),
|
|
115
|
-
config,
|
|
116
|
-
cwd,
|
|
117
|
-
memory,
|
|
118
|
-
})
|
|
119
|
-
agent.providers = providers
|
|
120
|
-
// MODEL-MERGE-SESSION:config 无 active*——会话运行时起点 = defaultModel 复合解析
|
|
121
|
-
// (provider 对象带解析后 model——API/spec 消费点零改);无效 → {} + 原因走 D-S1 标记
|
|
122
|
-
agent.activeProvider = provider.name ?? ""
|
|
123
|
-
agent.activeModel = provider.model ?? null
|
|
124
|
-
agent._mcpWarnings = mcpWarnings
|
|
125
|
-
// M1 装配钩子(会话起点①——装配期):判据 = 会话权威值(`slotData` 槽值优先 + config 回退);
|
|
126
|
-
// 附着 agent.manifest 供下游 M2–M9 读面(普通会话 = null);非 ok 态不抛(启动零拒绝)。
|
|
127
|
-
// 钩子后移的可观察后果(报明 / 建档晚于 memory sync / MCP 连接 / 工具装配——功能等价,仍在
|
|
128
|
-
// 进入正常循环之前)见 docs/core/design/MANIFEST.md §2.2。
|
|
129
|
-
attachManifest(agent, { cwd, slotData })
|
|
130
|
-
// SESSION.md §6.8 D-S1:assembleAgent 后唯一校验点(TUI/chat 两路径同源)——不抛错不退出,
|
|
131
|
-
// 标记由调用侧消费(TUI 弹重选 / headless 报错)。空 provider 由 TUI 路径在 startTUI 前清空。
|
|
132
|
-
validateProvider(agent)
|
|
133
|
-
// defaultModel 无效/未设的具体原因覆盖通用判据文案(providers 存在时更有指导性)
|
|
134
|
-
if (agent._providerInvalid && config.providerInvalidReason) {
|
|
135
|
-
agent._providerInvalidReason = config.providerInvalidReason
|
|
136
|
-
}
|
|
137
|
-
return agent
|
|
101
|
+
return applyToolExclusions([...baseTools, ...mcpTools], excludeTools)
|
|
138
102
|
}
|
|
139
103
|
|
|
140
104
|
/**
|
|
@@ -174,42 +138,3 @@ export function attachManifest(agent, { cwd = process.cwd(), slotData } = {}) {
|
|
|
174
138
|
agent.manifest = r.ok ? r.manifest : null
|
|
175
139
|
return agent.manifest
|
|
176
140
|
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* SESSION.md §6.8 D-S1 — provider 有效性校验(assembleAgent 后唯一校验点,TUI/chat 两路径同源)。
|
|
180
|
-
* 判据(评审 #1/#2):仅 model/baseURL 缺失判 invalid——**不得用 MODEL_SPECS 成员资格判无效**
|
|
181
|
-
* (未知模型 = 受支持场景:自定义端点模型不在 spec 表是常态,误判会让自定义模型用户每次恢复都弹重选)。
|
|
182
|
-
* apiKey 缺失不判(既有 wizard /model 流程处理)。幂等:有效时清标记,无效时置标记 + 原因。
|
|
183
|
-
* 不抛错、不退出。返回 agent 便于链式调用。
|
|
184
|
-
*/
|
|
185
|
-
export function validateProvider(agent) {
|
|
186
|
-
const ok = Boolean(agent.provider?.name && agent.provider.model && agent.provider.baseURL)
|
|
187
|
-
if (ok) {
|
|
188
|
-
delete agent._providerInvalid
|
|
189
|
-
delete agent._providerInvalidReason
|
|
190
|
-
} else {
|
|
191
|
-
agent._providerInvalid = true
|
|
192
|
-
agent._providerInvalidReason = !agent.provider?.name
|
|
193
|
-
? "provider 不存在"
|
|
194
|
-
: !agent.provider.model ? "model 缺失"
|
|
195
|
-
: "缺少 baseURL"
|
|
196
|
-
}
|
|
197
|
-
return agent
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/** Read team config and fill in default dir; return null if not configured */
|
|
201
|
-
export function teamConfig(config) {
|
|
202
|
-
const team = config.memory?.team
|
|
203
|
-
if (!team?.repo) return null
|
|
204
|
-
const name = team.name ?? "default"
|
|
205
|
-
return { name, repo: team.repo, dir: team.dir ?? join(configDir, "teams", name) }
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/** Entry author: git config user.name, fallback "unknown" */
|
|
209
|
-
export function gitAuthor() {
|
|
210
|
-
try {
|
|
211
|
-
return execSync("git config user.name", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || "unknown"
|
|
212
|
-
} catch {
|
|
213
|
-
return "unknown"
|
|
214
|
-
}
|
|
215
|
-
}
|
|
@@ -3,10 +3,12 @@ import { loadConfig } from "@thincoder/core/config.mjs"
|
|
|
3
3
|
import { teamConfig } from "./make-agent.mjs"
|
|
4
4
|
import { put, search, list } from "@thincoder/core/memory/core.mjs"
|
|
5
5
|
import { deleteByUid } from "@thincoder/core/memory/delete.mjs"
|
|
6
|
+
import { sweepMemory, formatSweepReport } from "@thincoder/core/memory/sweep.mjs"
|
|
6
7
|
|
|
7
|
-
/** thincoder memory <list|search|put|remove> subcommands.
|
|
8
|
+
/** thincoder memory <list|search|put|remove|sweep> subcommands.
|
|
8
9
|
* opts.dirs: { project, team } layer directories for project/team file deletion (tests inject their own);
|
|
9
|
-
* falls back to the same config-derived dirs the agent uses.
|
|
10
|
+
* falls back to the same config-derived dirs the agent uses.
|
|
11
|
+
* opts.dbPath: 库路径覆盖(sweep 的备份命名依据——缺省 = `config.memory.dbPath` 展开值)。 */
|
|
10
12
|
export async function memoryCommand(memory, args, opts = {}) {
|
|
11
13
|
const [sub, ...rest] = args
|
|
12
14
|
|
|
@@ -57,12 +59,52 @@ export async function memoryCommand(memory, args, opts = {}) {
|
|
|
57
59
|
}
|
|
58
60
|
break
|
|
59
61
|
}
|
|
62
|
+
case "sweep": {
|
|
63
|
+
const parsed = parseSweepArgs(rest)
|
|
64
|
+
if (parsed.error) {
|
|
65
|
+
console.error(parsed.error)
|
|
66
|
+
console.error(SWEEP_USAGE)
|
|
67
|
+
return 1
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const result = sweepMemory(memory, { origin: parsed.origin, path: parsed.path, confirm: parsed.confirm, dbPath: opts.dbPath ?? loadConfig().memory.dbPath })
|
|
71
|
+
for (const line of formatSweepReport(result)) console.log(line)
|
|
72
|
+
if (result.dryRun) console.log("(dry-run——零写;要落写加 --confirm)")
|
|
73
|
+
} catch (e) {
|
|
74
|
+
console.error(e.message)
|
|
75
|
+
return 1
|
|
76
|
+
}
|
|
77
|
+
break
|
|
78
|
+
}
|
|
60
79
|
default:
|
|
61
|
-
console.error("Usage: thincoder memory <list|search|put|remove>")
|
|
80
|
+
console.error("Usage: thincoder memory <list|search|put|remove|sweep>")
|
|
62
81
|
return 1
|
|
63
82
|
}
|
|
64
83
|
}
|
|
65
84
|
|
|
85
|
+
const SWEEP_USAGE = "Usage: thincoder memory sweep [--origin <o>] [--path <sub>] [--dry-run|--confirm]"
|
|
86
|
+
|
|
87
|
+
/** sweep 参数面:`--origin <o>` ∕ `--path <sub>`(各两形:空格 ∕ `=`)+ 裸 `--dry-run` ∕ `--confirm`
|
|
88
|
+
* (缺省 = 干跑;两者互斥);`--path` 须与 `--origin` 同用(§6.14 面①-3 存量剪枝窄化档)。 */
|
|
89
|
+
function parseSweepArgs(rest) {
|
|
90
|
+
let origin = null, path = null, sawDry = false, sawConfirm = false
|
|
91
|
+
for (let i = 0; i < rest.length; i++) {
|
|
92
|
+
const a = rest[i]
|
|
93
|
+
const eq = a.match(/^--(origin|path)=(.*)$/)
|
|
94
|
+
if (eq) { if (eq[1] === "origin") origin = eq[2]; else path = eq[2]; continue }
|
|
95
|
+
if (a === "--origin") { origin = rest[++i] ?? ""; continue }
|
|
96
|
+
if (a === "--path") { path = rest[++i] ?? ""; continue }
|
|
97
|
+
if (a === "--dry-run") { sawDry = true; continue }
|
|
98
|
+
if (a === "--confirm") { sawConfirm = true; continue }
|
|
99
|
+
return { error: `Unknown argument: ${a}` }
|
|
100
|
+
}
|
|
101
|
+
if (origin === "") return { error: "--origin 需要一个非空路径" }
|
|
102
|
+
if (path === "") return { error: "--path 需要一个非空前缀" }
|
|
103
|
+
if (path !== null && origin === null) return { error: "--path 须与 --origin 同用" }
|
|
104
|
+
if (sawDry && sawConfirm) return { error: "--dry-run 与 --confirm 互斥" }
|
|
105
|
+
return { origin, path, confirm: sawConfirm }
|
|
106
|
+
}
|
|
107
|
+
|
|
66
108
|
/** Layer directories for project/team file deletion — derived from the same config the agent uses. */
|
|
67
109
|
function cliDirs() {
|
|
68
110
|
const config = loadConfig()
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* command-interactive.mjs — 交互长驻三命令(chat ∕ tui ∕ acp)——`bin/thincoder.mjs` 命令分发表
|
|
3
|
+
* 外提(2026-09-28 拆档批 R4 · 纯移动——命令行为零改):case 块体逐字搬移为函数(唯二机械改 =
|
|
4
|
+
* `break` → `return` · 相对 import 路径随住档改指);依赖经 ctx 注入(`exitSoon` ∕ `noKeyMessage`
|
|
5
|
+
* ——bin 装配面注入;先例 = `src/tui/turn-face.mjs` 出档);分发落点 = `src/command-table.mjs`。
|
|
6
|
+
*/
|
|
7
|
+
import { isAbsolute, join } from "node:path"
|
|
8
|
+
import { runAgent } from "@thincoder/core/agent.mjs"
|
|
9
|
+
import { loadConfig, configPath } from "@thincoder/core/config.mjs"
|
|
10
|
+
// 批 1 CORE-DEFECT-FIXES B3:onWait 相位值域 + 文案单源(消费面禁各自枚举——PROVIDER.md §6.20)
|
|
11
|
+
import { waitStatusText } from "@thincoder/core/provider/wait-status.mjs"
|
|
12
|
+
import { assembleAgent, attachManifest, teamConfig, gitAuthor, validateProvider } from "./cli/make-agent.mjs"
|
|
13
|
+
import { setupWizard } from "./cli/setup-wizard.mjs"
|
|
14
|
+
import { summarize, askPermission } from "./cli/permission.mjs"
|
|
15
|
+
import { recentCrashHint } from "./crash-reports.mjs"
|
|
16
|
+
|
|
17
|
+
/** chat —— 一次性 agent 运行(原 `bin/thincoder.mjs` `case "chat"` 块体逐字搬移)。 */
|
|
18
|
+
export async function chatCommand(args, ctx) {
|
|
19
|
+
const { exitSoon, noKeyMessage } = ctx
|
|
20
|
+
const auto = args.includes("--auto")
|
|
21
|
+
const prompt = args.filter((a) => a !== "--auto").join(" ").trim()
|
|
22
|
+
if (!prompt) {
|
|
23
|
+
console.error('Usage: thincoder chat [--auto] "<prompt>"')
|
|
24
|
+
exitSoon(1)
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// R25(F-R25c):非交互/chat 模式——上次异常终止提示写 stderr 一行(无匹配不提示)
|
|
29
|
+
const crashNotice = recentCrashHint()
|
|
30
|
+
if (crashNotice) console.error(crashNotice)
|
|
31
|
+
|
|
32
|
+
const agent = await assembleAgent()
|
|
33
|
+
// SESSION.md §6.8 D-S4(#841 收正):headless 无 TUI——按**核统一态分流**(判据单源 = PROVIDER.md §6.22,
|
|
34
|
+
// 与 TUI 面同式):**invalid 类**(`state === "invalid"` ∨ `providerInvalidReason` 非空——无渠/key
|
|
35
|
+
// ⇒ 不可运行)⇒ 可读错误 + 退出码 1(不弹 UI、不崩溃);**fallback**(无有效 defaultModel 但可运行)
|
|
36
|
+
// ⇒ stderr 一行明示 + 继续运行(不退出——D-S2 同态同解)。条目结构不全不在本门(负向锁 =
|
|
37
|
+
// PROVIDER.md:437)——归运行期失败径(stderr + 非 0 退出)。
|
|
38
|
+
const headlessCfg = agent.config ?? {}
|
|
39
|
+
const invalidClass = headlessCfg.providerState === "invalid"
|
|
40
|
+
|| (typeof headlessCfg.providerInvalidReason === "string" && headlessCfg.providerInvalidReason !== "")
|
|
41
|
+
if (invalidClass) {
|
|
42
|
+
console.error(`[error] 未配置有效渠道(${agent._providerInvalidReason ?? headlessCfg.providerStateReason ?? "provider unavailable"})。请运行 thincoder 进入 TUI 设置(/config 设置渠道与密钥;或 /model 选择后以会话槽生效),或编辑 ${configPath}`)
|
|
43
|
+
exitSoon(1)
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
if (headlessCfg.providerState === "fallback" && agent.provider?.name) {
|
|
47
|
+
const m = typeof agent.provider.model === "string" && agent.provider.model ? `:${agent.provider.model}` : ""
|
|
48
|
+
console.error(`尚未设置默认模型:本次使用 \`${agent.provider.name}${m}\`——/config → 默认模型 设置一次;/model 仅改本会话`)
|
|
49
|
+
}
|
|
50
|
+
if (!agent.provider.apiKey) {
|
|
51
|
+
if (!process.stdin.isTTY) {
|
|
52
|
+
console.error(noKeyMessage())
|
|
53
|
+
exitSoon(1)
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
const p = await setupWizard()
|
|
57
|
+
if (!p) {
|
|
58
|
+
exitSoon(1)
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
agent.provider = p
|
|
62
|
+
agent.activeProvider = p.name
|
|
63
|
+
// Wizard may have configured an embedding key: attach vector search
|
|
64
|
+
const fresh = loadConfig()
|
|
65
|
+
if (fresh.embedding?.apiKey && agent.memory && !agent.memory.embedder) {
|
|
66
|
+
const { createEmbedder } = await import("@thincoder/core/embedding.mjs")
|
|
67
|
+
agent.memory.embedder = createEmbedder(fresh.embedding)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (auto) agent.autoApprove = true
|
|
71
|
+
// Accumulate token usage, output to stderr at the end (don't pollute stdout pipe)
|
|
72
|
+
const usageTotal = { prompt: 0, completion: 0, cacheHit: 0, cacheMiss: 0 }
|
|
73
|
+
try {
|
|
74
|
+
await runAgent(agent, prompt, {
|
|
75
|
+
onToken: (text) => process.stdout.write(text),
|
|
76
|
+
onWait: (ev) => {
|
|
77
|
+
const s = waitStatusText(ev)
|
|
78
|
+
if (s) console.error(`[rate-limit] ${s}`)
|
|
79
|
+
},
|
|
80
|
+
onToolCall: (name, toolArgs) => {
|
|
81
|
+
console.error(`\n[tool] ${name} ${summarize(toolArgs)}`)
|
|
82
|
+
},
|
|
83
|
+
onToolResult: (name, result) => {
|
|
84
|
+
const preview = result.length > 200 ? result.slice(0, 200) + "..." : result
|
|
85
|
+
console.error(`[done] ${name} -> ${preview.split("\n")[0]}`)
|
|
86
|
+
},
|
|
87
|
+
onToolOutput: (name, chunk) => process.stderr.write(chunk),
|
|
88
|
+
onCompress: () => console.error(`\n[context] Context too long, auto-compacted (early conversation summarized by LLM)`),
|
|
89
|
+
onTaskUpdate: (items) => {
|
|
90
|
+
const done = items.filter((i) => i.status === "done").length
|
|
91
|
+
const current = items.find((i) => i.status === "in_progress")
|
|
92
|
+
console.error(`[task] ${done}/${items.length}${current ? ` ▶ ${current.title}` : ""}`)
|
|
93
|
+
},
|
|
94
|
+
onUsage: (usage) => {
|
|
95
|
+
usageTotal.prompt += usage.prompt_tokens ?? 0
|
|
96
|
+
usageTotal.completion += usage.completion_tokens ?? 0
|
|
97
|
+
usageTotal.cacheHit += usage.prompt_cache_hit_tokens ?? 0
|
|
98
|
+
usageTotal.cacheMiss += usage.prompt_cache_miss_tokens ?? 0
|
|
99
|
+
},
|
|
100
|
+
onPermissionRequest: (name, toolArgs) => (agent.autoApprove ? true : askPermission(name, toolArgs)),
|
|
101
|
+
})
|
|
102
|
+
process.stdout.write("\n")
|
|
103
|
+
if (usageTotal.prompt > 0) {
|
|
104
|
+
const cacheTotal = usageTotal.cacheHit + usageTotal.cacheMiss
|
|
105
|
+
const hitPart = cacheTotal > 0 ? ` cache-hit ${Math.round((usageTotal.cacheHit / cacheTotal) * 100)}%` : ""
|
|
106
|
+
console.error(`[usage] prompt ${usageTotal.prompt} + completion ${usageTotal.completion}${hitPart}`)
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
// 用 name 判断而非 instanceof:不依赖"与 runAgent 同一个模块实例"这一隐式约定
|
|
110
|
+
if (error.name === "ContinueError") {
|
|
111
|
+
console.error(`\n[paused] Agent stopped after ${error.turn} turns. Run in TUI to continue.`)
|
|
112
|
+
exitSoon(0)
|
|
113
|
+
} else {
|
|
114
|
+
console.error(`\n[error] ${error.message}`)
|
|
115
|
+
exitSoon(1)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** tui ∕ 无参默认 —— 交互 TUI 启动(原 `case "tui": case undefined:` 块体逐字搬移)。 */
|
|
121
|
+
export async function tuiCommand(ctx) {
|
|
122
|
+
const { exitSoon } = ctx
|
|
123
|
+
// 恢复上次的会话(同一项目目录);provider 按保存的名字切回(用户上次可能换过模型)
|
|
124
|
+
// 2026-09-05 §6.10(R4):恢复决策按本端记录 resumeSlot(D-2 ①②③)——manifest active
|
|
125
|
+
// 只作"无记录端"的一次性继承源,不再作本端恢复第一依据(D-6)。
|
|
126
|
+
// 2026-09-18(装配门禁批 #30 · KD-M1-13 / KD-M1-15):resumeSlot **前移至装配之前**——
|
|
127
|
+
// 装配期模式门判据 = 会话权威值(槽优先 + config 回退);恢复记录经 slotData 形参进
|
|
128
|
+
// 装配钩子(同进程同一调用前移 ⇒ 零新增写动作;钩子内不调 resumeSlot——非纯读)。
|
|
129
|
+
const { resumeSlot, applySession, sessionDescriptor } = await import("@thincoder/core/session.mjs")
|
|
130
|
+
// F-MI7:resumeSlot = async(探测束不阻塞事件循环)——此处必须 await
|
|
131
|
+
const { slot, data } = await resumeSlot(process.cwd())
|
|
132
|
+
const agent = await assembleAgent({ slotData: data })
|
|
133
|
+
// SESSION.md §6.8 D-S1(#841 收正):TUI 路径**仅在 `state === "invalid"`**(无渠/key)于 startTUI
|
|
134
|
+
// 前置 `agent.provider = null`——空 provider 不流入 runAgent(崩溃源:chat() 缺 model → 网关 400
|
|
135
|
+
// 或 fetch("undefined/...") TypeError);`fallback` 态 provider 有效——不清(可运行 + 明示)。
|
|
136
|
+
if (agent.config?.providerState === "invalid") agent.provider = null
|
|
137
|
+
const config = loadConfig()
|
|
138
|
+
if (data) {
|
|
139
|
+
// applySession 内部已按槽复合重算 compactThreshold(auto 时)——不再需要 switched 分支
|
|
140
|
+
// TUI-OOM-ROOTCAUSE(SESSION.md §6.14):传 slot → 绑定记录存储(身份核验 + 对账)
|
|
141
|
+
// ——人读线 = 尾窗(磁盘为准);未传 = 模式 F(全量数组)
|
|
142
|
+
applySession(agent, data, { slot })
|
|
143
|
+
// SESSION.md §6.11(2026-09-08——N6 评审 🔴 修复):process restarted 句 = 进程级
|
|
144
|
+
// 信号——仅本启动 resume 路径设一次(真进程重启恢复盘上会话);/session 切换与 ACP
|
|
145
|
+
// 加载走同一 applySession 收敛但不设(不误报进程重启)。prepareRun 发句即清——进程
|
|
146
|
+
// 内一次。resumed:yes 独立由 applySession 的恢复事件武装(data.history 非空)。
|
|
147
|
+
agent._processRestartPending = true
|
|
148
|
+
}
|
|
149
|
+
// D-3 钉槽:applySession 清 _slot 后立即钉回恢复槽——首保存必落恢复槽(消除
|
|
150
|
+
// "load → 首回合保存"窗口内并发方翻 active 导致的静默迁移);/new 经 resetSessionState
|
|
151
|
+
// 清 _slot(newSession 已认领 + 写记录,语义保留)
|
|
152
|
+
agent._slot = slot
|
|
153
|
+
// M1 重估点(会话起点②——applySession 之后 · KD-M1-13 取值点②):CLI 的工程模式权威值 =
|
|
154
|
+
// 会话槽(/eng 只写槽不写 config.json);此时 agent.config.agent.engineering 已由
|
|
155
|
+
// applySession 按槽订正 ⇒ 与装配期判据同值 ⇒ 幂等重估(值同 ⇒ 无副作用)。
|
|
156
|
+
attachManifest(agent)
|
|
157
|
+
// D-S3 优先级补全:applySession 可能已用会话中的有效 provider 修复(config 无效 + 会话有效)——
|
|
158
|
+
// 修复后复验清除标记,仅当两者都无效才弹重选(validateProvider 幂等)
|
|
159
|
+
if (agent._providerInvalid) validateProvider(agent)
|
|
160
|
+
// MCP 连接失败在 TUI alt-buffer 下 stderr 不可见,注入为下一条 user 消息后的提醒
|
|
161
|
+
if (agent._mcpWarnings?.length) {
|
|
162
|
+
agent._pendingReminders = agent._pendingReminders ?? []
|
|
163
|
+
agent._pendingReminders.push(
|
|
164
|
+
`[System reminder: ${agent._mcpWarnings.length} MCP server(s) failed to connect at startup:\n` +
|
|
165
|
+
agent._mcpWarnings.map((w) => ` - ${w}`).join("\n") +
|
|
166
|
+
`\nYou can try reconnecting with /mcp connect <name>.]`
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
const { startTUI } = await import("./tui.mjs")
|
|
170
|
+
try {
|
|
171
|
+
await startTUI(agent, {
|
|
172
|
+
projectDir: config.memory.projectDir ? (isAbsolute(config.memory.projectDir) ? config.memory.projectDir : join(process.cwd(), config.memory.projectDir)) : null,
|
|
173
|
+
team: teamConfig(config),
|
|
174
|
+
author: gitAuthor(),
|
|
175
|
+
restored: data ? sessionDescriptor(agent, data) : data,
|
|
176
|
+
// R25(F-R25c):TUI 启动显示一行"上次运行异常终止"提示(showStartup 渲染——无匹配不传)
|
|
177
|
+
crashNotice: recentCrashHint() ?? undefined,
|
|
178
|
+
})
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error(`[error] ${error.message}`)
|
|
181
|
+
exitSoon(1)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** acp —— Agent Client Protocol 服务(原 `case "acp"` 块体逐字搬移)。 */
|
|
186
|
+
export async function acpCommand(args, ctx) {
|
|
187
|
+
const { exitSoon } = ctx
|
|
188
|
+
const { runAcpServer, runAcpLogin } = await import("./acp.mjs")
|
|
189
|
+
// §11.2 改法 4:`args` 语义 = 追加到已配置的 agent 调用——`--login` = 终端认证流程,
|
|
190
|
+
// 不进 stdio 服务模式(不带该旗标时行为零变);退出码语义 = 0 成功 / 非 0 失败。
|
|
191
|
+
if (args.includes("--login")) {
|
|
192
|
+
exitSoon(await runAcpLogin())
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
await runAcpServer()
|
|
196
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* command-table.mjs — CLI 命令分发表(`bin/thincoder.mjs` 外提——2026-09-28 拆档批 R4 · 纯移动 ·
|
|
3
|
+
* 命令行为 ∕ USAGE 文本 ∕ 旗标语义零改):分发骨架 + 八薄命令族(memory ∕ sync ∕ distill ∕
|
|
4
|
+
* reindex ∕ completion ∕ upgrade ∕ session ∕ ledger)+ help ∕ version;交互长驻三命令
|
|
5
|
+
* (chat ∕ tui ∕ acp)经 `command-interactive.mjs` 承接。依赖经 ctx 注入(`usage` ∕ `version` ∕
|
|
6
|
+
* `exitSoon`——bin 装配面注入;先例 = `src/tui/turn-face.mjs` 出档)。
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync } from "node:fs"
|
|
9
|
+
import { isAbsolute, join } from "node:path"
|
|
10
|
+
import { loadConfig } from "@thincoder/core/config.mjs"
|
|
11
|
+
import { createMemory, syncDir } from "@thincoder/core/memory.mjs"
|
|
12
|
+
import { teamConfig } from "./cli/make-agent.mjs"
|
|
13
|
+
import { memoryCommand } from "./cli/memory-command.mjs"
|
|
14
|
+
import { distillCommand } from "./cli/distill-command.mjs"
|
|
15
|
+
import { chatCommand, tuiCommand, acpCommand } from "./command-interactive.mjs"
|
|
16
|
+
|
|
17
|
+
/** 命令分发(原 `bin/thincoder.mjs` 顶层 switch 骨架逐字搬移——命令行为零改)。 */
|
|
18
|
+
export async function runCommandTable(command, args, ctx) {
|
|
19
|
+
const { usage, version, exitSoon } = ctx
|
|
20
|
+
switch (command) {
|
|
21
|
+
case "chat": {
|
|
22
|
+
await chatCommand(args, ctx)
|
|
23
|
+
break
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
case "memory": {
|
|
27
|
+
const config = loadConfig()
|
|
28
|
+
const memory = createMemory({ dbPath: config.memory.dbPath })
|
|
29
|
+
if (config.embedding?.apiKey) {
|
|
30
|
+
const { createEmbedder } = await import("@thincoder/core/embedding.mjs")
|
|
31
|
+
memory.embedder = createEmbedder(config.embedding)
|
|
32
|
+
}
|
|
33
|
+
if (config.memory.projectDir) {
|
|
34
|
+
memory.projectOrigin = isAbsolute(config.memory.projectDir) ? config.memory.projectDir : join(process.cwd(), config.memory.projectDir)
|
|
35
|
+
}
|
|
36
|
+
const code = await memoryCommand(memory, args)
|
|
37
|
+
if (code) exitSoon(code)
|
|
38
|
+
break
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
case "sync": {
|
|
42
|
+
const config = loadConfig()
|
|
43
|
+
const team = teamConfig(config)
|
|
44
|
+
if (!team) {
|
|
45
|
+
console.error("Team memory not configured. Set memory.team in ~/.thincoder/config.json:")
|
|
46
|
+
console.error(' "team": { "name": "myteam", "repo": "git@github.com:org/team-memory.git" }')
|
|
47
|
+
exitSoon(1)
|
|
48
|
+
break
|
|
49
|
+
}
|
|
50
|
+
const memory = createMemory({ dbPath: config.memory.dbPath })
|
|
51
|
+
const { ensureClone, pullTeam } = await import("@thincoder/core/git/gitmem.mjs")
|
|
52
|
+
try {
|
|
53
|
+
const cloned = await ensureClone(team)
|
|
54
|
+
if (cloned) console.log(`Cloned team repo to ${team.dir}`)
|
|
55
|
+
await pullTeam(team.dir)
|
|
56
|
+
const stats = await syncDir(memory, { layer: "team", dir: team.dir })
|
|
57
|
+
console.log(`Synced. Index: +${stats.added} ~${stats.updated} -${stats.removed}`)
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error(`[error] ${error.message}`)
|
|
60
|
+
exitSoon(1)
|
|
61
|
+
}
|
|
62
|
+
break
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
case "distill": {
|
|
66
|
+
const code = await distillCommand(args, exitSoon)
|
|
67
|
+
if (code) exitSoon(code)
|
|
68
|
+
break
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
case "reindex": {
|
|
72
|
+
const config = loadConfig()
|
|
73
|
+
const memory = createMemory({ dbPath: config.memory.dbPath })
|
|
74
|
+
// files 层(project/team 的 markdown 索引)全量重建;索引是易失品,真相在 markdown
|
|
75
|
+
memory.db.prepare(`DELETE FROM files`).run()
|
|
76
|
+
const cwd = process.cwd()
|
|
77
|
+
let total = { added: 0, removed: 0 }
|
|
78
|
+
if (config.memory.projectDir) {
|
|
79
|
+
const s = await syncDir(memory, { layer: "project", dir: isAbsolute(config.memory.projectDir) ? config.memory.projectDir : join(cwd, config.memory.projectDir) })
|
|
80
|
+
total.added += s.added
|
|
81
|
+
}
|
|
82
|
+
const team = teamConfig(config)
|
|
83
|
+
if (team) {
|
|
84
|
+
const { ensureClone } = await import("@thincoder/core/git/gitmem.mjs")
|
|
85
|
+
await ensureClone(team)
|
|
86
|
+
const s = await syncDir(memory, { layer: "team", dir: team.dir })
|
|
87
|
+
total.added += s.added
|
|
88
|
+
}
|
|
89
|
+
console.log(`Reindexed ${total.added} markdown entries (project${team ? " + team" : ""}). Vectors will be lazily regenerated on next search.`)
|
|
90
|
+
break
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
case "tui":
|
|
94
|
+
case undefined: {
|
|
95
|
+
await tuiCommand(ctx)
|
|
96
|
+
break
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
case "completion": {
|
|
100
|
+
const shell = args[0]
|
|
101
|
+
if (!["bash", "zsh", "fish"].includes(shell)) {
|
|
102
|
+
console.error(`Usage: thincoder completion <bash|zsh|fish>`)
|
|
103
|
+
exitSoon(1)
|
|
104
|
+
break
|
|
105
|
+
}
|
|
106
|
+
// §11.2 交付行数债(2026-09-08——500 行硬限触碰执行):直写段逐字迁出
|
|
107
|
+
// src/completions.mjs(脚本字节不变——printCompletion 发射)
|
|
108
|
+
const { printCompletion } = await import("./completions.mjs")
|
|
109
|
+
printCompletion(shell)
|
|
110
|
+
break
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
case "upgrade": {
|
|
114
|
+
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"))
|
|
115
|
+
const local = pkg.version
|
|
116
|
+
const { checkForUpdate } = await import("./upgrade.mjs")
|
|
117
|
+
const result = await checkForUpdate(local)
|
|
118
|
+
if (!result) {
|
|
119
|
+
console.error("[upgrade] Unable to query npm registry — check your network connection and that npm is installed")
|
|
120
|
+
exitSoon(1)
|
|
121
|
+
break
|
|
122
|
+
}
|
|
123
|
+
if (!result.newer) {
|
|
124
|
+
console.log(`ThinCoder ${local} is already the latest.`)
|
|
125
|
+
} else {
|
|
126
|
+
console.log(`Upgrading: ${local} → ${result.latest}`)
|
|
127
|
+
const { execSync } = await import("node:child_process")
|
|
128
|
+
execSync("npm install -g thincoder@latest", { stdio: "inherit" })
|
|
129
|
+
console.log(`Upgraded to ${result.latest}`)
|
|
130
|
+
}
|
|
131
|
+
break
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
case "acp": {
|
|
135
|
+
await acpCommand(args, ctx)
|
|
136
|
+
break
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
case "session": {
|
|
140
|
+
// SESSION.md §6.12 / §6.17:会话目录 GC 显式面(冷 cwd 90 天面 + 三合取存量组全量面)——
|
|
141
|
+
// 双端同面(2026-09-21 端差注销:VS Code 端命令 `thincoder.sessionGc` 走同一核数据面)。
|
|
142
|
+
// SESSION.md §6.19 D-SE46(2026-09-22 会话索引批):`session index` = 派生索引库显式面
|
|
143
|
+
// (--status 读数 / --rebuild 全量重扫)——同一 `session` 分发族(壳侧分发,实现在核)。
|
|
144
|
+
if (args[0] === "index") {
|
|
145
|
+
const { runSessionIndex } = await import("@thincoder/core/session-index-cmd.mjs")
|
|
146
|
+
process.exitCode = await runSessionIndex(args)
|
|
147
|
+
break
|
|
148
|
+
}
|
|
149
|
+
const { runSessionGc } = await import("@thincoder/core/session-gc.mjs")
|
|
150
|
+
process.exitCode = await runSessionGc(args)
|
|
151
|
+
break
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
case "ledger": {
|
|
155
|
+
// LEDGER.md §2.2(2026-09-25 批):台账存量收正面——变体键合并迁移(migrate)/ 残档审计(audit);
|
|
156
|
+
// 核内实现(ledger-migrate.mjs),壳侧只分发(先例 = `session gc` 双档:--dry-run 只读 / --confirm 执行)。
|
|
157
|
+
if (args[0] === "migrate" || args[0] === "audit") {
|
|
158
|
+
const m = await import("@thincoder/core/ledger-migrate.mjs")
|
|
159
|
+
process.exitCode = await (args[0] === "migrate" ? m.runLedgerMigrate(args) : m.runLedgerAudit(args))
|
|
160
|
+
break
|
|
161
|
+
}
|
|
162
|
+
console.error("Usage: thincoder ledger migrate --dry-run | --confirm [--from <key>] | thincoder ledger audit [--root <dir>]")
|
|
163
|
+
exitSoon(1)
|
|
164
|
+
break
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
case "--help":
|
|
168
|
+
case "-h": {
|
|
169
|
+
process.stdout.write(usage)
|
|
170
|
+
break
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
case "--version":
|
|
174
|
+
case "-v": {
|
|
175
|
+
console.log(version)
|
|
176
|
+
break
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
default: {
|
|
180
|
+
console.error(`Unknown command: ${command}\n`)
|
|
181
|
+
process.stdout.write(usage)
|
|
182
|
+
exitSoon(1)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|