mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-03 04:17:54 +00:00
feat: set up quality gates for CCS TypeScript package
- Migrate package manager from npm to bun - Add ESLint configuration with TypeScript support - Add Prettier configuration and .prettierignore - Format all TypeScript source files - Update CLAUDE.md with bun instructions - Verify 39 tests passing
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
dist/
|
||||
node_modules/
|
||||
*.md
|
||||
*.json
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100,
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "always"
|
||||
}
|
||||
@@ -13,47 +13,59 @@ CLI wrapper for instant switching between multiple Claude accounts and alternati
|
||||
- **DRY**: One source of truth (config.json)
|
||||
- **CLI-First**: All features must have CLI interface
|
||||
|
||||
## TypeScript Quality Gates (CORE PURPOSE)
|
||||
|
||||
**The npm package is 100% TypeScript. Quality gates MUST pass before publish.**
|
||||
|
||||
**Package Manager: bun (preferred)** - 10-25x faster than npm
|
||||
```bash
|
||||
bun install # Install dependencies (creates bun.lockb)
|
||||
bun run build # Compile src/ → dist/
|
||||
bun run validate # Full validation: typecheck + lint + format + test
|
||||
```
|
||||
|
||||
**Quality gate scripts:**
|
||||
```bash
|
||||
bun run typecheck # Type-check without emit (tsc --noEmit)
|
||||
bun run lint # ESLint TypeScript rules
|
||||
bun run lint:fix # Auto-fix lint issues
|
||||
bun run format # Prettier formatting (write)
|
||||
bun run format:check # Prettier check (CI)
|
||||
bun run test # Build + run all tests
|
||||
```
|
||||
|
||||
**Automatic enforcement:**
|
||||
- `prepublishOnly` runs `validate` before `npm publish`
|
||||
- `prepack` runs `validate` before `npm pack`
|
||||
- CI/CD should run `bun run validate` on every PR
|
||||
|
||||
**File structure:**
|
||||
- `src/` - TypeScript source (development)
|
||||
- `dist/` - Compiled JavaScript (production, npm package)
|
||||
- `lib/` - Native shell scripts (bash, PowerShell)
|
||||
|
||||
**Linting rules (eslint.config.mjs):**
|
||||
- `no-unused-vars` - warn (upgrade to error incrementally)
|
||||
- `no-explicit-any` - warn (upgrade to error incrementally)
|
||||
- `no-non-null-assertion` - warn
|
||||
- `prefer-const`, `no-var`, `eqeqeq` - error
|
||||
|
||||
**Type safety rules:**
|
||||
- Avoid `any` types - use proper typing or `unknown`
|
||||
- Avoid `@ts-ignore` - fix the type error properly
|
||||
- Strict mode enabled in tsconfig.json
|
||||
|
||||
## Critical Constraints (NEVER VIOLATE)
|
||||
|
||||
1. **NO EMOJIS** - ASCII only: [OK], [!], [X], [i]
|
||||
2. **TTY-aware colors** - Respect NO_COLOR env var
|
||||
3. **Non-invasive** - NEVER modify `~/.claude/settings.json`
|
||||
4. **Cross-platform parity** - bash/PowerShell/Node.js must behave identically
|
||||
5. **CLI documentation** - ALL changes MUST update `--help` in bin/ccs.js, lib/ccs, lib/ccs.ps1
|
||||
5. **CLI documentation** - ALL changes MUST update `--help` in src/ccs.ts, lib/ccs, lib/ccs.ps1
|
||||
6. **Idempotent** - All install operations safe to run multiple times
|
||||
|
||||
## Key Technical Details
|
||||
|
||||
### GLMT Implementation Notes
|
||||
|
||||
**[!] GLMT only in Node.js version** (`bin/ccs.js`). Native shell versions don't support GLMT (requires HTTP server).
|
||||
|
||||
**Critical files when working on GLMT**:
|
||||
- `bin/glmt/glmt-proxy.js`: HTTP proxy server with streaming + auto-fallback
|
||||
- `bin/glmt/glmt-transformer.js`: Format conversion + delta handling + tool transformation
|
||||
- `bin/glmt/locale-enforcer.js`: Enforces English output
|
||||
- `bin/glmt/reasoning-enforcer.js`: Injects explicit reasoning instructions (hybrid approach)
|
||||
- `bin/glmt/sse-parser.js`: SSE stream parser
|
||||
- `bin/glmt/delta-accumulator.js`: State tracking for streaming + tool calls
|
||||
- `tests/unit/glmt/glmt-transformer.test.js`: Unit tests (35 tests passing)
|
||||
- `tests/unit/glmt/reasoning-enforcer.test.js`: ReasoningEnforcer unit tests (15 tests passing)
|
||||
|
||||
**Reasoning control mechanisms (hybrid approach)**:
|
||||
- **Keywords**: `think`, `think hard`, `think harder`, `ultrathink`
|
||||
- **Tags**: `<Thinking:On|Off>`, `<Effort:Low|Medium|High>`
|
||||
- **Precedence**: CLI parameter > message tags > keywords
|
||||
- **Hybrid mode**: Uses BOTH API parameters (`reasoning: true`) AND prompt injection
|
||||
- API params: Native Z.AI support (deterministic, zero overhead)
|
||||
- Prompt injection: Explicit format instructions using `<reasoning_content>` tags
|
||||
- ReasoningEnforcer has 4 effort-aware prompts (low/medium/high/max)
|
||||
- **Enabled by default** for all GLMT usage (always active)
|
||||
|
||||
**Security limits** (DoS protection):
|
||||
- SSE buffer: 1MB max
|
||||
- Content buffers: 10MB max per block
|
||||
- Content blocks: 100 max per message
|
||||
- Request timeout: 120s
|
||||
|
||||
### Profile Mechanisms
|
||||
|
||||
**Settings-based**: `--settings` flag → GLM, GLMT, Kimi, default
|
||||
@@ -75,8 +87,11 @@ Windows fallback: Copies if symlinks unavailable
|
||||
- PowerShell 5.1+, `$ErrorActionPreference = "Stop"`
|
||||
- Native JSON only, no external dependencies
|
||||
|
||||
### Node.js (bin/ccs.js)
|
||||
- Node.js 14+, `child_process.spawn`, handle SIGINT/SIGTERM
|
||||
### TypeScript/Node.js (src/*.ts → dist/*.js)
|
||||
- Node.js 14+, Bun 1.0+, TypeScript 5.3, strict mode
|
||||
- `child_process.spawn`, handle SIGINT/SIGTERM
|
||||
- Run `bun run lint && bun run typecheck` before committing
|
||||
- Format with `bun run format` if needed
|
||||
|
||||
### Terminal Output (ENFORCE)
|
||||
- ASCII only: [OK], [!], [X], [i] (NO emojis)
|
||||
@@ -107,7 +122,7 @@ rm -rf ~/.ccs # Clean environment
|
||||
### New Feature Checklist
|
||||
1. Verify YAGNI/KISS/DRY alignment - reject if doesn't align
|
||||
2. Implement in bash + PowerShell + Node.js (all three)
|
||||
3. **REQUIRED**: Update `--help` in bin/ccs.js, lib/ccs, lib/ccs.ps1
|
||||
3. **REQUIRED**: Update `--help` in src/ccs.ts, lib/ccs, lib/ccs.ps1
|
||||
4. Test on macOS/Linux/Windows
|
||||
5. Add test cases to tests/edge-cases.*
|
||||
6. Update README.md if user-facing
|
||||
@@ -128,8 +143,9 @@ Code standards:
|
||||
- [ ] ASCII only (NO emojis)
|
||||
- [ ] TTY colors disabled when piped
|
||||
- [ ] NO_COLOR respected
|
||||
- [ ] `--help` updated in bin/ccs.js, lib/ccs, lib/ccs.ps1
|
||||
- [ ] `--help` updated in src/ccs.ts, lib/ccs, lib/ccs.ps1
|
||||
- [ ] `--help` consistent across all three
|
||||
- [ ] `bun run validate` passes (typecheck + lint + format + tests)
|
||||
|
||||
Install/behavior:
|
||||
- [ ] Idempotent install
|
||||
@@ -159,31 +175,6 @@ All env values MUST be strings (not booleans/objects) to prevent PowerShell cras
|
||||
}
|
||||
```
|
||||
|
||||
## GLMT Debugging (Common Issues)
|
||||
|
||||
### Debug Mode
|
||||
```bash
|
||||
export CCS_DEBUG=1
|
||||
ccs glmt --verbose "test" # File logs: ~/.ccs/logs/
|
||||
```
|
||||
|
||||
### Known Issues & Fixes
|
||||
|
||||
**No Thinking Blocks**:
|
||||
- Check Z.AI API plan supports reasoning_content
|
||||
- Test with keywords: `ccs glmt "think about the solution"`
|
||||
|
||||
**Empty Thinking Blocks** (v3.5.1+):
|
||||
- Fixed: Signature timing race (see tests/unit/glmt/test-thinking-signature-race.js)
|
||||
|
||||
**Tool Execution Issues**:
|
||||
- MCP tools outputting XML: Fixed in v3.5
|
||||
- Debug with `CCS_DEBUG=1` to inspect transformation
|
||||
|
||||
**Streaming Issues**:
|
||||
- Buffer errors: Hit DoS limits (1MB SSE, 10MB content)
|
||||
- Auto-fallback to buffered mode on error
|
||||
|
||||
## Error Handling Principles
|
||||
|
||||
- Validate early, fail fast with clear messages
|
||||
|
||||
@@ -0,0 +1,407 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "@kaitranntt/ccs",
|
||||
"dependencies": {
|
||||
"cli-table3": "^0.6.5",
|
||||
"ora": "^9.0.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.19.25",
|
||||
"@typescript-eslint/eslint-plugin": "^8.48.0",
|
||||
"@typescript-eslint/parser": "^8.48.0",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"mocha": "^11.7.5",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "5.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="],
|
||||
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="],
|
||||
|
||||
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
|
||||
|
||||
"@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="],
|
||||
|
||||
"@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
|
||||
|
||||
"@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
|
||||
|
||||
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="],
|
||||
|
||||
"@eslint/js": ["@eslint/js@9.39.1", "", {}, "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw=="],
|
||||
|
||||
"@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
|
||||
|
||||
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
|
||||
|
||||
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
|
||||
|
||||
"@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="],
|
||||
|
||||
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
|
||||
|
||||
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
|
||||
|
||||
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
|
||||
|
||||
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
|
||||
"@types/node": ["@types/node@20.19.25", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.48.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/type-utils": "8.48.0", "@typescript-eslint/utils": "8.48.0", "@typescript-eslint/visitor-keys": "8.48.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.48.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ=="],
|
||||
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.48.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/types": "8.48.0", "@typescript-eslint/typescript-estree": "8.48.0", "@typescript-eslint/visitor-keys": "8.48.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ=="],
|
||||
|
||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.48.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.48.0", "@typescript-eslint/types": "^8.48.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw=="],
|
||||
|
||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.48.0", "", { "dependencies": { "@typescript-eslint/types": "8.48.0", "@typescript-eslint/visitor-keys": "8.48.0" } }, "sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ=="],
|
||||
|
||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.48.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w=="],
|
||||
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.48.0", "", { "dependencies": { "@typescript-eslint/types": "8.48.0", "@typescript-eslint/typescript-estree": "8.48.0", "@typescript-eslint/utils": "8.48.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw=="],
|
||||
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.48.0", "", {}, "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.48.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.48.0", "@typescript-eslint/tsconfig-utils": "8.48.0", "@typescript-eslint/types": "8.48.0", "@typescript-eslint/visitor-keys": "8.48.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ=="],
|
||||
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.48.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/types": "8.48.0", "@typescript-eslint/typescript-estree": "8.48.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.48.0", "", { "dependencies": { "@typescript-eslint/types": "8.48.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg=="],
|
||||
|
||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||
|
||||
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||
|
||||
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
|
||||
|
||||
"browser-stdout": ["browser-stdout@1.3.1", "", {}, "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw=="],
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
"camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
|
||||
|
||||
"cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
|
||||
|
||||
"cli-spinners": ["cli-spinners@3.3.0", "", {}, "sha512-/+40ljC3ONVnYIttjMWrlL51nItDAbBrq2upN8BPyvGU/2n5Oxw3tbNwORCaNuNqLJnxGqOfjUuhsv7l5Q4IsQ=="],
|
||||
|
||||
"cli-table3": ["cli-table3@0.6.5", "", { "dependencies": { "string-width": "^4.2.0" }, "optionalDependencies": { "@colors/colors": "1.5.0" } }, "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ=="],
|
||||
|
||||
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"decamelize": ["decamelize@4.0.0", "", {}, "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ=="],
|
||||
|
||||
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
||||
|
||||
"diff": ["diff@7.0.0", "", {}, "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw=="],
|
||||
|
||||
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="],
|
||||
|
||||
"eslint-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="],
|
||||
|
||||
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
|
||||
|
||||
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
||||
|
||||
"espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
|
||||
|
||||
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
|
||||
|
||||
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
|
||||
|
||||
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
|
||||
|
||||
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
|
||||
|
||||
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
|
||||
|
||||
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
|
||||
|
||||
"flat": ["flat@5.0.2", "", { "bin": { "flat": "cli.js" } }, "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ=="],
|
||||
|
||||
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
|
||||
|
||||
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
|
||||
|
||||
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
||||
|
||||
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||
|
||||
"get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="],
|
||||
|
||||
"glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
||||
|
||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||
|
||||
"globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
|
||||
|
||||
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="],
|
||||
|
||||
"ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
||||
|
||||
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="],
|
||||
|
||||
"is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="],
|
||||
|
||||
"is-plain-obj": ["is-plain-obj@2.1.0", "", {}, "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA=="],
|
||||
|
||||
"is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
||||
|
||||
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
|
||||
|
||||
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
||||
|
||||
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
|
||||
|
||||
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
|
||||
|
||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||
|
||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||
|
||||
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
||||
|
||||
"log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="],
|
||||
|
||||
"lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="],
|
||||
|
||||
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
|
||||
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
||||
|
||||
"mocha": ["mocha@11.7.5", "", { "dependencies": { "browser-stdout": "^1.3.1", "chokidar": "^4.0.1", "debug": "^4.3.5", "diff": "^7.0.0", "escape-string-regexp": "^4.0.0", "find-up": "^5.0.0", "glob": "^10.4.5", "he": "^1.2.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", "minimatch": "^9.0.5", "ms": "^2.1.3", "picocolors": "^1.1.1", "serialize-javascript": "^6.0.2", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", "workerpool": "^9.2.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1", "yargs-unparser": "^2.0.0" }, "bin": { "mocha": "bin/mocha.js", "_mocha": "bin/_mocha" } }, "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||
|
||||
"onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
|
||||
|
||||
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
|
||||
|
||||
"ora": ["ora@9.0.0", "", { "dependencies": { "chalk": "^5.6.2", "cli-cursor": "^5.0.0", "cli-spinners": "^3.2.0", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.1.0", "log-symbols": "^7.0.1", "stdin-discarder": "^0.2.2", "string-width": "^8.1.0", "strip-ansi": "^7.1.2" } }, "sha512-m0pg2zscbYgWbqRR6ABga5c3sZdEon7bSgjnlXC64kxtxLOyjRcbbUkLj7HFyy/FTD+P2xdBWu8snGhYI0jc4A=="],
|
||||
|
||||
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||
|
||||
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||
|
||||
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
|
||||
|
||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||
|
||||
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||
|
||||
"prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
|
||||
|
||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="],
|
||||
|
||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||
|
||||
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
||||
|
||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
||||
"restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||
|
||||
"serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
|
||||
"stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="],
|
||||
|
||||
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
|
||||
|
||||
"strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||
|
||||
"supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
|
||||
"ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
|
||||
|
||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||
|
||||
"typescript": ["typescript@5.3.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||
|
||||
"workerpool": ["workerpool@9.3.4", "", {}, "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||
|
||||
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||
|
||||
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
|
||||
|
||||
"yargs-unparser": ["yargs-unparser@2.0.0", "", { "dependencies": { "camelcase": "^6.0.0", "decamelize": "^4.0.0", "flat": "^5.0.2", "is-plain-obj": "^2.1.0" } }, "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="],
|
||||
|
||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
||||
|
||||
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
|
||||
"chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
|
||||
"log-symbols/is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
|
||||
|
||||
"mocha/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
|
||||
"ora/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||
|
||||
"ora/log-symbols": ["log-symbols@7.0.1", "", { "dependencies": { "is-unicode-supported": "^2.0.0", "yoctocolors": "^2.1.1" } }, "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg=="],
|
||||
|
||||
"ora/string-width": ["string-width@8.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg=="],
|
||||
|
||||
"string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
"@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
|
||||
"cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
|
||||
"mocha/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
|
||||
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import tseslint from '@typescript-eslint/eslint-plugin';
|
||||
import tsparser from '@typescript-eslint/parser';
|
||||
import prettier from 'eslint-config-prettier';
|
||||
|
||||
export default [
|
||||
{
|
||||
files: ['src/**/*.ts'],
|
||||
languageOptions: {
|
||||
parser: tsparser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module',
|
||||
project: './tsconfig.json',
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
'@typescript-eslint': tseslint,
|
||||
},
|
||||
rules: {
|
||||
// TypeScript rules - warnings for now, upgrade to errors incrementally
|
||||
// TODO: Upgrade these to 'error' as codebase is cleaned up
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' },
|
||||
],
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'warn',
|
||||
|
||||
// General code quality
|
||||
'no-console': 'off', // CLI tool needs console
|
||||
'prefer-const': 'error',
|
||||
'no-var': 'error',
|
||||
'eqeqeq': ['error', 'always'],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['tests/**/*.js', 'scripts/**/*.js'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'commonjs',
|
||||
},
|
||||
rules: {
|
||||
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
'prefer-const': 'error',
|
||||
'no-var': 'error',
|
||||
},
|
||||
},
|
||||
prettier,
|
||||
];
|
||||
Generated
-1495
File diff suppressed because it is too large
Load Diff
+20
-7
@@ -39,8 +39,10 @@
|
||||
"LICENSE"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
"node": ">=14.0.0",
|
||||
"bun": ">=1.0.0"
|
||||
},
|
||||
"packageManager": "bun@1.2.21",
|
||||
"os": [
|
||||
"darwin",
|
||||
"linux",
|
||||
@@ -51,14 +53,20 @@
|
||||
"build": "tsc && node scripts/add-shebang.js",
|
||||
"build:watch": "tsc --watch",
|
||||
"prebuild": "rm -rf dist tsconfig.tsbuildinfo",
|
||||
"test": "npm run build && npm run test:all",
|
||||
"test:all": "npm run test:unit && npm run test:npm",
|
||||
"test:unit": "npx mocha tests/shared/unit/**/*.test.js --timeout 5000",
|
||||
"test:npm": "npx mocha tests/npm/**/*.test.js --timeout 10000",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "eslint src/",
|
||||
"lint:fix": "eslint src/ --fix",
|
||||
"format": "prettier --write src/",
|
||||
"format:check": "prettier --check src/",
|
||||
"validate": "bun run typecheck && bun run lint && bun run format:check && bun run test",
|
||||
"test": "bun run build && bun run test:all",
|
||||
"test:all": "bun run test:unit && bun run test:npm",
|
||||
"test:unit": "mocha tests/shared/unit/**/*.test.js --timeout 5000",
|
||||
"test:npm": "mocha tests/npm/**/*.test.js --timeout 10000",
|
||||
"test:native": "bash tests/native/unix/edge-cases.sh",
|
||||
"test:edge-cases": "bash tests/edge-cases.sh",
|
||||
"prepublishOnly": "npm run build && node scripts/sync-version.js",
|
||||
"prepack": "npm run build && node scripts/sync-version.js",
|
||||
"prepublishOnly": "npm run validate && node scripts/sync-version.js",
|
||||
"prepack": "npm run validate && node scripts/sync-version.js",
|
||||
"prepare": "node scripts/check-executables.js",
|
||||
"postinstall": "node scripts/postinstall.js"
|
||||
},
|
||||
@@ -68,7 +76,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.19.25",
|
||||
"@typescript-eslint/eslint-plugin": "^8.48.0",
|
||||
"@typescript-eslint/parser": "^8.48.0",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"mocha": "^11.7.5",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "5.3"
|
||||
}
|
||||
}
|
||||
|
||||
+61
-36
@@ -71,21 +71,43 @@ class AuthCommands {
|
||||
console.log(` ${colored('default <profile>', 'yellow')} Set default profile`);
|
||||
console.log('');
|
||||
console.log(colored('Examples:', 'cyan'));
|
||||
console.log(` ${colored('ccs auth create work', 'yellow')} # Create & login to work profile`);
|
||||
console.log(` ${colored('ccs auth default work', 'yellow')} # Set work as default`);
|
||||
console.log(` ${colored('ccs auth list', 'yellow')} # List all profiles`);
|
||||
console.log(` ${colored('ccs work "review code"', 'yellow')} # Use work profile`);
|
||||
console.log(` ${colored('ccs "review code"', 'yellow')} # Use default profile`);
|
||||
console.log(
|
||||
` ${colored('ccs auth create work', 'yellow')} # Create & login to work profile`
|
||||
);
|
||||
console.log(
|
||||
` ${colored('ccs auth default work', 'yellow')} # Set work as default`
|
||||
);
|
||||
console.log(
|
||||
` ${colored('ccs auth list', 'yellow')} # List all profiles`
|
||||
);
|
||||
console.log(
|
||||
` ${colored('ccs work "review code"', 'yellow')} # Use work profile`
|
||||
);
|
||||
console.log(
|
||||
` ${colored('ccs "review code"', 'yellow')} # Use default profile`
|
||||
);
|
||||
console.log('');
|
||||
console.log(colored('Options:', 'cyan'));
|
||||
console.log(` ${colored('--force', 'yellow')} Allow overwriting existing profile (create)`);
|
||||
console.log(` ${colored('--yes, -y', 'yellow')} Skip confirmation prompts (remove)`);
|
||||
console.log(` ${colored('--json', 'yellow')} Output in JSON format (list, show)`);
|
||||
console.log(` ${colored('--verbose', 'yellow')} Show additional details (list)`);
|
||||
console.log(
|
||||
` ${colored('--force', 'yellow')} Allow overwriting existing profile (create)`
|
||||
);
|
||||
console.log(
|
||||
` ${colored('--yes, -y', 'yellow')} Skip confirmation prompts (remove)`
|
||||
);
|
||||
console.log(
|
||||
` ${colored('--json', 'yellow')} Output in JSON format (list, show)`
|
||||
);
|
||||
console.log(
|
||||
` ${colored('--verbose', 'yellow')} Show additional details (list)`
|
||||
);
|
||||
console.log('');
|
||||
console.log(colored('Note:', 'cyan'));
|
||||
console.log(` By default, ${colored('ccs', 'yellow')} uses Claude CLI defaults from ~/.claude/`);
|
||||
console.log(` Use ${colored('ccs auth default <profile>', 'yellow')} to change the default profile.`);
|
||||
console.log(
|
||||
` By default, ${colored('ccs', 'yellow')} uses Claude CLI defaults from ~/.claude/`
|
||||
);
|
||||
console.log(
|
||||
` Use ${colored('ccs auth default <profile>', 'yellow')} to change the default profile.`
|
||||
);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
@@ -93,13 +115,13 @@ class AuthCommands {
|
||||
* Parse command arguments
|
||||
*/
|
||||
private parseArgs(args: string[]): AuthCommandArgs {
|
||||
const profileName = args.find(arg => !arg.startsWith('--'));
|
||||
const profileName = args.find((arg) => !arg.startsWith('--'));
|
||||
return {
|
||||
profileName,
|
||||
force: args.includes('--force'),
|
||||
verbose: args.includes('--verbose'),
|
||||
json: args.includes('--json'),
|
||||
yes: args.includes('--yes') || args.includes('-y')
|
||||
yes: args.includes('--yes') || args.includes('-y'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -134,11 +156,11 @@ class AuthCommands {
|
||||
// Create/update profile entry
|
||||
if (this.registry.hasProfile(profileName)) {
|
||||
this.registry.updateProfile(profileName, {
|
||||
type: 'account'
|
||||
type: 'account',
|
||||
});
|
||||
} else {
|
||||
this.registry.createProfile(profileName, {
|
||||
type: 'account'
|
||||
type: 'account',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -161,7 +183,7 @@ class AuthCommands {
|
||||
// Execute Claude in isolated instance (will auto-prompt for login if no credentials)
|
||||
const child: ChildProcess = spawn(claudeCli, [], {
|
||||
stdio: 'inherit',
|
||||
env: { ...process.env, CLAUDE_CONFIG_DIR: instancePath }
|
||||
env: { ...process.env, CLAUDE_CONFIG_DIR: instancePath },
|
||||
});
|
||||
|
||||
child.on('exit', (code: number | null) => {
|
||||
@@ -173,7 +195,9 @@ class AuthCommands {
|
||||
console.log(` Instance: ${instancePath}`);
|
||||
console.log('');
|
||||
console.log('Usage:');
|
||||
console.log(` ${colored(`ccs ${profileName} "your prompt here"`, 'yellow')} # Use this specific profile`);
|
||||
console.log(
|
||||
` ${colored(`ccs ${profileName} "your prompt here"`, 'yellow')} # Use this specific profile`
|
||||
);
|
||||
console.log('');
|
||||
console.log('To set as default (so you can use just "ccs"):');
|
||||
console.log(` ${colored(`ccs auth default ${profileName}`, 'yellow')}`);
|
||||
@@ -194,7 +218,6 @@ class AuthCommands {
|
||||
console.error(`[X] Failed to execute Claude CLI: ${err.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[X] Failed to create profile: ${(error as Error).message}`);
|
||||
process.exit(1);
|
||||
@@ -216,7 +239,7 @@ class AuthCommands {
|
||||
if (json) {
|
||||
const output: ListOutput = {
|
||||
version: this.version,
|
||||
profiles: profileNames.map(name => {
|
||||
profiles: profileNames.map((name) => {
|
||||
const profile = profiles[name];
|
||||
const isDefault = name === defaultProfile;
|
||||
const instancePath = this.instanceMgr.getInstancePath(name);
|
||||
@@ -227,9 +250,9 @@ class AuthCommands {
|
||||
is_default: isDefault,
|
||||
created: profile.created,
|
||||
last_used: profile.last_used || null,
|
||||
instance_path: instancePath
|
||||
instance_path: instancePath,
|
||||
};
|
||||
})
|
||||
}),
|
||||
};
|
||||
console.log(JSON.stringify(output, null, 2));
|
||||
return;
|
||||
@@ -240,7 +263,9 @@ class AuthCommands {
|
||||
console.log(colored('No account profiles found', 'yellow'));
|
||||
console.log('');
|
||||
console.log('To create your first profile:');
|
||||
console.log(` ${colored('ccs auth create <profile>', 'yellow')} # Create and login to profile`);
|
||||
console.log(
|
||||
` ${colored('ccs auth create <profile>', 'yellow')} # Create and login to profile`
|
||||
);
|
||||
console.log('');
|
||||
console.log('Example:');
|
||||
console.log(` ${colored('ccs auth create work', 'yellow')}`);
|
||||
@@ -271,12 +296,14 @@ class AuthCommands {
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
|
||||
sorted.forEach(name => {
|
||||
sorted.forEach((name) => {
|
||||
const profile = profiles[name];
|
||||
const isDefault = name === defaultProfile;
|
||||
const indicator = isDefault ? colored('[*]', 'green') : '[ ]';
|
||||
|
||||
console.log(`${indicator} ${colored(name, 'cyan')}${isDefault ? colored(' (default)', 'green') : ''}`);
|
||||
console.log(
|
||||
`${indicator} ${colored(name, 'cyan')}${isDefault ? colored(' (default)', 'green') : ''}`
|
||||
);
|
||||
|
||||
console.log(` Type: ${profile.type || 'account'}`);
|
||||
|
||||
@@ -292,7 +319,6 @@ class AuthCommands {
|
||||
|
||||
console.log(`Total profiles: ${profileNames.length}`);
|
||||
console.log('');
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[X] Failed to list profiles: ${(error as Error).message}`);
|
||||
process.exit(1);
|
||||
@@ -324,7 +350,7 @@ class AuthCommands {
|
||||
const sessionsDir = path.join(instancePath, 'session-env');
|
||||
if (fs.existsSync(sessionsDir)) {
|
||||
const files = fs.readdirSync(sessionsDir);
|
||||
sessionCount = files.filter(f => f.endsWith('.json')).length;
|
||||
sessionCount = files.filter((f) => f.endsWith('.json')).length;
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore errors counting sessions
|
||||
@@ -339,7 +365,7 @@ class AuthCommands {
|
||||
created: profile.created,
|
||||
last_used: profile.last_used || null,
|
||||
instance_path: instancePath,
|
||||
session_count: sessionCount
|
||||
session_count: sessionCount,
|
||||
};
|
||||
console.log(JSON.stringify(output, null, 2));
|
||||
return;
|
||||
@@ -360,7 +386,6 @@ class AuthCommands {
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[X] ${(error as Error).message}`);
|
||||
process.exit(1);
|
||||
@@ -394,7 +419,7 @@ class AuthCommands {
|
||||
const sessionsDir = path.join(instancePath, 'session-env');
|
||||
if (fs.existsSync(sessionsDir)) {
|
||||
const files = fs.readdirSync(sessionsDir);
|
||||
sessionCount = files.filter(f => f.endsWith('.json')).length;
|
||||
sessionCount = files.filter((f) => f.endsWith('.json')).length;
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore errors counting sessions
|
||||
@@ -408,10 +433,12 @@ class AuthCommands {
|
||||
console.log('');
|
||||
|
||||
// Interactive confirmation (or --yes flag)
|
||||
const confirmed = yes || await InteractivePrompt.confirm(
|
||||
'Delete this profile?',
|
||||
{ default: false } // Default to NO (safe)
|
||||
);
|
||||
const confirmed =
|
||||
yes ||
|
||||
(await InteractivePrompt.confirm(
|
||||
'Delete this profile?',
|
||||
{ default: false } // Default to NO (safe)
|
||||
));
|
||||
|
||||
if (!confirmed) {
|
||||
console.log('[i] Cancelled');
|
||||
@@ -427,7 +454,6 @@ class AuthCommands {
|
||||
console.log(colored('[OK] Profile removed successfully', 'green'));
|
||||
console.log(` Profile: ${profileName}`);
|
||||
console.log('');
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[X] Failed to remove profile: ${(error as Error).message}`);
|
||||
process.exit(1);
|
||||
@@ -456,7 +482,6 @@ class AuthCommands {
|
||||
console.log('Now you can use:');
|
||||
console.log(` ${colored('ccs "your prompt"', 'yellow')} # Uses ${profileName} profile`);
|
||||
console.log('');
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[X] ${(error as Error).message}`);
|
||||
process.exit(1);
|
||||
@@ -530,4 +555,4 @@ class AuthCommands {
|
||||
}
|
||||
}
|
||||
|
||||
export default AuthCommands;
|
||||
export default AuthCommands;
|
||||
|
||||
@@ -95,7 +95,7 @@ class ProfileDetector {
|
||||
return {
|
||||
type: 'settings',
|
||||
name: profileName,
|
||||
settingsPath: config.profiles[profileName]
|
||||
settingsPath: config.profiles[profileName],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ class ProfileDetector {
|
||||
return {
|
||||
type: 'account',
|
||||
name: profileName,
|
||||
profile: profiles.profiles[profileName]
|
||||
profile: profiles.profiles[profileName],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ class ProfileDetector {
|
||||
return {
|
||||
type: 'account',
|
||||
name: profiles.default,
|
||||
profile: profiles.profiles[profiles.default]
|
||||
profile: profiles.profiles[profiles.default],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ class ProfileDetector {
|
||||
return {
|
||||
type: 'settings',
|
||||
name: 'default',
|
||||
settingsPath: config.profiles['default']
|
||||
settingsPath: config.profiles['default'],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ class ProfileDetector {
|
||||
return {
|
||||
type: 'default',
|
||||
name: 'default',
|
||||
message: 'No profile configured. Using Claude CLI defaults from ~/.claude/'
|
||||
message: 'No profile configured. Using Claude CLI defaults from ~/.claude/',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ class ProfileDetector {
|
||||
|
||||
if (settingsProfiles.length > 0) {
|
||||
lines.push('Settings-based profiles (GLM, Kimi, etc.):');
|
||||
settingsProfiles.forEach(name => {
|
||||
settingsProfiles.forEach((name) => {
|
||||
lines.push(` - ${name}`);
|
||||
});
|
||||
}
|
||||
@@ -179,15 +179,17 @@ class ProfileDetector {
|
||||
|
||||
if (accountProfiles.length > 0) {
|
||||
lines.push('Account-based profiles:');
|
||||
accountProfiles.forEach(name => {
|
||||
accountProfiles.forEach((name) => {
|
||||
const isDefault = name === profiles.default;
|
||||
lines.push(` - ${name}${isDefault ? ' [DEFAULT]' : ''}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (lines.length === 0) {
|
||||
return ' (no profiles configured)\n' +
|
||||
' Run "ccs auth save <profile>" to create your first account profile.';
|
||||
return (
|
||||
' (no profiles configured)\n' +
|
||||
' Run "ccs auth save <profile>" to create your first account profile.'
|
||||
);
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
@@ -215,9 +217,9 @@ class ProfileDetector {
|
||||
return {
|
||||
settings: Object.keys(config.profiles || {}),
|
||||
accounts: Object.keys(profiles.profiles || {}),
|
||||
default: profiles.default
|
||||
default: profiles.default,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default ProfileDetector;
|
||||
export default ProfileDetector;
|
||||
|
||||
@@ -49,7 +49,7 @@ export class ProfileRegistry {
|
||||
return {
|
||||
version: '2.0.0',
|
||||
profiles: {},
|
||||
default: null
|
||||
default: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ export class ProfileRegistry {
|
||||
data.profiles[name] = {
|
||||
type: metadata.type || 'account',
|
||||
created: metadata.created || new Date().toISOString(),
|
||||
last_used: metadata.last_used || null
|
||||
last_used: metadata.last_used || null,
|
||||
};
|
||||
|
||||
// Note: No longer auto-set as default
|
||||
@@ -138,7 +138,7 @@ export class ProfileRegistry {
|
||||
|
||||
data.profiles[name] = {
|
||||
...data.profiles[name],
|
||||
...updates
|
||||
...updates,
|
||||
};
|
||||
|
||||
this._write(data);
|
||||
@@ -217,9 +217,9 @@ export class ProfileRegistry {
|
||||
*/
|
||||
touchProfile(name: string): void {
|
||||
this.updateProfile(name, {
|
||||
last_used: new Date().toISOString()
|
||||
last_used: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default ProfileRegistry;
|
||||
export default ProfileRegistry;
|
||||
|
||||
+74
-33
@@ -17,7 +17,9 @@ import { getSettingsPath, getConfigPath } from './utils/config-manager';
|
||||
import { ErrorManager } from './utils/error-manager';
|
||||
|
||||
// Version (sync with package.json)
|
||||
const CCS_VERSION = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf8')).version;
|
||||
const CCS_VERSION = JSON.parse(
|
||||
fs.readFileSync(path.join(__dirname, '../package.json'), 'utf8')
|
||||
).version;
|
||||
|
||||
// ========== Helper Functions ==========
|
||||
|
||||
@@ -31,7 +33,11 @@ function escapeShellArg(arg: string): string {
|
||||
/**
|
||||
* Execute Claude CLI with unified spawn logic
|
||||
*/
|
||||
function execClaude(claudeCli: string, args: string[], envVars: NodeJS.ProcessEnv | null = null): void {
|
||||
function execClaude(
|
||||
claudeCli: string,
|
||||
args: string[],
|
||||
envVars: NodeJS.ProcessEnv | null = null
|
||||
): void {
|
||||
const isWindows = process.platform === 'win32';
|
||||
const needsShell = isWindows && /\.(cmd|bat|ps1)$/i.test(claudeCli);
|
||||
|
||||
@@ -46,14 +52,14 @@ function execClaude(claudeCli: string, args: string[], envVars: NodeJS.ProcessEn
|
||||
stdio: 'inherit',
|
||||
windowsHide: true,
|
||||
shell: true,
|
||||
env
|
||||
env,
|
||||
});
|
||||
} else {
|
||||
// When no shell needed: use array form (faster, no shell overhead)
|
||||
child = spawn(claudeCli, args, {
|
||||
stdio: 'inherit',
|
||||
windowsHide: true,
|
||||
env
|
||||
env,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -125,7 +131,9 @@ function handleVersionCommand(): void {
|
||||
|
||||
if (readyProfiles.length > 0) {
|
||||
console.log(colored('Delegation Ready:', 'cyan'));
|
||||
console.log(` ${colored('[OK]', 'yellow')} ${readyProfiles.join(', ')} profiles are ready for delegation`);
|
||||
console.log(
|
||||
` ${colored('[OK]', 'yellow')} ${readyProfiles.join(', ')} profiles are ready for delegation`
|
||||
);
|
||||
console.log('');
|
||||
} else if (delegationEnabled) {
|
||||
console.log(colored('Delegation Ready:', 'cyan'));
|
||||
@@ -136,7 +144,7 @@ function handleVersionCommand(): void {
|
||||
console.log(`${colored('Documentation:', 'cyan')} https://github.com/kaitranntt/ccs`);
|
||||
console.log(`${colored('License:', 'cyan')} MIT`);
|
||||
console.log('');
|
||||
console.log(colored('Run \'ccs --help\' for usage information', 'yellow'));
|
||||
console.log(colored("Run 'ccs --help' for usage information", 'yellow'));
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -145,7 +153,9 @@ function handleVersionCommand(): void {
|
||||
* Handle help command
|
||||
*/
|
||||
function handleHelpCommand(): void {
|
||||
console.log(colored('CCS (Claude Code Switch) - Instant profile switching for Claude CLI', 'bold'));
|
||||
console.log(
|
||||
colored('CCS (Claude Code Switch) - Instant profile switching for Claude CLI', 'bold')
|
||||
);
|
||||
console.log('');
|
||||
|
||||
console.log(colored('Usage:', 'cyan'));
|
||||
@@ -162,34 +172,52 @@ function handleHelpCommand(): void {
|
||||
console.log(colored('Model Switching:', 'cyan'));
|
||||
console.log(` ${colored('ccs', 'yellow')} Use default Claude account`);
|
||||
console.log(` ${colored('ccs glm', 'yellow')} Switch to GLM 4.6 model`);
|
||||
console.log(` ${colored('ccs glmt', 'yellow')} Switch to GLM with thinking mode`);
|
||||
console.log(
|
||||
` ${colored('ccs glmt', 'yellow')} Switch to GLM with thinking mode`
|
||||
);
|
||||
console.log(` ${colored('ccs glmt --verbose', 'yellow')} Enable debug logging`);
|
||||
console.log(` ${colored('ccs kimi', 'yellow')} Switch to Kimi for Coding`);
|
||||
console.log(` ${colored('ccs glm', 'yellow')} "debug this code" Use GLM and run command`);
|
||||
console.log('');
|
||||
|
||||
console.log(colored('Account Management:', 'cyan'));
|
||||
console.log(` ${colored('ccs auth --help', 'yellow')} Run multiple Claude accounts concurrently`);
|
||||
console.log(
|
||||
` ${colored('ccs auth --help', 'yellow')} Run multiple Claude accounts concurrently`
|
||||
);
|
||||
console.log('');
|
||||
|
||||
console.log(colored('Delegation (inside Claude Code CLI):', 'cyan'));
|
||||
console.log(` ${colored('/ccs "task"', 'yellow')} Delegate task (auto-selects best profile)`);
|
||||
console.log(` ${colored('/ccs --glm "task"', 'yellow')} Force GLM-4.6 for simple tasks`);
|
||||
console.log(
|
||||
` ${colored('/ccs "task"', 'yellow')} Delegate task (auto-selects best profile)`
|
||||
);
|
||||
console.log(
|
||||
` ${colored('/ccs --glm "task"', 'yellow')} Force GLM-4.6 for simple tasks`
|
||||
);
|
||||
console.log(` ${colored('/ccs --kimi "task"', 'yellow')} Force Kimi for long context`);
|
||||
console.log(` ${colored('/ccs:continue "follow-up"', 'yellow')} Continue last delegation session`);
|
||||
console.log(
|
||||
` ${colored('/ccs:continue "follow-up"', 'yellow')} Continue last delegation session`
|
||||
);
|
||||
console.log(' Save tokens by delegating simple tasks to cost-optimized models');
|
||||
console.log('');
|
||||
|
||||
console.log(colored('Diagnostics:', 'cyan'));
|
||||
console.log(` ${colored('ccs doctor', 'yellow')} Run health check and diagnostics`);
|
||||
console.log(` ${colored('ccs sync', 'yellow')} Sync delegation commands and skills`);
|
||||
console.log(
|
||||
` ${colored('ccs doctor', 'yellow')} Run health check and diagnostics`
|
||||
);
|
||||
console.log(
|
||||
` ${colored('ccs sync', 'yellow')} Sync delegation commands and skills`
|
||||
);
|
||||
console.log(` ${colored('ccs update', 'yellow')} Update CCS to latest version`);
|
||||
console.log('');
|
||||
|
||||
console.log(colored('Flags:', 'cyan'));
|
||||
console.log(` ${colored('-h, --help', 'yellow')} Show this help message`);
|
||||
console.log(` ${colored('-v, --version', 'yellow')} Show version and installation info`);
|
||||
console.log(` ${colored('-sc, --shell-completion', 'yellow')} Install shell auto-completion`);
|
||||
console.log(
|
||||
` ${colored('-v, --version', 'yellow')} Show version and installation info`
|
||||
);
|
||||
console.log(
|
||||
` ${colored('-sc, --shell-completion', 'yellow')} Install shell auto-completion`
|
||||
);
|
||||
console.log('');
|
||||
|
||||
console.log(colored('Configuration:', 'cyan'));
|
||||
@@ -212,7 +240,9 @@ function handleHelpCommand(): void {
|
||||
console.log(` ${colored('$ ccs', 'yellow')} # Use default account`);
|
||||
console.log(` ${colored('$ ccs glm "implement API"', 'yellow')} # Cost-optimized model`);
|
||||
console.log('');
|
||||
console.log(` For more: ${colored('https://github.com/kaitranntt/ccs/blob/main/README.md', 'cyan')}`);
|
||||
console.log(
|
||||
` For more: ${colored('https://github.com/kaitranntt/ccs/blob/main/README.md', 'cyan')}`
|
||||
);
|
||||
console.log('');
|
||||
|
||||
console.log(colored('Uninstall:', 'yellow'));
|
||||
@@ -312,7 +342,7 @@ function detectInstallationMethod(): 'npm' | 'direct' {
|
||||
/\.npm\/global\/bin\//,
|
||||
/\/\.nvm\/versions\/node\/[^/]+\/bin\//,
|
||||
/\/usr\/local\/bin\//,
|
||||
/\/usr\/bin\//
|
||||
/\/usr\/bin\//,
|
||||
];
|
||||
|
||||
for (const pattern of npmGlobalBinPatterns) {
|
||||
@@ -388,7 +418,7 @@ function detectPackageManager(): 'npm' | 'yarn' | 'pnpm' | 'bun' {
|
||||
const yarnResult = spawnSync('yarn', ['global', 'list', '--pattern', '@kaitranntt/ccs'], {
|
||||
encoding: 'utf8',
|
||||
shell: true,
|
||||
timeout: 5000
|
||||
timeout: 5000,
|
||||
});
|
||||
if (yarnResult.status === 0 && yarnResult.stdout.includes('@kaitranntt/ccs')) {
|
||||
return 'yarn';
|
||||
@@ -401,7 +431,7 @@ function detectPackageManager(): 'npm' | 'yarn' | 'pnpm' | 'bun' {
|
||||
const pnpmResult = spawnSync('pnpm', ['list', '-g', '--pattern', '@kaitranntt/ccs'], {
|
||||
encoding: 'utf8',
|
||||
shell: true,
|
||||
timeout: 5000
|
||||
timeout: 5000,
|
||||
});
|
||||
if (pnpmResult.status === 0 && pnpmResult.stdout.includes('@kaitranntt/ccs')) {
|
||||
return 'pnpm';
|
||||
@@ -414,7 +444,7 @@ function detectPackageManager(): 'npm' | 'yarn' | 'pnpm' | 'bun' {
|
||||
const bunResult = spawnSync('bun', ['pm', 'ls', '-g', '--pattern', '@kaitranntt/ccs'], {
|
||||
encoding: 'utf8',
|
||||
shell: true,
|
||||
timeout: 5000
|
||||
timeout: 5000,
|
||||
});
|
||||
if (bunResult.status === 0 && bunResult.stdout.includes('@kaitranntt/ccs')) {
|
||||
return 'bun';
|
||||
@@ -501,7 +531,9 @@ async function handleUpdateCommand(): Promise<void> {
|
||||
}
|
||||
|
||||
// Update available
|
||||
console.log(colored(`[i] Update available: ${updateResult.current} -> ${updateResult.latest}`, 'yellow'));
|
||||
console.log(
|
||||
colored(`[i] Update available: ${updateResult.current} -> ${updateResult.latest}`, 'yellow')
|
||||
);
|
||||
console.log('');
|
||||
|
||||
if (isNpmInstall) {
|
||||
@@ -548,7 +580,7 @@ async function handleUpdateCommand(): Promise<void> {
|
||||
|
||||
const performUpdate = (): void => {
|
||||
const child = spawn(updateCommand, updateArgs, {
|
||||
stdio: 'inherit'
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
child.on('exit', (code) => {
|
||||
@@ -583,7 +615,7 @@ async function handleUpdateCommand(): Promise<void> {
|
||||
if (cacheCommand && cacheArgs) {
|
||||
console.log(colored('Clearing package cache...', 'cyan'));
|
||||
const cacheChild = spawn(cacheCommand, cacheArgs, {
|
||||
stdio: 'inherit'
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
cacheChild.on('exit', (code) => {
|
||||
@@ -611,15 +643,20 @@ async function handleUpdateCommand(): Promise<void> {
|
||||
|
||||
if (isWindows) {
|
||||
command = 'powershell.exe';
|
||||
args = ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command',
|
||||
'irm ccs.kaitran.ca/install | iex'];
|
||||
args = [
|
||||
'-NoProfile',
|
||||
'-ExecutionPolicy',
|
||||
'Bypass',
|
||||
'-Command',
|
||||
'irm ccs.kaitran.ca/install | iex',
|
||||
];
|
||||
} else {
|
||||
command = '/bin/bash';
|
||||
args = ['-c', 'curl -fsSL ccs.kaitran.ca/install | bash'];
|
||||
}
|
||||
|
||||
const child = spawn(command, args, {
|
||||
stdio: 'inherit'
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
child.on('exit', (code) => {
|
||||
@@ -685,7 +722,11 @@ function detectProfile(args: string[]): DetectedProfile {
|
||||
/**
|
||||
* Execute Claude CLI with embedded proxy (for GLMT profile)
|
||||
*/
|
||||
async function execClaudeWithProxy(claudeCli: string, profileName: string, args: string[]): Promise<void> {
|
||||
async function execClaudeWithProxy(
|
||||
claudeCli: string,
|
||||
profileName: string,
|
||||
args: string[]
|
||||
): Promise<void> {
|
||||
// 1. Read settings to get API key
|
||||
const settingsPath = getSettingsPath(profileName);
|
||||
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
||||
@@ -705,7 +746,7 @@ async function execClaudeWithProxy(claudeCli: string, profileName: string, args:
|
||||
const proxyArgs = verbose ? ['--verbose'] : [];
|
||||
// Use process.execPath for Windows compatibility (CVE-2024-27980)
|
||||
const proxy = spawn(process.execPath, [proxyPath, ...proxyArgs], {
|
||||
stdio: ['ignore', 'pipe', verbose ? 'pipe' : 'inherit']
|
||||
stdio: ['ignore', 'pipe', verbose ? 'pipe' : 'inherit'],
|
||||
});
|
||||
|
||||
// 3. Wait for proxy ready signal (with timeout)
|
||||
@@ -765,7 +806,7 @@ async function execClaudeWithProxy(claudeCli: string, profileName: string, args:
|
||||
const envVars: NodeJS.ProcessEnv = {
|
||||
ANTHROPIC_BASE_URL: `http://127.0.0.1:${port}`,
|
||||
ANTHROPIC_AUTH_TOKEN: apiKey,
|
||||
ANTHROPIC_MODEL: 'glm-4.6'
|
||||
ANTHROPIC_MODEL: 'glm-4.6',
|
||||
};
|
||||
|
||||
const isWindows = process.platform === 'win32';
|
||||
@@ -779,13 +820,13 @@ async function execClaudeWithProxy(claudeCli: string, profileName: string, args:
|
||||
stdio: 'inherit',
|
||||
windowsHide: true,
|
||||
shell: true,
|
||||
env
|
||||
env,
|
||||
});
|
||||
} else {
|
||||
claude = spawn(claudeCli, args, {
|
||||
stdio: 'inherit',
|
||||
windowsHide: true,
|
||||
env
|
||||
env,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1019,7 +1060,7 @@ async function main(): Promise<void> {
|
||||
}
|
||||
|
||||
// Run main
|
||||
main().catch(error => {
|
||||
main().catch((error) => {
|
||||
console.error('Fatal error:', error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -42,11 +42,7 @@ export class DelegationHandler {
|
||||
this._validateProfile(parsed.profile);
|
||||
|
||||
// 4. Execute via HeadlessExecutor
|
||||
const result = await HeadlessExecutor.execute(
|
||||
parsed.profile,
|
||||
parsed.prompt,
|
||||
parsed.options
|
||||
);
|
||||
const result = await HeadlessExecutor.execute(parsed.profile, parsed.prompt, parsed.options);
|
||||
|
||||
// 5. Format and display results
|
||||
const formatted = ResultFormatter.format(result);
|
||||
@@ -81,15 +77,11 @@ export class DelegationHandler {
|
||||
}
|
||||
|
||||
// Execute with resume flag
|
||||
const result = await HeadlessExecutor.execute(
|
||||
baseProfile,
|
||||
parsed.prompt,
|
||||
{
|
||||
...parsed.options,
|
||||
resumeSession: true,
|
||||
sessionId: lastSession.sessionId
|
||||
}
|
||||
);
|
||||
const result = await HeadlessExecutor.execute(baseProfile, parsed.prompt, {
|
||||
...parsed.options,
|
||||
resumeSession: true,
|
||||
sessionId: lastSession.sessionId,
|
||||
});
|
||||
|
||||
const formatted = ResultFormatter.format(result);
|
||||
console.log(formatted);
|
||||
@@ -178,7 +170,7 @@ export class DelegationHandler {
|
||||
const options: ParsedArgs['options'] = {
|
||||
cwd,
|
||||
outputFormat: 'stream-json',
|
||||
permissionMode: defaultPermissionMode
|
||||
permissionMode: defaultPermissionMode,
|
||||
};
|
||||
|
||||
// Parse permission-mode (CLI flag overrides settings file)
|
||||
@@ -219,4 +211,4 @@ export class DelegationHandler {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ export class HeadlessExecutor {
|
||||
timeout = 600000, // 10 minutes default
|
||||
permissionMode = 'acceptEdits',
|
||||
resumeSession = false,
|
||||
sessionId = null
|
||||
sessionId = null,
|
||||
} = options;
|
||||
|
||||
// Validate permission mode
|
||||
@@ -93,7 +93,9 @@ export class HeadlessExecutor {
|
||||
// Detect Claude CLI path
|
||||
const claudeCli = this._detectClaudeCli();
|
||||
if (!claudeCli) {
|
||||
throw new Error('Claude CLI not found in PATH. Install from: https://docs.claude.com/en/docs/claude-code/installation');
|
||||
throw new Error(
|
||||
'Claude CLI not found in PATH. Install from: https://docs.claude.com/en/docs/claude-code/installation'
|
||||
);
|
||||
}
|
||||
|
||||
// Get settings path for profile
|
||||
@@ -101,7 +103,9 @@ export class HeadlessExecutor {
|
||||
|
||||
// Validate settings file exists
|
||||
if (!fs.existsSync(settingsPath)) {
|
||||
throw new Error(`Settings file not found: ${settingsPath}\nProfile "${profile}" may not be configured.`);
|
||||
throw new Error(
|
||||
`Settings file not found: ${settingsPath}\nProfile "${profile}" may not be configured.`
|
||||
);
|
||||
}
|
||||
|
||||
// Smart slash command detection and preservation
|
||||
@@ -122,7 +126,9 @@ export class HeadlessExecutor {
|
||||
// Warn about dangerous mode
|
||||
if (process.env.CCS_DEBUG) {
|
||||
console.warn('[!] WARNING: Using --dangerously-skip-permissions mode');
|
||||
console.warn('[!] This bypasses ALL permission checks. Use only in trusted environments.');
|
||||
console.warn(
|
||||
'[!] This bypasses ALL permission checks. Use only in trusted environments.'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
args.push('--permission-mode', permissionMode);
|
||||
@@ -136,8 +142,13 @@ export class HeadlessExecutor {
|
||||
if (lastSession) {
|
||||
args.push('--resume', lastSession.sessionId);
|
||||
if (process.env.CCS_DEBUG) {
|
||||
const cost = lastSession.totalCost !== undefined && lastSession.totalCost !== null ? lastSession.totalCost.toFixed(4) : '0.0000';
|
||||
console.error(`[i] Resuming session: ${lastSession.sessionId} (${lastSession.turns} turns, $${cost})`);
|
||||
const cost =
|
||||
lastSession.totalCost !== undefined && lastSession.totalCost !== null
|
||||
? lastSession.totalCost.toFixed(4)
|
||||
: '0.0000';
|
||||
console.error(
|
||||
`[i] Resuming session: ${lastSession.sessionId} (${lastSession.turns} turns, $${cost})`
|
||||
);
|
||||
}
|
||||
} else if (sessionId) {
|
||||
args.push('--resume', sessionId);
|
||||
@@ -159,12 +170,12 @@ export class HeadlessExecutor {
|
||||
|
||||
if (toolRestrictions.allowedTools.length > 0) {
|
||||
args.push('--allowedTools');
|
||||
toolRestrictions.allowedTools.forEach(tool => args.push(tool));
|
||||
toolRestrictions.allowedTools.forEach((tool) => args.push(tool));
|
||||
}
|
||||
|
||||
if (toolRestrictions.disallowedTools.length > 0) {
|
||||
args.push('--disallowedTools');
|
||||
toolRestrictions.disallowedTools.forEach(tool => args.push(tool));
|
||||
toolRestrictions.disallowedTools.forEach((tool) => args.push(tool));
|
||||
}
|
||||
|
||||
// Note: No max-turns limit - using time-based limits instead (default 10min timeout)
|
||||
@@ -183,14 +194,15 @@ export class HeadlessExecutor {
|
||||
|
||||
// Show initial progress message
|
||||
if (showProgress) {
|
||||
const modelName = profile === 'glm' ? 'GLM-4.6' : profile === 'kimi' ? 'Kimi' : profile.toUpperCase();
|
||||
const modelName =
|
||||
profile === 'glm' ? 'GLM-4.6' : profile === 'kimi' ? 'Kimi' : profile.toUpperCase();
|
||||
console.error(`[i] Delegating to ${modelName}...`);
|
||||
}
|
||||
|
||||
const proc = spawn(claudeCli, args, {
|
||||
cwd,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
timeout
|
||||
timeout,
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
@@ -256,7 +268,7 @@ export class HeadlessExecutor {
|
||||
|
||||
// Show real-time tool use with verbose details
|
||||
if (showProgress && msg.type === 'assistant') {
|
||||
const toolUses = msg.message?.content?.filter(c => c.type === 'tool_use') || [];
|
||||
const toolUses = msg.message?.content?.filter((c) => c.type === 'tool_use') || [];
|
||||
|
||||
for (const tool of toolUses) {
|
||||
process.stderr.write('\r\x1b[K'); // Clear line
|
||||
@@ -270,9 +282,10 @@ export class HeadlessExecutor {
|
||||
case 'Bash':
|
||||
if (toolInput.command) {
|
||||
// Truncate long commands
|
||||
const cmd = toolInput.command.length > 80
|
||||
? toolInput.command.substring(0, 77) + '...'
|
||||
: toolInput.command;
|
||||
const cmd =
|
||||
toolInput.command.length > 80
|
||||
? toolInput.command.substring(0, 77) + '...'
|
||||
: toolInput.command;
|
||||
verboseMsg += `: ${cmd}`;
|
||||
}
|
||||
break;
|
||||
@@ -317,9 +330,10 @@ export class HeadlessExecutor {
|
||||
if (toolInput.description) {
|
||||
verboseMsg += `: ${toolInput.description}`;
|
||||
} else if (toolInput.prompt) {
|
||||
const prompt = toolInput.prompt.length > 60
|
||||
? toolInput.prompt.substring(0, 57) + '...'
|
||||
: toolInput.prompt;
|
||||
const prompt =
|
||||
toolInput.prompt.length > 60
|
||||
? toolInput.prompt.substring(0, 57) + '...'
|
||||
: toolInput.prompt;
|
||||
verboseMsg += `: ${prompt}`;
|
||||
}
|
||||
break;
|
||||
@@ -327,7 +341,9 @@ export class HeadlessExecutor {
|
||||
case 'TodoWrite':
|
||||
if (toolInput.todos && Array.isArray(toolInput.todos)) {
|
||||
// Show in_progress task instead of just count
|
||||
const inProgressTask = toolInput.todos.find((t: any) => t.status === 'in_progress');
|
||||
const inProgressTask = toolInput.todos.find(
|
||||
(t: any) => t.status === 'in_progress'
|
||||
);
|
||||
if (inProgressTask && inProgressTask.activeForm) {
|
||||
verboseMsg += `: ${inProgressTask.activeForm}`;
|
||||
} else {
|
||||
@@ -366,7 +382,9 @@ export class HeadlessExecutor {
|
||||
} catch (parseError) {
|
||||
// Skip malformed JSON lines (shouldn't happen with stream-json)
|
||||
if (process.env.CCS_DEBUG) {
|
||||
console.error(`[!] Failed to parse stream-json line: ${(parseError as Error).message}`);
|
||||
console.error(
|
||||
`[!] Failed to parse stream-json line: ${(parseError as Error).message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,12 +434,12 @@ export class HeadlessExecutor {
|
||||
profile,
|
||||
duration,
|
||||
timedOut: false,
|
||||
success: (exitCode === 0) && !timedOut,
|
||||
messages // Include all stream-json messages
|
||||
success: exitCode === 0 && !timedOut,
|
||||
messages, // Include all stream-json messages
|
||||
};
|
||||
|
||||
// Extract metadata from final 'result' message in stream-json
|
||||
const resultMessage = messages.find(m => m.type === 'result');
|
||||
const resultMessage = messages.find((m) => m.type === 'result');
|
||||
if (resultMessage) {
|
||||
// Add parsed fields from result message
|
||||
result.sessionId = resultMessage.session_id || undefined;
|
||||
@@ -449,19 +467,20 @@ export class HeadlessExecutor {
|
||||
if (resumeSession || sessionId) {
|
||||
// Update existing session
|
||||
sessionMgr.updateSession(profile, result.sessionId, {
|
||||
totalCost: result.totalCost
|
||||
totalCost: result.totalCost,
|
||||
});
|
||||
} else {
|
||||
// Store new session
|
||||
sessionMgr.storeSession(profile, {
|
||||
sessionId: result.sessionId,
|
||||
totalCost: result.totalCost,
|
||||
cwd: result.cwd
|
||||
cwd: result.cwd,
|
||||
});
|
||||
}
|
||||
|
||||
// Cleanup expired sessions periodically
|
||||
if (Math.random() < 0.1) { // 10% chance
|
||||
if (Math.random() < 0.1) {
|
||||
// 10% chance
|
||||
sessionMgr.cleanupExpired();
|
||||
}
|
||||
}
|
||||
@@ -490,7 +509,9 @@ export class HeadlessExecutor {
|
||||
}
|
||||
|
||||
if (process.env.CCS_DEBUG) {
|
||||
console.error(`[!] Timeout reached after ${timeout}ms, sending SIGTERM for graceful shutdown...`);
|
||||
console.error(
|
||||
`[!] Timeout reached after ${timeout}ms, sending SIGTERM for graceful shutdown...`
|
||||
);
|
||||
}
|
||||
|
||||
// Send SIGTERM for graceful shutdown
|
||||
@@ -523,9 +544,7 @@ export class HeadlessExecutor {
|
||||
private static _validatePermissionMode(mode: string): void {
|
||||
const VALID_MODES = ['default', 'plan', 'acceptEdits', 'bypassPermissions'];
|
||||
if (!VALID_MODES.includes(mode)) {
|
||||
throw new Error(
|
||||
`Invalid permission mode: "${mode}". Valid modes: ${VALID_MODES.join(', ')}`
|
||||
);
|
||||
throw new Error(`Invalid permission mode: "${mode}". Valid modes: ${VALID_MODES.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -587,7 +606,9 @@ export class HeadlessExecutor {
|
||||
lastError = error as Error;
|
||||
|
||||
if (attempt < maxRetries) {
|
||||
console.error(`[!] Attempt ${attempt + 1} errored: ${(error as Error).message}, retrying...`);
|
||||
console.error(
|
||||
`[!] Attempt ${attempt + 1} errored: ${(error as Error).message}, retrying...`
|
||||
);
|
||||
await this._sleep(1000 * (attempt + 1));
|
||||
}
|
||||
}
|
||||
@@ -604,7 +625,7 @@ export class HeadlessExecutor {
|
||||
* @private
|
||||
*/
|
||||
private static _sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -659,11 +680,11 @@ export class HeadlessExecutor {
|
||||
static async testProfile(profile: string): Promise<boolean> {
|
||||
try {
|
||||
const result = await this.execute(profile, 'Say "test successful"', {
|
||||
timeout: 10000
|
||||
timeout: 10000,
|
||||
});
|
||||
return result.success;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,23 @@ class ResultFormatter {
|
||||
* Format execution result with complete source-of-truth
|
||||
*/
|
||||
static format(result: ExecutionResult): string {
|
||||
const { profile, cwd, exitCode, stdout, stderr, duration, success, content, sessionId, totalCost, numTurns, subtype, permissionDenials, errors, timedOut } = result;
|
||||
const {
|
||||
profile,
|
||||
cwd,
|
||||
exitCode,
|
||||
stdout,
|
||||
stderr,
|
||||
duration,
|
||||
success,
|
||||
content,
|
||||
sessionId,
|
||||
totalCost,
|
||||
numTurns,
|
||||
subtype,
|
||||
permissionDenials,
|
||||
errors,
|
||||
timedOut,
|
||||
} = result;
|
||||
|
||||
// Handle timeout (graceful termination)
|
||||
if (timedOut) {
|
||||
@@ -124,7 +140,7 @@ class ResultFormatter {
|
||||
/write:\s*([^\n\r]+)/gi,
|
||||
/new file:\s*([^\n\r]+)/gi,
|
||||
/generated:\s*([^\n\r]+)/gi,
|
||||
/added:\s*([^\n\r]+)/gi
|
||||
/added:\s*([^\n\r]+)/gi,
|
||||
];
|
||||
|
||||
const modifiedPatterns = [
|
||||
@@ -133,7 +149,7 @@ class ResultFormatter {
|
||||
/updated:\s*([^\n\r]+)/gi,
|
||||
/edit:\s*([^\n\r]+)/gi,
|
||||
/edited:\s*([^\n\r]+)/gi,
|
||||
/changed:\s*([^\n\r]+)/gi
|
||||
/changed:\s*([^\n\r]+)/gi,
|
||||
];
|
||||
|
||||
// Helper to check if file is infrastructure (should be ignored)
|
||||
@@ -158,7 +174,12 @@ class ResultFormatter {
|
||||
while ((match = pattern.exec(output)) !== null) {
|
||||
const filePath = match[1].trim();
|
||||
// Don't include if already in created list or is infrastructure
|
||||
if (filePath && !modified.includes(filePath) && !created.includes(filePath) && !isInfrastructure(filePath)) {
|
||||
if (
|
||||
filePath &&
|
||||
!modified.includes(filePath) &&
|
||||
!created.includes(filePath) &&
|
||||
!isInfrastructure(filePath)
|
||||
) {
|
||||
modified.push(filePath);
|
||||
}
|
||||
}
|
||||
@@ -171,8 +192,8 @@ class ResultFormatter {
|
||||
const findCmd = `find . -type f -mmin -5 -not -path "./.git/*" -not -path "./node_modules/*" -not -path "./.claude/*" 2>/dev/null | head -20`;
|
||||
const result = execSync(findCmd, { cwd, encoding: 'utf8', timeout: 5000 });
|
||||
|
||||
const files = result.split('\n').filter(f => f.trim());
|
||||
files.forEach(file => {
|
||||
const files = result.split('\n').filter((f) => f.trim());
|
||||
files.forEach((file) => {
|
||||
const fullPath = path.join(cwd, file);
|
||||
|
||||
// Double-check not infrastructure
|
||||
@@ -188,7 +209,7 @@ class ResultFormatter {
|
||||
|
||||
// If both mtime and ctime are very recent (within 10 minutes), likely created
|
||||
// ctime = inode change time, for new files this is close to creation time
|
||||
const isVeryRecent = (now - mtime) < 600000 && (now - ctime) < 600000;
|
||||
const isVeryRecent = now - mtime < 600000 && now - ctime < 600000;
|
||||
const timeDiff = Math.abs(mtime - ctime);
|
||||
|
||||
// If mtime and ctime are very close (< 1 second apart) and both recent, it's created
|
||||
@@ -232,7 +253,15 @@ class ResultFormatter {
|
||||
/**
|
||||
* Format info box with delegation details
|
||||
*/
|
||||
private static formatInfoBox(cwd: string, profile: string, duration: number, exitCode: number, sessionId?: string, totalCost?: number, numTurns?: number): string {
|
||||
private static formatInfoBox(
|
||||
cwd: string,
|
||||
profile: string,
|
||||
duration: number,
|
||||
exitCode: number,
|
||||
sessionId?: string,
|
||||
totalCost?: number,
|
||||
numTurns?: number
|
||||
): string {
|
||||
const modelName = this.getModelDisplayName(profile);
|
||||
const durationSec = (duration / 1000).toFixed(1);
|
||||
|
||||
@@ -245,7 +274,7 @@ class ResultFormatter {
|
||||
`Working Directory: ${this.truncate(cwd, boxWidth - 22)}`,
|
||||
`Model: ${modelName}`,
|
||||
`Duration: ${durationSec}s`,
|
||||
`Exit Code: ${exitCode}`
|
||||
`Exit Code: ${exitCode}`,
|
||||
];
|
||||
|
||||
// Add JSON-specific fields if available
|
||||
@@ -321,10 +350,10 @@ class ResultFormatter {
|
||||
*/
|
||||
private static getModelDisplayName(profile: string): string {
|
||||
const displayNames: Record<string, string> = {
|
||||
'glm': 'GLM-4.6',
|
||||
'glmt': 'GLM-4.6 (Thinking)',
|
||||
'kimi': 'Kimi',
|
||||
'default': 'Claude'
|
||||
glm: 'GLM-4.6',
|
||||
glmt: 'GLM-4.6 (Thinking)',
|
||||
kimi: 'Kimi',
|
||||
default: 'Claude',
|
||||
};
|
||||
|
||||
return displayNames[profile] || profile.toUpperCase();
|
||||
@@ -462,4 +491,4 @@ class ResultFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
export { ResultFormatter };
|
||||
export { ResultFormatter };
|
||||
|
||||
@@ -56,7 +56,7 @@ class SessionManager {
|
||||
lastTurnTime: Date.now(),
|
||||
totalCost: sessionData.totalCost || 0,
|
||||
turns: 1,
|
||||
cwd: sessionData.cwd || process.cwd()
|
||||
cwd: sessionData.cwd || process.cwd(),
|
||||
};
|
||||
|
||||
this.saveSessions(sessions);
|
||||
@@ -80,8 +80,13 @@ class SessionManager {
|
||||
this.saveSessions(sessions);
|
||||
|
||||
if (process.env.CCS_DEBUG) {
|
||||
const cost = sessions[key].totalCost !== undefined && sessions[key].totalCost !== null ? sessions[key].totalCost.toFixed(4) : '0.0000';
|
||||
console.error(`[i] Updated session: ${sessionId}, total: $${cost}, turns: ${sessions[key].turns}`);
|
||||
const cost =
|
||||
sessions[key].totalCost !== undefined && sessions[key].totalCost !== null
|
||||
? sessions[key].totalCost.toFixed(4)
|
||||
: '0.0000';
|
||||
console.error(
|
||||
`[i] Updated session: ${sessionId}, total: $${cost}, turns: ${sessions[key].turns}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,7 +119,7 @@ class SessionManager {
|
||||
const maxAge = 30 * 24 * 60 * 60 * 1000; // 30 days
|
||||
|
||||
let cleaned = 0;
|
||||
Object.keys(sessions).forEach(key => {
|
||||
Object.keys(sessions).forEach((key) => {
|
||||
if (now - sessions[key].lastTurnTime > maxAge) {
|
||||
delete sessions[key];
|
||||
cleaned++;
|
||||
@@ -156,15 +161,11 @@ class SessionManager {
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
||||
}
|
||||
fs.writeFileSync(
|
||||
this.sessionsPath,
|
||||
JSON.stringify(sessions, null, 2),
|
||||
{ mode: 0o600 }
|
||||
);
|
||||
fs.writeFileSync(this.sessionsPath, JSON.stringify(sessions, null, 2), { mode: 0o600 });
|
||||
} catch (error) {
|
||||
console.error(`[!] Failed to save sessions: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { SessionManager };
|
||||
export { SessionManager };
|
||||
|
||||
@@ -57,7 +57,7 @@ class SettingsParser {
|
||||
|
||||
return {
|
||||
allowedTools: allowed,
|
||||
disallowedTools: denied
|
||||
disallowedTools: denied,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -78,17 +78,11 @@ class SettingsParser {
|
||||
// Merge permissions (local overrides shared)
|
||||
return {
|
||||
permissions: {
|
||||
allow: [
|
||||
...(shared.permissions?.allow || []),
|
||||
...(local.permissions?.allow || [])
|
||||
],
|
||||
deny: [
|
||||
...(shared.permissions?.deny || []),
|
||||
...(local.permissions?.deny || [])
|
||||
],
|
||||
allow: [...(shared.permissions?.allow || []), ...(local.permissions?.allow || [])],
|
||||
deny: [...(shared.permissions?.deny || []), ...(local.permissions?.deny || [])],
|
||||
// Local defaultMode takes priority over shared
|
||||
defaultMode: local.permissions?.defaultMode || shared.permissions?.defaultMode || undefined
|
||||
}
|
||||
defaultMode: local.permissions?.defaultMode || shared.permissions?.defaultMode || undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -112,4 +106,4 @@ class SettingsParser {
|
||||
}
|
||||
}
|
||||
|
||||
export { SettingsParser };
|
||||
export { SettingsParser };
|
||||
|
||||
@@ -156,7 +156,7 @@ export class DeltaAccumulator {
|
||||
type: type,
|
||||
content: '',
|
||||
started: true,
|
||||
stopped: false
|
||||
stopped: false,
|
||||
};
|
||||
this.contentBlocks.push(block);
|
||||
|
||||
@@ -215,7 +215,9 @@ export class DeltaAccumulator {
|
||||
|
||||
// FIX: Log block closure for debugging (helps diagnose timing issues)
|
||||
if (block.type === 'thinking' && process.env.CCS_DEBUG === '1') {
|
||||
console.error(`[DeltaAccumulator] Stopped thinking block ${block.index}: ${block.content?.length || 0} chars`);
|
||||
console.error(
|
||||
`[DeltaAccumulator] Stopped thinking block ${block.index}: ${block.content?.length || 0} chars`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,8 +248,8 @@ export class DeltaAccumulator {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: '',
|
||||
arguments: ''
|
||||
}
|
||||
arguments: '',
|
||||
},
|
||||
};
|
||||
this.toolCalls.push(toolCall);
|
||||
this.toolCallsIndex[index] = toolCall;
|
||||
@@ -304,7 +306,7 @@ export class DeltaAccumulator {
|
||||
const recentBlocks = this.contentBlocks.slice(-this.loopDetectionThreshold);
|
||||
|
||||
// Check if all recent blocks are thinking blocks
|
||||
const allThinking = recentBlocks.every(b => b.type === 'thinking');
|
||||
const allThinking = recentBlocks.every((b) => b.type === 'thinking');
|
||||
|
||||
// Check if no tool calls have been made at all
|
||||
const noToolCalls = this.toolCalls.length === 0;
|
||||
@@ -342,8 +344,8 @@ export class DeltaAccumulator {
|
||||
loopDetected: this.loopDetected,
|
||||
usage: {
|
||||
input_tokens: this.inputTokens,
|
||||
output_tokens: this.outputTokens
|
||||
}
|
||||
output_tokens: this.outputTokens,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -479,4 +481,4 @@ export class DeltaAccumulator {
|
||||
getToolCall(index: number): ToolCall | undefined {
|
||||
return this.toolCallsIndex[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+51
-40
@@ -83,10 +83,12 @@ export class GlmtProxy {
|
||||
constructor(config: GlmtProxyConfig = {}) {
|
||||
this.transformer = new GlmtTransformer({
|
||||
verbose: config.verbose,
|
||||
debugLog: config.debugLog || process.env.CCS_DEBUG === '1' || process.env.CCS_DEBUG_LOG === '1'
|
||||
debugLog:
|
||||
config.debugLog || process.env.CCS_DEBUG === '1' || process.env.CCS_DEBUG_LOG === '1',
|
||||
});
|
||||
// Use ANTHROPIC_BASE_URL from environment (set by settings.json) or fallback to Z.AI default
|
||||
this.upstreamUrl = process.env.ANTHROPIC_BASE_URL || 'https://api.z.ai/api/coding/paas/v4/chat/completions';
|
||||
this.upstreamUrl =
|
||||
process.env.ANTHROPIC_BASE_URL || 'https://api.z.ai/api/coding/paas/v4/chat/completions';
|
||||
this.server = null;
|
||||
this.port = null;
|
||||
this.verbose = config.verbose || false;
|
||||
@@ -111,12 +113,16 @@ export class GlmtProxy {
|
||||
|
||||
// Info message (only show in verbose mode)
|
||||
if (this.verbose) {
|
||||
console.error(`[glmt] Proxy listening on port ${this.port} (streaming with auto-fallback)`);
|
||||
console.error(
|
||||
`[glmt] Proxy listening on port ${this.port} (streaming with auto-fallback)`
|
||||
);
|
||||
}
|
||||
|
||||
// Debug mode notice
|
||||
if ((this.transformer as unknown as { debugLog: boolean }).debugLog) {
|
||||
console.error(`[glmt] Debug logging enabled: ${(this.transformer as unknown as { debugLogDir: string }).debugLogDir}`);
|
||||
console.error(
|
||||
`[glmt] Debug logging enabled: ${(this.transformer as unknown as { debugLogDir: string }).debugLogDir}`
|
||||
);
|
||||
console.error(`[glmt] WARNING: Debug logs contain full request/response data`);
|
||||
}
|
||||
|
||||
@@ -157,18 +163,22 @@ export class GlmtProxy {
|
||||
} catch (jsonError) {
|
||||
const err = jsonError as Error;
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
error: {
|
||||
type: 'invalid_request_error',
|
||||
message: 'Invalid JSON in request body: ' + err.message
|
||||
}
|
||||
}));
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
error: {
|
||||
type: 'invalid_request_error',
|
||||
message: 'Invalid JSON in request body: ' + err.message,
|
||||
},
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Log thinking parameter for debugging
|
||||
if (anthropicRequest.thinking) {
|
||||
this.log(`Request contains thinking parameter: ${JSON.stringify(anthropicRequest.thinking)}`);
|
||||
this.log(
|
||||
`Request contains thinking parameter: ${JSON.stringify(anthropicRequest.thinking)}`
|
||||
);
|
||||
} else {
|
||||
this.log(`Request does NOT contain thinking parameter (will use message tags or default)`);
|
||||
}
|
||||
@@ -192,7 +202,6 @@ export class GlmtProxy {
|
||||
} else {
|
||||
await this.handleBufferedRequest(req, res, anthropicRequest, startTime);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
console.error('[glmt-proxy] Request error:', err.message);
|
||||
@@ -200,12 +209,14 @@ export class GlmtProxy {
|
||||
this.log(`Request failed after ${duration}ms: ${err.message}`);
|
||||
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
error: {
|
||||
type: 'proxy_error',
|
||||
message: err.message
|
||||
}
|
||||
}));
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
error: {
|
||||
type: 'proxy_error',
|
||||
message: err.message,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,16 +230,17 @@ export class GlmtProxy {
|
||||
startTime: number
|
||||
): Promise<void> {
|
||||
// Transform to OpenAI format
|
||||
const { openaiRequest, thinkingConfig } =
|
||||
this.transformer.transformRequest(anthropicRequest as unknown as Parameters<typeof this.transformer.transformRequest>[0]);
|
||||
const { openaiRequest, thinkingConfig } = this.transformer.transformRequest(
|
||||
anthropicRequest as unknown as Parameters<typeof this.transformer.transformRequest>[0]
|
||||
);
|
||||
|
||||
this.log(`Transformed request, thinking: ${thinkingConfig.thinking}`);
|
||||
|
||||
// Forward to Z.AI
|
||||
const openaiResponse = await this.forwardToUpstream(
|
||||
const openaiResponse = (await this.forwardToUpstream(
|
||||
openaiRequest as unknown as OpenAIRequest,
|
||||
{}
|
||||
) as OpenAIResponse;
|
||||
)) as OpenAIResponse;
|
||||
|
||||
this.log(`Received response from upstream`);
|
||||
|
||||
@@ -241,7 +253,7 @@ export class GlmtProxy {
|
||||
// Return to Claude CLI
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
});
|
||||
res.end(JSON.stringify(anthropicResponse));
|
||||
|
||||
@@ -261,8 +273,9 @@ export class GlmtProxy {
|
||||
this.log('Using streaming mode');
|
||||
|
||||
// Transform request
|
||||
const { openaiRequest, thinkingConfig } =
|
||||
this.transformer.transformRequest(anthropicRequest as unknown as Parameters<typeof this.transformer.transformRequest>[0]);
|
||||
const { openaiRequest, thinkingConfig } = this.transformer.transformRequest(
|
||||
anthropicRequest as unknown as Parameters<typeof this.transformer.transformRequest>[0]
|
||||
);
|
||||
|
||||
// Force streaming
|
||||
(openaiRequest as OpenAIRequest).stream = true;
|
||||
@@ -271,9 +284,9 @@ export class GlmtProxy {
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive',
|
||||
Connection: 'keep-alive',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'X-Accel-Buffering': 'no' // Disable proxy buffering
|
||||
'X-Accel-Buffering': 'no', // Disable proxy buffering
|
||||
});
|
||||
|
||||
// Disable Nagle's algorithm to prevent buffering at socket level
|
||||
@@ -335,9 +348,9 @@ export class GlmtProxy {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': Buffer.byteLength(requestBody),
|
||||
'Authorization': process.env.ANTHROPIC_AUTH_TOKEN || '',
|
||||
'User-Agent': 'CCS-GLMT-Proxy/1.0'
|
||||
}
|
||||
Authorization: process.env.ANTHROPIC_AUTH_TOKEN || '',
|
||||
'User-Agent': 'CCS-GLMT-Proxy/1.0',
|
||||
},
|
||||
};
|
||||
|
||||
// Debug logging
|
||||
@@ -362,9 +375,7 @@ export class GlmtProxy {
|
||||
|
||||
// Check for non-200 status
|
||||
if (res.statusCode !== 200) {
|
||||
reject(new Error(
|
||||
`Upstream error: ${res.statusCode} ${res.statusMessage}\n${body}`
|
||||
));
|
||||
reject(new Error(`Upstream error: ${res.statusCode} ${res.statusMessage}\n${body}`));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -409,10 +420,10 @@ export class GlmtProxy {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': Buffer.byteLength(requestBody),
|
||||
'Authorization': process.env.ANTHROPIC_AUTH_TOKEN || '',
|
||||
Authorization: process.env.ANTHROPIC_AUTH_TOKEN || '',
|
||||
'User-Agent': 'CCS-GLMT-Proxy/1.0',
|
||||
'Accept': 'text/event-stream'
|
||||
}
|
||||
Accept: 'text/event-stream',
|
||||
},
|
||||
};
|
||||
|
||||
this.log(`Forwarding streaming request to: ${url.hostname}${url.pathname}`);
|
||||
@@ -427,7 +438,7 @@ export class GlmtProxy {
|
||||
clearTimeout(timeoutHandle);
|
||||
if (upstreamRes.statusCode !== 200) {
|
||||
let body = '';
|
||||
upstreamRes.on('data', (chunk: Buffer) => body += chunk.toString());
|
||||
upstreamRes.on('data', (chunk: Buffer) => (body += chunk.toString()));
|
||||
upstreamRes.on('end', () => {
|
||||
reject(new Error(`Upstream error: ${upstreamRes.statusCode}\n${body}`));
|
||||
});
|
||||
@@ -441,12 +452,12 @@ export class GlmtProxy {
|
||||
try {
|
||||
const events = parser.parse(chunk);
|
||||
|
||||
events.forEach(event => {
|
||||
events.forEach((event) => {
|
||||
// Transform OpenAI delta → Anthropic events
|
||||
const anthropicEvents = this.transformer.transformDelta(event, accumulator);
|
||||
|
||||
// Forward to Claude CLI with immediate flush
|
||||
anthropicEvents.forEach(evt => {
|
||||
anthropicEvents.forEach((evt) => {
|
||||
const eventLine = `event: ${evt.event}\n`;
|
||||
const dataLine = `data: ${JSON.stringify(evt.data)}\n\n`;
|
||||
clientRes.write(eventLine + dataLine);
|
||||
@@ -521,7 +532,7 @@ if (require.main === module) {
|
||||
|
||||
const proxy = new GlmtProxy({ verbose });
|
||||
|
||||
proxy.start().catch(error => {
|
||||
proxy.start().catch((error) => {
|
||||
console.error('[glmt-proxy] Failed to start:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
+122
-104
@@ -203,7 +203,7 @@ export class GlmtTransformer {
|
||||
private verbose: boolean;
|
||||
private debugLog: boolean;
|
||||
private debugMode: boolean;
|
||||
debugLogDir: string; // public for external access
|
||||
debugLogDir: string; // public for external access
|
||||
private modelMaxTokens: Record<string, number>;
|
||||
private localeEnforcer: LocaleEnforcer;
|
||||
private reasoningEnforcer: ReasoningEnforcer;
|
||||
@@ -221,7 +221,7 @@ export class GlmtTransformer {
|
||||
this.modelMaxTokens = {
|
||||
'GLM-4.6': 128000,
|
||||
'GLM-4.5': 96000,
|
||||
'GLM-4.5-air': 16000
|
||||
'GLM-4.5-air': 16000,
|
||||
};
|
||||
|
||||
// Initialize locale enforcer (always enforce English)
|
||||
@@ -229,7 +229,7 @@ export class GlmtTransformer {
|
||||
|
||||
// Initialize reasoning enforcer (enabled by default for all GLMT usage)
|
||||
this.reasoningEnforcer = new ReasoningEnforcer({
|
||||
enabled: config.explicitReasoning ?? true
|
||||
enabled: config.explicitReasoning ?? true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -242,9 +242,7 @@ export class GlmtTransformer {
|
||||
|
||||
try {
|
||||
// 1. Extract thinking control from messages (tags like <Thinking:On|Off>)
|
||||
const thinkingConfig = this.extractThinkingControl(
|
||||
anthropicRequest.messages || []
|
||||
);
|
||||
const thinkingConfig = this.extractThinkingControl(anthropicRequest.messages || []);
|
||||
const hasControlTags = this.hasThinkingTags(anthropicRequest.messages || []);
|
||||
|
||||
// 2. Detect "think" keywords in user prompts (Anthropic-style)
|
||||
@@ -252,7 +250,9 @@ export class GlmtTransformer {
|
||||
if (keywordConfig && !anthropicRequest.thinking && !hasControlTags) {
|
||||
thinkingConfig.thinking = keywordConfig.thinking;
|
||||
thinkingConfig.effort = keywordConfig.effort;
|
||||
this.log(`Detected think keyword: ${keywordConfig.keyword}, effort=${keywordConfig.effort}`);
|
||||
this.log(
|
||||
`Detected think keyword: ${keywordConfig.keyword}, effort=${keywordConfig.effort}`
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Check anthropicRequest.thinking parameter (takes precedence)
|
||||
@@ -275,12 +275,16 @@ export class GlmtTransformer {
|
||||
|
||||
// 4. Inject locale instruction before sanitization
|
||||
const messagesWithLocale = this.localeEnforcer.injectInstruction(
|
||||
(anthropicRequest.messages || []) as Parameters<typeof this.localeEnforcer.injectInstruction>[0]
|
||||
(anthropicRequest.messages || []) as Parameters<
|
||||
typeof this.localeEnforcer.injectInstruction
|
||||
>[0]
|
||||
) as unknown as Message[];
|
||||
|
||||
// 4.5. Inject reasoning instruction (if enabled or thinking requested)
|
||||
const messagesWithReasoning = this.reasoningEnforcer.injectInstruction(
|
||||
messagesWithLocale as unknown as Parameters<typeof this.reasoningEnforcer.injectInstruction>[0],
|
||||
messagesWithLocale as unknown as Parameters<
|
||||
typeof this.reasoningEnforcer.injectInstruction
|
||||
>[0],
|
||||
thinkingConfig
|
||||
) as unknown as Message[];
|
||||
|
||||
@@ -289,14 +293,14 @@ export class GlmtTransformer {
|
||||
model: glmModel,
|
||||
messages: this.sanitizeMessages(messagesWithReasoning),
|
||||
max_tokens: this.getMaxTokens(glmModel),
|
||||
stream: anthropicRequest.stream ?? false
|
||||
stream: anthropicRequest.stream ?? false,
|
||||
};
|
||||
|
||||
// 5.5. Transform tools parameter if present
|
||||
if (anthropicRequest.tools && anthropicRequest.tools.length > 0) {
|
||||
openaiRequest.tools = this.transformTools(anthropicRequest.tools);
|
||||
// Always use "auto" as Z.AI doesn't support other modes
|
||||
openaiRequest.tool_choice = "auto";
|
||||
openaiRequest.tool_choice = 'auto';
|
||||
this.log(`Transformed ${anthropicRequest.tools.length} tools for OpenAI format`);
|
||||
}
|
||||
|
||||
@@ -327,7 +331,7 @@ export class GlmtTransformer {
|
||||
return {
|
||||
openaiRequest: anthropicRequest,
|
||||
thinkingConfig: { thinking: false, effort: 'medium' },
|
||||
error: err.message
|
||||
error: err.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -335,7 +339,10 @@ export class GlmtTransformer {
|
||||
/**
|
||||
* Transform OpenAI response to Anthropic format
|
||||
*/
|
||||
transformResponse(openaiResponse: OpenAIResponse, _thinkingConfig: ThinkingConfig = { thinking: false, effort: 'medium' }): AnthropicResponse {
|
||||
transformResponse(
|
||||
openaiResponse: OpenAIResponse,
|
||||
_thinkingConfig: ThinkingConfig = { thinking: false, effort: 'medium' }
|
||||
): AnthropicResponse {
|
||||
// Log original response
|
||||
this.writeDebugLog('response-openai', openaiResponse);
|
||||
|
||||
@@ -352,10 +359,7 @@ export class GlmtTransformer {
|
||||
if (message.reasoning_content) {
|
||||
const length = message.reasoning_content.length;
|
||||
const lineCount = message.reasoning_content.split('\n').length;
|
||||
const preview = message.reasoning_content
|
||||
.substring(0, 100)
|
||||
.replace(/\n/g, ' ')
|
||||
.trim();
|
||||
const preview = message.reasoning_content.substring(0, 100).replace(/\n/g, ' ').trim();
|
||||
|
||||
this.log(`Detected reasoning_content:`);
|
||||
this.log(` Length: ${length} characters`);
|
||||
@@ -365,7 +369,7 @@ export class GlmtTransformer {
|
||||
content.push({
|
||||
type: 'thinking',
|
||||
thinking: message.reasoning_content,
|
||||
signature: this.generateThinkingSignature(message.reasoning_content)
|
||||
signature: this.generateThinkingSignature(message.reasoning_content),
|
||||
});
|
||||
} else {
|
||||
this.log('No reasoning_content in OpenAI response');
|
||||
@@ -376,13 +380,13 @@ export class GlmtTransformer {
|
||||
if (message.content) {
|
||||
content.push({
|
||||
type: 'text',
|
||||
text: message.content
|
||||
text: message.content,
|
||||
});
|
||||
}
|
||||
|
||||
// Handle tool_calls if present
|
||||
if (message.tool_calls && message.tool_calls.length > 0) {
|
||||
message.tool_calls.forEach(toolCall => {
|
||||
message.tool_calls.forEach((toolCall) => {
|
||||
let parsedInput: Record<string, unknown>;
|
||||
try {
|
||||
parsedInput = JSON.parse(toolCall.function.arguments || '{}');
|
||||
@@ -396,7 +400,7 @@ export class GlmtTransformer {
|
||||
type: 'tool_use',
|
||||
id: toolCall.id,
|
||||
name: toolCall.function.name,
|
||||
input: parsedInput
|
||||
input: parsedInput,
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -410,14 +414,16 @@ export class GlmtTransformer {
|
||||
stop_reason: this.mapStopReason(choice.finish_reason || 'stop'),
|
||||
usage: {
|
||||
input_tokens: openaiResponse.usage?.prompt_tokens || 0,
|
||||
output_tokens: openaiResponse.usage?.completion_tokens || 0
|
||||
}
|
||||
output_tokens: openaiResponse.usage?.completion_tokens || 0,
|
||||
},
|
||||
};
|
||||
|
||||
// Validate transformation in verbose mode
|
||||
if (this.verbose) {
|
||||
const validation = this.validateTransformation(anthropicResponse);
|
||||
this.log(`Transformation validation: ${validation.passed}/${validation.total} checks passed`);
|
||||
this.log(
|
||||
`Transformation validation: ${validation.passed}/${validation.total} checks passed`
|
||||
);
|
||||
if (!validation.valid) {
|
||||
this.log(`Failed checks: ${JSON.stringify(validation.checks, null, 2)}`);
|
||||
}
|
||||
@@ -435,13 +441,15 @@ export class GlmtTransformer {
|
||||
id: 'msg_error_' + Date.now(),
|
||||
type: 'message',
|
||||
role: 'assistant',
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: '[Transformation Error] ' + err.message
|
||||
}],
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: '[Transformation Error] ' + err.message,
|
||||
},
|
||||
],
|
||||
model: 'glm-4.6',
|
||||
stop_reason: 'end_turn',
|
||||
usage: { input_tokens: 0, output_tokens: 0 }
|
||||
usage: { input_tokens: 0, output_tokens: 0 },
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -462,29 +470,31 @@ export class GlmtTransformer {
|
||||
// If content is an array, process blocks
|
||||
if (Array.isArray(msg.content)) {
|
||||
// Separate tool_result blocks from other content
|
||||
const toolResults = msg.content.filter(block => block.type === 'tool_result');
|
||||
const textBlocks = msg.content.filter(block => block.type === 'text');
|
||||
const toolResults = msg.content.filter((block) => block.type === 'tool_result');
|
||||
const textBlocks = msg.content.filter((block) => block.type === 'text');
|
||||
// const toolUseBlocks = msg.content.filter(block => block.type === 'tool_use');
|
||||
|
||||
// CRITICAL: Tool messages must come BEFORE user text in OpenAI API
|
||||
for (const toolResult of toolResults) {
|
||||
result.push({
|
||||
role: 'tool',
|
||||
content: typeof toolResult.content === 'string'
|
||||
? toolResult.content
|
||||
: JSON.stringify(toolResult.content)
|
||||
content:
|
||||
typeof toolResult.content === 'string'
|
||||
? toolResult.content
|
||||
: JSON.stringify(toolResult.content),
|
||||
} as Message & { tool_call_id: string });
|
||||
}
|
||||
|
||||
// Add text content as user/assistant message AFTER tool messages
|
||||
if (textBlocks.length > 0) {
|
||||
const textContent = textBlocks.length === 1
|
||||
? textBlocks[0].text || ''
|
||||
: textBlocks.map(b => b.text || '').join('\n');
|
||||
const textContent =
|
||||
textBlocks.length === 1
|
||||
? textBlocks[0].text || ''
|
||||
: textBlocks.map((b) => b.text || '').join('\n');
|
||||
|
||||
result.push({
|
||||
role: msg.role,
|
||||
content: textContent
|
||||
content: textContent,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -492,7 +502,7 @@ export class GlmtTransformer {
|
||||
if (textBlocks.length === 0 && toolResults.length === 0) {
|
||||
result.push({
|
||||
role: msg.role,
|
||||
content: ''
|
||||
content: '',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -510,13 +520,13 @@ export class GlmtTransformer {
|
||||
* Transform Anthropic tools to OpenAI tools format
|
||||
*/
|
||||
private transformTools(anthropicTools: AnthropicTool[]): OpenAITool[] {
|
||||
return anthropicTools.map(tool => ({
|
||||
return anthropicTools.map((tool) => ({
|
||||
type: 'function' as const,
|
||||
function: {
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
parameters: tool.input_schema || {}
|
||||
}
|
||||
parameters: tool.input_schema || {},
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -543,7 +553,7 @@ export class GlmtTransformer {
|
||||
private extractThinkingControl(messages: Message[]): ThinkingConfig {
|
||||
const config: ThinkingConfig = {
|
||||
thinking: this.defaultThinking,
|
||||
effort: 'medium'
|
||||
effort: 'medium',
|
||||
};
|
||||
|
||||
// Scan user messages for control tags
|
||||
@@ -574,34 +584,33 @@ export class GlmtTransformer {
|
||||
*/
|
||||
private generateThinkingSignature(thinking: string): ThinkingSignature {
|
||||
// Generate signature hash
|
||||
const hash = crypto.createHash('sha256')
|
||||
.update(thinking)
|
||||
.digest('hex')
|
||||
.substring(0, 16);
|
||||
const hash = crypto.createHash('sha256').update(thinking).digest('hex').substring(0, 16);
|
||||
|
||||
return {
|
||||
type: 'thinking_signature',
|
||||
hash: hash,
|
||||
length: thinking.length,
|
||||
timestamp: Date.now()
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect Anthropic-style "think" keywords in user prompts
|
||||
*/
|
||||
private detectThinkKeywords(messages: Message[]): { thinking: boolean; effort: string; keyword: string } | null {
|
||||
private detectThinkKeywords(
|
||||
messages: Message[]
|
||||
): { thinking: boolean; effort: string; keyword: string } | null {
|
||||
if (!messages || messages.length === 0) return null;
|
||||
|
||||
// Extract text from user messages
|
||||
const text = messages
|
||||
.filter(m => m.role === 'user')
|
||||
.map(m => {
|
||||
.filter((m) => m.role === 'user')
|
||||
.map((m) => {
|
||||
if (typeof m.content === 'string') return m.content;
|
||||
if (Array.isArray(m.content)) {
|
||||
return m.content
|
||||
.filter(block => block.type === 'text')
|
||||
.map(block => block.text || '')
|
||||
.filter((block) => block.type === 'text')
|
||||
.map((block) => block.text || '')
|
||||
.join(' ');
|
||||
}
|
||||
return '';
|
||||
@@ -628,7 +637,10 @@ export class GlmtTransformer {
|
||||
/**
|
||||
* Inject reasoning parameters into OpenAI request
|
||||
*/
|
||||
private injectReasoningParams(openaiRequest: OpenAIRequest, thinkingConfig: ThinkingConfig): void {
|
||||
private injectReasoningParams(
|
||||
openaiRequest: OpenAIRequest,
|
||||
thinkingConfig: ThinkingConfig
|
||||
): void {
|
||||
// Always enable sampling for temperature/top_p to work
|
||||
openaiRequest.do_sample = true;
|
||||
|
||||
@@ -659,10 +671,10 @@ export class GlmtTransformer {
|
||||
*/
|
||||
private mapStopReason(openaiReason: string): string {
|
||||
const mapping: Record<string, string> = {
|
||||
'stop': 'end_turn',
|
||||
'length': 'max_tokens',
|
||||
'tool_calls': 'tool_use',
|
||||
'content_filter': 'stop_sequence'
|
||||
stop: 'end_turn',
|
||||
length: 'max_tokens',
|
||||
tool_calls: 'tool_use',
|
||||
content_filter: 'stop_sequence',
|
||||
};
|
||||
return mapping[openaiReason] || 'end_turn';
|
||||
}
|
||||
@@ -699,10 +711,11 @@ export class GlmtTransformer {
|
||||
private validateTransformation(anthropicResponse: AnthropicResponse): ValidationResult {
|
||||
const checks: Record<string, boolean> = {
|
||||
hasContent: Boolean(anthropicResponse.content && anthropicResponse.content.length > 0),
|
||||
hasThinking: anthropicResponse.content?.some(block => block.type === 'thinking') || false,
|
||||
hasText: anthropicResponse.content?.some(block => block.type === 'text') || false,
|
||||
validStructure: anthropicResponse.type === 'message' && anthropicResponse.role === 'assistant',
|
||||
hasUsage: Boolean(anthropicResponse.usage)
|
||||
hasThinking: anthropicResponse.content?.some((block) => block.type === 'thinking') || false,
|
||||
hasText: anthropicResponse.content?.some((block) => block.type === 'text') || false,
|
||||
validStructure:
|
||||
anthropicResponse.type === 'message' && anthropicResponse.role === 'assistant',
|
||||
hasUsage: Boolean(anthropicResponse.usage),
|
||||
};
|
||||
|
||||
const passed = Object.values(checks).filter(Boolean).length;
|
||||
@@ -767,7 +780,9 @@ export class GlmtTransformer {
|
||||
|
||||
if (this.debugMode) {
|
||||
console.error(`[GLMT-DEBUG] Reasoning delta: ${delta.reasoning_content.length} chars`);
|
||||
console.error(`[GLMT-DEBUG] Current block: ${currentBlock?.type || 'none'}, index: ${currentBlock?.index ?? 'N/A'}`);
|
||||
console.error(
|
||||
`[GLMT-DEBUG] Current block: ${currentBlock?.type || 'none'}, index: ${currentBlock?.index ?? 'N/A'}`
|
||||
);
|
||||
}
|
||||
|
||||
if (!currentBlock || currentBlock.type !== 'thinking') {
|
||||
@@ -781,10 +796,9 @@ export class GlmtTransformer {
|
||||
}
|
||||
|
||||
accumulator.addDelta(delta.reasoning_content);
|
||||
events.push(this.createThinkingDeltaEvent(
|
||||
accumulator.getCurrentBlock()!,
|
||||
delta.reasoning_content
|
||||
));
|
||||
events.push(
|
||||
this.createThinkingDeltaEvent(accumulator.getCurrentBlock()!, delta.reasoning_content)
|
||||
);
|
||||
}
|
||||
|
||||
// Text content delta
|
||||
@@ -808,15 +822,14 @@ export class GlmtTransformer {
|
||||
}
|
||||
|
||||
accumulator.addDelta(delta.content);
|
||||
events.push(this.createTextDeltaEvent(
|
||||
accumulator.getCurrentBlock()!,
|
||||
delta.content
|
||||
));
|
||||
events.push(this.createTextDeltaEvent(accumulator.getCurrentBlock()!, delta.content));
|
||||
}
|
||||
|
||||
// Check for planning loop
|
||||
if (accumulator.checkForLoop()) {
|
||||
this.log('WARNING: Planning loop detected - 3 consecutive thinking blocks with no tool calls');
|
||||
this.log(
|
||||
'WARNING: Planning loop detected - 3 consecutive thinking blocks with no tool calls'
|
||||
);
|
||||
this.log('Forcing early finalization to prevent unbounded planning');
|
||||
|
||||
// Close current block if any
|
||||
@@ -872,9 +885,9 @@ export class GlmtTransformer {
|
||||
content_block: {
|
||||
type: 'tool_use',
|
||||
id: toolCall?.id || `tool_${toolCallDelta.index}`,
|
||||
name: toolCall?.function?.name || ''
|
||||
}
|
||||
}
|
||||
name: toolCall?.function?.name || '',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -889,9 +902,9 @@ export class GlmtTransformer {
|
||||
index: currentToolBlock.index,
|
||||
delta: {
|
||||
type: 'input_json_delta',
|
||||
partial_json: toolCallDelta.function.arguments
|
||||
}
|
||||
}
|
||||
partial_json: toolCallDelta.function.arguments,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -910,7 +923,10 @@ export class GlmtTransformer {
|
||||
|
||||
// Debug logging for generated events
|
||||
if (this.debugLog && events.length > 0) {
|
||||
this.writeDebugLog('delta-anthropic-events', { events, accumulator: accumulator.getSummary() });
|
||||
this.writeDebugLog('delta-anthropic-events', {
|
||||
events,
|
||||
accumulator: accumulator.getSummary(),
|
||||
});
|
||||
}
|
||||
|
||||
return events;
|
||||
@@ -945,21 +961,21 @@ export class GlmtTransformer {
|
||||
data: {
|
||||
type: 'message_delta',
|
||||
delta: {
|
||||
stop_reason: this.mapStopReason(accumulator.getFinishReason() || 'stop')
|
||||
stop_reason: this.mapStopReason(accumulator.getFinishReason() || 'stop'),
|
||||
},
|
||||
usage: {
|
||||
input_tokens: accumulator.getInputTokens(),
|
||||
output_tokens: accumulator.getOutputTokens()
|
||||
}
|
||||
}
|
||||
output_tokens: accumulator.getOutputTokens(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Message stop
|
||||
events.push({
|
||||
event: 'message_stop',
|
||||
data: {
|
||||
type: 'message_stop'
|
||||
}
|
||||
type: 'message_stop',
|
||||
},
|
||||
});
|
||||
|
||||
accumulator.setFinalized(true);
|
||||
@@ -983,10 +999,10 @@ export class GlmtTransformer {
|
||||
stop_reason: null,
|
||||
usage: {
|
||||
input_tokens: accumulator.getInputTokens(),
|
||||
output_tokens: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
output_tokens: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1001,9 +1017,9 @@ export class GlmtTransformer {
|
||||
index: block.index,
|
||||
content_block: {
|
||||
type: block.type,
|
||||
[block.type]: ''
|
||||
}
|
||||
}
|
||||
[block.type]: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1018,9 +1034,9 @@ export class GlmtTransformer {
|
||||
index: block.index,
|
||||
delta: {
|
||||
type: 'thinking_delta',
|
||||
thinking: delta
|
||||
}
|
||||
}
|
||||
thinking: delta,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1035,9 +1051,9 @@ export class GlmtTransformer {
|
||||
index: block.index,
|
||||
delta: {
|
||||
type: 'text_delta',
|
||||
text: delta
|
||||
}
|
||||
}
|
||||
text: delta,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1049,7 +1065,9 @@ export class GlmtTransformer {
|
||||
if (!block.content || block.content.length === 0) {
|
||||
if (this.verbose) {
|
||||
this.log(`WARNING: Skipping signature for empty thinking block ${block.index}`);
|
||||
this.log(`This indicates a race condition - signature requested before content accumulated`);
|
||||
this.log(
|
||||
`This indicates a race condition - signature requested before content accumulated`
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1067,9 +1085,9 @@ export class GlmtTransformer {
|
||||
index: block.index,
|
||||
delta: {
|
||||
type: 'thinking_signature_delta',
|
||||
signature: signature
|
||||
}
|
||||
}
|
||||
signature: signature,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1081,8 +1099,8 @@ export class GlmtTransformer {
|
||||
event: 'content_block_stop',
|
||||
data: {
|
||||
type: 'content_block_stop',
|
||||
index: block.index
|
||||
}
|
||||
index: block.index,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,9 @@ export class LocaleEnforcer {
|
||||
private instruction: string;
|
||||
|
||||
constructor(options: LocaleEnforcerOptions = {}) {
|
||||
this.instruction = options.instruction || "CRITICAL: You MUST respond in English only, regardless of the input language or context. This is a strict requirement.";
|
||||
this.instruction =
|
||||
options.instruction ||
|
||||
'CRITICAL: You MUST respond in English only, regardless of the input language or context. This is a strict requirement.';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,7 +44,7 @@ export class LocaleEnforcer {
|
||||
const modifiedMessages = JSON.parse(JSON.stringify(messages)) as Message[];
|
||||
|
||||
// Strategy 1: Inject into system prompt (preferred)
|
||||
const systemIndex = modifiedMessages.findIndex(m => m.role === 'system');
|
||||
const systemIndex = modifiedMessages.findIndex((m) => m.role === 'system');
|
||||
if (systemIndex >= 0) {
|
||||
const systemMsg = modifiedMessages[systemIndex];
|
||||
|
||||
@@ -51,7 +53,7 @@ export class LocaleEnforcer {
|
||||
} else if (Array.isArray(systemMsg.content)) {
|
||||
systemMsg.content.unshift({
|
||||
type: 'text',
|
||||
text: this.instruction
|
||||
text: this.instruction,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -59,7 +61,7 @@ export class LocaleEnforcer {
|
||||
}
|
||||
|
||||
// Strategy 2: Prepend to first user message
|
||||
const userIndex = modifiedMessages.findIndex(m => m.role === 'user');
|
||||
const userIndex = modifiedMessages.findIndex((m) => m.role === 'user');
|
||||
if (userIndex >= 0) {
|
||||
const userMsg = modifiedMessages[userIndex];
|
||||
|
||||
@@ -68,7 +70,7 @@ export class LocaleEnforcer {
|
||||
} else if (Array.isArray(userMsg.content)) {
|
||||
userMsg.content.unshift({
|
||||
type: 'text',
|
||||
text: this.instruction
|
||||
text: this.instruction,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -78,4 +80,4 @@ export class LocaleEnforcer {
|
||||
// No system or user messages found (edge case)
|
||||
return modifiedMessages;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ export class ReasoningEnforcer {
|
||||
const prompt = this.selectPrompt(effort);
|
||||
|
||||
// Strategy 1: Inject into system prompt (preferred)
|
||||
const systemIndex = modifiedMessages.findIndex(m => m.role === 'system');
|
||||
const systemIndex = modifiedMessages.findIndex((m) => m.role === 'system');
|
||||
if (systemIndex >= 0) {
|
||||
const systemMsg = modifiedMessages[systemIndex];
|
||||
|
||||
@@ -72,7 +72,7 @@ export class ReasoningEnforcer {
|
||||
} else if (Array.isArray(systemMsg.content)) {
|
||||
systemMsg.content.unshift({
|
||||
type: 'text',
|
||||
text: prompt
|
||||
text: prompt,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ export class ReasoningEnforcer {
|
||||
}
|
||||
|
||||
// Strategy 2: Prepend to first user message
|
||||
const userIndex = modifiedMessages.findIndex(m => m.role === 'user');
|
||||
const userIndex = modifiedMessages.findIndex((m) => m.role === 'user');
|
||||
if (userIndex >= 0) {
|
||||
const userMsg = modifiedMessages[userIndex];
|
||||
|
||||
@@ -89,7 +89,7 @@ export class ReasoningEnforcer {
|
||||
} else if (Array.isArray(userMsg.content)) {
|
||||
userMsg.content.unshift({
|
||||
type: 'text',
|
||||
text: prompt
|
||||
text: prompt,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ OUTPUT FORMAT:
|
||||
- Final conclusion with rigorous justification)
|
||||
</reasoning_content>
|
||||
|
||||
(Write your final answer here based on your exhaustive reasoning above)`
|
||||
(Write your final answer here based on your exhaustive reasoning above)`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ export class SSEParser {
|
||||
events.push({
|
||||
event: 'done',
|
||||
data: null,
|
||||
index: this.eventCount
|
||||
index: this.eventCount,
|
||||
});
|
||||
currentEvent = { event: 'message', data: '' };
|
||||
} else {
|
||||
@@ -85,7 +85,12 @@ export class SSEParser {
|
||||
} catch (e) {
|
||||
// H-01 Fix: Log parse errors for debugging
|
||||
if (typeof console !== 'undefined' && console.error) {
|
||||
console.error('[SSEParser] Malformed JSON event:', (e as Error).message, 'Data:', data.substring(0, 100));
|
||||
console.error(
|
||||
'[SSEParser] Malformed JSON event:',
|
||||
(e as Error).message,
|
||||
'Data:',
|
||||
data.substring(0, 100)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,4 +112,4 @@ export class SSEParser {
|
||||
this.buffer = '';
|
||||
this.eventCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+159
-74
@@ -28,15 +28,15 @@ try {
|
||||
ora = oraModule.default || oraModule;
|
||||
} catch (e) {
|
||||
// ora not available, create fallback spinner that uses console.log
|
||||
ora = function(text: string): Spinner {
|
||||
ora = function (text: string): Spinner {
|
||||
return {
|
||||
start: () => ({
|
||||
succeed: (msg?: string) => console.log(msg || `[OK] ${text}`),
|
||||
fail: (msg?: string) => console.log(msg || `[X] ${text}`),
|
||||
warn: (msg?: string) => console.log(msg || `[!] ${text}`),
|
||||
info: (msg?: string) => console.log(msg || `[i] ${text}`),
|
||||
text: ''
|
||||
})
|
||||
text: '',
|
||||
}),
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -71,7 +71,13 @@ class HealthCheck {
|
||||
public errors: HealthIssue[] = [];
|
||||
public details: Record<string, HealthCheckDetails> = {};
|
||||
|
||||
addCheck(name: string, status: 'success' | 'error' | 'warning', message = '', fix: string | undefined = undefined, details: HealthCheckDetails | undefined = undefined): void {
|
||||
addCheck(
|
||||
name: string,
|
||||
status: 'success' | 'error' | 'warning',
|
||||
message = '',
|
||||
fix: string | undefined = undefined,
|
||||
details: HealthCheckDetails | undefined = undefined
|
||||
): void {
|
||||
this.checks.push({ name, status, message, fix });
|
||||
|
||||
if (status === 'error') this.errors.push({ name, message, fix });
|
||||
@@ -179,12 +185,12 @@ class Doctor {
|
||||
const result = await new Promise<string>((resolve, reject) => {
|
||||
const child = spawn(claudeCli, ['--version'], {
|
||||
stdio: 'pipe',
|
||||
timeout: 5000
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
let output = '';
|
||||
child.stdout?.on('data', (data: Buffer) => output += data);
|
||||
child.stderr?.on('data', (data: Buffer) => output += data);
|
||||
child.stdout?.on('data', (data: Buffer) => (output += data));
|
||||
child.stderr?.on('data', (data: Buffer) => (output += data));
|
||||
|
||||
child.on('close', (code: number | null) => {
|
||||
if (code === 0) resolve(output);
|
||||
@@ -198,13 +204,17 @@ class Doctor {
|
||||
const versionMatch = result.match(/(\d+\.\d+\.\d+)/);
|
||||
const version = versionMatch ? versionMatch[1] : 'unknown';
|
||||
|
||||
spinner.succeed(` ${'Claude CLI'.padEnd(26)}${colored('[OK]', 'green')} ${claudeCli} (v${version})`);
|
||||
spinner.succeed(
|
||||
` ${'Claude CLI'.padEnd(26)}${colored('[OK]', 'green')} ${claudeCli} (v${version})`
|
||||
);
|
||||
this.results.addCheck('Claude CLI', 'success', `Found: ${claudeCli}`, undefined, {
|
||||
status: 'OK',
|
||||
info: `v${version} (${claudeCli})`
|
||||
info: `v${version} (${claudeCli})`,
|
||||
});
|
||||
} catch (err) {
|
||||
spinner.fail(` ${'Claude CLI'.padEnd(26)}${colored('[X]', 'red')} Not found or not working`);
|
||||
spinner.fail(
|
||||
` ${'Claude CLI'.padEnd(26)}${colored('[X]', 'red')} Not found or not working`
|
||||
);
|
||||
this.results.addCheck(
|
||||
'Claude CLI',
|
||||
'error',
|
||||
@@ -225,7 +235,7 @@ class Doctor {
|
||||
spinner.succeed(` ${'CCS Directory'.padEnd(26)}${colored('[OK]', 'green')} ~/.ccs/`);
|
||||
this.results.addCheck('CCS Directory', 'success', undefined, undefined, {
|
||||
status: 'OK',
|
||||
info: '~/.ccs/'
|
||||
info: '~/.ccs/',
|
||||
});
|
||||
} else {
|
||||
spinner.fail(` ${'CCS Directory'.padEnd(26)}${colored('[X]', 'red')} Not found`);
|
||||
@@ -245,8 +255,18 @@ class Doctor {
|
||||
private checkConfigFiles(): void {
|
||||
const files = [
|
||||
{ path: path.join(this.ccsDir, 'config.json'), name: 'config.json', key: 'config.json' },
|
||||
{ path: path.join(this.ccsDir, 'glm.settings.json'), name: 'glm.settings.json', key: 'GLM Settings', profile: 'glm' },
|
||||
{ path: path.join(this.ccsDir, 'kimi.settings.json'), name: 'kimi.settings.json', key: 'Kimi Settings', profile: 'kimi' }
|
||||
{
|
||||
path: path.join(this.ccsDir, 'glm.settings.json'),
|
||||
name: 'glm.settings.json',
|
||||
key: 'GLM Settings',
|
||||
profile: 'glm',
|
||||
},
|
||||
{
|
||||
path: path.join(this.ccsDir, 'kimi.settings.json'),
|
||||
name: 'kimi.settings.json',
|
||||
key: 'Kimi Settings',
|
||||
profile: 'kimi',
|
||||
},
|
||||
];
|
||||
|
||||
const { DelegationValidator } = require('../utils/delegation-validator');
|
||||
@@ -299,10 +319,16 @@ class Doctor {
|
||||
spinner.succeed(` ${file.name.padEnd(26)}${statusIcon} ${info}`);
|
||||
}
|
||||
|
||||
this.results.addCheck(file.name, status === 'OK' ? 'success' : 'warning', undefined, undefined, {
|
||||
status: status,
|
||||
info: info
|
||||
});
|
||||
this.results.addCheck(
|
||||
file.name,
|
||||
status === 'OK' ? 'success' : 'warning',
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
status: status,
|
||||
info: info,
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
spinner.fail(` ${file.name.padEnd(26)}${colored('[X]', 'red')} Invalid JSON`);
|
||||
this.results.addCheck(
|
||||
@@ -324,7 +350,9 @@ class Doctor {
|
||||
const settingsPath = path.join(this.claudeDir, 'settings.json');
|
||||
|
||||
if (!fs.existsSync(settingsPath)) {
|
||||
spinner.warn(` ${'~/.claude/settings.json'.padEnd(26)}${colored('[!]', 'yellow')} Not found`);
|
||||
spinner.warn(
|
||||
` ${'~/.claude/settings.json'.padEnd(26)}${colored('[!]', 'yellow')} Not found`
|
||||
);
|
||||
this.results.addCheck(
|
||||
'Claude Settings',
|
||||
'warning',
|
||||
@@ -341,7 +369,9 @@ class Doctor {
|
||||
spinner.succeed(` ${'~/.claude/settings.json'.padEnd(26)}${colored('[OK]', 'green')}`);
|
||||
this.results.addCheck('Claude Settings', 'success');
|
||||
} catch (e) {
|
||||
spinner.warn(` ${'~/.claude/settings.json'.padEnd(26)}${colored('[!]', 'yellow')} Invalid JSON`);
|
||||
spinner.warn(
|
||||
` ${'~/.claude/settings.json'.padEnd(26)}${colored('[!]', 'yellow')} Invalid JSON`
|
||||
);
|
||||
this.results.addCheck(
|
||||
'Claude Settings',
|
||||
'warning',
|
||||
@@ -381,16 +411,24 @@ class Doctor {
|
||||
const profileCount = Object.keys(config.profiles).length;
|
||||
const profileNames = Object.keys(config.profiles).join(', ');
|
||||
|
||||
spinner.succeed(` ${'Profiles'.padEnd(26)}${colored('[OK]', 'green')} ${profileCount} configured (${profileNames})`);
|
||||
this.results.addCheck('Profiles', 'success', `${profileCount} profiles configured`, undefined, {
|
||||
status: 'OK',
|
||||
info: `${profileCount} configured (${profileNames.length > 30 ? profileNames.substring(0, 27) + '...' : profileNames})`
|
||||
});
|
||||
spinner.succeed(
|
||||
` ${'Profiles'.padEnd(26)}${colored('[OK]', 'green')} ${profileCount} configured (${profileNames})`
|
||||
);
|
||||
this.results.addCheck(
|
||||
'Profiles',
|
||||
'success',
|
||||
`${profileCount} profiles configured`,
|
||||
undefined,
|
||||
{
|
||||
status: 'OK',
|
||||
info: `${profileCount} configured (${profileNames.length > 30 ? profileNames.substring(0, 27) + '...' : profileNames})`,
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
spinner.fail(` ${'Profiles'.padEnd(26)}${colored('[X]', 'red')} ${(e as Error).message}`);
|
||||
this.results.addCheck('Profiles', 'error', (e as Error).message, undefined, {
|
||||
status: 'ERROR',
|
||||
info: (e as Error).message
|
||||
info: (e as Error).message,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -408,7 +446,7 @@ class Doctor {
|
||||
return;
|
||||
}
|
||||
|
||||
const instances = fs.readdirSync(instancesDir).filter(name => {
|
||||
const instances = fs.readdirSync(instancesDir).filter((name) => {
|
||||
return fs.statSync(path.join(instancesDir, name)).isDirectory();
|
||||
});
|
||||
|
||||
@@ -418,7 +456,9 @@ class Doctor {
|
||||
return;
|
||||
}
|
||||
|
||||
spinner.succeed(` ${'Instances'.padEnd(26)}${colored('[OK]', 'green')} ${instances.length} account profiles`);
|
||||
spinner.succeed(
|
||||
` ${'Instances'.padEnd(26)}${colored('[OK]', 'green')} ${instances.length} account profiles`
|
||||
);
|
||||
this.results.addCheck('Instances', 'success', `${instances.length} account profiles`);
|
||||
}
|
||||
|
||||
@@ -468,7 +508,9 @@ class Doctor {
|
||||
return;
|
||||
}
|
||||
|
||||
spinner.succeed(` ${'Delegation'.padEnd(26)}${colored('[OK]', 'green')} ${readyProfiles.length} profiles ready (${readyProfiles.join(', ')})`);
|
||||
spinner.succeed(
|
||||
` ${'Delegation'.padEnd(26)}${colored('[OK]', 'green')} ${readyProfiles.length} profiles ready (${readyProfiles.join(', ')})`
|
||||
);
|
||||
this.results.addCheck(
|
||||
'Delegation',
|
||||
'success',
|
||||
@@ -488,13 +530,17 @@ class Doctor {
|
||||
try {
|
||||
fs.writeFileSync(testFile, 'test', 'utf8');
|
||||
fs.unlinkSync(testFile);
|
||||
spinner.succeed(` ${'Permissions'.padEnd(26)}${colored('[OK]', 'green')} Write access verified`);
|
||||
spinner.succeed(
|
||||
` ${'Permissions'.padEnd(26)}${colored('[OK]', 'green')} Write access verified`
|
||||
);
|
||||
this.results.addCheck('Permissions', 'success', undefined, undefined, {
|
||||
status: 'OK',
|
||||
info: 'Write access verified'
|
||||
info: 'Write access verified',
|
||||
});
|
||||
} catch (e) {
|
||||
spinner.fail(` ${'Permissions'.padEnd(26)}${colored('[X]', 'red')} Cannot write to ~/.ccs/`);
|
||||
spinner.fail(
|
||||
` ${'Permissions'.padEnd(26)}${colored('[X]', 'red')} Cannot write to ~/.ccs/`
|
||||
);
|
||||
this.results.addCheck(
|
||||
'Permissions',
|
||||
'error',
|
||||
@@ -518,13 +564,23 @@ class Doctor {
|
||||
|
||||
if (health.healthy) {
|
||||
const itemCount = manager.ccsItems.length;
|
||||
spinner.succeed(` ${'CCS Symlinks'.padEnd(26)}${colored('[OK]', 'green')} ${itemCount}/${itemCount} items linked`);
|
||||
this.results.addCheck('CCS Symlinks', 'success', 'All CCS items properly symlinked', undefined, {
|
||||
status: 'OK',
|
||||
info: `${itemCount}/${itemCount} items synced`
|
||||
});
|
||||
spinner.succeed(
|
||||
` ${'CCS Symlinks'.padEnd(26)}${colored('[OK]', 'green')} ${itemCount}/${itemCount} items linked`
|
||||
);
|
||||
this.results.addCheck(
|
||||
'CCS Symlinks',
|
||||
'success',
|
||||
'All CCS items properly symlinked',
|
||||
undefined,
|
||||
{
|
||||
status: 'OK',
|
||||
info: `${itemCount}/${itemCount} items synced`,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
spinner.warn(` ${'CCS Symlinks'.padEnd(26)}${colored('[!]', 'yellow')} ${health.issues.length} issues found`);
|
||||
spinner.warn(
|
||||
` ${'CCS Symlinks'.padEnd(26)}${colored('[!]', 'yellow')} ${health.issues.length} issues found`
|
||||
);
|
||||
this.results.addCheck(
|
||||
'CCS Symlinks',
|
||||
'warning',
|
||||
@@ -558,7 +614,9 @@ class Doctor {
|
||||
|
||||
// Check shared settings exists and points to ~/.claude/
|
||||
if (!fs.existsSync(sharedSettings)) {
|
||||
spinner.warn(` ${'settings.json (shared)'.padEnd(26)}${colored('[!]', 'yellow')} Not found`);
|
||||
spinner.warn(
|
||||
` ${'settings.json (shared)'.padEnd(26)}${colored('[!]', 'yellow')} Not found`
|
||||
);
|
||||
this.results.addCheck(
|
||||
'Settings Symlinks',
|
||||
'warning',
|
||||
@@ -570,7 +628,9 @@ class Doctor {
|
||||
|
||||
const sharedStats = fs.lstatSync(sharedSettings);
|
||||
if (!sharedStats.isSymbolicLink()) {
|
||||
spinner.warn(` ${'settings.json (shared)'.padEnd(26)}${colored('[!]', 'yellow')} Not a symlink`);
|
||||
spinner.warn(
|
||||
` ${'settings.json (shared)'.padEnd(26)}${colored('[!]', 'yellow')} Not a symlink`
|
||||
);
|
||||
this.results.addCheck(
|
||||
'Settings Symlinks',
|
||||
'warning',
|
||||
@@ -584,7 +644,9 @@ class Doctor {
|
||||
const resolvedShared = path.resolve(path.dirname(sharedSettings), sharedTarget);
|
||||
|
||||
if (resolvedShared !== claudeSettings) {
|
||||
spinner.warn(` ${'settings.json (shared)'.padEnd(26)}${colored('[!]', 'yellow')} Wrong target`);
|
||||
spinner.warn(
|
||||
` ${'settings.json (shared)'.padEnd(26)}${colored('[!]', 'yellow')} Wrong target`
|
||||
);
|
||||
this.results.addCheck(
|
||||
'Settings Symlinks',
|
||||
'warning',
|
||||
@@ -597,15 +659,17 @@ class Doctor {
|
||||
// Check each instance
|
||||
const instancesDir = path.join(this.ccsDir, 'instances');
|
||||
if (!fs.existsSync(instancesDir)) {
|
||||
spinner.succeed(` ${'settings.json'.padEnd(26)}${colored('[OK]', 'green')} Shared symlink valid`);
|
||||
spinner.succeed(
|
||||
` ${'settings.json'.padEnd(26)}${colored('[OK]', 'green')} Shared symlink valid`
|
||||
);
|
||||
this.results.addCheck('Settings Symlinks', 'success', 'Shared symlink valid', undefined, {
|
||||
status: 'OK',
|
||||
info: 'Shared symlink valid'
|
||||
info: 'Shared symlink valid',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const instances = fs.readdirSync(instancesDir).filter(name => {
|
||||
const instances = fs.readdirSync(instancesDir).filter((name) => {
|
||||
return fs.statSync(path.join(instancesDir, name)).isDirectory();
|
||||
});
|
||||
|
||||
@@ -638,7 +702,9 @@ class Doctor {
|
||||
}
|
||||
|
||||
if (broken > 0) {
|
||||
spinner.warn(` ${'settings.json'.padEnd(26)}${colored('[!]', 'yellow')} ${broken} broken instance(s)`);
|
||||
spinner.warn(
|
||||
` ${'settings.json'.padEnd(26)}${colored('[!]', 'yellow')} ${broken} broken instance(s)`
|
||||
);
|
||||
this.results.addCheck(
|
||||
'Settings Symlinks',
|
||||
'warning',
|
||||
@@ -647,13 +713,20 @@ class Doctor {
|
||||
{ status: 'WARN', info: `${broken} broken instance(s)` }
|
||||
);
|
||||
} else {
|
||||
spinner.succeed(` ${'settings.json'.padEnd(26)}${colored('[OK]', 'green')} ${instances.length} instance(s) valid`);
|
||||
this.results.addCheck('Settings Symlinks', 'success', 'All instance symlinks valid', undefined, {
|
||||
status: 'OK',
|
||||
info: `${instances.length} instance(s) valid`
|
||||
});
|
||||
spinner.succeed(
|
||||
` ${'settings.json'.padEnd(26)}${colored('[OK]', 'green')} ${instances.length} instance(s) valid`
|
||||
);
|
||||
this.results.addCheck(
|
||||
'Settings Symlinks',
|
||||
'success',
|
||||
'All instance symlinks valid',
|
||||
undefined,
|
||||
{
|
||||
status: 'OK',
|
||||
info: `${instances.length} instance(s) valid`,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
spinner.warn(` ${'settings.json'.padEnd(26)}${colored('[!]', 'yellow')} Check failed`);
|
||||
this.results.addCheck(
|
||||
@@ -687,21 +760,29 @@ class Doctor {
|
||||
colWidths: [20, 10, 35],
|
||||
wordWrap: true,
|
||||
chars: {
|
||||
'top': '═', 'top-mid': '╤', 'top-left': '╔', 'top-right': '╗',
|
||||
'bottom': '═', 'bottom-mid': '╧', 'bottom-left': '╚', 'bottom-right': '╝',
|
||||
'left': '║', 'left-mid': '╟', 'mid': '─', 'mid-mid': '┼',
|
||||
'right': '║', 'right-mid': '╢', 'middle': '│'
|
||||
}
|
||||
top: '═',
|
||||
'top-mid': '╤',
|
||||
'top-left': '╔',
|
||||
'top-right': '╗',
|
||||
bottom: '═',
|
||||
'bottom-mid': '╧',
|
||||
'bottom-left': '╚',
|
||||
'bottom-right': '╝',
|
||||
left: '║',
|
||||
'left-mid': '╟',
|
||||
mid: '─',
|
||||
'mid-mid': '┼',
|
||||
right: '║',
|
||||
'right-mid': '╢',
|
||||
middle: '│',
|
||||
},
|
||||
});
|
||||
|
||||
// Populate table with collected details
|
||||
for (const [component, detail] of Object.entries(this.results.details)) {
|
||||
const statusColor = detail.status === 'OK' ? 'green' : detail.status === 'ERROR' ? 'red' : 'yellow';
|
||||
table.push([
|
||||
component,
|
||||
colored(detail.status, statusColor),
|
||||
detail.info || ''
|
||||
]);
|
||||
const statusColor =
|
||||
detail.status === 'OK' ? 'green' : detail.status === 'ERROR' ? 'red' : 'yellow';
|
||||
table.push([component, colored(detail.status, statusColor), detail.info || '']);
|
||||
}
|
||||
|
||||
console.log(table.toString());
|
||||
@@ -710,7 +791,7 @@ class Doctor {
|
||||
// Show errors and warnings if present
|
||||
if (this.results.hasErrors()) {
|
||||
console.log(colored('Errors:', 'red'));
|
||||
this.results.errors.forEach(err => {
|
||||
this.results.errors.forEach((err) => {
|
||||
console.log(` [X] ${err.name}: ${err.message}`);
|
||||
if (err.fix) {
|
||||
console.log(` Fix: ${err.fix}`);
|
||||
@@ -721,7 +802,7 @@ class Doctor {
|
||||
|
||||
if (this.results.hasWarnings()) {
|
||||
console.log(colored('Warnings:', 'yellow'));
|
||||
this.results.warnings.forEach(warn => {
|
||||
this.results.warnings.forEach((warn) => {
|
||||
console.log(` [!] ${warn.name}: ${warn.message}`);
|
||||
if (warn.fix) {
|
||||
console.log(` Fix: ${warn.fix}`);
|
||||
@@ -747,16 +828,20 @@ class Doctor {
|
||||
* Generate JSON report
|
||||
*/
|
||||
generateJsonReport(): string {
|
||||
return JSON.stringify({
|
||||
timestamp: new Date().toISOString(),
|
||||
platform: process.platform,
|
||||
nodeVersion: process.version,
|
||||
ccsVersion: packageJson.version,
|
||||
checks: this.results.checks,
|
||||
errors: this.results.errors,
|
||||
warnings: this.results.warnings,
|
||||
healthy: this.results.isHealthy()
|
||||
}, null, 2);
|
||||
return JSON.stringify(
|
||||
{
|
||||
timestamp: new Date().toISOString(),
|
||||
platform: process.platform,
|
||||
nodeVersion: process.version,
|
||||
ccsVersion: packageJson.version,
|
||||
checks: this.results.checks,
|
||||
errors: this.results.errors,
|
||||
warnings: this.results.warnings,
|
||||
healthy: this.results.isHealthy(),
|
||||
},
|
||||
null,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -767,4 +852,4 @@ class Doctor {
|
||||
}
|
||||
}
|
||||
|
||||
export default Doctor;
|
||||
export default Doctor;
|
||||
|
||||
@@ -64,10 +64,10 @@ class InstanceManager {
|
||||
'file-history',
|
||||
'shell-snapshots',
|
||||
'debug',
|
||||
'.anthropic'
|
||||
'.anthropic',
|
||||
];
|
||||
|
||||
subdirs.forEach(dir => {
|
||||
subdirs.forEach((dir) => {
|
||||
const dirPath = path.join(instancePath, dir);
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true, mode: 0o700 });
|
||||
@@ -80,7 +80,9 @@ class InstanceManager {
|
||||
// Copy global configs if exist (settings.json only)
|
||||
this.copyGlobalConfigs(instancePath);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to initialize instance for ${profileName}: ${(error as Error).message}`);
|
||||
throw new Error(
|
||||
`Failed to initialize instance for ${profileName}: ${(error as Error).message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +98,7 @@ class InstanceManager {
|
||||
'file-history',
|
||||
'shell-snapshots',
|
||||
'debug',
|
||||
'.anthropic'
|
||||
'.anthropic',
|
||||
];
|
||||
|
||||
for (const dir of requiredDirs) {
|
||||
@@ -132,11 +134,10 @@ class InstanceManager {
|
||||
return [];
|
||||
}
|
||||
|
||||
return fs.readdirSync(this.instancesDir)
|
||||
.filter(name => {
|
||||
const instancePath = path.join(this.instancesDir, name);
|
||||
return fs.statSync(instancePath).isDirectory();
|
||||
});
|
||||
return fs.readdirSync(this.instancesDir).filter((name) => {
|
||||
const instancePath = path.join(this.instancesDir, name);
|
||||
return fs.statSync(instancePath).isDirectory();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,4 +191,4 @@ class InstanceManager {
|
||||
}
|
||||
|
||||
export { InstanceManager };
|
||||
export default InstanceManager;
|
||||
export default InstanceManager;
|
||||
|
||||
@@ -59,8 +59,8 @@ class RecoveryManager {
|
||||
profiles: {
|
||||
glm: '~/.ccs/glm.settings.json',
|
||||
kimi: '~/.ccs/kimi.settings.json',
|
||||
default: '~/.claude/settings.json'
|
||||
}
|
||||
default: '~/.claude/settings.json',
|
||||
},
|
||||
};
|
||||
|
||||
const tmpPath = `${configPath}.tmp`;
|
||||
@@ -124,10 +124,10 @@ class RecoveryManager {
|
||||
|
||||
console.log('');
|
||||
console.log('[i] Auto-recovery completed:');
|
||||
this.recovered.forEach(msg => console.log(` - ${msg}`));
|
||||
this.recovered.forEach((msg) => console.log(` - ${msg}`));
|
||||
|
||||
// Show login hint if created Claude settings
|
||||
if (this.recovered.some(msg => msg.includes('settings.json'))) {
|
||||
if (this.recovered.some((msg) => msg.includes('settings.json'))) {
|
||||
console.log('');
|
||||
console.log('[i] Next step: Login to Claude CLI');
|
||||
console.log(' Run: claude /login');
|
||||
@@ -137,4 +137,4 @@ class RecoveryManager {
|
||||
}
|
||||
}
|
||||
|
||||
export default RecoveryManager;
|
||||
export default RecoveryManager;
|
||||
|
||||
@@ -35,7 +35,7 @@ class SharedManager {
|
||||
{ name: 'skills', type: 'directory' },
|
||||
{ name: 'agents', type: 'directory' },
|
||||
{ name: 'plugins', type: 'directory' },
|
||||
{ name: 'settings.json', type: 'file' }
|
||||
{ name: 'settings.json', type: 'file' },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -144,7 +144,9 @@ class SharedManager {
|
||||
} else if (item.type === 'file') {
|
||||
fs.copyFileSync(claudePath, sharedPath);
|
||||
}
|
||||
console.log(`[!] Symlink failed for ${item.name}, copied instead (enable Developer Mode)`);
|
||||
console.log(
|
||||
`[!] Symlink failed for ${item.name}, copied instead (enable Developer Mode)`
|
||||
);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
@@ -183,7 +185,9 @@ class SharedManager {
|
||||
} else if (item.type === 'file') {
|
||||
fs.copyFileSync(targetPath, linkPath);
|
||||
}
|
||||
console.log(`[!] Symlink failed for ${item.name}, copied instead (enable Developer Mode)`);
|
||||
console.log(
|
||||
`[!] Symlink failed for ${item.name}, copied instead (enable Developer Mode)`
|
||||
);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
@@ -319,7 +323,7 @@ class SharedManager {
|
||||
return;
|
||||
}
|
||||
|
||||
const instances = fs.readdirSync(this.instancesDir).filter(name => {
|
||||
const instances = fs.readdirSync(this.instancesDir).filter((name) => {
|
||||
const instancePath = path.join(this.instancesDir, name);
|
||||
return fs.statSync(instancePath).isDirectory();
|
||||
});
|
||||
@@ -367,7 +371,6 @@ class SharedManager {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.log(`[!] Failed to migrate ${instance}: ${(err as Error).message}`);
|
||||
}
|
||||
@@ -404,4 +407,4 @@ class SharedManager {
|
||||
}
|
||||
}
|
||||
|
||||
export default SharedManager;
|
||||
export default SharedManager;
|
||||
|
||||
+5
-5
@@ -8,10 +8,10 @@ import { SpawnOptions as NodeSpawnOptions } from 'child_process';
|
||||
* Parsed CLI arguments
|
||||
*/
|
||||
export interface ParsedArgs {
|
||||
profile?: string; // Profile name (glm, kimi, work, etc.)
|
||||
prompt?: string; // -p/--prompt flag value
|
||||
isDelegation: boolean; // -p flag present
|
||||
isContinue: boolean; // :continue suffix detected
|
||||
profile?: string; // Profile name (glm, kimi, work, etc.)
|
||||
prompt?: string; // -p/--prompt flag value
|
||||
isDelegation: boolean; // -p flag present
|
||||
isContinue: boolean; // :continue suffix detected
|
||||
remainingArgs: string[]; // Args to pass to Claude CLI
|
||||
}
|
||||
|
||||
@@ -50,4 +50,4 @@ export enum ExitCode {
|
||||
CONFIG_ERROR = 2,
|
||||
DELEGATION_ERROR = 3,
|
||||
TIMEOUT = 124,
|
||||
}
|
||||
}
|
||||
|
||||
+3
-6
@@ -55,10 +55,7 @@ export interface ProfilesRegistry {
|
||||
*/
|
||||
export function isConfig(obj: unknown): obj is Config {
|
||||
return (
|
||||
typeof obj === 'object' &&
|
||||
obj !== null &&
|
||||
'profiles' in obj &&
|
||||
typeof obj.profiles === 'object'
|
||||
typeof obj === 'object' && obj !== null && 'profiles' in obj && typeof obj.profiles === 'object'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -67,5 +64,5 @@ export function isSettings(obj: unknown): obj is Settings {
|
||||
if (!('env' in obj)) return true; // env is optional
|
||||
if (typeof obj.env !== 'object' || obj.env === null) return false;
|
||||
// Validate all env values are strings
|
||||
return Object.values(obj.env).every(v => typeof v === 'string');
|
||||
}
|
||||
return Object.values(obj.env).every((v) => typeof v === 'string');
|
||||
}
|
||||
|
||||
+17
-17
@@ -6,14 +6,14 @@
|
||||
* Session metadata for delegation tracking
|
||||
*/
|
||||
export interface SessionMetadata {
|
||||
id: string; // Unique session ID
|
||||
profile: string; // Target profile (glm, kimi)
|
||||
prompt: string; // Initial prompt
|
||||
workingDir: string; // CWD at execution time
|
||||
startTime: number; // Unix timestamp (ms)
|
||||
endTime?: number; // Unix timestamp (ms)
|
||||
exitCode?: number; // Process exit code
|
||||
duration?: number; // Execution duration (seconds)
|
||||
id: string; // Unique session ID
|
||||
profile: string; // Target profile (glm, kimi)
|
||||
prompt: string; // Initial prompt
|
||||
workingDir: string; // CWD at execution time
|
||||
startTime: number; // Unix timestamp (ms)
|
||||
endTime?: number; // Unix timestamp (ms)
|
||||
exitCode?: number; // Process exit code
|
||||
duration?: number; // Execution duration (seconds)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21,8 +21,8 @@ export interface SessionMetadata {
|
||||
*/
|
||||
export interface DelegationSession {
|
||||
metadata: SessionMetadata;
|
||||
turns: number; // Conversation turns
|
||||
lastPrompt?: string; // Last user prompt (for :continue)
|
||||
turns: number; // Conversation turns
|
||||
lastPrompt?: string; // Last user prompt (for :continue)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,7 +31,7 @@ export interface DelegationSession {
|
||||
*/
|
||||
export interface DelegationSessionsRegistry {
|
||||
sessions: Record<string, DelegationSession>; // sessionId → session
|
||||
lastSessionId?: string; // Most recent session ID
|
||||
lastSessionId?: string; // Most recent session ID
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,12 +39,12 @@ export interface DelegationSessionsRegistry {
|
||||
*/
|
||||
export interface ExecutionResult {
|
||||
exitCode: number;
|
||||
duration: number; // Seconds
|
||||
duration: number; // Seconds
|
||||
workingDir: string;
|
||||
sessionId: string;
|
||||
profile: string;
|
||||
model?: string; // Model name from settings
|
||||
cost?: number; // Estimated cost (if available)
|
||||
model?: string; // Model name from settings
|
||||
cost?: number; // Estimated cost (if available)
|
||||
turns: number;
|
||||
}
|
||||
|
||||
@@ -53,8 +53,8 @@ export interface ExecutionResult {
|
||||
*/
|
||||
export interface ToolEvent {
|
||||
type: 'tool';
|
||||
tool: string; // Tool name (Write, Edit, Bash, etc.)
|
||||
args: string; // Simplified args (file path, command, etc.)
|
||||
tool: string; // Tool name (Write, Edit, Bash, etc.)
|
||||
args: string; // Simplified args (file path, command, etc.)
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
@@ -64,4 +64,4 @@ export interface OutputEvent {
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export type DelegationEvent = ToolEvent | OutputEvent;
|
||||
export type DelegationEvent = ToolEvent | OutputEvent;
|
||||
|
||||
Vendored
+1
-1
@@ -44,4 +44,4 @@ declare module 'ora' {
|
||||
|
||||
function ora(options?: string | Options): Ora;
|
||||
export = ora;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -102,4 +102,4 @@ export interface TransformationContext {
|
||||
verbose: boolean;
|
||||
debugLog: boolean;
|
||||
streaming: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-11
@@ -16,12 +16,7 @@ export type {
|
||||
export { isConfig, isSettings } from './config';
|
||||
|
||||
// CLI types
|
||||
export type {
|
||||
ParsedArgs,
|
||||
ClaudeSpawnOptions,
|
||||
Platform,
|
||||
ClaudeCliInfo,
|
||||
} from './cli';
|
||||
export type { ParsedArgs, ClaudeSpawnOptions, Platform, ClaudeCliInfo } from './cli';
|
||||
export { ExitCode } from './cli';
|
||||
|
||||
// Delegation types
|
||||
@@ -52,8 +47,4 @@ export type {
|
||||
|
||||
// Utility types
|
||||
export { ErrorCode, LogLevel } from './utils';
|
||||
export type {
|
||||
ColorName,
|
||||
TerminalInfo,
|
||||
Result,
|
||||
} from './utils';
|
||||
export type { ColorName, TerminalInfo, Result } from './utils';
|
||||
|
||||
+1
-3
@@ -32,6 +32,4 @@ export interface TerminalInfo {
|
||||
/**
|
||||
* Helper result types
|
||||
*/
|
||||
export type Result<T, E = Error> =
|
||||
| { ok: true; value: T }
|
||||
| { ok: false; error: E };
|
||||
export type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
|
||||
|
||||
@@ -29,16 +29,19 @@ export function detectClaudeCli(): string | null {
|
||||
const result = execSync(cmd, {
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
timeout: 5000 // 5 second timeout to prevent hangs
|
||||
timeout: 5000, // 5 second timeout to prevent hangs
|
||||
}).trim();
|
||||
|
||||
// where.exe may return multiple lines (all matches in PATH order)
|
||||
const matches = result.split('\n').map(p => p.trim()).filter(p => p);
|
||||
const matches = result
|
||||
.split('\n')
|
||||
.map((p) => p.trim())
|
||||
.filter((p) => p);
|
||||
|
||||
if (isWindows) {
|
||||
// On Windows, prefer executables with extensions (.exe, .cmd, .bat)
|
||||
// where.exe often returns file without extension first, then the actual .cmd wrapper
|
||||
const withExtension = matches.find(p => /\.(exe|cmd|bat|ps1)$/i.test(p));
|
||||
const withExtension = matches.find((p) => /\.(exe|cmd|bat|ps1)$/i.test(p));
|
||||
const claudePath = withExtension || matches[0];
|
||||
|
||||
if (claudePath && fs.existsSync(claudePath)) {
|
||||
@@ -77,7 +80,7 @@ export function getClaudeCliInfo(): ClaudeCliInfo | null {
|
||||
return {
|
||||
path: claudePath,
|
||||
isWindows,
|
||||
needsShell
|
||||
needsShell,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -88,4 +91,4 @@ export function showClaudeNotFoundError(): never {
|
||||
console.error('ERROR: Claude CLI not found in PATH');
|
||||
console.error('Install from: https://docs.claude.com/en/docs/claude-code/installation');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,15 +29,15 @@ try {
|
||||
ora = oraModule.default || oraModule;
|
||||
} catch {
|
||||
// ora not available, create fallback spinner that uses console.log
|
||||
ora = function(text: string): OraInstance {
|
||||
ora = function (text: string): OraInstance {
|
||||
return {
|
||||
start: () => ({
|
||||
succeed: (msg?: string) => console.log(msg || `[OK] ${text}`),
|
||||
fail: (msg?: string) => console.log(msg || `[X] ${text}`),
|
||||
warn: (msg?: string) => console.log(msg || `[!] ${text}`),
|
||||
info: (msg?: string) => console.log(msg || `[i] ${text}`),
|
||||
text: ''
|
||||
})
|
||||
text: '',
|
||||
}),
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -71,7 +71,8 @@ export class ClaudeDirInstaller {
|
||||
* @param silent - Suppress spinner output
|
||||
*/
|
||||
install(packageDir?: string, silent = false): boolean {
|
||||
const spinner = (silent || !ora) ? null : ora('Copying .claude/ items to ~/.ccs/.claude/').start();
|
||||
const spinner =
|
||||
silent || !ora ? null : ora('Copying .claude/ items to ~/.ccs/.claude/').start();
|
||||
|
||||
try {
|
||||
// Auto-detect package directory if not provided
|
||||
@@ -197,7 +198,12 @@ export class ClaudeDirInstaller {
|
||||
cleanupDeprecated(silent = false): CleanupResult {
|
||||
const deprecatedFile = path.join(this.ccsClaudeDir, 'agents', 'ccs-delegator.md');
|
||||
const userSymlinkFile = path.join(this.homeDir, '.claude', 'agents', 'ccs-delegator.md');
|
||||
const migrationMarker = path.join(this.homeDir, '.ccs', '.migrations', 'v435-delegator-cleanup');
|
||||
const migrationMarker = path.join(
|
||||
this.homeDir,
|
||||
'.ccs',
|
||||
'.migrations',
|
||||
'v435-delegator-cleanup'
|
||||
);
|
||||
|
||||
const cleanedFiles: string[] = [];
|
||||
|
||||
@@ -239,7 +245,8 @@ export class ClaudeDirInstaller {
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').split('T')[0];
|
||||
const backupPath = `${deprecatedFile}.backup-${timestamp}`;
|
||||
fs.renameSync(deprecatedFile, backupPath);
|
||||
if (!silent) console.log(`[i] Backed up modified deprecated file to ${path.basename(backupPath)}`);
|
||||
if (!silent)
|
||||
console.log(`[i] Backed up modified deprecated file to ${path.basename(backupPath)}`);
|
||||
} else {
|
||||
fs.rmSync(deprecatedFile, { force: true });
|
||||
}
|
||||
|
||||
@@ -38,15 +38,15 @@ try {
|
||||
ora = oraModule.default || oraModule;
|
||||
} catch {
|
||||
// ora not available, create fallback spinner that uses console.log
|
||||
ora = function(text: string): OraInstance {
|
||||
ora = function (text: string): OraInstance {
|
||||
return {
|
||||
start: () => ({
|
||||
succeed: (msg?: string) => console.log(msg || `[OK] ${text}`),
|
||||
fail: (msg?: string) => console.log(msg || `[X] ${text}`),
|
||||
warn: (msg?: string) => console.log(msg || `[!] ${text}`),
|
||||
info: (msg?: string) => console.log(msg || `[i] ${text}`),
|
||||
text: ''
|
||||
})
|
||||
text: '',
|
||||
}),
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -80,7 +80,7 @@ export class ClaudeSymlinkManager {
|
||||
this.ccsItems = [
|
||||
{ source: 'commands/ccs.md', target: 'commands/ccs.md', type: 'file' },
|
||||
{ source: 'commands/ccs', target: 'commands/ccs', type: 'directory' },
|
||||
{ source: 'skills/ccs-delegation', target: 'skills/ccs-delegation', type: 'directory' }
|
||||
{ source: 'skills/ccs-delegation', target: 'skills/ccs-delegation', type: 'directory' },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ export class ClaudeSymlinkManager {
|
||||
* Safe: backs up existing files before creating symlinks
|
||||
*/
|
||||
install(silent = false): void {
|
||||
const spinner = (silent || !ora) ? null : ora('Installing CCS items to ~/.claude/').start();
|
||||
const spinner = silent || !ora ? null : ora('Installing CCS items to ~/.claude/').start();
|
||||
|
||||
// Ensure ~/.ccs/.claude/ exists (should be shipped with package)
|
||||
if (!fs.existsSync(this.ccsClaudeDir)) {
|
||||
|
||||
@@ -92,7 +92,7 @@ export function getSettingsPath(profile: string): string {
|
||||
|
||||
if (!settingsPath) {
|
||||
const availableProfiles = Object.keys(config.profiles);
|
||||
const profileList = availableProfiles.map(p => ` - ${p}`);
|
||||
const profileList = availableProfiles.map((p) => ` - ${p}`);
|
||||
error(`Profile '${profile}' not found. Available profiles:\n${profileList.join('\n')}`);
|
||||
}
|
||||
|
||||
@@ -126,4 +126,4 @@ export function getSettingsPath(profile: string): string {
|
||||
}
|
||||
|
||||
return expandedPath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,11 +31,12 @@ export class DelegationValidator {
|
||||
return {
|
||||
valid: false,
|
||||
error: `Profile not found: ${profileName}`,
|
||||
suggestion: `Profile settings missing at: ${settingsPath}\n\n` +
|
||||
`To set up ${profileName} profile:\n` +
|
||||
` 1. Copy base settings: cp config/base-${profileName}.settings.json ~/.ccs/${profileName}.settings.json\n` +
|
||||
` 2. Edit settings: Edit ~/.ccs/${profileName}.settings.json\n` +
|
||||
` 3. Set your API key in ANTHROPIC_AUTH_TOKEN field`
|
||||
suggestion:
|
||||
`Profile settings missing at: ${settingsPath}\n\n` +
|
||||
`To set up ${profileName} profile:\n` +
|
||||
` 1. Copy base settings: cp config/base-${profileName}.settings.json ~/.ccs/${profileName}.settings.json\n` +
|
||||
` 2. Edit settings: Edit ~/.ccs/${profileName}.settings.json\n` +
|
||||
` 3. Set your API key in ANTHROPIC_AUTH_TOKEN field`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -48,11 +49,12 @@ export class DelegationValidator {
|
||||
return {
|
||||
valid: false,
|
||||
error: `Failed to parse settings.json for ${profileName}`,
|
||||
suggestion: `Settings file is corrupted or invalid JSON.\n\n` +
|
||||
`Location: ${settingsPath}\n` +
|
||||
`Parse error: ${(error as Error).message}\n\n` +
|
||||
`Fix: Restore from base config:\n` +
|
||||
` cp config/base-${profileName}.settings.json ~/.ccs/${profileName}.settings.json`
|
||||
suggestion:
|
||||
`Settings file is corrupted or invalid JSON.\n\n` +
|
||||
`Location: ${settingsPath}\n` +
|
||||
`Parse error: ${(error as Error).message}\n\n` +
|
||||
`Fix: Restore from base config:\n` +
|
||||
` cp config/base-${profileName}.settings.json ~/.ccs/${profileName}.settings.json`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -63,9 +65,10 @@ export class DelegationValidator {
|
||||
return {
|
||||
valid: false,
|
||||
error: `API key not configured for ${profileName}`,
|
||||
suggestion: `Missing ANTHROPIC_AUTH_TOKEN in settings.\n\n` +
|
||||
`Edit: ${settingsPath}\n` +
|
||||
`Set: env.ANTHROPIC_AUTH_TOKEN to your API key`
|
||||
suggestion:
|
||||
`Missing ANTHROPIC_AUTH_TOKEN in settings.\n\n` +
|
||||
`Edit: ${settingsPath}\n` +
|
||||
`Set: env.ANTHROPIC_AUTH_TOKEN to your API key`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,20 +78,21 @@ export class DelegationValidator {
|
||||
'YOUR_KIMI_API_KEY_HERE',
|
||||
'YOUR_API_KEY_HERE',
|
||||
'your-api-key-here',
|
||||
'PLACEHOLDER'
|
||||
'PLACEHOLDER',
|
||||
];
|
||||
|
||||
if (defaultPlaceholders.some(placeholder => apiKey.includes(placeholder))) {
|
||||
if (defaultPlaceholders.some((placeholder) => apiKey.includes(placeholder))) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `Default API key placeholder detected for ${profileName}`,
|
||||
suggestion: `API key is still set to default placeholder.\n\n` +
|
||||
`To configure your profile:\n` +
|
||||
` 1. Edit: ${settingsPath}\n` +
|
||||
` 2. Replace ANTHROPIC_AUTH_TOKEN with your actual API key\n\n` +
|
||||
`Get API key:\n` +
|
||||
` GLM: https://z.ai/manage-apikey/apikey-list\n` +
|
||||
` Kimi: https://platform.moonshot.cn/console/api-keys`
|
||||
suggestion:
|
||||
`API key is still set to default placeholder.\n\n` +
|
||||
`To configure your profile:\n` +
|
||||
` 1. Edit: ${settingsPath}\n` +
|
||||
` 2. Replace ANTHROPIC_AUTH_TOKEN with your actual API key\n\n` +
|
||||
`Get API key:\n` +
|
||||
` GLM: https://z.ai/manage-apikey/apikey-list\n` +
|
||||
` Kimi: https://platform.moonshot.cn/console/api-keys`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -96,7 +100,7 @@ export class DelegationValidator {
|
||||
return {
|
||||
valid: true,
|
||||
settingsPath,
|
||||
apiKey: apiKey.substring(0, 8) + '...' // Show first 8 chars for verification
|
||||
apiKey: apiKey.substring(0, 8) + '...', // Show first 8 chars for verification
|
||||
};
|
||||
}
|
||||
|
||||
@@ -156,4 +160,4 @@ export class DelegationValidator {
|
||||
|
||||
return profiles;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,10 +34,10 @@ export const ERROR_CODES = {
|
||||
|
||||
// Internal Errors (E900-E999)
|
||||
INTERNAL_ERROR: 'E900',
|
||||
INVALID_STATE: 'E901'
|
||||
INVALID_STATE: 'E901',
|
||||
} as const;
|
||||
|
||||
export type ErrorCode = typeof ERROR_CODES[keyof typeof ERROR_CODES];
|
||||
export type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
|
||||
|
||||
/**
|
||||
* Error code documentation URL generator
|
||||
@@ -58,4 +58,4 @@ export function getErrorCategory(errorCode: ErrorCode): string {
|
||||
if (code >= 500 && code < 600) return 'File System';
|
||||
if (code >= 900 && code < 1000) return 'Internal';
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@ export const ErrorTypes = {
|
||||
INVALID_CONFIG: 'INVALID_CONFIG',
|
||||
UNKNOWN_PROFILE: 'UNKNOWN_PROFILE',
|
||||
PERMISSION_DENIED: 'PERMISSION_DENIED',
|
||||
GENERIC: 'GENERIC'
|
||||
GENERIC: 'GENERIC',
|
||||
} as const;
|
||||
|
||||
export type ErrorType = typeof ErrorTypes[keyof typeof ErrorTypes];
|
||||
export type ErrorType = (typeof ErrorTypes)[keyof typeof ErrorTypes];
|
||||
|
||||
/**
|
||||
* Enhanced error manager with context-aware messages
|
||||
@@ -55,7 +55,8 @@ export class ErrorManager {
|
||||
* Show settings file not found error
|
||||
*/
|
||||
static showSettingsNotFound(settingsPath: string): void {
|
||||
const isClaudeSettings = settingsPath.includes('.claude') && settingsPath.endsWith('settings.json');
|
||||
const isClaudeSettings =
|
||||
settingsPath.includes('.claude') && settingsPath.endsWith('settings.json');
|
||||
|
||||
console.error('');
|
||||
console.error(colored('[X] Settings file not found', 'red'));
|
||||
@@ -118,12 +119,12 @@ export class ErrorManager {
|
||||
|
||||
if (suggestions && suggestions.length > 0) {
|
||||
console.error(colored('Did you mean:', 'yellow'));
|
||||
suggestions.forEach(s => console.error(` ${s}`));
|
||||
suggestions.forEach((s) => console.error(` ${s}`));
|
||||
console.error('');
|
||||
}
|
||||
|
||||
console.error(colored('Available profiles:', 'cyan'));
|
||||
availableProfiles.forEach(line => console.error(` ${line}`));
|
||||
availableProfiles.forEach((line) => console.error(` ${line}`));
|
||||
console.error('');
|
||||
console.error(colored('Solutions:', 'yellow'));
|
||||
console.error(' # Use existing profile');
|
||||
@@ -156,4 +157,4 @@ export class ErrorManager {
|
||||
console.error('');
|
||||
this.showErrorCode(ERROR_CODES.FS_CANNOT_WRITE_FILE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -8,7 +8,7 @@ import { ColorName, TerminalInfo } from '../types';
|
||||
function getColors(): Record<ColorName, string> {
|
||||
const forcedColors = process.env.FORCE_COLOR;
|
||||
const noColor = process.env.NO_COLOR;
|
||||
const isTTY = process.stdout.isTTY === true; // Must be explicitly true
|
||||
const isTTY = process.stdout.isTTY === true; // Must be explicitly true
|
||||
|
||||
const useColors = !!forcedColors || (isTTY && !noColor);
|
||||
|
||||
@@ -20,7 +20,7 @@ function getColors(): Record<ColorName, string> {
|
||||
green: '\x1b[0;32m',
|
||||
blue: '\x1b[0;34m',
|
||||
bold: '\x1b[1m',
|
||||
reset: '\x1b[0m'
|
||||
reset: '\x1b[0m',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -106,8 +106,8 @@ export function levenshteinDistance(a: string, b: string): number {
|
||||
} else {
|
||||
matrix[i][j] = Math.min(
|
||||
matrix[i - 1][j - 1] + 1, // substitution
|
||||
matrix[i][j - 1] + 1, // insertion
|
||||
matrix[i - 1][j] + 1 // deletion
|
||||
matrix[i][j - 1] + 1, // insertion
|
||||
matrix[i - 1][j] + 1 // deletion
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -127,14 +127,14 @@ export function findSimilarStrings(
|
||||
const targetLower = target.toLowerCase();
|
||||
|
||||
const matches = candidates
|
||||
.map(candidate => ({
|
||||
.map((candidate) => ({
|
||||
name: candidate,
|
||||
distance: levenshteinDistance(targetLower, candidate.toLowerCase())
|
||||
distance: levenshteinDistance(targetLower, candidate.toLowerCase()),
|
||||
}))
|
||||
.filter(item => item.distance <= maxDistance && item.distance > 0)
|
||||
.filter((item) => item.distance <= maxDistance && item.distance > 0)
|
||||
.sort((a, b) => a.distance - b.distance)
|
||||
.slice(0, 3) // Show at most 3 suggestions
|
||||
.map(item => item.name);
|
||||
.slice(0, 3) // Show at most 3 suggestions
|
||||
.map((item) => item.name);
|
||||
|
||||
return matches;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -28,7 +28,11 @@ export class InteractivePrompt {
|
||||
const { default: defaultValue = false } = options;
|
||||
|
||||
// Check for --yes flag (automation) - always returns true
|
||||
if (process.env.CCS_YES === '1' || process.argv.includes('--yes') || process.argv.includes('-y')) {
|
||||
if (
|
||||
process.env.CCS_YES === '1' ||
|
||||
process.argv.includes('--yes') ||
|
||||
process.argv.includes('-y')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -46,12 +50,10 @@ export class InteractivePrompt {
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stderr,
|
||||
terminal: true
|
||||
terminal: true,
|
||||
});
|
||||
|
||||
const promptText = defaultValue
|
||||
? `${message} [Y/n]: `
|
||||
: `${message} [y/N]: `;
|
||||
const promptText = defaultValue ? `${message} [Y/n]: ` : `${message} [y/N]: `;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
rl.question(promptText, (answer: string) => {
|
||||
@@ -100,12 +102,10 @@ export class InteractivePrompt {
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stderr,
|
||||
terminal: true
|
||||
terminal: true,
|
||||
});
|
||||
|
||||
const promptText = defaultValue
|
||||
? `${message} [${defaultValue}]: `
|
||||
: `${message}: `;
|
||||
const promptText = defaultValue ? `${message} [${defaultValue}]: ` : `${message}: `;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
rl.question(promptText, (answer: string) => {
|
||||
@@ -127,4 +127,4 @@ export class InteractivePrompt {
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export class ShellCompletionInstaller {
|
||||
|
||||
// Copy completion scripts
|
||||
const files = ['ccs.bash', 'ccs.zsh', 'ccs.fish', 'ccs.ps1'];
|
||||
files.forEach(file => {
|
||||
files.forEach((file) => {
|
||||
const src = path.join(this.scriptsDir, file);
|
||||
const dest = path.join(this.completionDir, file);
|
||||
|
||||
@@ -72,7 +72,7 @@ export class ShellCompletionInstaller {
|
||||
if (!stat.isDirectory()) {
|
||||
throw new Error(
|
||||
`Cannot create directory: ${dirPath} exists but is a file.\n` +
|
||||
`Please remove or rename this file and try again.`
|
||||
`Please remove or rename this file and try again.`
|
||||
);
|
||||
}
|
||||
// Directory exists, nothing to do
|
||||
@@ -118,7 +118,7 @@ export class ShellCompletionInstaller {
|
||||
return {
|
||||
success: true,
|
||||
message: `Added to ${rcFile}`,
|
||||
reload: 'source ~/.bashrc'
|
||||
reload: 'source ~/.bashrc',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -142,10 +142,7 @@ export class ShellCompletionInstaller {
|
||||
fs.copyFileSync(completionPath, destFile);
|
||||
|
||||
const marker = '# CCS shell completion';
|
||||
const setupCmds = [
|
||||
'fpath=(~/.zsh/completion $fpath)',
|
||||
'autoload -Uz compinit && compinit'
|
||||
];
|
||||
const setupCmds = ['fpath=(~/.zsh/completion $fpath)', 'autoload -Uz compinit && compinit'];
|
||||
const block = `\n${marker}\n${setupCmds.join('\n')}\n`;
|
||||
|
||||
// Check if already installed
|
||||
@@ -162,7 +159,7 @@ export class ShellCompletionInstaller {
|
||||
return {
|
||||
success: true,
|
||||
message: `Added to ${rcFile}`,
|
||||
reload: 'source ~/.zshrc'
|
||||
reload: 'source ~/.zshrc',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -187,7 +184,7 @@ export class ShellCompletionInstaller {
|
||||
return {
|
||||
success: true,
|
||||
message: `Installed to ${destFile}`,
|
||||
reload: 'Fish auto-loads completions (no reload needed)'
|
||||
reload: 'Fish auto-loads completions (no reload needed)',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -195,12 +192,9 @@ export class ShellCompletionInstaller {
|
||||
* Install PowerShell completion
|
||||
*/
|
||||
private installPowerShell(): InstallResult {
|
||||
const profilePath = process.env.PROFILE || path.join(
|
||||
this.homeDir,
|
||||
'Documents',
|
||||
'PowerShell',
|
||||
'Microsoft.PowerShell_profile.ps1'
|
||||
);
|
||||
const profilePath =
|
||||
process.env.PROFILE ||
|
||||
path.join(this.homeDir, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1');
|
||||
const completionPath = path.join(this.completionDir, 'ccs.ps1');
|
||||
|
||||
if (!fs.existsSync(completionPath)) {
|
||||
@@ -229,7 +223,7 @@ export class ShellCompletionInstaller {
|
||||
return {
|
||||
success: true,
|
||||
message: `Added to ${profilePath}`,
|
||||
reload: '. $PROFILE'
|
||||
reload: '. $PROFILE',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -240,7 +234,9 @@ export class ShellCompletionInstaller {
|
||||
const targetShell = shell || this.detectShell();
|
||||
|
||||
if (!targetShell) {
|
||||
throw new Error('Could not detect shell. Please specify: --bash, --zsh, --fish, or --powershell');
|
||||
throw new Error(
|
||||
'Could not detect shell. Please specify: --bash, --zsh, --fish, or --powershell'
|
||||
);
|
||||
}
|
||||
|
||||
// Ensure completion files exist
|
||||
@@ -260,4 +256,4 @@ export class ShellCompletionInstaller {
|
||||
throw new Error(`Unsupported shell: ${targetShell}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+55
-45
@@ -50,31 +50,35 @@ export function compareVersions(v1: string, v2: string): number {
|
||||
*/
|
||||
function fetchLatestVersionFromGitHub(): Promise<string | null> {
|
||||
return new Promise((resolve) => {
|
||||
const req = https.get(GITHUB_API_URL, {
|
||||
headers: { 'User-Agent': 'CCS-Update-Checker' },
|
||||
timeout: REQUEST_TIMEOUT
|
||||
}, (res) => {
|
||||
let data = '';
|
||||
const req = https.get(
|
||||
GITHUB_API_URL,
|
||||
{
|
||||
headers: { 'User-Agent': 'CCS-Update-Checker' },
|
||||
timeout: REQUEST_TIMEOUT,
|
||||
},
|
||||
(res) => {
|
||||
let data = '';
|
||||
|
||||
res.on('data', (chunk: Buffer) => {
|
||||
data += chunk.toString();
|
||||
});
|
||||
res.on('data', (chunk: Buffer) => {
|
||||
data += chunk.toString();
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
if (res.statusCode !== 200) {
|
||||
res.on('end', () => {
|
||||
try {
|
||||
if (res.statusCode !== 200) {
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const release = JSON.parse(data) as { tag_name?: string };
|
||||
const version = release.tag_name?.replace(/^v/, '') || null;
|
||||
resolve(version);
|
||||
} catch {
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const release = JSON.parse(data) as { tag_name?: string };
|
||||
const version = release.tag_name?.replace(/^v/, '') || null;
|
||||
resolve(version);
|
||||
} catch {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
req.on('error', () => resolve(null));
|
||||
req.on('timeout', () => {
|
||||
@@ -89,31 +93,35 @@ function fetchLatestVersionFromGitHub(): Promise<string | null> {
|
||||
*/
|
||||
function fetchLatestVersionFromNpm(): Promise<string | null> {
|
||||
return new Promise((resolve) => {
|
||||
const req = https.get(NPM_REGISTRY_URL, {
|
||||
headers: { 'User-Agent': 'CCS-Update-Checker' },
|
||||
timeout: REQUEST_TIMEOUT
|
||||
}, (res) => {
|
||||
let data = '';
|
||||
const req = https.get(
|
||||
NPM_REGISTRY_URL,
|
||||
{
|
||||
headers: { 'User-Agent': 'CCS-Update-Checker' },
|
||||
timeout: REQUEST_TIMEOUT,
|
||||
},
|
||||
(res) => {
|
||||
let data = '';
|
||||
|
||||
res.on('data', (chunk: Buffer) => {
|
||||
data += chunk.toString();
|
||||
});
|
||||
res.on('data', (chunk: Buffer) => {
|
||||
data += chunk.toString();
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
if (res.statusCode !== 200) {
|
||||
res.on('end', () => {
|
||||
try {
|
||||
if (res.statusCode !== 200) {
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const packageData = JSON.parse(data) as { version?: string };
|
||||
const version = packageData.version || null;
|
||||
resolve(version);
|
||||
} catch {
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const packageData = JSON.parse(data) as { version?: string };
|
||||
const version = packageData.version || null;
|
||||
resolve(version);
|
||||
} catch {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
req.on('error', () => resolve(null));
|
||||
req.on('timeout', () => {
|
||||
@@ -170,7 +178,7 @@ export async function checkForUpdates(
|
||||
const now = Date.now();
|
||||
|
||||
// Check if we should check for updates
|
||||
if (!force && (now - cache.last_check < CHECK_INTERVAL)) {
|
||||
if (!force && now - cache.last_check < CHECK_INTERVAL) {
|
||||
// Use cached result if available
|
||||
if (cache.latest_version && compareVersions(cache.latest_version, currentVersion) > 0) {
|
||||
// Don't show if user dismissed this version
|
||||
@@ -206,7 +214,7 @@ export async function checkForUpdates(
|
||||
return {
|
||||
status: 'check_failed',
|
||||
reason: fetchError,
|
||||
message: `Failed to check for updates: ${fetchError.replace(/_/g, ' ')}`
|
||||
message: `Failed to check for updates: ${fetchError.replace(/_/g, ' ')}`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -228,7 +236,9 @@ export async function checkForUpdates(
|
||||
export function showUpdateNotification(updateInfo: { current: string; latest: string }): void {
|
||||
console.log('');
|
||||
console.log(colored('═══════════════════════════════════════════════════════', 'cyan'));
|
||||
console.log(colored(` Update available: ${updateInfo.current} → ${updateInfo.latest}`, 'yellow'));
|
||||
console.log(
|
||||
colored(` Update available: ${updateInfo.current} → ${updateInfo.latest}`, 'yellow')
|
||||
);
|
||||
console.log(colored('═══════════════════════════════════════════════════════', 'cyan'));
|
||||
console.log('');
|
||||
console.log(` Run ${colored('ccs update', 'yellow')} to update`);
|
||||
|
||||
Reference in New Issue
Block a user