mirror of
https://github.com/tiennm99/ccs.git
synced 2026-08-16 10:22:40 +00:00
Merge remote-tracking branch 'origin/dev' into kai/feat/antigravity-failover
This commit is contained in:
@@ -1,3 +1,13 @@
|
||||
## [7.13.1](https://github.com/kaitranntt/ccs/compare/v7.13.0...v7.13.1) (2026-01-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cliproxy:** add management_key support for remote proxy auth separation ([0e58d0e](https://github.com/kaitranntt/ccs/commit/0e58d0e8b7fd07004990e99fbdc6a080380c0304))
|
||||
* **cliproxy:** add missing kiro/ghcp provider mappings in remote-auth-fetcher ([dea0e87](https://github.com/kaitranntt/ccs/commit/dea0e872bd529b2c6f825dc1d9e901d0896b7f41))
|
||||
* **cliproxy:** extract unique accountId from token filename for Kiro/GHCP ([7bb7ccc](https://github.com/kaitranntt/ccs/commit/7bb7ccc27fe0d9885c4dd7f23de664e2c8b4866f)), closes [#258](https://github.com/kaitranntt/ccs/issues/258)
|
||||
* **cliproxy:** proactive token refresh to prevent UND_ERR_SOCKET ([a6a653f](https://github.com/kaitranntt/ccs/commit/a6a653f14580888bcdccbf6b83b90d41b6b52136)), closes [#256](https://github.com/kaitranntt/ccs/issues/256)
|
||||
* **validation:** add Windows reserved name validation and version format edge cases ([ae1847d](https://github.com/kaitranntt/ccs/commit/ae1847d9011c8bff9caff206cf1d7082c61faf40))
|
||||
|
||||
## [7.13.0](https://github.com/kaitranntt/ccs/compare/v7.12.2...v7.13.0) (2026-01-03)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -268,7 +268,9 @@ bun run test:unit # Unit tests
|
||||
### Local Development
|
||||
```bash
|
||||
bun run dev # Build + start config server (http://localhost:3000)
|
||||
./scripts/dev-install.sh # Build, pack, install globally
|
||||
bun run dev:symlink # Symlink global 'ccs' → dev dist/ccs.js (fast iteration)
|
||||
bun run dev:unlink # Restore original global ccs
|
||||
./scripts/dev-install.sh # Build, pack, install globally (full install)
|
||||
rm -rf ~/.ccs # Clean environment
|
||||
```
|
||||
|
||||
|
||||
+15
-1
@@ -265,8 +265,22 @@ cd ccs
|
||||
# Create feature branch
|
||||
git checkout -b your-feature-name
|
||||
|
||||
# Option 1: Test with built binary
|
||||
# Test locally with ./dist/ccs.js
|
||||
|
||||
# Option 2: Symlink for seamless testing (recommended)
|
||||
bun run build
|
||||
bun run dev:symlink # Symlinks global 'ccs' to dev version
|
||||
# Now 'ccs' command uses your dev changes!
|
||||
|
||||
# Make changes
|
||||
# Test locally with ./ccs
|
||||
# Test with: ccs <command>
|
||||
|
||||
# When done developing:
|
||||
bun run dev:unlink # Restores original global ccs
|
||||
|
||||
# Run tests
|
||||
# Test with: ccs <command>
|
||||
|
||||
# Run tests
|
||||
bun run test # All tests
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
"ANTHROPIC_MODEL": "gemini-3-pro-preview",
|
||||
"ANTHROPIC_DEFAULT_OPUS_MODEL": "gemini-3-pro-preview",
|
||||
"ANTHROPIC_DEFAULT_SONNET_MODEL": "gemini-3-pro-preview",
|
||||
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "gemini-3-flash-preview"
|
||||
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "gemini-claude-sonnet-4-5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,10 @@ process.stdin.on('error', () => {
|
||||
*/
|
||||
function isCliAvailable(cmd) {
|
||||
try {
|
||||
const result = spawnSync('which', [cmd], {
|
||||
const isWindows = process.platform === 'win32';
|
||||
const whichCmd = isWindows ? 'where.exe' : 'which';
|
||||
|
||||
const result = spawnSync(whichCmd, [cmd], {
|
||||
encoding: 'utf8',
|
||||
timeout: 2000,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
|
||||
+3
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kaitranntt/ccs",
|
||||
"version": "7.13.0-dev.3",
|
||||
"version": "7.13.1-dev.3",
|
||||
"description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
|
||||
"keywords": [
|
||||
"cli",
|
||||
@@ -72,6 +72,8 @@
|
||||
"test:npm": "bun test tests/npm/",
|
||||
"test:native": "bash tests/native/unix/edge-cases.sh",
|
||||
"dev": "bun run build:server && bun dist/ccs.js config --dev",
|
||||
"dev:symlink": "bash scripts/dev-symlink.sh",
|
||||
"dev:unlink": "bash scripts/dev-symlink.sh --restore",
|
||||
"ui:build": "cd ui && bun run build",
|
||||
"ui:preview": "cd ui && bun run preview",
|
||||
"ui:validate": "cd ui && bun run validate",
|
||||
|
||||
Executable
+115
@@ -0,0 +1,115 @@
|
||||
#!/bin/bash
|
||||
# CCS Dev Symlink Setup
|
||||
# Creates symlinks for testing dev version with 'ccs' command
|
||||
#
|
||||
# Usage: ./scripts/dev-symlink.sh [--restore]
|
||||
#
|
||||
# Without --restore: Creates symlink from global 'ccs' to dist/ccs.js
|
||||
# With --restore: Restores original global 'ccs' from backup
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
RESTORE=false
|
||||
|
||||
# Parse arguments
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--restore) RESTORE=true ;;
|
||||
-h|--help)
|
||||
echo "Usage: $0 [--restore]"
|
||||
echo ""
|
||||
echo "Create symlink for dev testing:"
|
||||
echo " $0"
|
||||
echo ""
|
||||
echo "Restore original global ccs:"
|
||||
echo " $0 --restore"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "[X] Unknown option: $arg"
|
||||
echo "Use --help for usage"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Get to the right directory
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# Check if dist/ccs.js exists
|
||||
if [ ! -f "dist/ccs.js" ]; then
|
||||
echo "[X] ERROR: dist/ccs.js not found. Run 'bun run build' first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get absolute path to dev ccs.js
|
||||
DEV_CCS_PATH="$(pwd)/dist/ccs.js"
|
||||
|
||||
# Find global ccs installation
|
||||
GLOBAL_CCS_PATH=$(which ccs 2>/dev/null || true)
|
||||
|
||||
if [ -z "$GLOBAL_CCS_PATH" ]; then
|
||||
echo "[X] ERROR: No global 'ccs' installation found."
|
||||
echo "Install CCS globally first: npm install -g @kaitranntt/ccs"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[i] Found global ccs at: $GLOBAL_CCS_PATH"
|
||||
|
||||
if [ "$RESTORE" = true ]; then
|
||||
# Restore original ccs from backup
|
||||
BACKUP_PATH="${GLOBAL_CCS_PATH}.backup-dev"
|
||||
|
||||
if [ ! -f "$BACKUP_PATH" ] && [ ! -L "$BACKUP_PATH" ]; then
|
||||
echo "[X] ERROR: No backup found at $BACKUP_PATH"
|
||||
echo "Cannot restore - backup may have been deleted"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[i] Restoring original ccs from backup..."
|
||||
rm -f "$GLOBAL_CCS_PATH"
|
||||
if [ -L "$BACKUP_PATH" ]; then
|
||||
# Restore symlink
|
||||
cp -P "$BACKUP_PATH" "$GLOBAL_CCS_PATH"
|
||||
else
|
||||
# Restore regular file
|
||||
cp "$BACKUP_PATH" "$GLOBAL_CCS_PATH"
|
||||
fi
|
||||
chmod +x "$GLOBAL_CCS_PATH"
|
||||
rm -f "$BACKUP_PATH"
|
||||
|
||||
echo "[OK] Restored original global ccs"
|
||||
echo "Run 'ccs --version' to verify"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if already symlinked to our dev version
|
||||
if [ -L "$GLOBAL_CCS_PATH" ]; then
|
||||
CURRENT_TARGET=$(readlink "$GLOBAL_CCS_PATH" 2>/dev/null || true)
|
||||
if [ "$CURRENT_TARGET" = "$DEV_CCS_PATH" ]; then
|
||||
echo "[OK] Already symlinked to dev version"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create backup of current global ccs
|
||||
BACKUP_PATH="${GLOBAL_CCS_PATH}.backup-dev"
|
||||
if [ -f "$BACKUP_PATH" ] || [ -L "$BACKUP_PATH" ]; then
|
||||
echo "[i] Backup already exists, skipping backup creation"
|
||||
else
|
||||
echo "[i] Creating backup of current global ccs..."
|
||||
cp -P "$GLOBAL_CCS_PATH" "$BACKUP_PATH"
|
||||
echo "[OK] Backup created at: $BACKUP_PATH"
|
||||
fi
|
||||
|
||||
# Create symlink
|
||||
echo "[i] Creating symlink to dev version..."
|
||||
rm -f "$GLOBAL_CCS_PATH"
|
||||
ln -s "$DEV_CCS_PATH" "$GLOBAL_CCS_PATH"
|
||||
|
||||
echo "[OK] Symlinked global 'ccs' to dev version"
|
||||
echo ""
|
||||
echo "Now you can test dev changes with: ccs <command>"
|
||||
echo "To restore original: $0 --restore"
|
||||
echo ""
|
||||
echo "Test with: ccs --version"
|
||||
@@ -5,7 +5,7 @@
|
||||
* Extracted from api-command.ts for reuse and testability.
|
||||
*/
|
||||
|
||||
import { isReservedName } from '../../config/reserved-names';
|
||||
import { isReservedName, isWindowsReservedName } from '../../config/reserved-names';
|
||||
|
||||
/**
|
||||
* Validate API profile name
|
||||
@@ -24,6 +24,9 @@ export function validateApiName(name: string): string | null {
|
||||
if (isReservedName(name)) {
|
||||
return `'${name}' is a reserved name`;
|
||||
}
|
||||
if (isWindowsReservedName(name)) {
|
||||
return `'${name}' is a Windows reserved device name and cannot be used`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -84,10 +84,10 @@ export async function checkLatestVersion(): Promise<LatestVersionResult> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate version format
|
||||
* Validate version format (supports X.Y.Z or X.Y.Z-N suffix)
|
||||
*/
|
||||
export function isValidVersionFormat(version: string): boolean {
|
||||
return /^\d+\.\d+\.\d+$/.test(version);
|
||||
return /^\d+\.\d+\.\d+(-\d+)?$/.test(version);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { CLIProxyProfileName } from '../../auth/profile-detector';
|
||||
import { CLIProxyProvider } from '../types';
|
||||
import { isReservedName } from '../../config/reserved-names';
|
||||
import { isReservedName, isWindowsReservedName } from '../../config/reserved-names';
|
||||
import { isUnifiedMode } from '../../config/unified-config-loader';
|
||||
import { deleteConfigForPort } from '../config-generator';
|
||||
import { deleteSessionLockForPort } from '../session-tracker';
|
||||
@@ -58,6 +58,9 @@ export function validateProfileName(name: string): string | null {
|
||||
if (isReservedName(name)) {
|
||||
return `'${name}' is a reserved name`;
|
||||
}
|
||||
if (isWindowsReservedName(name)) {
|
||||
return `'${name}' is a Windows reserved device name and cannot be used`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -732,13 +732,15 @@ export async function handleCliproxyCommand(args: string[]): Promise<void> {
|
||||
|
||||
const installIdx = args.indexOf('--install');
|
||||
if (installIdx !== -1) {
|
||||
const version = args[installIdx + 1];
|
||||
let version = args[installIdx + 1];
|
||||
if (!version || version.startsWith('-')) {
|
||||
console.error(fail('Missing version argument for --install'));
|
||||
console.error(' Usage: ccs cliproxy --install <version>');
|
||||
console.error(' Example: ccs cliproxy --install 6.5.53');
|
||||
console.error(' Example: ccs cliproxy --install 6.6.80-0');
|
||||
process.exit(1);
|
||||
}
|
||||
// Strip leading 'v' prefix and whitespace (user may type " v6.6.80-0 ")
|
||||
version = version.trim().replace(/^v/, '');
|
||||
await handleInstallVersion(version, verbose);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,35 @@ export const RESERVED_PROFILE_NAMES = [
|
||||
|
||||
export type ReservedProfileName = (typeof RESERVED_PROFILE_NAMES)[number];
|
||||
|
||||
/**
|
||||
* Windows reserved device names - cannot be used as filenames on Windows.
|
||||
* Case-insensitive on Windows filesystem.
|
||||
*/
|
||||
export const WINDOWS_RESERVED_NAMES = [
|
||||
'CON',
|
||||
'PRN',
|
||||
'AUX',
|
||||
'NUL',
|
||||
'COM1',
|
||||
'COM2',
|
||||
'COM3',
|
||||
'COM4',
|
||||
'COM5',
|
||||
'COM6',
|
||||
'COM7',
|
||||
'COM8',
|
||||
'COM9',
|
||||
'LPT1',
|
||||
'LPT2',
|
||||
'LPT3',
|
||||
'LPT4',
|
||||
'LPT5',
|
||||
'LPT6',
|
||||
'LPT7',
|
||||
'LPT8',
|
||||
'LPT9',
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Check if a name is reserved and cannot be used for user profiles.
|
||||
* @param name - The profile name to check
|
||||
@@ -28,6 +57,18 @@ export function isReservedName(name: string): boolean {
|
||||
return RESERVED_PROFILE_NAMES.includes(name.toLowerCase() as ReservedProfileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a name is a Windows reserved device name.
|
||||
* These cause filesystem errors on Windows systems.
|
||||
* @param name - The name to check
|
||||
* @returns true if the name is a Windows reserved name
|
||||
*/
|
||||
export function isWindowsReservedName(name: string): boolean {
|
||||
return WINDOWS_RESERVED_NAMES.includes(
|
||||
name.toUpperCase() as (typeof WINDOWS_RESERVED_NAMES)[number]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a profile name and throw if reserved.
|
||||
* @param name - The profile name to validate
|
||||
|
||||
Reference in New Issue
Block a user