@curdx/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 WDX
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # curdx
2
+
3
+ AI 代码压榨机 — 多模型对抗、五级验证、PUA 鞭策,逼 AI 写出完美代码。
4
+
5
+ ## 安装
6
+
7
+ ### Homebrew(推荐)
8
+
9
+ ```bash
10
+ brew tap curdx/curdx-ralph https://github.com/curdx/curdx-ralph
11
+ brew install curdx
12
+ ```
13
+
14
+ ### npx(零安装)
15
+
16
+ ```bash
17
+ npx curdx init
18
+ ```
19
+
20
+ ### npm 全局安装
21
+
22
+ ```bash
23
+ npm install -g curdx
24
+ ```
25
+
26
+ ## 使用
27
+
28
+ ```bash
29
+ # 初始化项目
30
+ curdx init
31
+
32
+ # 启动 AI 压榨(默认使用 claude)
33
+ curdx
34
+
35
+ # 多模型对抗(同时使用 claude + codex + gemini)
36
+ curdx codex gemini
37
+
38
+ # 恢复上次会话
39
+ curdx -r
40
+
41
+ # 安装 zsh 补全
42
+ eval "$(curdx completion)"
43
+ ```
44
+
45
+ ## 要求
46
+
47
+ - Node.js >= 18
48
+ - macOS 或 Linux
49
+
50
+ ## 许可证
51
+
52
+ MIT
package/bin/curdx ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createCli } from '../dist/cli.js'
4
+
5
+ const program = createCli()
6
+ program.parseAsync(process.argv).catch((err) => {
7
+ process.stderr.write(`curdx: ${err.message}\n`)
8
+ process.exit(1)
9
+ })
package/dist/cli.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { Command } from 'commander';
2
+
3
+ declare const KNOWN_PROVIDERS: readonly ["claude", "codex", "gemini"];
4
+ type Provider = (typeof KNOWN_PROVIDERS)[number];
5
+ interface ParsedArgs {
6
+ providers: Provider[];
7
+ resume: boolean;
8
+ }
9
+ /**
10
+ * Parse CLI arguments into structured options.
11
+ * Exported for testability — does not call process.exit.
12
+ */
13
+ declare function parseArgs(argv: string[]): ParsedArgs;
14
+ /**
15
+ * Create the main CLI program for direct execution.
16
+ * Used by bin/curdx entry point.
17
+ */
18
+ declare function createCli(): Command;
19
+
20
+ export { KNOWN_PROVIDERS, type ParsedArgs, type Provider, createCli, parseArgs };
package/dist/cli.js ADDED
@@ -0,0 +1,651 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/runtime/detect.ts
12
+ import { execa } from "execa";
13
+ async function detectAvailableClis() {
14
+ const check = async (cmd) => {
15
+ try {
16
+ const result = await execa("which", [cmd], { reject: false });
17
+ return result.exitCode === 0;
18
+ } catch {
19
+ return false;
20
+ }
21
+ };
22
+ const [claude, codex, gemini] = await Promise.all([
23
+ check("claude"),
24
+ check("codex"),
25
+ check("gemini")
26
+ ]);
27
+ return { claude, codex, gemini };
28
+ }
29
+ function validateProviders(requested, available) {
30
+ const valid = [];
31
+ const missing = [];
32
+ for (const provider of requested) {
33
+ if (provider in available && available[provider]) {
34
+ valid.push(provider);
35
+ } else {
36
+ missing.push(provider);
37
+ }
38
+ }
39
+ return { valid, missing };
40
+ }
41
+ var init_detect = __esm({
42
+ "src/runtime/detect.ts"() {
43
+ "use strict";
44
+ }
45
+ });
46
+
47
+ // src/runtime/terminal.ts
48
+ import { execaCommand } from "execa";
49
+ async function detectTerminalBackend() {
50
+ try {
51
+ const result = await execaCommand("which wezterm", { reject: false });
52
+ if (result.exitCode === 0) return "wezterm";
53
+ } catch {
54
+ }
55
+ try {
56
+ const result = await execaCommand("which tmux", { reject: false });
57
+ if (result.exitCode === 0) return "tmux";
58
+ } catch {
59
+ }
60
+ return null;
61
+ }
62
+ var init_terminal = __esm({
63
+ "src/runtime/terminal.ts"() {
64
+ "use strict";
65
+ }
66
+ });
67
+
68
+ // src/runtime/wezterm.ts
69
+ import { execa as execa2 } from "execa";
70
+ var MAX_ARGV_TEXT_LENGTH, WeztermTerminal;
71
+ var init_wezterm = __esm({
72
+ "src/runtime/wezterm.ts"() {
73
+ "use strict";
74
+ MAX_ARGV_TEXT_LENGTH = 4e3;
75
+ WeztermTerminal = class {
76
+ backend = "wezterm";
77
+ async sendText(paneId, text) {
78
+ if (text.length > MAX_ARGV_TEXT_LENGTH) {
79
+ await execa2("wezterm", ["cli", "send-text", "--pane-id", paneId, "--no-paste"], {
80
+ input: text
81
+ });
82
+ } else {
83
+ await execa2("wezterm", [
84
+ "cli",
85
+ "send-text",
86
+ "--pane-id",
87
+ paneId,
88
+ "--no-paste",
89
+ "--",
90
+ text
91
+ ]);
92
+ }
93
+ }
94
+ async createPane(cmd, opts) {
95
+ if (!cmd || /[;&|`$]/.test(cmd)) {
96
+ throw new Error(`Invalid pane command: ${cmd}`);
97
+ }
98
+ const percent = opts.percent ?? 50;
99
+ const dirFlag = opts.direction === "right" ? "--right" : "--bottom";
100
+ const args = ["cli", "split-pane", dirFlag, "--percent", String(percent)];
101
+ if (opts.cwd) {
102
+ args.push("--cwd", opts.cwd);
103
+ }
104
+ args.push("--", cmd);
105
+ const result = await execa2("wezterm", args);
106
+ const paneId = result.stdout.trim();
107
+ if (!paneId) {
108
+ throw new Error("wezterm split-pane returned empty pane ID");
109
+ }
110
+ return paneId;
111
+ }
112
+ async isAlive(paneId) {
113
+ try {
114
+ const panes = await this.listPanes();
115
+ return panes.some((p) => p.paneId === paneId);
116
+ } catch {
117
+ return false;
118
+ }
119
+ }
120
+ async killPane(paneId) {
121
+ await execa2("wezterm", ["cli", "kill-pane", "--pane-id", paneId]);
122
+ }
123
+ async activatePane(paneId) {
124
+ await execa2("wezterm", ["cli", "activate-pane", "--pane-id", paneId]);
125
+ }
126
+ async listPanes() {
127
+ try {
128
+ const result = await execa2("wezterm", ["cli", "list", "--format", "json"]);
129
+ const raw = JSON.parse(result.stdout);
130
+ return raw.map((p) => ({
131
+ paneId: String(p.pane_id),
132
+ title: p.title,
133
+ isActive: p.is_active
134
+ }));
135
+ } catch {
136
+ return [];
137
+ }
138
+ }
139
+ };
140
+ }
141
+ });
142
+
143
+ // src/shared/fs-atomic.ts
144
+ import { writeFileSync, renameSync, unlinkSync, mkdirSync } from "fs";
145
+ import { dirname, join } from "path";
146
+ import { randomBytes } from "crypto";
147
+ import { stringify as yamlStringify } from "yaml";
148
+ function ensureDir(filePath) {
149
+ mkdirSync(dirname(filePath), { recursive: true });
150
+ }
151
+ function tempPath(filePath) {
152
+ const rand = randomBytes(6).toString("hex");
153
+ return join(dirname(filePath), `.${rand}.tmp`);
154
+ }
155
+ function atomicWriteImpl(filePath, content) {
156
+ ensureDir(filePath);
157
+ const tmp = tempPath(filePath);
158
+ try {
159
+ writeFileSync(tmp, content, "utf-8");
160
+ renameSync(tmp, filePath);
161
+ } catch (err) {
162
+ try {
163
+ unlinkSync(tmp);
164
+ } catch {
165
+ }
166
+ throw err;
167
+ }
168
+ }
169
+ function atomicWriteJson(filePath, data) {
170
+ const content = JSON.stringify(data, null, 2);
171
+ atomicWriteImpl(filePath, content);
172
+ }
173
+ function atomicWriteYaml(filePath, data) {
174
+ const content = yamlStringify(data);
175
+ atomicWriteImpl(filePath, content);
176
+ }
177
+ var init_fs_atomic = __esm({
178
+ "src/shared/fs-atomic.ts"() {
179
+ "use strict";
180
+ }
181
+ });
182
+
183
+ // src/runtime/registry.ts
184
+ import { existsSync, readFileSync } from "fs";
185
+ import { join as join2 } from "path";
186
+ async function saveSession(data, projectRoot) {
187
+ const filePath = join2(projectRoot, ".curdx", REGISTRY_FILENAME);
188
+ const wrapper = {
189
+ version: 1,
190
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
191
+ data
192
+ };
193
+ await atomicWriteJson(filePath, wrapper);
194
+ }
195
+ async function loadSession(projectRoot) {
196
+ const filePath = join2(projectRoot, ".curdx", REGISTRY_FILENAME);
197
+ if (!existsSync(filePath)) {
198
+ return null;
199
+ }
200
+ try {
201
+ const raw = readFileSync(filePath, "utf-8");
202
+ const wrapper = JSON.parse(raw);
203
+ return wrapper.data;
204
+ } catch {
205
+ return null;
206
+ }
207
+ }
208
+ function cleanExpiredEntries(registry, ttlDays = SESSION_TTL_DAYS) {
209
+ const cutoff = Date.now() - ttlDays * 24 * 60 * 60 * 1e3;
210
+ const cleaned = {
211
+ ...registry,
212
+ providers: {}
213
+ };
214
+ for (const [key, entry] of Object.entries(registry.providers)) {
215
+ if (entry.updatedAt >= cutoff) {
216
+ cleaned.providers[key] = entry;
217
+ }
218
+ }
219
+ return cleaned;
220
+ }
221
+ var SESSION_TTL_DAYS, REGISTRY_FILENAME;
222
+ var init_registry = __esm({
223
+ "src/runtime/registry.ts"() {
224
+ "use strict";
225
+ init_fs_atomic();
226
+ SESSION_TTL_DAYS = 7;
227
+ REGISTRY_FILENAME = "pane-registry.json";
228
+ }
229
+ });
230
+
231
+ // src/shared/types.ts
232
+ var CurdxError, RuntimeError;
233
+ var init_types = __esm({
234
+ "src/shared/types.ts"() {
235
+ "use strict";
236
+ CurdxError = class extends Error {
237
+ constructor(code, message, cause) {
238
+ super(message);
239
+ this.code = code;
240
+ this.cause = cause;
241
+ this.name = "CurdxError";
242
+ }
243
+ };
244
+ RuntimeError = class extends CurdxError {
245
+ constructor(code, message, cause) {
246
+ super(code, message, cause);
247
+ this.name = "RuntimeError";
248
+ }
249
+ };
250
+ }
251
+ });
252
+
253
+ // src/runtime/launcher.ts
254
+ var launcher_exports = {};
255
+ __export(launcher_exports, {
256
+ launch: () => launch
257
+ });
258
+ import { randomBytes as randomBytes2 } from "crypto";
259
+ async function launch(options) {
260
+ const projectRoot = process.cwd();
261
+ if (options.resume) {
262
+ return resumeSession(options, projectRoot);
263
+ }
264
+ return freshLaunch(options, projectRoot);
265
+ }
266
+ async function freshLaunch(options, projectRoot) {
267
+ const available = await detectAvailableClis();
268
+ const { valid, missing } = validateProviders(options.providers, available);
269
+ if (missing.length > 0) {
270
+ process.stderr.write(
271
+ `\u26A0\uFE0F \u672A\u68C0\u6D4B\u5230\u4EE5\u4E0B AI CLI: ${missing.join(", ")}\uFF08\u5DF2\u964D\u7EA7\u5230\u53EF\u7528\u6A21\u578B\uFF09
272
+ `
273
+ );
274
+ }
275
+ if (valid.length === 0) {
276
+ throw new RuntimeError(
277
+ "NO_CLI_AVAILABLE",
278
+ "\u6CA1\u6709\u53EF\u7528\u7684 AI CLI \u5DE5\u5177\u3002\u8BF7\u5148\u5B89\u88C5 Claude Code: npm install -g @anthropic-ai/claude-code"
279
+ );
280
+ }
281
+ const mode = valid.length >= 3 ? 3 : valid.length >= 2 ? 2 : 1;
282
+ const backend = await detectTerminalBackend();
283
+ let terminal = null;
284
+ const paneIds = {};
285
+ if (backend === "wezterm") {
286
+ terminal = new WeztermTerminal();
287
+ const additionalProviders = valid.slice(1);
288
+ for (let i = 0; i < additionalProviders.length; i++) {
289
+ const provider = additionalProviders[i];
290
+ try {
291
+ const paneId = await terminal.createPane(provider, {
292
+ direction: "right",
293
+ percent: Math.floor(100 / (additionalProviders.length - i + 1))
294
+ });
295
+ paneIds[provider] = paneId;
296
+ } catch {
297
+ process.stderr.write(`\u26A0\uFE0F \u65E0\u6CD5\u4E3A ${provider} \u521B\u5EFA\u7EC8\u7AEF\u7A97\u683C
298
+ `);
299
+ }
300
+ }
301
+ }
302
+ process.stdout.write(`\u{1F680} curdx \u542F\u52A8 \u2014 \u6A21\u5F0F: ${mode} \u6A21\u578B (${valid.join(", ")})
303
+ `);
304
+ if (terminal && Object.keys(paneIds).length > 0) {
305
+ process.stdout.write(`\u{1F4FA} \u7EC8\u7AEF\u5206\u5C4F: ${Object.entries(paneIds).map(([p, id]) => `${p}\u2192pane:${id}`).join(", ")}
306
+ `);
307
+ } else if (!terminal) {
308
+ process.stderr.write("\u26A0\uFE0F \u672A\u68C0\u6D4B\u5230 WezTerm/tmux\uFF0C\u8FD0\u884C\u5728\u5355\u7A97\u683C\u6A21\u5F0F\n");
309
+ }
310
+ const now = Date.now();
311
+ const registry = {
312
+ ccbSessionId: `sess-${randomBytes2(6).toString("hex")}`,
313
+ workDir: projectRoot,
314
+ terminal: backend ?? "none",
315
+ updatedAt: now,
316
+ providers: {}
317
+ };
318
+ for (const [provider, paneId] of Object.entries(paneIds)) {
319
+ registry.providers[provider] = {
320
+ paneId,
321
+ provider,
322
+ sessionId: "",
323
+ paneTitleMarker: `CCB-${provider}`,
324
+ updatedAt: now
325
+ };
326
+ }
327
+ await saveSession(registry, projectRoot).catch(() => {
328
+ });
329
+ return {
330
+ providers: valid,
331
+ missing,
332
+ resume: false,
333
+ launched: true,
334
+ terminal,
335
+ paneIds
336
+ };
337
+ }
338
+ async function resumeSession(options, projectRoot) {
339
+ const saved = await loadSession(projectRoot);
340
+ if (!saved) {
341
+ process.stderr.write("\u26A0\uFE0F \u65E0\u5386\u53F2\u4F1A\u8BDD\uFF0C\u5C06\u542F\u52A8\u65B0\u4F1A\u8BDD\n");
342
+ return freshLaunch({ ...options, resume: false }, projectRoot);
343
+ }
344
+ const cleaned = cleanExpiredEntries(saved);
345
+ const providerKeys = Object.keys(cleaned.providers);
346
+ if (providerKeys.length === 0) {
347
+ process.stderr.write("\u26A0\uFE0F \u5386\u53F2\u4F1A\u8BDD\u5DF2\u8FC7\u671F\uFF0C\u5C06\u542F\u52A8\u65B0\u4F1A\u8BDD\n");
348
+ return freshLaunch({ ...options, resume: false }, projectRoot);
349
+ }
350
+ const backend = await detectTerminalBackend();
351
+ let terminal = null;
352
+ const paneIds = {};
353
+ let aliveCount = 0;
354
+ if (backend === "wezterm") {
355
+ terminal = new WeztermTerminal();
356
+ for (const [provider, entry] of Object.entries(cleaned.providers)) {
357
+ const alive = await terminal.isAlive(entry.paneId);
358
+ if (alive) {
359
+ paneIds[provider] = entry.paneId;
360
+ aliveCount++;
361
+ }
362
+ }
363
+ }
364
+ if (aliveCount === 0) {
365
+ process.stderr.write("\u26A0\uFE0F \u5386\u53F2\u4F1A\u8BDD\u7A97\u683C\u5DF2\u5173\u95ED\uFF0C\u5C06\u542F\u52A8\u65B0\u4F1A\u8BDD\n");
366
+ return freshLaunch({ ...options, resume: false }, projectRoot);
367
+ }
368
+ const allProviders = [.../* @__PURE__ */ new Set(["claude", ...providerKeys])];
369
+ const mode = allProviders.length >= 3 ? 3 : allProviders.length >= 2 ? 2 : 1;
370
+ process.stdout.write(`\u{1F504} \u6062\u590D\u4F1A\u8BDD \u2014 \u6A21\u5F0F: ${mode} \u6A21\u578B (${allProviders.join(", ")})
371
+ `);
372
+ process.stdout.write(`\u{1F4FA} \u6062\u590D\u7A97\u683C: ${Object.entries(paneIds).map(([p, id]) => `${p}\u2192pane:${id}`).join(", ")}
373
+ `);
374
+ return {
375
+ providers: allProviders,
376
+ missing: [],
377
+ resume: true,
378
+ launched: true,
379
+ terminal,
380
+ paneIds
381
+ };
382
+ }
383
+ var init_launcher = __esm({
384
+ "src/runtime/launcher.ts"() {
385
+ "use strict";
386
+ init_detect();
387
+ init_terminal();
388
+ init_wezterm();
389
+ init_registry();
390
+ init_types();
391
+ }
392
+ });
393
+
394
+ // src/runtime/completion.ts
395
+ var completion_exports = {};
396
+ __export(completion_exports, {
397
+ generateZshCompletion: () => generateZshCompletion,
398
+ printCompletionInstallGuide: () => printCompletionInstallGuide
399
+ });
400
+ function generateZshCompletion() {
401
+ return `#compdef curdx
402
+
403
+ _curdx() {
404
+ local -a commands providers options
405
+
406
+ commands=(
407
+ 'init:\u521D\u59CB\u5316 curdx \u9879\u76EE'
408
+ 'completion:\u8F93\u51FA zsh \u8865\u5168\u811A\u672C'
409
+ )
410
+
411
+ providers=(
412
+ 'codex:\u542F\u7528 Codex CLI'
413
+ 'gemini:\u542F\u7528 Gemini CLI'
414
+ )
415
+
416
+ options=(
417
+ '-r[\u6062\u590D\u4E0A\u6B21\u7EC8\u7AEF\u4F1A\u8BDD]'
418
+ '--resume[\u6062\u590D\u4E0A\u6B21\u7EC8\u7AEF\u4F1A\u8BDD]'
419
+ '-V[\u663E\u793A\u7248\u672C\u53F7]'
420
+ '--version[\u663E\u793A\u7248\u672C\u53F7]'
421
+ '-h[\u663E\u793A\u5E2E\u52A9]'
422
+ '--help[\u663E\u793A\u5E2E\u52A9]'
423
+ )
424
+
425
+ _arguments -C \\
426
+ '1:command or provider:->first' \\
427
+ '2:provider:->second' \\
428
+ '*:options:->opts'
429
+
430
+ case $state in
431
+ first)
432
+ _describe 'commands' commands
433
+ _describe 'providers' providers
434
+ _describe 'options' options
435
+ ;;
436
+ second)
437
+ _describe 'providers' providers
438
+ ;;
439
+ opts)
440
+ _describe 'options' options
441
+ ;;
442
+ esac
443
+ }
444
+
445
+ compdef _curdx curdx
446
+ `;
447
+ }
448
+ function printCompletionInstallGuide() {
449
+ process.stdout.write(`
450
+ \u{1F4A1} zsh \u8865\u5168\u914D\u7F6E:
451
+ `);
452
+ process.stdout.write(` \u8FD0\u884C \`curdx completion > ~/.zsh/completions/_curdx\`
453
+ `);
454
+ process.stdout.write(` \u7136\u540E\u5728 .zshrc \u4E2D\u6DFB\u52A0: fpath=(~/.zsh/completions $fpath)
455
+ `);
456
+ }
457
+ var init_completion = __esm({
458
+ "src/runtime/completion.ts"() {
459
+ "use strict";
460
+ }
461
+ });
462
+
463
+ // src/runtime/init.ts
464
+ var init_exports = {};
465
+ __export(init_exports, {
466
+ detectEnvironment: () => detectEnvironment,
467
+ generateConfig: () => generateConfig,
468
+ installClaudeMem: () => installClaudeMem,
469
+ runInit: () => runInit
470
+ });
471
+ import { existsSync as existsSync2 } from "fs";
472
+ import { join as join3, basename } from "path";
473
+ import { homedir } from "os";
474
+ import { execa as execa3 } from "execa";
475
+ async function detectEnvironment(projectRoot) {
476
+ const [clis, terminal] = await Promise.all([
477
+ detectAvailableClis(),
478
+ detectTerminalBackend()
479
+ ]);
480
+ const claudeMemPath = join3(homedir(), ".claude", "plugins", "claude-mem");
481
+ const claudeMem = existsSync2(claudeMemPath);
482
+ const projectName = basename(projectRoot);
483
+ return { clis, terminal, claudeMem, projectName };
484
+ }
485
+ async function generateConfig(env, projectRoot) {
486
+ const configPath = join3(projectRoot, ".curdx", "config.yaml");
487
+ if (existsSync2(configPath)) {
488
+ return "exists";
489
+ }
490
+ const models = [];
491
+ if (env.clis.claude) models.push("claude");
492
+ if (env.clis.codex) models.push("codex");
493
+ if (env.clis.gemini) models.push("gemini");
494
+ if (models.length === 0) models.push("claude");
495
+ const mode = models.length >= 3 ? 3 : models.length >= 2 ? 2 : 1;
496
+ const config = {
497
+ models,
498
+ mode,
499
+ verifyLevel: 5,
500
+ puaEnabled: true,
501
+ projectName: env.projectName,
502
+ wezterm: env.terminal === "wezterm",
503
+ tmux: env.terminal === "tmux"
504
+ };
505
+ await atomicWriteYaml(configPath, config);
506
+ return "created";
507
+ }
508
+ async function installClaudeMem(installPath) {
509
+ const targetPath = installPath ?? join3(homedir(), ".claude", "plugins", "claude-mem");
510
+ if (existsSync2(targetPath)) {
511
+ return "already-installed";
512
+ }
513
+ try {
514
+ await execa3("git", [
515
+ "clone",
516
+ "https://github.com/thedotmack/claude-mem.git",
517
+ targetPath
518
+ ]);
519
+ return "installed";
520
+ } catch {
521
+ return "failed";
522
+ }
523
+ }
524
+ async function runInit(projectRoot) {
525
+ process.stdout.write("\n\u{1F50D} curdx init \u2014 \u68C0\u6D4B\u73AF\u5883...\n\n");
526
+ const env = await detectEnvironment(projectRoot);
527
+ const check = (v) => v ? "\u2705" : "\u274C";
528
+ process.stdout.write(` AI CLI \u68C0\u6D4B:
529
+ `);
530
+ process.stdout.write(` ${check(env.clis.claude)} Claude Code
531
+ `);
532
+ process.stdout.write(` ${check(env.clis.codex)} Codex CLI
533
+ `);
534
+ process.stdout.write(` ${check(env.clis.gemini)} Gemini CLI
535
+ `);
536
+ process.stdout.write(` \u7EC8\u7AEF\u540E\u7AEF:
537
+ `);
538
+ process.stdout.write(` ${check(env.terminal === "wezterm")} WezTerm
539
+ `);
540
+ process.stdout.write(` ${check(env.terminal === "tmux")} tmux
541
+ `);
542
+ const configResult = await generateConfig(env, projectRoot);
543
+ if (configResult === "created") {
544
+ process.stdout.write(` \u914D\u7F6E\u6587\u4EF6:
545
+ `);
546
+ process.stdout.write(` \u2705 \u5DF2\u751F\u6210 .curdx/config.yaml
547
+ `);
548
+ } else {
549
+ process.stdout.write(` \u914D\u7F6E\u6587\u4EF6:
550
+ `);
551
+ process.stdout.write(` \u23ED\uFE0F .curdx/config.yaml \u5DF2\u5B58\u5728\uFF08\u672A\u8986\u76D6\uFF09
552
+ `);
553
+ }
554
+ const memResult = await installClaudeMem();
555
+ process.stdout.write(` claude-mem:
556
+ `);
557
+ if (memResult === "already-installed") {
558
+ process.stdout.write(` \u2705 \u5DF2\u5B89\u88C5
559
+ `);
560
+ } else if (memResult === "installed") {
561
+ process.stdout.write(` \u2705 \u5B89\u88C5\u6210\u529F
562
+ `);
563
+ } else {
564
+ process.stderr.write(` \u26A0\uFE0F \u5B89\u88C5\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u6838\u5FC3\u529F\u80FD\uFF09
565
+ `);
566
+ }
567
+ const { printCompletionInstallGuide: printCompletionInstallGuide2 } = await Promise.resolve().then(() => (init_completion(), completion_exports));
568
+ printCompletionInstallGuide2();
569
+ process.stdout.write(`
570
+ \u{1F680} curdx \u521D\u59CB\u5316\u5B8C\u6210\uFF01\u9879\u76EE: ${env.projectName}
571
+ `);
572
+ process.stdout.write(` \u8FD0\u884C \`curdx\` \u5F00\u59CB\u4F7F\u7528
573
+
574
+ `);
575
+ }
576
+ var init_init = __esm({
577
+ "src/runtime/init.ts"() {
578
+ "use strict";
579
+ init_detect();
580
+ init_terminal();
581
+ init_fs_atomic();
582
+ }
583
+ });
584
+
585
+ // src/runtime/cli.ts
586
+ import { Command } from "commander";
587
+ import { readFileSync as readFileSync2 } from "fs";
588
+ import { join as join4, dirname as dirname2 } from "path";
589
+ import { fileURLToPath } from "url";
590
+ function loadVersion() {
591
+ let dir = dirname2(fileURLToPath(import.meta.url));
592
+ for (let i = 0; i < 5; i++) {
593
+ try {
594
+ const pkg = JSON.parse(readFileSync2(join4(dir, "package.json"), "utf-8"));
595
+ if (pkg.name === "@curdx/cli") return pkg.version;
596
+ } catch {
597
+ }
598
+ dir = dirname2(dir);
599
+ }
600
+ return "0.0.0";
601
+ }
602
+ var version = loadVersion();
603
+ var KNOWN_PROVIDERS = ["claude", "codex", "gemini"];
604
+ function parseArgs(argv) {
605
+ const program = new Command();
606
+ program.name("curdx").description("AI \u4EE3\u7801\u538B\u69A8\u673A \u2014 \u591A\u6A21\u578B\u5BF9\u6297\u3001\u4E94\u7EA7\u9A8C\u8BC1\u3001PUA \u97AD\u7B56").version(version).argument("[providers...]", "\u989D\u5916\u7684 AI \u6A21\u578B (codex, gemini)").option("-r, --resume", "\u6062\u590D\u4E0A\u6B21\u7EC8\u7AEF\u4F1A\u8BDD", false).exitOverride().configureOutput({
607
+ writeOut: () => {
608
+ },
609
+ writeErr: () => {
610
+ }
611
+ });
612
+ program.parse(argv);
613
+ const opts = program.opts();
614
+ const rawProviders = program.args;
615
+ const requestedProviders = rawProviders.filter(
616
+ (p) => KNOWN_PROVIDERS.includes(p)
617
+ );
618
+ const providers = ["claude"];
619
+ for (const p of requestedProviders) {
620
+ if (!providers.includes(p)) {
621
+ providers.push(p);
622
+ }
623
+ }
624
+ return {
625
+ providers,
626
+ resume: opts.resume
627
+ };
628
+ }
629
+ function createCli() {
630
+ const program = new Command();
631
+ program.name("curdx").description("AI \u4EE3\u7801\u538B\u69A8\u673A \u2014 \u591A\u6A21\u578B\u5BF9\u6297\u3001\u4E94\u7EA7\u9A8C\u8BC1\u3001PUA \u97AD\u7B56").version(version).argument("[providers...]", "\u989D\u5916\u7684 AI \u6A21\u578B (codex, gemini)").option("-r, --resume", "\u6062\u590D\u4E0A\u6B21\u7EC8\u7AEF\u4F1A\u8BDD", false).action(async (providers, opts) => {
632
+ const { launch: launch2 } = await Promise.resolve().then(() => (init_launcher(), launcher_exports));
633
+ const parsed = parseArgs(["node", "curdx", ...opts.resume ? ["-r"] : [], ...providers]);
634
+ await launch2(parsed);
635
+ });
636
+ program.command("init").description("\u521D\u59CB\u5316 curdx \u9879\u76EE\uFF08\u68C0\u6D4B\u73AF\u5883\u3001\u751F\u6210\u914D\u7F6E\u3001\u5B89\u88C5\u63D2\u4EF6\uFF09").action(async () => {
637
+ const { runInit: runInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
638
+ await runInit2(process.cwd());
639
+ });
640
+ program.command("completion").description("\u8F93\u51FA zsh \u8865\u5168\u811A\u672C").action(async () => {
641
+ const { generateZshCompletion: generateZshCompletion2 } = await Promise.resolve().then(() => (init_completion(), completion_exports));
642
+ process.stdout.write(generateZshCompletion2());
643
+ });
644
+ return program;
645
+ }
646
+ export {
647
+ KNOWN_PROVIDERS,
648
+ createCli,
649
+ parseArgs
650
+ };
651
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/runtime/detect.ts","../src/runtime/terminal.ts","../src/runtime/wezterm.ts","../src/shared/fs-atomic.ts","../src/runtime/registry.ts","../src/shared/types.ts","../src/runtime/launcher.ts","../src/runtime/completion.ts","../src/runtime/init.ts","../src/runtime/cli.ts"],"sourcesContent":["import { execa } from 'execa'\nimport type { Provider } from './cli.js'\n\nexport interface CliAvailability {\n claude: boolean\n codex: boolean\n gemini: boolean\n}\n\nexport interface ValidationResult {\n valid: string[]\n missing: string[]\n}\n\n/**\n * Detect which AI CLI tools are installed on the system.\n * Uses `which` command for silent detection — never throws.\n */\nexport async function detectAvailableClis(): Promise<CliAvailability> {\n const check = async (cmd: string): Promise<boolean> => {\n try {\n const result = await execa('which', [cmd], { reject: false })\n return result.exitCode === 0\n } catch {\n return false\n }\n }\n\n const [claude, codex, gemini] = await Promise.all([\n check('claude'),\n check('codex'),\n check('gemini'),\n ])\n\n return { claude, codex, gemini }\n}\n\n/**\n * Validate requested providers against detected availability.\n * Returns which providers are valid and which are missing.\n */\nexport function validateProviders(\n requested: string[],\n available: CliAvailability,\n): ValidationResult {\n const valid: string[] = []\n const missing: string[] = []\n\n for (const provider of requested) {\n if (provider in available && available[provider as keyof CliAvailability]) {\n valid.push(provider)\n } else {\n missing.push(provider)\n }\n }\n\n return { valid, missing }\n}\n","import { execaCommand } from 'execa'\n\n/** Information about a terminal pane */\nexport interface PaneInfo {\n paneId: string\n title: string\n isActive: boolean\n}\n\n/** Options for creating a new pane */\nexport interface CreatePaneOptions {\n direction: 'right' | 'bottom'\n percent?: number\n cwd?: string\n title?: string\n}\n\n/** Supported terminal backends */\nexport type TerminalBackend = 'wezterm' | 'tmux'\n\n/** Abstract terminal interface — all backends must implement this */\nexport interface Terminal {\n readonly backend: TerminalBackend\n\n /** Send text to a specific pane */\n sendText(paneId: string, text: string): Promise<void>\n\n /** Create a new split pane running a command */\n createPane(cmd: string, opts: CreatePaneOptions): Promise<string>\n\n /** Check if a pane is still alive */\n isAlive(paneId: string): Promise<boolean>\n\n /** Kill/close a pane */\n killPane(paneId: string): Promise<void>\n\n /** Activate/focus a pane */\n activatePane(paneId: string): Promise<void>\n\n /** List all panes in current window */\n listPanes(): Promise<PaneInfo[]>\n}\n\n/**\n * Detect which terminal backend is available.\n * Prefers WezTerm over tmux.\n */\nexport async function detectTerminalBackend(): Promise<TerminalBackend | null> {\n try {\n const result = await execaCommand('which wezterm', { reject: false })\n if (result.exitCode === 0) return 'wezterm'\n } catch { /* not available */ }\n\n try {\n const result = await execaCommand('which tmux', { reject: false })\n if (result.exitCode === 0) return 'tmux'\n } catch { /* not available */ }\n\n return null\n}\n","import { execa } from 'execa'\nimport type { Terminal, PaneInfo, CreatePaneOptions, TerminalBackend } from './terminal.js'\n\nconst MAX_ARGV_TEXT_LENGTH = 4000\n\n/** WezTerm terminal backend — operates via `wezterm cli` API */\nexport class WeztermTerminal implements Terminal {\n readonly backend: TerminalBackend = 'wezterm'\n\n async sendText(paneId: string, text: string): Promise<void> {\n if (text.length > MAX_ARGV_TEXT_LENGTH) {\n await execa('wezterm', ['cli', 'send-text', '--pane-id', paneId, '--no-paste'], {\n input: text,\n })\n } else {\n await execa('wezterm', [\n 'cli', 'send-text', '--pane-id', paneId, '--no-paste', '--', text,\n ])\n }\n }\n\n async createPane(cmd: string, opts: CreatePaneOptions): Promise<string> {\n // 校验 cmd 防止命令注入\n if (!cmd || /[;&|`$]/.test(cmd)) {\n throw new Error(`Invalid pane command: ${cmd}`)\n }\n\n const percent = opts.percent ?? 50\n const dirFlag = opts.direction === 'right' ? '--right' : '--bottom'\n\n const args = ['cli', 'split-pane', dirFlag, '--percent', String(percent)]\n\n if (opts.cwd) {\n args.push('--cwd', opts.cwd)\n }\n\n args.push('--', cmd)\n\n const result = await execa('wezterm', args)\n const paneId = result.stdout.trim()\n if (!paneId) {\n throw new Error('wezterm split-pane returned empty pane ID')\n }\n return paneId\n }\n\n async isAlive(paneId: string): Promise<boolean> {\n try {\n const panes = await this.listPanes()\n return panes.some((p) => p.paneId === paneId)\n } catch {\n return false\n }\n }\n\n async killPane(paneId: string): Promise<void> {\n await execa('wezterm', ['cli', 'kill-pane', '--pane-id', paneId])\n }\n\n async activatePane(paneId: string): Promise<void> {\n await execa('wezterm', ['cli', 'activate-pane', '--pane-id', paneId])\n }\n\n async listPanes(): Promise<PaneInfo[]> {\n try {\n const result = await execa('wezterm', ['cli', 'list', '--format', 'json'])\n const raw = JSON.parse(result.stdout) as Array<{\n pane_id: number\n title: string\n is_active: boolean\n }>\n return raw.map((p) => ({\n paneId: String(p.pane_id),\n title: p.title,\n isActive: p.is_active,\n }))\n } catch {\n return []\n }\n }\n}\n","import { writeFileSync, renameSync, unlinkSync, mkdirSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { randomBytes } from 'node:crypto'\nimport { stringify as yamlStringify } from 'yaml'\n\nfunction ensureDir(filePath: string): void {\n mkdirSync(dirname(filePath), { recursive: true })\n}\n\nfunction tempPath(filePath: string): string {\n const rand = randomBytes(6).toString('hex')\n // tmp 文件放同目录,避免跨设备 EXDEV 错误\n return join(dirname(filePath), `.${rand}.tmp`)\n}\n\nfunction atomicWriteImpl(filePath: string, content: string): void {\n ensureDir(filePath)\n const tmp = tempPath(filePath)\n try {\n writeFileSync(tmp, content, 'utf-8')\n renameSync(tmp, filePath)\n } catch (err) {\n try { unlinkSync(tmp) } catch { /* 清理失败忽略 */ }\n throw err\n }\n}\n\n/** Atomically write text to file (synchronous) */\nexport function atomicWriteSync(filePath: string, content: string): void {\n atomicWriteImpl(filePath, content)\n}\n\n/** Atomically write JSON data to file */\nexport function atomicWriteJson(filePath: string, data: unknown): void {\n const content = JSON.stringify(data, null, 2)\n atomicWriteImpl(filePath, content)\n}\n\n/** Atomically write YAML data to file */\nexport function atomicWriteYaml(filePath: string, data: unknown): void {\n const content = yamlStringify(data)\n atomicWriteImpl(filePath, content)\n}\n\n/** Atomically write plain text to file */\nexport function atomicWriteText(filePath: string, content: string): void {\n atomicWriteImpl(filePath, content)\n}\n","import { existsSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { atomicWriteJson } from '../shared/fs-atomic.js'\nimport type { PaneRegistry, StateFileWrapper } from '../shared/types.js'\n\nexport const SESSION_TTL_DAYS = 7\nconst REGISTRY_FILENAME = 'pane-registry.json'\n\n/**\n * Save pane registry to .curdx/pane-registry.json wrapped in StateFileWrapper.\n */\nexport async function saveSession(data: PaneRegistry, projectRoot: string): Promise<void> {\n const filePath = join(projectRoot, '.curdx', REGISTRY_FILENAME)\n const wrapper: StateFileWrapper<PaneRegistry> = {\n version: 1,\n updatedAt: new Date().toISOString(),\n data,\n }\n await atomicWriteJson(filePath, wrapper)\n}\n\n/**\n * Load pane registry from .curdx/pane-registry.json.\n * Returns null if file doesn't exist.\n */\nexport async function loadSession(projectRoot: string): Promise<PaneRegistry | null> {\n const filePath = join(projectRoot, '.curdx', REGISTRY_FILENAME)\n\n if (!existsSync(filePath)) {\n return null\n }\n\n try {\n const raw = readFileSync(filePath, 'utf-8')\n const wrapper = JSON.parse(raw) as StateFileWrapper<PaneRegistry>\n return wrapper.data\n } catch {\n return null\n }\n}\n\n/**\n * Remove provider entries older than TTL days.\n */\nexport function cleanExpiredEntries(\n registry: PaneRegistry,\n ttlDays: number = SESSION_TTL_DAYS,\n): PaneRegistry {\n const cutoff = Date.now() - ttlDays * 24 * 60 * 60 * 1000\n const cleaned: PaneRegistry = {\n ...registry,\n providers: {},\n }\n\n for (const [key, entry] of Object.entries(registry.providers)) {\n if (entry.updatedAt >= cutoff) {\n cleaned.providers[key] = entry\n }\n }\n\n return cleaned\n}\n","/** Unified state file wrapper — all JSON state files use this format */\nexport interface StateFileWrapper<T> {\n version: number\n updatedAt: string // ISO 8601\n data: T\n}\n\n/** curdx project configuration (.curdx/config.yaml) */\nexport interface CurdxConfig {\n models: string[]\n mode: 0 | 1 | 2 | 3\n verifyLevel: number\n puaEnabled: true // always true, cannot be disabled\n projectName: string\n wezterm: boolean\n tmux: boolean\n}\n\n/** Loop execution state (.curdx/loop-state.json) */\nexport interface LoopState {\n taskIndex: number\n totalTasks: number\n retryCount: number\n phase: 'idle' | 'running' | 'waiting' | 'paused' | 'complete' | 'failed'\n maxRetries: number\n planName: string\n completedTaskIds: string[]\n failedTaskIds: string[]\n lastError: string | null\n}\n\n/** Single pane registry entry */\nexport interface PaneRegistryEntry {\n paneId: string\n provider: 'claude' | 'codex' | 'gemini'\n sessionId: string\n paneTitleMarker: string\n updatedAt: number // unix timestamp\n}\n\n/** Pane registry (.curdx/pane-registry.json) */\nexport interface PaneRegistry {\n ccbSessionId: string\n workDir: string\n terminal: 'wezterm' | 'tmux' | 'none'\n updatedAt: number\n providers: Record<string, PaneRegistryEntry>\n}\n\n/** Workflow execution state (.curdx/workflow-state.json) */\nexport interface WorkflowState {\n currentStage: 'start' | 'research' | 'spec' | 'plan' | 'dev' | 'review' | 'ship' | 'quick' | 'fix' | 'tdd' | 'idle'\n tasks: WorkflowTask[]\n waveIndex: number\n startedAt: string\n}\n\n/** Individual task within a workflow */\nexport interface WorkflowTask {\n id: string\n name: string\n status: 'pending' | 'in-progress' | 'complete' | 'failed'\n wave: number\n files: string[]\n verify: string\n done: string\n commitHash: string | null\n}\n\n/** Context layer type — three-tier context architecture */\nexport type ContextLayerType = 'persistent' | 'session' | 'ephemeral'\n\n/** A single context layer with estimated token usage */\nexport interface ContextLayer {\n type: ContextLayerType\n files: string[]\n estimatedTokens: number\n}\n\n/** Context utilization snapshot */\nexport interface ContextUtilization {\n totalTokens: number\n usedTokens: number\n ratio: number // 0.0 - 1.0\n layers: ContextLayer[]\n}\n\n/** Agent context bundle — clean context prepared for a sub-agent */\nexport interface AgentContextBundle {\n taskId: string\n projectContext: string // project-context.md content\n layers: ContextLayer[]\n totalTokens: number\n createdAt: string // ISO 8601\n}\n\n/** Base error class for all curdx errors */\nexport class CurdxError extends Error {\n constructor(\n public readonly code: string,\n message: string,\n public readonly cause?: unknown,\n ) {\n super(message)\n this.name = 'CurdxError'\n }\n}\n\n/** Layer 1: Runtime errors */\nexport class RuntimeError extends CurdxError {\n constructor(code: string, message: string, cause?: unknown) {\n super(code, message, cause)\n this.name = 'RuntimeError'\n }\n}\n\n/** Layer 2: Flow engine errors */\nexport class FlowError extends CurdxError {\n constructor(code: string, message: string, cause?: unknown) {\n super(code, message, cause)\n this.name = 'FlowError'\n }\n}\n\n/** Layer 3: Context engine errors */\nexport class ContextError extends CurdxError {\n constructor(code: string, message: string, cause?: unknown) {\n super(code, message, cause)\n this.name = 'ContextError'\n }\n}\n\n/** Layer 4: Collaboration engine errors */\nexport class CollabError extends CurdxError {\n constructor(code: string, message: string, cause?: unknown) {\n super(code, message, cause)\n this.name = 'CollabError'\n }\n}\n\n/** Layer 5: Verification engine errors */\nexport class VerifyError extends CurdxError {\n constructor(code: string, message: string, cause?: unknown) {\n super(code, message, cause)\n this.name = 'VerifyError'\n }\n}\n\n/** Configuration errors */\nexport class ConfigError extends CurdxError {\n constructor(code: string, message: string, cause?: unknown) {\n super(code, message, cause)\n this.name = 'ConfigError'\n }\n}\n","import { randomBytes } from 'node:crypto'\nimport type { ParsedArgs, Provider } from './cli.js'\nimport { detectAvailableClis, validateProviders } from './detect.js'\nimport { detectTerminalBackend } from './terminal.js'\nimport type { Terminal } from './terminal.js'\nimport { WeztermTerminal } from './wezterm.js'\nimport { saveSession, loadSession, cleanExpiredEntries } from './registry.js'\nimport { RuntimeError } from '../shared/types.js'\nimport type { PaneRegistry, PaneRegistryEntry } from '../shared/types.js'\n\nexport interface LaunchResult {\n providers: string[]\n missing: string[]\n resume: boolean\n launched: boolean\n terminal: Terminal | null\n paneIds: Record<string, string>\n}\n\n/**\n * Launch the curdx orchestrator.\n * Detects available CLIs, validates providers, creates terminal panes, saves session.\n * When resume=true, attempts to restore previous session.\n */\nexport async function launch(options: ParsedArgs): Promise<LaunchResult> {\n const projectRoot = process.cwd()\n\n // Resume mode: try to restore previous session\n if (options.resume) {\n return resumeSession(options, projectRoot)\n }\n\n return freshLaunch(options, projectRoot)\n}\n\nasync function freshLaunch(options: ParsedArgs, projectRoot: string): Promise<LaunchResult> {\n const available = await detectAvailableClis()\n const { valid, missing } = validateProviders(options.providers, available)\n\n if (missing.length > 0) {\n process.stderr.write(\n `⚠️ 未检测到以下 AI CLI: ${missing.join(', ')}(已降级到可用模型)\\n`,\n )\n }\n\n if (valid.length === 0) {\n throw new RuntimeError(\n 'NO_CLI_AVAILABLE',\n '没有可用的 AI CLI 工具。请先安装 Claude Code: npm install -g @anthropic-ai/claude-code',\n )\n }\n\n const mode = valid.length >= 3 ? 3 : valid.length >= 2 ? 2 : 1\n\n // Detect and create terminal\n const backend = await detectTerminalBackend()\n let terminal: Terminal | null = null\n const paneIds: Record<string, string> = {}\n\n if (backend === 'wezterm') {\n terminal = new WeztermTerminal()\n\n const additionalProviders = valid.slice(1)\n for (let i = 0; i < additionalProviders.length; i++) {\n const provider = additionalProviders[i]\n try {\n const paneId = await terminal.createPane(provider, {\n direction: 'right',\n percent: Math.floor(100 / (additionalProviders.length - i + 1)),\n })\n paneIds[provider] = paneId\n } catch {\n process.stderr.write(`⚠️ 无法为 ${provider} 创建终端窗格\\n`)\n }\n }\n }\n\n process.stdout.write(`🚀 curdx 启动 — 模式: ${mode} 模型 (${valid.join(', ')})\\n`)\n\n if (terminal && Object.keys(paneIds).length > 0) {\n process.stdout.write(`📺 终端分屏: ${Object.entries(paneIds).map(([p, id]) => `${p}→pane:${id}`).join(', ')}\\n`)\n } else if (!terminal) {\n process.stderr.write('⚠️ 未检测到 WezTerm/tmux,运行在单窗格模式\\n')\n }\n\n // Save session to registry\n const now = Date.now()\n const registry: PaneRegistry = {\n ccbSessionId: `sess-${randomBytes(6).toString('hex')}`,\n workDir: projectRoot,\n terminal: backend ?? 'none',\n updatedAt: now,\n providers: {},\n }\n for (const [provider, paneId] of Object.entries(paneIds)) {\n registry.providers[provider] = {\n paneId,\n provider: provider as PaneRegistryEntry['provider'],\n sessionId: '',\n paneTitleMarker: `CCB-${provider}`,\n updatedAt: now,\n }\n }\n await saveSession(registry, projectRoot).catch(() => {\n // Non-fatal: session save failure doesn't block launch\n })\n\n return {\n providers: valid,\n missing,\n resume: false,\n launched: true,\n terminal,\n paneIds,\n }\n}\n\nasync function resumeSession(options: ParsedArgs, projectRoot: string): Promise<LaunchResult> {\n const saved = await loadSession(projectRoot)\n\n if (!saved) {\n process.stderr.write('⚠️ 无历史会话,将启动新会话\\n')\n return freshLaunch({ ...options, resume: false }, projectRoot)\n }\n\n const cleaned = cleanExpiredEntries(saved)\n const providerKeys = Object.keys(cleaned.providers)\n\n if (providerKeys.length === 0) {\n process.stderr.write('⚠️ 历史会话已过期,将启动新会话\\n')\n return freshLaunch({ ...options, resume: false }, projectRoot)\n }\n\n // Check if panes are still alive\n const backend = await detectTerminalBackend()\n let terminal: Terminal | null = null\n const paneIds: Record<string, string> = {}\n let aliveCount = 0\n\n if (backend === 'wezterm') {\n terminal = new WeztermTerminal()\n for (const [provider, entry] of Object.entries(cleaned.providers)) {\n const alive = await terminal.isAlive(entry.paneId)\n if (alive) {\n paneIds[provider] = entry.paneId\n aliveCount++\n }\n }\n }\n\n if (aliveCount === 0) {\n process.stderr.write('⚠️ 历史会话窗格已关闭,将启动新会话\\n')\n return freshLaunch({ ...options, resume: false }, projectRoot)\n }\n\n const allProviders = [...new Set(['claude', ...providerKeys])]\n const mode = allProviders.length >= 3 ? 3 : allProviders.length >= 2 ? 2 : 1\n process.stdout.write(`🔄 恢复会话 — 模式: ${mode} 模型 (${allProviders.join(', ')})\\n`)\n process.stdout.write(`📺 恢复窗格: ${Object.entries(paneIds).map(([p, id]) => `${p}→pane:${id}`).join(', ')}\\n`)\n\n return {\n providers: allProviders,\n missing: [],\n resume: true,\n launched: true,\n terminal,\n paneIds,\n }\n}\n","/**\n * Generate zsh completion script for curdx.\n */\nexport function generateZshCompletion(): string {\n return `#compdef curdx\n\n_curdx() {\n local -a commands providers options\n\n commands=(\n 'init:初始化 curdx 项目'\n 'completion:输出 zsh 补全脚本'\n )\n\n providers=(\n 'codex:启用 Codex CLI'\n 'gemini:启用 Gemini CLI'\n )\n\n options=(\n '-r[恢复上次终端会话]'\n '--resume[恢复上次终端会话]'\n '-V[显示版本号]'\n '--version[显示版本号]'\n '-h[显示帮助]'\n '--help[显示帮助]'\n )\n\n _arguments -C \\\\\n '1:command or provider:->first' \\\\\n '2:provider:->second' \\\\\n '*:options:->opts'\n\n case $state in\n first)\n _describe 'commands' commands\n _describe 'providers' providers\n _describe 'options' options\n ;;\n second)\n _describe 'providers' providers\n ;;\n opts)\n _describe 'options' options\n ;;\n esac\n}\n\ncompdef _curdx curdx\n`\n}\n\n/**\n * Print installation guide for zsh completion.\n */\nexport function printCompletionInstallGuide(): void {\n process.stdout.write(`\\n 💡 zsh 补全配置:\\n`)\n process.stdout.write(` 运行 \\`curdx completion > ~/.zsh/completions/_curdx\\`\\n`)\n process.stdout.write(` 然后在 .zshrc 中添加: fpath=(~/.zsh/completions $fpath)\\n`)\n}\n","import { existsSync } from 'node:fs'\nimport { join, basename } from 'node:path'\nimport { homedir } from 'node:os'\nimport { execa } from 'execa'\nimport { detectAvailableClis } from './detect.js'\nimport type { CliAvailability } from './detect.js'\nimport { detectTerminalBackend } from './terminal.js'\nimport type { TerminalBackend } from './terminal.js'\nimport { atomicWriteYaml } from '../shared/fs-atomic.js'\n\nexport interface EnvDetectionResult {\n clis: CliAvailability\n terminal: TerminalBackend | null\n claudeMem: boolean\n projectName: string\n}\n\n/**\n * Detect full environment: AI CLIs, terminal backend, claude-mem, project name.\n */\nexport async function detectEnvironment(projectRoot: string): Promise<EnvDetectionResult> {\n const [clis, terminal] = await Promise.all([\n detectAvailableClis(),\n detectTerminalBackend(),\n ])\n\n const claudeMemPath = join(homedir(), '.claude', 'plugins', 'claude-mem')\n const claudeMem = existsSync(claudeMemPath)\n const projectName = basename(projectRoot)\n\n return { clis, terminal, claudeMem, projectName }\n}\n\n/**\n * Generate .curdx/config.yaml from detection results.\n * Returns 'created' if new config was written, 'exists' if config already exists.\n */\nexport async function generateConfig(\n env: EnvDetectionResult,\n projectRoot: string,\n): Promise<'created' | 'exists'> {\n const configPath = join(projectRoot, '.curdx', 'config.yaml')\n\n if (existsSync(configPath)) {\n return 'exists'\n }\n\n const models: string[] = []\n if (env.clis.claude) models.push('claude')\n if (env.clis.codex) models.push('codex')\n if (env.clis.gemini) models.push('gemini')\n if (models.length === 0) models.push('claude') // default\n\n const mode = models.length >= 3 ? 3 : models.length >= 2 ? 2 : 1\n\n const config = {\n models,\n mode,\n verifyLevel: 5,\n puaEnabled: true,\n projectName: env.projectName,\n wezterm: env.terminal === 'wezterm',\n tmux: env.terminal === 'tmux',\n }\n\n await atomicWriteYaml(configPath, config)\n return 'created'\n}\n\n/**\n * Install claude-mem plugin if not already installed.\n * Returns 'already-installed', 'installed', or 'failed'.\n */\nexport async function installClaudeMem(\n installPath?: string,\n): Promise<'already-installed' | 'installed' | 'failed'> {\n const targetPath = installPath ?? join(homedir(), '.claude', 'plugins', 'claude-mem')\n\n if (existsSync(targetPath)) {\n return 'already-installed'\n }\n\n try {\n await execa('git', [\n 'clone',\n 'https://github.com/thedotmack/claude-mem.git',\n targetPath,\n ])\n return 'installed'\n } catch {\n return 'failed'\n }\n}\n\n/**\n * Run the complete init flow: detect → config → claude-mem → summary.\n */\nexport async function runInit(projectRoot: string): Promise<void> {\n process.stdout.write('\\n🔍 curdx init — 检测环境...\\n\\n')\n\n const env = await detectEnvironment(projectRoot)\n\n // Display detection results\n const check = (v: boolean) => (v ? '✅' : '❌')\n process.stdout.write(` AI CLI 检测:\\n`)\n process.stdout.write(` ${check(env.clis.claude)} Claude Code\\n`)\n process.stdout.write(` ${check(env.clis.codex)} Codex CLI\\n`)\n process.stdout.write(` ${check(env.clis.gemini)} Gemini CLI\\n`)\n process.stdout.write(` 终端后端:\\n`)\n process.stdout.write(` ${check(env.terminal === 'wezterm')} WezTerm\\n`)\n process.stdout.write(` ${check(env.terminal === 'tmux')} tmux\\n`)\n\n // Generate config\n const configResult = await generateConfig(env, projectRoot)\n if (configResult === 'created') {\n process.stdout.write(` 配置文件:\\n`)\n process.stdout.write(` ✅ 已生成 .curdx/config.yaml\\n`)\n } else {\n process.stdout.write(` 配置文件:\\n`)\n process.stdout.write(` ⏭️ .curdx/config.yaml 已存在(未覆盖)\\n`)\n }\n\n // Install claude-mem\n const memResult = await installClaudeMem()\n process.stdout.write(` claude-mem:\\n`)\n if (memResult === 'already-installed') {\n process.stdout.write(` ✅ 已安装\\n`)\n } else if (memResult === 'installed') {\n process.stdout.write(` ✅ 安装成功\\n`)\n } else {\n process.stderr.write(` ⚠️ 安装失败(不影响核心功能)\\n`)\n }\n\n // Completion install guide\n const { printCompletionInstallGuide } = await import('./completion.js')\n printCompletionInstallGuide()\n\n process.stdout.write(`\\n🚀 curdx 初始化完成!项目: ${env.projectName}\\n`)\n process.stdout.write(` 运行 \\`curdx\\` 开始使用\\n\\n`)\n}\n","import { Command } from 'commander'\nimport { readFileSync } from 'node:fs'\nimport { join, dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nfunction loadVersion(): string {\n // Walk up from current file to find package.json\n let dir = dirname(fileURLToPath(import.meta.url))\n for (let i = 0; i < 5; i++) {\n try {\n const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'))\n if (pkg.name === '@curdx/cli') return pkg.version\n } catch { /* continue */ }\n dir = dirname(dir)\n }\n return '0.0.0'\n}\n\nconst version = loadVersion()\n\nexport const KNOWN_PROVIDERS = ['claude', 'codex', 'gemini'] as const\nexport type Provider = (typeof KNOWN_PROVIDERS)[number]\n\nexport interface ParsedArgs {\n providers: Provider[]\n resume: boolean\n}\n\n/**\n * Parse CLI arguments into structured options.\n * Exported for testability — does not call process.exit.\n */\nexport function parseArgs(argv: string[]): ParsedArgs {\n const program = new Command()\n\n program\n .name('curdx')\n .description('AI 代码压榨机 — 多模型对抗、五级验证、PUA 鞭策')\n .version(version)\n .argument('[providers...]', '额外的 AI 模型 (codex, gemini)')\n .option('-r, --resume', '恢复上次终端会话', false)\n .exitOverride()\n .configureOutput({\n writeOut: () => {},\n writeErr: () => {},\n })\n\n program.parse(argv)\n\n const opts = program.opts<{ resume: boolean }>()\n const rawProviders = program.args as string[]\n\n // Filter to known providers, always include claude, deduplicate\n const requestedProviders = rawProviders.filter((p): p is Provider =>\n KNOWN_PROVIDERS.includes(p as Provider),\n )\n\n const providers: Provider[] = ['claude']\n for (const p of requestedProviders) {\n if (!providers.includes(p)) {\n providers.push(p)\n }\n }\n\n return {\n providers,\n resume: opts.resume,\n }\n}\n\n/**\n * Create the main CLI program for direct execution.\n * Used by bin/curdx entry point.\n */\nexport function createCli(): Command {\n const program = new Command()\n\n program\n .name('curdx')\n .description('AI 代码压榨机 — 多模型对抗、五级验证、PUA 鞭策')\n .version(version)\n .argument('[providers...]', '额外的 AI 模型 (codex, gemini)')\n .option('-r, --resume', '恢复上次终端会话', false)\n .action(async (providers: string[], opts: { resume: boolean }) => {\n const { launch } = await import('./launcher.js')\n const parsed = parseArgs(['node', 'curdx', ...(opts.resume ? ['-r'] : []), ...providers])\n await launch(parsed)\n })\n\n program\n .command('init')\n .description('初始化 curdx 项目(检测环境、生成配置、安装插件)')\n .action(async () => {\n const { runInit } = await import('./init.js')\n await runInit(process.cwd())\n })\n\n program\n .command('completion')\n .description('输出 zsh 补全脚本')\n .action(async () => {\n const { generateZshCompletion } = await import('./completion.js')\n process.stdout.write(generateZshCompletion())\n })\n\n return program\n}\n"],"mappings":"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","names":["execa","join","randomBytes","existsSync","join","execa","printCompletionInstallGuide","readFileSync","join","dirname","launch","runInit","generateZshCompletion"]}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@curdx/cli",
3
+ "version": "0.1.0",
4
+ "description": "AI 代码压榨机 — 多模型对抗、五级验证、PUA 鞭策,逼 AI 写出完美代码",
5
+ "type": "module",
6
+ "bin": {
7
+ "curdx": "./bin/curdx"
8
+ },
9
+ "scripts": {
10
+ "dev": "tsx src/runtime/cli.ts",
11
+ "build": "tsup",
12
+ "test": "vitest run",
13
+ "test:watch": "vitest",
14
+ "typecheck": "tsc --noEmit",
15
+ "lint": "eslint src/"
16
+ },
17
+ "dependencies": {
18
+ "commander": "^13.1.0",
19
+ "execa": "^9.5.2",
20
+ "fast-xml-parser": "^5.1.0",
21
+ "simple-git": "^3.27.0",
22
+ "yaml": "^2.7.0",
23
+ "zod": "^3.24.0"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^22.13.0",
27
+ "tsup": "^8.4.0",
28
+ "tsx": "^4.19.0",
29
+ "typescript": "^5.7.0",
30
+ "vitest": "^3.0.0"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "bin"
35
+ ],
36
+ "engines": {
37
+ "node": ">=18.0.0"
38
+ },
39
+ "license": "MIT"
40
+ }