@laivc/laicode 0.1.0 → 0.2.1
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/README.md +48 -1
- package/bin/laicode.js +626 -62
- package/lib/adapters.js +666 -0
- package/package.json +2 -1
- package/scripts/smoke-test.js +153 -5
package/scripts/smoke-test.js
CHANGED
|
@@ -5,14 +5,17 @@ const fs = require("fs")
|
|
|
5
5
|
const os = require("os")
|
|
6
6
|
const path = require("path")
|
|
7
7
|
|
|
8
|
-
const home = path.join(os.tmpdir(),
|
|
8
|
+
const home = path.join(os.tmpdir(), `laicode-npm-smoke-test-${process.pid}`)
|
|
9
|
+
const codexHome = path.join(home, "codex")
|
|
9
10
|
fs.rmSync(home, { recursive: true, force: true })
|
|
10
11
|
fs.mkdirSync(home, { recursive: true, mode: 0o700 })
|
|
12
|
+
fs.mkdirSync(codexHome, { recursive: true, mode: 0o700 })
|
|
11
13
|
fs.writeFileSync(
|
|
12
14
|
path.join(home, "config.json"),
|
|
13
15
|
JSON.stringify(
|
|
14
16
|
{
|
|
15
17
|
apiBaseUrl: "https://api.lai.vc/v1",
|
|
18
|
+
apiKey: "sk-smoke-test-secret-value",
|
|
16
19
|
defaultModel: "gpt-5.5",
|
|
17
20
|
dashboardCache: {
|
|
18
21
|
onlineModels: 8,
|
|
@@ -28,7 +31,14 @@ fs.writeFileSync(
|
|
|
28
31
|
{ mode: 0o600 }
|
|
29
32
|
)
|
|
30
33
|
|
|
31
|
-
const env = {
|
|
34
|
+
const env = {
|
|
35
|
+
...process.env,
|
|
36
|
+
CODEX_HOME: codexHome,
|
|
37
|
+
HOME: home,
|
|
38
|
+
LAICODE_HOME: home,
|
|
39
|
+
LAICODE_PLAIN: "1",
|
|
40
|
+
XDG_CONFIG_HOME: path.join(home, ".config"),
|
|
41
|
+
}
|
|
32
42
|
|
|
33
43
|
function run(args) {
|
|
34
44
|
const result = spawnSync(process.execPath, args, { encoding: "utf8", env })
|
|
@@ -73,10 +83,13 @@ const help = run(["bin/laicode.js", "--help"])
|
|
|
73
83
|
assertIncludes(help, "laicode status [--refresh] [--json]", "help lists status")
|
|
74
84
|
assertIncludes(help, "laicode commands [--json]", "help lists commands")
|
|
75
85
|
assertIncludes(help, "laicode completion [bash|zsh|fish|powershell]", "help lists completion")
|
|
86
|
+
assertIncludes(help, "laicode tools [scan|install|uninstall|reinstall|configure]", "help lists tools actions")
|
|
87
|
+
assertIncludes(help, "laicode init [--tool codex] [--apply] [--json]", "help lists init")
|
|
76
88
|
assertIncludes(help, "laicode doctor [--model gpt-5.5] [--json]", "help lists doctor json")
|
|
77
89
|
assertIncludes(help, "laicode bench [--model gpt-5.5] [--count 3] [--json]", "help lists bench json")
|
|
78
|
-
assertIncludes(
|
|
79
|
-
assertIncludes(run(["bin/laicode.js", "
|
|
90
|
+
assertIncludes(help, "LAICODE_NO_SPINNER", "help lists spinner toggle")
|
|
91
|
+
assertIncludes(run(["bin/laicode.js", "--version"]), "0.2.1", "long version")
|
|
92
|
+
assertIncludes(run(["bin/laicode.js", "-v"]), "0.2.1", "short version")
|
|
80
93
|
assertIncludes(run(["bin/laicode.js", "--plain", "brand"]), "Lai.vc 终端字标", "plain brand command")
|
|
81
94
|
assertIncludes(run(["bin/laicode.js", "--color", "brand"]), "品牌色板", "color brand command")
|
|
82
95
|
assertIncludes(run(["bin/laicode.js", "status"]), "快照 缓存", "status uses local dashboard cache")
|
|
@@ -89,9 +102,144 @@ if (!Array.isArray(status.nextActions) || !status.nextActions.find((action) => a
|
|
|
89
102
|
console.error("Smoke assertion failed: status json includes first-run next action")
|
|
90
103
|
process.exit(1)
|
|
91
104
|
}
|
|
92
|
-
|
|
105
|
+
const commandsJson = run(["bin/laicode.js", "commands", "--json"])
|
|
106
|
+
assertIncludes(commandsJson, '"name": "commands"', "commands json")
|
|
107
|
+
const commandCatalog = parseJson(commandsJson, "commands json parses")
|
|
108
|
+
if (commandCatalog.commands[0]?.name !== "tools") {
|
|
109
|
+
console.error("Smoke assertion failed: tools command is first in command catalog")
|
|
110
|
+
process.exit(1)
|
|
111
|
+
}
|
|
93
112
|
assertIncludes(run(["bin/laicode.js", "completion", "bash"]), "complete -F _laicode_completion laicode", "bash completion")
|
|
94
113
|
assertIncludes(run(["bin/laicode.js", "completion", "fish"]), "__fish_seen_subcommand_from bench", "fish completion")
|
|
114
|
+
const toolsJson = run(["bin/laicode.js", "tools", "--json"])
|
|
115
|
+
const tools = parseJson(toolsJson, "tools json parses")
|
|
116
|
+
const toolOrder = tools.tools.map((tool) => tool.id)
|
|
117
|
+
if (toolOrder.slice(0, 5).join(",") !== "claude,codex,vscode,cursor,opencode") {
|
|
118
|
+
console.error("Smoke assertion failed: high-priority tool order")
|
|
119
|
+
console.error(toolOrder.join(","))
|
|
120
|
+
process.exit(1)
|
|
121
|
+
}
|
|
122
|
+
if (!tools.tools.find((tool) => tool.id === "codex")) {
|
|
123
|
+
console.error("Smoke assertion failed: tools json includes codex")
|
|
124
|
+
process.exit(1)
|
|
125
|
+
}
|
|
126
|
+
const vscodeTool = tools.tools.find((tool) => tool.id === "vscode")
|
|
127
|
+
if (!vscodeTool || vscodeTool.support !== "guide") {
|
|
128
|
+
console.error("Smoke assertion failed: tools json includes guided vscode support")
|
|
129
|
+
process.exit(1)
|
|
130
|
+
}
|
|
131
|
+
const opencodeTool = tools.tools.find((tool) => tool.id === "opencode")
|
|
132
|
+
if (!opencodeTool || opencodeTool.support !== "auto") {
|
|
133
|
+
console.error("Smoke assertion failed: tools json includes automatic opencode support")
|
|
134
|
+
process.exit(1)
|
|
135
|
+
}
|
|
136
|
+
const claudeTool = tools.tools.find((tool) => tool.id === "claude")
|
|
137
|
+
if (!claudeTool || claudeTool.support !== "blocked") {
|
|
138
|
+
console.error("Smoke assertion failed: tools json includes blocked claude support")
|
|
139
|
+
process.exit(1)
|
|
140
|
+
}
|
|
141
|
+
const claudeInitJson = run(["bin/laicode.js", "init", "--tool", "claude", "--json"])
|
|
142
|
+
const claudeInit = parseJson(claudeInitJson, "claude init json parses")
|
|
143
|
+
if (claudeInit.plan.ok !== false || !String(claudeInit.plan.reason || "").includes("Anthropic Messages")) {
|
|
144
|
+
console.error("Smoke assertion failed: claude init explains Anthropic compatibility gap")
|
|
145
|
+
process.exit(1)
|
|
146
|
+
}
|
|
147
|
+
const vscodeInitJson = run(["bin/laicode.js", "init", "--tool", "vscode", "--json"])
|
|
148
|
+
const vscodeInit = parseJson(vscodeInitJson, "vscode init json parses")
|
|
149
|
+
if (vscodeInit.plan.ok !== false || !String(vscodeInit.plan.reason || "").includes("VS Code 是编辑器宿主")) {
|
|
150
|
+
console.error("Smoke assertion failed: vscode init explains editor-host boundary")
|
|
151
|
+
process.exit(1)
|
|
152
|
+
}
|
|
153
|
+
const opencodeInitJson = run(["bin/laicode.js", "init", "--tool", "opencode", "--json"])
|
|
154
|
+
assertExcludes(opencodeInitJson, "sk-smoke-test-secret-value", "opencode init preview redacts api key")
|
|
155
|
+
const opencodeInit = parseJson(opencodeInitJson, "opencode init json parses")
|
|
156
|
+
if (
|
|
157
|
+
opencodeInit.plan.ok !== true ||
|
|
158
|
+
opencodeInit.plan.tool !== "opencode" ||
|
|
159
|
+
!opencodeInit.plan.operations.find((op) => String(op.path || "").endsWith("opencode.json"))
|
|
160
|
+
) {
|
|
161
|
+
console.error("Smoke assertion failed: opencode init creates provider plan")
|
|
162
|
+
process.exit(1)
|
|
163
|
+
}
|
|
164
|
+
const opencodePath = path.join(home, ".config", "opencode", "opencode.json")
|
|
165
|
+
fs.mkdirSync(path.dirname(opencodePath), { recursive: true, mode: 0o700 })
|
|
166
|
+
fs.writeFileSync(
|
|
167
|
+
opencodePath,
|
|
168
|
+
JSON.stringify(
|
|
169
|
+
{
|
|
170
|
+
provider: {
|
|
171
|
+
other: { name: "Other Provider" },
|
|
172
|
+
laivc: {
|
|
173
|
+
options: { customOption: "keep-me" },
|
|
174
|
+
models: { "old-model": { name: "old-model" } },
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
null,
|
|
179
|
+
2
|
|
180
|
+
) + "\n",
|
|
181
|
+
{ mode: 0o600 }
|
|
182
|
+
)
|
|
183
|
+
const opencodeMergeJson = run(["bin/laicode.js", "init", "--tool", "opencode", "--json"])
|
|
184
|
+
assertExcludes(opencodeMergeJson, "sk-smoke-test-secret-value", "opencode merge preview redacts api key")
|
|
185
|
+
assertIncludes(opencodeMergeJson, "old-model", "opencode merge preserves existing models")
|
|
186
|
+
assertIncludes(opencodeMergeJson, "customOption", "opencode merge preserves existing options")
|
|
187
|
+
const opencodeMerge = parseJson(opencodeMergeJson, "opencode merge json parses")
|
|
188
|
+
if (opencodeMerge.plan.mode !== "json-merge") {
|
|
189
|
+
console.error("Smoke assertion failed: opencode merge uses json-merge mode")
|
|
190
|
+
process.exit(1)
|
|
191
|
+
}
|
|
192
|
+
const opencodeMergePreview = parseJson(opencodeMerge.plan.operations[0].preview, "opencode merge preview parses")
|
|
193
|
+
if (
|
|
194
|
+
opencodeMergePreview.provider.laivc.options.customOption !== "keep-me" ||
|
|
195
|
+
!opencodeMergePreview.provider.laivc.models["old-model"]
|
|
196
|
+
) {
|
|
197
|
+
console.error("Smoke assertion failed: opencode merge keeps existing laivc provider details")
|
|
198
|
+
process.exit(1)
|
|
199
|
+
}
|
|
200
|
+
const installCodexJson = run(["bin/laicode.js", "tools", "install", "--tool", "codex", "--json"])
|
|
201
|
+
const installCodex = parseJson(installCodexJson, "codex install plan json parses")
|
|
202
|
+
if (installCodex.plan.action !== "install" || !installCodex.plan.commands.find((cmd) => cmd.display.includes("@openai/codex"))) {
|
|
203
|
+
console.error("Smoke assertion failed: codex install plan includes npm package")
|
|
204
|
+
process.exit(1)
|
|
205
|
+
}
|
|
206
|
+
const installOpencodeJson = run(["bin/laicode.js", "tools", "install", "--tool", "opencode", "--json"])
|
|
207
|
+
const installOpencode = parseJson(installOpencodeJson, "opencode install plan json parses")
|
|
208
|
+
if (installOpencode.plan.action !== "install" || !installOpencode.plan.commands.find((cmd) => cmd.display.includes("opencode-ai"))) {
|
|
209
|
+
console.error("Smoke assertion failed: opencode install plan includes npm package")
|
|
210
|
+
process.exit(1)
|
|
211
|
+
}
|
|
212
|
+
const reinstallClaudeJson = run(["bin/laicode.js", "tools", "reinstall", "--tool", "claude", "--json"])
|
|
213
|
+
const reinstallClaude = parseJson(reinstallClaudeJson, "claude reinstall plan json parses")
|
|
214
|
+
if (reinstallClaude.plan.action !== "reinstall" || reinstallClaude.plan.commands.length !== 2) {
|
|
215
|
+
console.error("Smoke assertion failed: claude reinstall plan includes uninstall and install")
|
|
216
|
+
process.exit(1)
|
|
217
|
+
}
|
|
218
|
+
const initPreviewJson = run(["bin/laicode.js", "init", "--tool", "codex", "--json"])
|
|
219
|
+
assertExcludes(initPreviewJson, "sk-smoke-test-secret-value", "init preview redacts api key")
|
|
220
|
+
const initPreview = parseJson(initPreviewJson, "init preview json parses")
|
|
221
|
+
if (initPreview.apply !== false || initPreview.plan.tool !== "codex") {
|
|
222
|
+
console.error("Smoke assertion failed: init preview describes codex plan")
|
|
223
|
+
process.exit(1)
|
|
224
|
+
}
|
|
225
|
+
const initApplyJson = run(["bin/laicode.js", "init", "--tool", "codex", "--apply", "--json"])
|
|
226
|
+
const initApply = parseJson(initApplyJson, "init apply json parses")
|
|
227
|
+
if (initApply.apply !== true || initApply.plan.tool !== "codex") {
|
|
228
|
+
console.error("Smoke assertion failed: init apply json describes codex plan")
|
|
229
|
+
process.exit(1)
|
|
230
|
+
}
|
|
231
|
+
const profilePath = path.join(codexHome, "laicode.config.toml")
|
|
232
|
+
if (!fs.existsSync(profilePath)) {
|
|
233
|
+
console.error("Smoke assertion failed: init apply writes codex profile")
|
|
234
|
+
process.exit(1)
|
|
235
|
+
}
|
|
236
|
+
assertIncludes(fs.readFileSync(profilePath, "utf8"), "model_provider = \"laivc\"", "codex profile provider")
|
|
237
|
+
const rollbackJson = run(["bin/laicode.js", "rollback", "--json"])
|
|
238
|
+
const rollback = parseJson(rollbackJson, "rollback json parses")
|
|
239
|
+
if (!rollback.ok || fs.existsSync(profilePath)) {
|
|
240
|
+
console.error("Smoke assertion failed: rollback removes created codex profile")
|
|
241
|
+
process.exit(1)
|
|
242
|
+
}
|
|
95
243
|
const configJson = run(["bin/laicode.js", "config", "--json"])
|
|
96
244
|
assertIncludes(configJson, '"defaultModel": "gpt-5.5"', "config json")
|
|
97
245
|
assertExcludes(configJson, "sk-", "config json hides api keys")
|