Merge branch 'main' of https://github.com/y574444354/csc
This commit is contained in:
commit
417a2ada18
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
{"t":0,"agent":"a795097","agent_type":"unknown","event":"agent_stop","success":true}
|
||||
37
CHANGELOG.md
Normal file
37
CHANGELOG.md
Normal 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
442
build.ts
|
|
@ -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,
|
||||
};
|
||||
|
||||
// 处理 argv0:Bun 用 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 |
|
|
@ -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 → 完整诊断 + 安装编排
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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 自动补全 |
|
||||
|
|
|
|||
27
package.json
27
package.json
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(""),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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...]',
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
14
src/main.tsx
14
src/main.tsx
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user