This commit is contained in:
xixingde 2026-04-16 10:58:08 +08:00
commit 417a2ada18
21 changed files with 681 additions and 515 deletions

4
.gitignore vendored
View File

@ -8,6 +8,7 @@ coverage
.vscode
*.suo
*.lock
*.bun-build
*.exe
src/utils/vendor/
.tmp/
@ -17,7 +18,8 @@ src/utils/vendor/
.claude/
.codex/
.omx/
.omc/**/*.json
.omc/
security-review_result/
.claude/
.costrict/*.json

View File

@ -1,6 +1,6 @@
{
"version": "1.0.0",
"lastScanned": 1776136043107,
"lastScanned": 1776228576231,
"projectRoot": "D:\\source_codespace\\geroge\\costrict-repo\\csc",
"techStack": {
"languages": [
@ -21,16 +21,39 @@
]
}
],
"frameworks": [],
"frameworks": [
{
"name": "react",
"version": "19.2.4",
"category": "frontend"
}
],
"packageManager": "npm",
"runtime": null
},
"build": {
"buildCommand": null,
"testCommand": null,
"lintCommand": null,
"devCommand": null,
"scripts": {}
"buildCommand": "npm run build",
"testCommand": "npm test",
"lintCommand": "npm run lint",
"devCommand": "npm run dev",
"scripts": {
"prebuild": "bun run scripts/generate-skills.ts",
"build": "bun run build.ts",
"dev": "bun run scripts/dev.ts",
"dev:inspect": "bun run scripts/dev-debug.ts",
"prepublishOnly": "bun run build",
"lint": "biome lint src/",
"lint:fix": "biome lint --fix src/",
"format": "biome format --write src/",
"prepare": "git config core.hooksPath .githooks",
"test": "bun test",
"check:unused": "knip-bun",
"health": "bun run scripts/health-check.ts",
"postinstall": "node scripts/postinstall.cjs && node scripts/setup-chrome-mcp.mjs",
"docs:dev": "npx mintlify dev",
"generate:skills": "bun run scripts/generate-skills.ts",
"rcs": "bun run scripts/rcs.ts"
}
},
"conventions": {
"namingStyle": null,
@ -39,8 +62,11 @@
"fileOrganization": "type-based"
},
"structure": {
"isMonorepo": false,
"workspaces": [],
"isMonorepo": true,
"workspaces": [
"packages/*",
"packages/@ant/*"
],
"mainDirectories": [
"docs",
"scripts",
@ -54,24 +80,11 @@
},
"customNotes": [],
"directoryMap": {
"dist": {
"path": "dist",
"purpose": "Distribution/build output",
"fileCount": 525,
"lastAccessed": 1776136043029,
"keyFiles": [
"chunk-00m1myk4.js",
"chunk-01sc1b4m.js",
"chunk-04kbj0fg.js",
"chunk-05rsaxmy.js",
"chunk-06saje2v.js"
]
},
"docs": {
"path": "docs",
"purpose": "Documentation",
"fileCount": 7,
"lastAccessed": 1776136043049,
"lastAccessed": 1776228576189,
"keyFiles": [
"auto-updater.md",
"config-list.md",
@ -84,7 +97,7 @@
"path": "learn",
"purpose": null,
"fileCount": 5,
"lastAccessed": 1776136043049,
"lastAccessed": 1776228576191,
"keyFiles": [
"LEARN.md",
"phase-1-qa.md",
@ -97,14 +110,14 @@
"path": "packages",
"purpose": null,
"fileCount": 0,
"lastAccessed": 1776136043050,
"lastAccessed": 1776228576207,
"keyFiles": []
},
"scripts": {
"path": "scripts",
"purpose": "Build/utility scripts",
"fileCount": 10,
"lastAccessed": 1776136043051,
"lastAccessed": 1776228576208,
"keyFiles": [
"defines.ts",
"dev-debug.ts",
@ -117,7 +130,7 @@
"path": "src",
"purpose": "Source code",
"fileCount": 17,
"lastAccessed": 1776136043052,
"lastAccessed": 1776228576208,
"keyFiles": [
"commands.ts",
"context.ts",
@ -130,21 +143,21 @@
"path": "tests",
"purpose": "Test files",
"fileCount": 0,
"lastAccessed": 1776136043057,
"lastAccessed": 1776228576209,
"keyFiles": []
},
"vendor": {
"path": "vendor",
"purpose": "Third-party code",
"fileCount": 0,
"lastAccessed": 1776136043058,
"lastAccessed": 1776228576209,
"keyFiles": []
},
"src\\components": {
"path": "src\\components",
"purpose": "UI components",
"fileCount": 116,
"lastAccessed": 1776136043066,
"lastAccessed": 1776228576211,
"keyFiles": [
"AgentProgressLine.tsx",
"AntModelSwitchCallout.tsx",
@ -155,7 +168,7 @@
"path": "tests\\mocks",
"purpose": "Mock data",
"fileCount": 2,
"lastAccessed": 1776136043066,
"lastAccessed": 1776228576211,
"keyFiles": [
"api-responses.ts",
"file-system.ts"
@ -163,304 +176,22 @@
}
},
"hotPaths": [
{
"path": "C:\\Users\\SXF-Admin\\.claude\\projects\\D--source-codespace-geroge-costrict-repo-csc\\memory\\feedback_agent_architecture_preference.md",
"accessCount": 14,
"lastAccessed": 1775828572289,
"type": "file"
},
{
"path": "package.json",
"accessCount": 8,
"lastAccessed": 1776136487142,
"type": "file"
},
{
"path": "C:\\Users\\SXF-Admin\\.claude\\projects\\D--source-codespace-geroge-costrict-repo-csc\\memory\\project_pr15_strictspec_refactoring.md",
"accessCount": 7,
"lastAccessed": 1775828508474,
"type": "file"
},
{
"path": "README.md",
"accessCount": 7,
"lastAccessed": 1776136463895,
"type": "file"
},
{
"path": "src",
"accessCount": 5,
"lastAccessed": 1775816721051,
"accessCount": 1,
"lastAccessed": 1776228747216,
"type": "directory"
},
{
"path": "src\\tools\\AgentTool\\builtInAgents.ts",
"accessCount": 4,
"lastAccessed": 1775815092181,
"type": "file"
},
{
"path": "src\\costrict\\agents\\strictSpec.ts",
"accessCount": 4,
"lastAccessed": 1775815170149,
"type": "file"
},
{
"path": "README_EN.md",
"accessCount": 4,
"lastAccessed": 1775816609611,
"type": "file"
},
{
"path": "C:\\Users\\SXF-Admin\\.claude\\projects\\D--source-codespace-geroge-costrict-repo-csc\\memory\\user_strict_workflow_usage.md",
"accessCount": 4,
"lastAccessed": 1775828508613,
"type": "file"
},
{
"path": "C:\\Users\\SXF-Admin\\.claude\\projects\\D--source-codespace-geroge-costrict-repo-csc\\memory\\user_project_context.md",
"accessCount": 3,
"lastAccessed": 1775812854584,
"type": "file"
},
{
"path": "docs\\design\\nested-agent\\prompts\\coding.md",
"accessCount": 3,
"lastAccessed": 1775814789887,
"type": "file"
},
{
"path": "docs\\design\\nested-agent\\nested-agent-design.md",
"accessCount": 2,
"lastAccessed": 1775814652537,
"type": "file"
},
{
"path": "docs\\design\\nested-agent\\prompts\\strictPlan.md",
"accessCount": 2,
"lastAccessed": 1775814682608,
"type": "file"
},
{
"path": "docs\\design\\nested-agent\\prompts\\strictSpec.md",
"accessCount": 2,
"lastAccessed": 1775814699962,
"type": "file"
},
{
"path": "docs\\design\\nested-agent\\prompts\\requirement.md",
"accessCount": 2,
"lastAccessed": 1775814719406,
"type": "file"
},
{
"path": "docs\\design\\nested-agent\\prompts\\design.md",
"accessCount": 2,
"lastAccessed": 1775814735222,
"type": "file"
},
{
"path": "docs\\design\\nested-agent\\prompts\\task.md",
"accessCount": 2,
"lastAccessed": 1775814755929,
"type": "file"
},
{
"path": "docs\\design\\nested-agent\\prompts\\explore.md",
"accessCount": 2,
"lastAccessed": 1775814809320,
"type": "file"
},
{
"path": "docs\\design\\nested-agent\\README.md",
"accessCount": 2,
"lastAccessed": 1775814831958,
"type": "file"
},
{
"path": "src\\costrict\\agents\\strictPlan.ts",
"accessCount": 2,
"lastAccessed": 1775815210686,
"type": "file"
},
{
"path": "C:\\Users\\SXF-Admin\\.claude\\projects\\D--source-codespace-geroge-costrict-repo-csc\\memory\\feedback_validate_before_propose.md",
"path": "C:\\Users\\SXF-Admin\\.claude\\projects\\D--source-codespace-geroge-costrict-repo-csc\\memory\\project_windows_bun_command.md",
"accessCount": 1,
"lastAccessed": 1775808730612,
"lastAccessed": 1776229520309,
"type": "file"
},
{
"path": "src\\skills\\bundledSkills.ts",
"path": "C:\\Users\\SXF-Admin\\.claude\\projects\\D--source-codespace-geroge-costrict-repo-csc\\memory\\feedback_bun_install_in_session.md",
"accessCount": 1,
"lastAccessed": 1775811066190,
"type": "file"
},
{
"path": "src\\skills\\bundled\\verify.ts",
"accessCount": 1,
"lastAccessed": 1775811079662,
"type": "file"
},
{
"path": "src\\skills\\bundled\\simplify.ts",
"accessCount": 1,
"lastAccessed": 1775811079839,
"type": "file"
},
{
"path": "docs\\design\\nested-agent\\prompts\\tdd.md",
"accessCount": 1,
"lastAccessed": 1775812544524,
"type": "file"
},
{
"path": "docs\\design\\nested-agent\\prompts\\writeTask.md",
"accessCount": 1,
"lastAccessed": 1775812590059,
"type": "file"
},
{
"path": "src\\skills\\bundled\\verifyContent.ts",
"accessCount": 1,
"lastAccessed": 1775814416108,
"type": "file"
},
{
"path": "src\\skills\\bundled\\batch.ts",
"accessCount": 1,
"lastAccessed": 1775814416229,
"type": "file"
},
{
"path": "src\\skills\\bundled\\skillify.ts",
"accessCount": 1,
"lastAccessed": 1775814430451,
"type": "file"
},
{
"path": "src\\skills\\bundled\\dream.ts",
"accessCount": 1,
"lastAccessed": 1775814430500,
"type": "file"
},
{
"path": "src\\skills\\bundled\\verify\\SKILL.md",
"accessCount": 1,
"lastAccessed": 1775814445835,
"type": "file"
},
{
"path": "src\\skills\\bundled\\index.ts",
"accessCount": 1,
"lastAccessed": 1775814455161,
"type": "file"
},
{
"path": "src\\skills\\bundled\\scheduleRemoteAgents.ts",
"accessCount": 1,
"lastAccessed": 1775814455454,
"type": "file"
},
{
"path": "src\\costrict\\skill\\tdd.ts",
"accessCount": 1,
"lastAccessed": 1775814465999,
"type": "file"
},
{
"path": "src\\costrict\\skill\\strictPlan.ts",
"accessCount": 1,
"lastAccessed": 1775814466118,
"type": "file"
},
{
"path": "src\\costrict\\agents\\quickExplore.ts",
"accessCount": 1,
"lastAccessed": 1775814536214,
"type": "file"
},
{
"path": "src\\costrict\\agents\\taskPlan.ts",
"accessCount": 1,
"lastAccessed": 1775814553496,
"type": "file"
},
{
"path": "src\\costrict\\agents\\subCoding.ts",
"accessCount": 1,
"lastAccessed": 1775814553643,
"type": "file"
},
{
"path": "src\\costrict\\agents\\requirement.ts",
"accessCount": 1,
"lastAccessed": 1775814564309,
"type": "file"
},
{
"path": "src\\tools\\AgentTool\\loadAgentsDir.ts",
"accessCount": 1,
"lastAccessed": 1775815001572,
"type": "file"
},
{
"path": "docs\\design\\nested-agent\\IMPLEMENTATION_PLAN.md",
"accessCount": 1,
"lastAccessed": 1775815060388,
"type": "file"
},
{
"path": "src\\costrict\\agents\\depthGuard.ts",
"accessCount": 1,
"lastAccessed": 1775815127341,
"type": "file"
},
{
"path": "docs",
"accessCount": 1,
"lastAccessed": 1775816197727,
"type": "directory"
},
{
"path": "scripts\\dev.ts",
"accessCount": 1,
"lastAccessed": 1775816609737,
"type": "file"
},
{
"path": "src\\main.tsx",
"accessCount": 1,
"lastAccessed": 1775816623667,
"type": "file"
},
{
"path": "",
"accessCount": 1,
"lastAccessed": 1775816625229,
"type": "directory"
},
{
"path": "docs.json",
"accessCount": 1,
"lastAccessed": 1775816640615,
"type": "file"
},
{
"path": "docs\\introduction\\what-is-claude-code.mdx",
"accessCount": 1,
"lastAccessed": 1775816640638,
"type": "file"
},
{
"path": "src\\utils\\slashCommandParsing.ts",
"accessCount": 1,
"lastAccessed": 1775816662118,
"type": "file"
},
{
"path": "src\\utils\\processUserInput\\processSlashCommand.tsx",
"accessCount": 1,
"lastAccessed": 1775816662257,
"lastAccessed": 1776229520323,
"type": "file"
}
],

View File

@ -0,0 +1 @@
{"t":0,"agent":"a795097","agent_type":"unknown","event":"agent_stop","success":true}

37
CHANGELOG.md Normal file
View File

@ -0,0 +1,37 @@
# Changelog
All notable changes to this project will be documented in this file.
## [4.0.5] - 2026-04-15
### 新功能
- 工具层及 MCP 大重构
- 完成第一个 MCP Chrome 浏览器接入版本
- 添加 Langfuse 监控支持
- Langfuse 工具调用显示为嵌套结构
- Brave 作为 WebSearchTool 的备选搜索引擎
- 支持自托管 remote-control-server
- 支持Ultraplan Feature 支持高级多 Agent 规划
- 增加了upgrade/update 命令
### Agent 系统
- 新增 CoStrict agent 套件用于结构化工作流
- 新增 TDD 工作流test design / prepare / execute / run-and-fix 四个 agent
- 新增 QuickExploreAgent启用默认规划 agents
- 新增 planApply agent 工具列表
- 新增 StrictPlan 模式
### Skills
- 新增 `project-wiki` skill 自动生成技术文档
- 新增 `security-review` 内置 skill 支持代码审计
### 修复
- 修复内置 agent prompt 中工具名引用归一化
- 修复 mintlify ignore 和侧边栏
- 修复 Windows 上 `bun.exe` 命令识别问题
- 修复初次登陆的校验问题
- 修复类型问题(多处)

442
build.ts
View File

@ -120,14 +120,28 @@ const cliNode = join(outdir, 'cli-node.js')
await writeFile(cliBun, '#!/usr/bin/env bun\nimport "./cli.js"\n')
// Node.js entry needs a Bun API polyfill because Bun.build({ target: 'bun' })
// emits globalThis.Bun references (e.g. Bun.$ shell tag in computer-use-input,
// Bun.which in chunk-ys6smqg9) that crash at import time under plain Node.js.
// emits globalThis.Bun references that crash at import time under plain Node.js.
//
// Polyfill coverage:
// - Bun.which → PATH + accessSync lookup
// - Bun.hash → FNV-1a 32-bit (compatible with Bun.hash for strings)
// - Bun.$ → stub (only used by computer-use-input/darwin)
// - Bun.spawn → child_process.spawn (async, returns Subprocess-like)
// - Bun.spawnSync → child_process.spawnSync (sync, returns SpawnSyncResult)
// - Bun.file → fs.promises.readFile wrapper returning Blob-like object
// - Bun.gc → no-op (Node's V8 GC is automatic)
// - Bun.generateHeapSnapshot → v8.getHeapSnapshot()
// - Bun.embeddedFiles → [] (never present in npm package)
const NODE_BUN_POLYFILL = `#!/usr/bin/env node
// Bun API polyfill for Node.js runtime
// Bun API polyfill for Node.js runtime — makes target: 'bun' output runnable under Node.js
if (typeof globalThis.Bun === "undefined") {
const { execFileSync } = await import("child_process");
const cp = await import("child_process");
const { resolve, delimiter } = await import("path");
const { accessSync, constants: { X_OK } } = await import("fs");
const { accessSync, constants: { X_OK }, statSync } = await import("fs");
const fsp = await import("fs/promises");
// ── Bun.which ─────────────────────────────────────────────────────────
// 沿 PATH 查找可执行文件路径
function which(bin) {
const isWin = process.platform === "win32";
const pathExt = isWin ? (process.env.PATHEXT || ".EXE").split(";") : [""];
@ -139,13 +153,11 @@ if (typeof globalThis.Bun === "undefined") {
}
return null;
}
// Bun.$ is the shell template tag (e.g. $\`osascript ...\`). Only used by
// computer-use-input/darwin — stub it so the top-level destructuring
// \`var { $ } = globalThis.Bun\` doesn't crash.
function $(parts, ...args) {
throw new Error("Bun.$ shell API is not available in Node.js. Use Bun runtime for this feature.");
}
// ── Bun.hash ──────────────────────────────────────────────────────────
// FNV-1a 32-bit 散列,与 Bun.hash 行为对齐
function hash(data, seed) {
if (typeof data !== "string") return 0;
let h = ((seed || 0) ^ 0x811c9dc5) >>> 0;
for (let i = 0; i < data.length; i++) {
h ^= data.charCodeAt(i);
@ -153,7 +165,413 @@ if (typeof globalThis.Bun === "undefined") {
}
return h;
}
globalThis.Bun = { which, $, hash };
// ── Bun.$ ─────────────────────────────────────────────────────────────
// Bun shell template tag仅在 computer-use-input/darwin 中使用
// 提供 stub 使顶层解构 var { $ } = globalThis.Bun 不崩溃
function $(parts, ...args) {
throw new Error("Bun.$ shell API is not available in Node.js. Use Bun runtime for this feature.");
}
// ── Bun.spawn ─────────────────────────────────────────────────────────
// 异步子进程启动,返回与 Bun.Subprocess 兼容的对象
//
// 调用签名(本代码库实际使用的模式):
// Bun.spawn([cmd, ...args], { stdin, stdout, stderr, env, argv0 })
// Bun.spawn({ cmd: [cmd, ...args], stdin, stdout, stderr }) — 不存在,但防错
//
// 返回值需支持:
// proc.stdin → Writable (当 stdin: 'pipe' 时)
// proc.stdout → ReadableStream<Uint8Array> (当 stdout: 'pipe' 时)
// proc.stderr → ReadableStream<Uint8Array> (当 stderr: 'pipe' 时)
// proc.exited → Promise<number> (退出码)
// proc.kill() → 终止进程
// proc.pid → 进程 ID
function spawn(cmdOrOpts, opts) {
let cmd, spawnOpts;
if (Array.isArray(cmdOrOpts)) {
cmd = cmdOrOpts;
spawnOpts = opts || {};
} else {
const o = cmdOrOpts || {};
cmd = o.cmd;
spawnOpts = o;
}
if (!Array.isArray(cmd) || cmd.length === 0) {
throw new TypeError("Bun.spawn requires a non-empty command array");
}
// 解析 stdin/stdout/stderr 管道配置
const toStdio = (v) => {
if (v === "pipe") return "pipe";
if (v === "ignore") return "ignore";
if (v === "inherit") return "inherit";
return v == null ? "pipe" : "pipe";
};
const nodeOpts = {
stdio: [toStdio(spawnOpts.stdin), toStdio(spawnOpts.stdout), toStdio(spawnOpts.stderr)],
env: spawnOpts.env || process.env,
windowsHide: true,
};
// 处理 argv0Bun 用 argv0 重命名进程Node 中通过 spawn 的第一个参数传递
const executable = spawnOpts.argv0 || cmd[0];
const args = spawnOpts.argv0 ? cmd : cmd.slice(1);
const child = cp.spawn(executable, args, nodeOpts);
// 构建 Bun 兼容的 stdin 封装
let bunStdin = null;
if (spawnOpts.stdin === "pipe" || spawnOpts.stdin == null) {
bunStdin = child.stdin;
}
// 构建 Bun 兼容的 stdout/stderr 封装
// Bun.spawn 返回 ReadableStream<Uint8Array>Node 返回 Readable
// 需要转换以支持 .getReader() 和 new Response(stream).text()
function toReadableStream(nodeReadable) {
if (!nodeReadable) return null;
if (nodeReadable instanceof ReadableStream) return nodeReadable;
return new ReadableStream({
start(controller) {
nodeReadable.on("data", (chunk) => controller.enqueue(new Uint8Array(chunk)));
nodeReadable.on("end", () => controller.close());
nodeReadable.on("error", (err) => controller.error(err));
},
cancel() {
nodeReadable.destroy();
},
});
}
const bunStdout = toReadableStream(child.stdout);
const bunStderr = toReadableStream(child.stderr);
// 构建 exited Promise
const exited = new Promise((resolve) => {
child.on("close", (code) => resolve(code ?? 0));
});
return {
stdin: bunStdin,
stdout: bunStdout,
stderr: bunStderr,
exited,
pid: child.pid,
kill(sig) { child.kill(sig || "SIGTERM"); },
get exitCode() { return child.exitCode; },
};
}
// ── Bun.spawnSync ─────────────────────────────────────────────────────
// 同步子进程执行,返回与 Bun.SpawnSyncResult 兼容的对象
//
// 调用签名(本代码库实际使用的模式):
// Bun.spawnSync({ cmd: [cmd, ...args], stdin, stdout, stderr, env, timeout })
// Bun.spawnSync({ cmd: [cmd, ...args], stdin: Buffer, stdout: 'pipe' })
//
// 返回值需支持:
// result.stdout → Uint8Array | null
// result.stderr → Uint8Array | null
// result.exitCode → number
function spawnSync(optsOrCmd) {
let cmd, syncOpts;
if (optsOrCmd && typeof optsOrCmd === "object" && !Array.isArray(optsOrCmd)) {
syncOpts = optsOrCmd;
cmd = syncOpts.cmd;
} else {
throw new TypeError("Bun.spawnSync requires an options object with 'cmd' property");
}
if (!Array.isArray(cmd) || cmd.length === 0) {
throw new TypeError("Bun.spawnSync requires a non-empty cmd array");
}
const toStdio = (v) => {
if (v === "pipe") return "pipe";
if (v === "ignore") return "ignore";
if (v === "inherit") return "inherit";
return v == null ? "pipe" : "pipe";
};
const nodeOpts = {
stdio: [toStdio(syncOpts.stdin), toStdio(syncOpts.stdout), toStdio(syncOpts.stderr)],
env: syncOpts.env || process.env,
windowsHide: true,
timeout: syncOpts.timeout,
};
// 如果 stdin 是 Buffer 或 string将其作为输入传入
if (Buffer.isBuffer(syncOpts.stdin) || typeof syncOpts.stdin === "string") {
nodeOpts.input = Buffer.from(syncOpts.stdin);
nodeOpts.stdio[0] = "pipe";
}
const result = cp.spawnSync(cmd[0], cmd.slice(1), nodeOpts);
return {
stdout: result.stdout ? Uint8Array.from(Buffer.isBuffer(result.stdout) ? result.stdout : Buffer.from(result.stdout)) : null,
stderr: result.stderr ? Uint8Array.from(Buffer.isBuffer(result.stderr) ? result.stderr : Buffer.from(result.stderr)) : null,
exitCode: result.status ?? 1,
success: result.status === 0,
};
}
// ── Bun.file ──────────────────────────────────────────────────────────
// 返回类 BunFile 对象,支持 .arrayBuffer()、.text()、.json()、.exists()
// 本代码库中仅 linux.ts 使用Bun.file(path).arrayBuffer()
function bunFile(filePath) {
return {
async arrayBuffer() {
const buf = await fsp.readFile(filePath);
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
},
async text() {
return await fsp.readFile(filePath, "utf-8");
},
async json() {
return JSON.parse(await fsp.readFile(filePath, "utf-8"));
},
async exists() {
try { await fsp.access(filePath); return true; } catch { return false; }
},
get size() {
try { return statSync(filePath).size; } catch { return 0; }
},
};
}
// ── Bun.gc ────────────────────────────────────────────────────────────
// Node.js 的 V8 GC 是自动的,无需手动触发;暴露为 no-op
function gc() {}
// ── Bun.generateHeapSnapshot ──────────────────────────────────────────
// Node.js 用 v8.getHeapSnapshot() 替代
function generateHeapSnapshot() {
const v8 = require("v8");
const stream = v8.getHeapSnapshot();
const chunks = [];
for (const chunk of stream) chunks.push(chunk);
return Buffer.concat(chunks).toString("utf-8");
}
// ── Bun.embeddedFiles ─────────────────────────────────────────────────
// npm 包中不存在嵌入文件
const embeddedFiles = [];
// ── Bun.semver ────────────────────────────────────────────────────────
// Bun 内置 semver 比较,产物中通过 typeof Bun 守卫调用
// 用 semver 包的子集实现 order 和 satisfies
// semver.order(a, b): 1 if a>b, 0 if a==b, -1 if a<b
const semver = {
order(a, b) {
const pa = parseSemver(a);
const pb = parseSemver(b);
if (!pa || !pb) return 0;
const cmp = pa[0] - pb[0] || pa[1] - pb[1] || pa[2] - pb[2];
return cmp > 0 ? 1 : cmp < 0 ? -1 : 0;
},
satisfies(version, range) {
// 简化的 range 解析:支持 >=x.y.z, ^x.y.z, ~x.y.z, x.y.z, >x.y.z, <x.y.z, <=x.y.z, x.y.z - a.b.c
const pv = parseSemver(version);
if (!pv) return false;
const r = range.trim();
// 范围用 || 分隔
const ors = r.split("||").map(s => s.trim());
for (const part of ors) {
if (satisfiesRange(pv, part)) return true;
}
return false;
},
};
// 解析 x.y.z 为 [major, minor, patch],忽略 pre-release
function parseSemver(v) {
if (!v) return null;
const m = String(v).match(/(\\d+)\\.(\\d+)\\.(\\d+)/);
if (!m) return null;
return [Number(m[1]), Number(m[2]), Number(m[3])];
}
function satisfiesRange(pv, range) {
// 空范围或 * 匹配所有
if (!range || range === "*") return true;
// 空格分隔的 AND 条件
const conditions = range.split(/\\s+/).filter(Boolean);
for (const cond of conditions) {
if (!satisfiesCondition(pv, cond)) return false;
}
return true;
}
function satisfiesCondition(pv, cond) {
if (cond === "*") return true;
// ^x.y.z — 兼容 major
let m = cond.match(/^\\^(\\d+)\\.(\\d+)\\.(\\d+)/);
if (m) {
const [_, M, m2, p] = m.map(Number);
return pv[0] === M && (pv[0] > 0 ? pv[1] >= m2 || (pv[1] === m2 && pv[2] >= p) : pv[1] === m2 && pv[2] >= p) || pv[0] === M && pv[1] > m2;
}
// ~x.y.z — 兼容 major.minor
m = cond.match(/^~(>?=?(\\d+)\\.(\\d+)\\.(\\d+))/);
if (m) {
const raw = m[1];
const cm = raw.match(/(\\d+)\\.(\\d+)\\.(\\d+)/);
if (cm) {
const [_, M, m2] = cm.map(Number);
return pv[0] === M && pv[1] === m2 && pv[2] >= Number(cm[3]);
}
}
// ~x.y
m = cond.match(/^~(\\d+)\\.(\\d+)/);
if (m) {
const [_, M, m2] = m.map(Number);
return pv[0] === M && pv[1] === m2;
}
// >=x.y.z
m = cond.match(/^>=(\\d+)\\.(\\d+)\\.(\\d+)/);
if (m) {
const [_, M, m2, p] = m.map(Number);
return pv[0] > M || (pv[0] === M && pv[1] > m2) || (pv[0] === M && pv[1] === m2 && pv[2] >= p);
}
// >x.y.z
m = cond.match(/^>(\\d+)\\.(\\d+)\\.(\\d+)/);
if (m) {
const [_, M, m2, p] = m.map(Number);
return pv[0] > M || (pv[0] === M && pv[1] > m2) || (pv[0] === M && pv[1] === m2 && pv[2] > p);
}
// <=x.y.z
m = cond.match(/^<=(\\d+)\\.(\\d+)\\.(\\d+)/);
if (m) {
const [_, M, m2, p] = m.map(Number);
return pv[0] < M || (pv[0] === M && pv[1] < m2) || (pv[0] === M && pv[1] === m2 && pv[2] <= p);
}
// <x.y.z
m = cond.match(/^<(\\d+)\\.(\\d+)\\.(\\d+)/);
if (m) {
const [_, M, m2, p] = m.map(Number);
return pv[0] < M || (pv[0] === M && pv[1] < m2) || (pv[0] === M && pv[1] === m2 && pv[2] < p);
}
// 精确匹配 x.y.z
m = cond.match(/^(\\d+)\\.(\\d+)\\.(\\d+)$/);
if (m) {
const [_, M, m2, p] = m.map(Number);
return pv[0] === M && pv[1] === m2 && pv[2] === p;
}
// x.y.z - a.b.c 范围
m = cond.match(/^(\\d+\\.\\d+\\.\\d+)\\s*-\\s*(\\d+\\.\\d+\\.\\d+)$/);
if (m) {
const lo = parseSemver(m[1]);
const hi = parseSemver(m[2]);
if (!lo || !hi) return false;
const ordLo = semver.order([pv[0], pv[1], pv[2]].join("."), lo.join(".")) >= 0;
const ordHi = semver.order([pv[0], pv[1], pv[2]].join("."), hi.join(".")) <= 0;
return ordLo && ordHi;
}
// 无法识别,宽松通过
return true;
}
// ── Bun.YAML ──────────────────────────────────────────────────────────
// Bun 内置 YAML 解析器,产物中通过 typeof Bun 守卫调用
// 降级到 yaml 包(已作为依赖存在)
const YAML = {
parse(input) {
try {
const yaml = require("yaml");
return yaml.parse(input);
} catch {
throw new Error("Bun.YAML polyfill: 'yaml' package not available. Install it or use Bun runtime.");
}
},
};
// ── Bun.version ───────────────────────────────────────────────────────
// Bun 运行时版本字符串,用于 typeof 检测
const version = "1.2.0-polyfill";
// ── Bun.stringWidth ───────────────────────────────────────────────────
// 东亚宽度感知的字符串显示宽度,产物中有 typeof 守卫降级
// 提供基础实现,按 CJK 字符双宽计算
function stringWidth(str) {
let w = 0;
for (const ch of str) {
const code = ch.codePointAt(0);
if (code >= 0x1100 && (
code <= 0x115F ||
code === 0x2329 || code === 0x232A ||
(code >= 0x2E80 && code <= 0xA4CF && code !== 0x303F) ||
(code >= 0xAC00 && code <= 0xD7A3) ||
(code >= 0xF900 && code <= 0xFAFF) ||
(code >= 0xFE10 && code <= 0xFE19) ||
(code >= 0xFE30 && code <= 0xFE6F) ||
(code >= 0xFF01 && code <= 0xFF60) ||
(code >= 0xFFE0 && code <= 0xFFE6) ||
(code >= 0x1F300 && code <= 0x1F9FF) ||
(code >= 0x20000 && code <= 0x2FFFD) ||
(code >= 0x30000 && code <= 0x3FFFD)
)) {
w += 2;
} else if (code >= 0x20) {
w += 1;
}
}
return w;
}
// ── Bun.wrapAnsi ──────────────────────────────────────────────────────
// ANSI 转义序列感知的自动换行,产物中有 typeof 守卫降级到 null
// 提供 stub 即可
const wrapAnsi = null;
// ── Bun.listen ────────────────────────────────────────────────────────
// Daemon 模式专用 TCP 监听feature-gated (DAEMON)
// Node.js 中用 net.Server 替代
function listen(opts) {
const net = require("net");
const server = net.createServer((socket) => {
if (opts.socket && opts.socket.data) {
socket.data = { ...opts.socket.data() };
}
if (opts.socket && opts.socket.open) {
opts.socket.open(socket);
}
socket.on("data", (data) => {
if (opts.socket && opts.socket.data) {
opts.socket.data(socket, data);
}
});
socket.on("close", () => {
if (opts.socket && opts.socket.close) {
opts.socket.close(socket);
}
});
socket.on("error", () => {});
});
server.listen(opts.port || 0, opts.hostname || "0.0.0.0", () => {
const addr = server.address();
server.port = addr.port;
});
return server;
}
globalThis.Bun = {
which,
$,
hash,
spawn,
spawnSync,
file: bunFile,
gc,
generateHeapSnapshot,
embeddedFiles,
semver,
YAML,
version,
stringWidth,
wrapAnsi,
listen,
};
}
import "./cli.js"
`

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

@ -16,7 +16,7 @@ Claude Code 拥有一套复杂的多策略自动更新系统,支持三种安
| `npm-global` | `npm install -g` / `bun install -g` | 是(静默) |
| `npm-local` | `npm install``~/.claude/local/` | 是(静默) |
| `package-manager` | 显示通知,附带对应操作系统的升级命令 | 否(仅通知) |
| `development` | 不适用 — 执行 `claude update` 时报错 | 不适用 |
| `development` | 不适用 — 执行 `csc update` 时报错 | 不适用 |
### 策略路由
@ -95,7 +95,7 @@ void assertMinVersion();
## 手动 CLI 命令
### `claude update` / `claude upgrade`
### `csc update` / `claude upgrade`
**文件**: `src/cli/update.ts`
@ -258,7 +258,7 @@ React hook `useUpdateNotification(updatedVersion)` — 确保每次 semver 变
| 文件 | 职责 |
|---|---|
| `src/utils/autoUpdater.ts` | 核心逻辑版本检查、npm 安装、文件锁、最低/最高版本门控 |
| `src/cli/update.ts` | `claude update` 命令处理 |
| `src/cli/update.ts` | `csc update` 命令处理 |
| `src/utils/nativeInstaller/installer.ts` | 原生二进制安装器:下载、版本管理、符号链接、清理 |
| `src/utils/nativeInstaller/download.ts` | 从 GCS/Artifactory 下载二进制文件并校验 |
| `src/utils/localInstaller.ts` | 本地安装器(`~/.claude/local/`)基于 npm |
@ -308,5 +308,5 @@ REPL 运行中(每 30 分钟)
└── 显示 "Run: brew upgrade ..."(不自动安装)
手动操作
└── claude update → 完整诊断 + 安装编排
└── csc update → 完整诊断 + 安装编排
```

View File

@ -294,7 +294,7 @@ action(async (prompt, options) => {
| `claude setup-token` | 4267 | 设置长期认证 token |
| `claude agents` | 4278 | 列出已配置的 agents |
| `claude doctor` | 4346 | 健康检查 |
| `claude update` | 4362 | 检查更新 |
| `csc update` | 4362 | 检查更新 |
| `claude install` | 4394 | 安装原生构建 |
| `claude log` | 4411 | 查看对话日志(内部) |
| `claude completion` | 4491 | Shell 自动补全 |

View File

@ -1,8 +1,8 @@
{
"name": "@costrict/csc",
"version": "4.0.4",
"version": "4.0.5-3",
"description": "Reverse-engineered Anthropic Claude Code CLI — interactive AI coding assistant in the terminal",
"description": "CSC — 多模型终端 AI 编程助手,支持 Anthropic/OpenAI/Gemini/Grok 等多种 API 端点",
"type": "module",
"author": "claude-code-best <claude-code-best@proton.me>",
"repository": {
@ -14,21 +14,26 @@
"url": "https://github.com/y574444354/csc/issues"
},
"keywords": [
"claude",
"anthropic",
"csc",
"costrict",
"cli",
"ai",
"coding-assistant",
"terminal",
"repl"
"repl",
"multi-model",
"anthropic",
"openai",
"gemini",
"grok"
],
"engines": {
"bun": ">=1.2.0"
"node": ">=18.0.0"
},
"bin": {
"csc": "dist/cli.js",
"csc": "dist/cli-node.js",
"csc-bun": "dist/cli-bun.js",
"claude-code-best": "dist/cli.js"
"claude-code-best": "dist/cli-node.js"
},
"workspaces": [
"packages/*",
@ -58,7 +63,9 @@
"rcs": "bun run scripts/rcs.ts"
},
"dependencies": {
"@claude-code-best/mcp-chrome-bridge": "^2.0.7"
"@claude-code-best/mcp-chrome-bridge": "^2.0.7",
"ws": "^8.20.0",
"yaml": "^2.8.3"
},
"devDependencies": {
"@types/he": "^1.2.3",
@ -187,9 +194,7 @@
"vscode-languageserver-protocol": "^3.17.5",
"vscode-languageserver-types": "^3.17.5",
"wrap-ansi": "^10.0.0",
"ws": "^8.20.0",
"xss": "^1.0.15",
"yaml": "^2.8.3",
"zod": "^4.3.6"
}
}

View File

@ -7,12 +7,12 @@
*/
export function getMacroDefines(): Record<string, string> {
return {
"MACRO.VERSION": JSON.stringify("4.0.4"),
"MACRO.VERSION": JSON.stringify("4.0.5.1"),
"MACRO.BUILD_TIME": JSON.stringify(new Date().toISOString()),
"MACRO.FEEDBACK_CHANNEL": JSON.stringify(""),
"MACRO.ISSUES_EXPLAINER": JSON.stringify(""),
"MACRO.NATIVE_PACKAGE_URL": JSON.stringify(""),
"MACRO.PACKAGE_URL": JSON.stringify(""),
"MACRO.PACKAGE_URL": JSON.stringify("@costrict/csc"),
"MACRO.VERSION_CHANGELOG": JSON.stringify(""),
};
}

View File

@ -3,6 +3,9 @@
* Downloads builtin skills from their source repositories and generates
* src/costrict/skill/builtin.ts with all skill files embedded as string constants.
*
* Uses git SSH transport (git ls-remote + git clone).
* Compares remote commit SHA with cached version and skips download if unchanged.
*
* Usage: bun run scripts/generate-skills.ts
*/
@ -17,130 +20,18 @@ const __dirname = path.dirname(__filename)
const bundledSkillsDir = path.resolve(__dirname, '../.tmp/skills')
const builtinTsFile = path.resolve(__dirname, '../src/costrict/skills/builtin.ts')
const BUILTIN_SKILLS = {
type SkillConfig = {
repo: string
branch: string
subdir: string
}
const BUILTIN_SKILLS: Record<string, SkillConfig> = {
'security-review': {
repo: 'zgsm-ai/security-review-skill',
branch: 'main',
subdir: 'security-review',
},
} as const
type Index = {
skills: Array<{
name: string
description: string
files: string[]
}>
}
async function fetchCommitSha(repo: string, branch: string): Promise<string | null> {
const apiUrl = `https://api.github.com/repos/${repo}/commits/${branch}`
try {
const response = await fetch(apiUrl, {
headers: {
'User-Agent': 'csc-build',
Accept: 'application/vnd.github.v3+json',
},
})
if (!response.ok) {
console.warn(` ⚠ Could not fetch commit SHA from GitHub API: ${response.status}`)
return null
}
const data = (await response.json()) as { sha?: string }
return data.sha ?? null
} catch (err) {
console.warn(` ⚠ Failed to fetch commit SHA: ${err}`)
return null
}
}
async function fetchIndex(repo: string, branch: string): Promise<Index | null> {
const indexUrl = `https://raw.githubusercontent.com/${repo}/${branch}/index.json`
const response = await fetch(indexUrl)
if (!response.ok) {
throw new Error(`Failed to fetch index: ${indexUrl} (${response.status})`)
}
return response.json() as Promise<Index>
}
async function fetchFile(url: string): Promise<string> {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`Failed to fetch file: ${url} (${response.status})`)
}
return response.text()
}
async function downloadSkill(
name: string,
config: { repo: string; branch: string; subdir: string },
): Promise<{ name: string; commitSha: string | null } | null> {
const { repo, branch, subdir } = config
console.log(`\n📦 Downloading skill: ${name}`)
console.log(` From: https://github.com/${repo}`)
console.log(` Branch: ${branch}`)
// Try HTTP first
try {
return await downloadSkillHttp(name, config)
} catch (httpErr) {
console.warn(` ⚠ HTTP download failed (${httpErr}), trying git clone...`)
return await downloadSkillGit(name, config)
}
}
async function downloadSkillHttp(
name: string,
config: { repo: string; branch: string; subdir: string },
): Promise<{ name: string; commitSha: string | null } | null> {
const { repo, branch, subdir } = config
const commitSha = await fetchCommitSha(repo, branch)
if (commitSha) {
console.log(` Commit: ${commitSha.slice(0, 7)}`)
}
const index = await fetchIndex(repo, branch)
if (!index?.skills?.length) {
throw new Error(`Invalid index for skill: ${name}`)
}
const skill = index.skills.find(s => s.name === name)
if (!skill) {
throw new Error(`Skill "${name}" not found in index`)
}
console.log(` Found ${skill.files.length} files to download`)
const skillOutputDir = path.join(bundledSkillsDir, name)
await fs.mkdir(skillOutputDir, { recursive: true })
const pathPrefix = subdir ? `${subdir}/` : ''
for (const file of skill.files) {
const url = `https://raw.githubusercontent.com/${repo}/${branch}/${pathPrefix}${file}`
const targetPath = path.join(skillOutputDir, file)
await fs.mkdir(path.dirname(targetPath), { recursive: true })
try {
const content = await fetchFile(url)
await fs.writeFile(targetPath, content, 'utf-8')
console.log(`${file}`)
} catch (err) {
console.warn(` ✗ Failed to download ${file}: ${err}`)
}
}
const skillMdPath = path.join(skillOutputDir, 'SKILL.md')
try {
await fs.access(skillMdPath)
} catch {
throw new Error(`Skill "${name}" missing SKILL.md`)
}
console.log(` ✓ Skill ${name} downloaded successfully`)
return { name, commitSha }
}
function git(...args: string[]): { ok: boolean; stdout: string; stderr: string } {
@ -152,16 +43,77 @@ function git(...args: string[]): { ok: boolean; stdout: string; stderr: string }
}
}
async function downloadSkillGit(
function getCloneUrl(repo: string): string {
return `git@github.com:${repo}.git`
}
/**
* Get the latest commit SHA for a branch via `git ls-remote`.
* No clone needed lightweight remote query over SSH.
*/
function lsRemoteSha(repo: string, branch: string): string | null {
const cloneUrl = getCloneUrl(repo)
const ref = `refs/heads/${branch}`
const result = git('ls-remote', '--heads', cloneUrl, ref)
if (!result.ok || !result.stdout) {
return null
}
// Output format: "<sha>\t<ref>"
const sha = result.stdout.split('\t')[0] ?? ''
return sha.length >= 40 ? sha : null
}
/**
* Read the cached commit SHA from the generated builtin.ts file.
*/
async function readCachedSha(skillName: string): Promise<string | null> {
try {
const content = await fs.readFile(builtinTsFile, 'utf-8')
const regex = new RegExp(
`^\\s*${JSON.stringify(skillName)}:\\s*"([a-f0-9]{40})"`,
'm',
)
const match = content.match(regex)
return match ? match[1] : null
} catch {
return null
}
}
async function downloadSkill(
name: string,
config: { repo: string; branch: string; subdir: string },
config: SkillConfig,
): Promise<{ name: string; commitSha: string | null } | null> {
const { repo, branch, subdir } = config
const cloneUrl = `https://github.com/${repo}.git`
const cloneDir = path.join(bundledSkillsDir, `.clone-${name}`)
const skillOutputDir = path.join(bundledSkillsDir, name)
const cloneUrl = getCloneUrl(repo)
console.log(` git clone ${cloneUrl}`)
console.log(`\n📦 Skill: ${name}`)
console.log(` From: ${cloneUrl}`)
console.log(` Branch: ${branch}`)
// Step 1: Get remote commit SHA via git ls-remote (no clone)
const remoteSha = lsRemoteSha(repo, branch)
if (!remoteSha) {
throw new Error(`git ls-remote failed for ${cloneUrl} (branch: ${branch})`)
}
console.log(` Remote commit: ${remoteSha.slice(0, 7)}`)
// Step 2: Compare with cached SHA — skip only if SHA matches AND cached files exist
const cachedSha = await readCachedSha(name)
const skillOutputDir = path.join(bundledSkillsDir, name)
const hasCachedFiles = (await walk(skillOutputDir)).length > 0
if (cachedSha && cachedSha === remoteSha && hasCachedFiles) {
console.log(` ✓ Cached version matches remote, skipping download`)
return { name, commitSha: remoteSha }
}
if (cachedSha) {
console.log(` Cached: ${cachedSha.slice(0, 7)} → Remote: ${remoteSha.slice(0, 7)}, updating...`)
}
// Step 3: Clone and extract files
const cloneDir = path.join(bundledSkillsDir, `.clone-${name}`)
console.log(` git clone --depth 1 ${cloneUrl}`)
await fs.rm(cloneDir, { recursive: true, force: true })
@ -170,12 +122,6 @@ async function downloadSkillGit(
throw new Error(`git clone failed: ${cloneResult.stderr}`)
}
const shaResult = git('-C', cloneDir, 'rev-parse', 'HEAD')
const commitSha = shaResult.ok ? shaResult.stdout : null
if (commitSha) {
console.log(` Commit: ${commitSha.slice(0, 7)}`)
}
const srcDir = subdir ? path.join(cloneDir, subdir) : cloneDir
await fs.rm(skillOutputDir, { recursive: true, force: true })
await fs.cp(srcDir, skillOutputDir, { recursive: true })
@ -190,8 +136,8 @@ async function downloadSkillGit(
}
const fileCount = (await walk(skillOutputDir)).length
console.log(`${fileCount} files copied via git clone`)
return { name, commitSha }
console.log(`${fileCount} files copied`)
return { name, commitSha: remoteSha }
}
async function walk(dir: string, base = ''): Promise<string[]> {
@ -272,7 +218,6 @@ async function main() {
const result = await downloadSkill(name, config)
if (result) results.push(result)
} catch (err) {
// Fall back to cached files in .tmp/skills/ if download fails
const skillDir = path.join(bundledSkillsDir, name)
const cached = await walk(skillDir)
if (cached.length > 0) {

View File

@ -421,7 +421,7 @@ export async function initReplBridge(
`[bridge:repl] Skipping: ${versionError}`,
true,
)
onStateChange?.('failed', 'run `claude update` to upgrade')
onStateChange?.('failed', 'run `csc update` to upgrade')
return null
}
logForDebugging(
@ -462,7 +462,7 @@ export async function initReplBridge(
const versionError = checkBridgeMinVersion()
if (versionError) {
logBridgeSkip('version_too_old', `[bridge:repl] Skipping: ${versionError}`)
onStateChange?.('failed', 'run `claude update` to upgrade')
onStateChange?.('failed', 'run `csc update` to upgrade')
return null
}

View File

@ -5051,7 +5051,7 @@ async function loadInitialMessages(
)
if (!parsedSessionId) {
let errorMessage =
'Error: --resume requires a valid session ID when used with --print. Usage: claude -p --resume <session-id>'
'Error: --resume requires a valid session ID when used with --print. Usage: csc -p --resume <session-id>'
if (typeof options.resume === 'string') {
errorMessage += `. Session IDs must be in UUID format (e.g., 550e8400-e29b-41d4-a716-446655440000). Provided value "${options.resume}" is not a valid UUID`
}

View File

@ -50,7 +50,7 @@ const agentsPlatform =
? require('./commands/agents-platform/index.js').default
: null
/* eslint-enable @typescript-eslint/no-require-imports */
import securityReview from './commands/security-review.js'
import bughunter from './commands/bughunter/index.js'
import terminalSetup from './commands/terminalSetup/index.js'
import usage from './commands/usage/index.js'
@ -341,7 +341,6 @@ const COMMANDS = memoize((): Command[] => [
review,
ultrareview,
rewind,
securityReview,
terminalSetup,
upgrade,
extraUsage,

View File

@ -87,12 +87,12 @@ export function registerMcpAddCommand(mcp: Command): void {
if (!name) {
cliError(
'Error: Server name is required.\n' +
'Usage: claude mcp add <name> <command> [args...]',
'Usage: csc mcp add <name> <command> [args...]',
)
} else if (!actualCommand) {
cliError(
'Error: Command is required when server name is provided.\n' +
'Usage: claude mcp add <name> <command> [args...]',
'Usage: csc mcp add <name> <command> [args...]',
)
}

View File

@ -124,6 +124,22 @@ export function createCoStrictFetch() {
return response
}
costrictFetch.preconnect = fetch.preconnect.bind(fetch)
// Bun 原生支持 fetch.preconnect共享连接池预热
// Node.js 没有 preconnect API降级为 net.createConnection 做 TCP 预热
if (typeof fetch.preconnect === 'function') {
costrictFetch.preconnect = fetch.preconnect.bind(fetch)
} else {
costrictFetch.preconnect = (url: string | URL) => {
try {
const { hostname, port } = new URL(typeof url === 'string' ? url : url.toString())
const { createConnection } = require('node:net') as typeof import('node:net')
const sock = createConnection(Number(port) || 443, hostname, () => sock.destroy())
sock.setTimeout(3000, () => sock.destroy())
sock.on('error', () => sock.destroy())
} catch {
// 预连接失败不影响正常流程
}
}
}
return costrictFetch
}

View File

@ -6,10 +6,10 @@ export function registerCodeReviewSecuritySkill(): void {
const skillMd = files['SKILL.md'] ?? ''
registerBundledSkill({
name: 'strict-security-review',
description: 'Systematic code security audit — identifies vulnerabilities like SQL injection, XSS, command injection, SSRF, etc.',
name: 'security-review',
description: 'CoStrict Security — identifies code vulnerabilities including SQL injection, XSS, command injection, SSRF, path traversal, deserialization, and business logic flaws',
whenToUse:
'Use when the user requests a code audit, security audit, or vulnerability scan; or mentions /code-review-security, /audit, or wants a security check before deployment',
'Use when the user requests a code audit, security audit, vulnerability scan, or wants to review vulnerabilities before deployment. Also triggers on /security-review.',
userInvocable: true,
disableModelInvocation: true,
files,

View File

@ -4937,7 +4937,7 @@ async function run(): Promise<CommanderCommand> {
if (!isRemoteTuiEnabled && !hasInitialPrompt) {
return await exitWithError(
root,
'Error: --remote requires a description.\nUsage: claude --remote "your task description"',
'Error: --remote requires a description.\nUsage: csc --remote "your task description"',
() => gracefulShutdown(1),
);
}
@ -6479,7 +6479,7 @@ async function run(): Promise<CommanderCommand> {
// (e.g. `--debug assistant`) and the position-0 predicate
// didn't match. Print usage like the ssh stub does.
process.stderr.write(
"Usage: claude assistant [sessionId]\n\n" +
"Usage: csc assistant [sessionId]\n\n" +
"Attach the REPL as a viewer client to a running bridge session.\n" +
"Omit sessionId to discover and pick from available sessions.\n",
);
@ -6487,6 +6487,16 @@ async function run(): Promise<CommanderCommand> {
});
}
// Update command - check and install updates
program
.command('update')
.alias('upgrade')
.description('Check for updates and install the latest version')
.action(async () => {
const { update } = await import('./cli/update.js')
await update()
})
// Doctor command - check installation health
program
.command('doctor')

View File

@ -61,7 +61,7 @@ export type MaxVersionConfig = {
*
* Versioning approach:
* 1. For version requirements/compatibility (assertMinVersion), we use semver comparison that ignores build metadata
* 2. For updates ('claude update'), we use exact string comparison to detect any change, including SHA
* 2. For updates ('csc update'), we use exact string comparison to detect any change, including SHA
* - This ensures users always get the latest build, even when only the SHA changes
* - The UI clearly shows both versions including build metadata
*
@ -87,7 +87,7 @@ It looks like your version of CoStrict (${MACRO.VERSION}) needs an update.
A newer version (${versionConfig.minVersion} or higher) is required to continue.
To update, please run:
claude update
csc update
This will ensure you have access to the latest features and improvements.
`)
@ -488,7 +488,7 @@ This configuration is not supported for updates.
To fix this issue:
1. Install Node.js within your Linux distribution: e.g. sudo apt install nodejs npm
2. Make sure Linux NPM is in your PATH before the Windows version
3. Try updating again with 'claude update'
3. Try updating again with 'csc update'
`)
return 'install_failed'
}

View File

@ -135,7 +135,7 @@ export async function setupShellCompletion(theme: ThemeName): Promise<string> {
/**
* Regenerate cached shell completion scripts in ~/.claude/.
* Called after `claude update` so completions stay in sync with the new binary.
* Called after `csc update` so completions stay in sync with the new binary.
*/
export async function regenerateCompletionCache(): Promise<void> {
const shell = detectShell()

View File

@ -137,7 +137,7 @@ function printResumeHint(): void {
) {
try {
const sessionId = getSessionId()
// Don't show resume hint if no session file exists (e.g., subcommands like `claude update`)
// Don't show resume hint if no session file exists (e.g., subcommands like `csc update`)
if (!sessionIdExists(sessionId)) {
return
}