Skip to content

Commit 71d618a

Browse files
authored
Merge pull request #54 from kaitranntt/dev
fix(cliproxy): model config improvements and fallback version update
2 parents ba803ce + ebb4e77 commit 71d618a

21 files changed

+1627
-30
lines changed

.github/workflows/dev-release.yml

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
name: Dev Release
2+
3+
on:
4+
push:
5+
branches: [dev]
6+
7+
jobs:
8+
release:
9+
# Skip if commit message contains [skip ci] or is a release commit
10+
if: "!contains(github.event.head_commit.message, '[skip ci]')"
11+
runs-on: ubuntu-latest
12+
13+
permissions:
14+
contents: write
15+
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v4
19+
with:
20+
fetch-depth: 0
21+
token: ${{ secrets.PAT_TOKEN }}
22+
23+
- name: Setup Bun
24+
uses: oven-sh/setup-bun@v2
25+
with:
26+
bun-version: latest
27+
28+
- name: Setup Node.js
29+
uses: actions/setup-node@v4
30+
with:
31+
node-version: '22'
32+
registry-url: 'https://registry.npmjs.org'
33+
34+
- name: Install dependencies
35+
run: bun install --frozen-lockfile
36+
37+
- name: Build and validate
38+
run: |
39+
bun run build
40+
bun run validate
41+
42+
- name: Bump dev version
43+
id: bump
44+
run: |
45+
CURRENT=$(cat VERSION)
46+
PKG_NAME=$(jq -r '.name' package.json)
47+
48+
# Extract base version
49+
if [[ "$CURRENT" =~ ^([0-9]+\.[0-9]+\.[0-9]+)(-dev\.([0-9]+))?$ ]]; then
50+
BASE="${BASH_REMATCH[1]}"
51+
else
52+
echo "Invalid version format: $CURRENT"
53+
exit 1
54+
fi
55+
56+
# Find highest published dev version for this base
57+
LATEST_DEV=$(npm view "${PKG_NAME}" versions --json 2>/dev/null | \
58+
jq -r '.[]' | \
59+
grep "^${BASE}-dev\." | \
60+
sed "s/${BASE}-dev\.//" | \
61+
sort -n | \
62+
tail -1)
63+
64+
if [[ -z "$LATEST_DEV" ]]; then
65+
NEW_DEV=1
66+
else
67+
NEW_DEV=$((LATEST_DEV + 1))
68+
fi
69+
70+
NEW_VERSION="${BASE}-dev.${NEW_DEV}"
71+
72+
echo "current=$CURRENT" >> $GITHUB_OUTPUT
73+
echo "new=$NEW_VERSION" >> $GITHUB_OUTPUT
74+
echo "Bumping: $CURRENT -> $NEW_VERSION"
75+
76+
- name: Update version files
77+
run: |
78+
NEW_VERSION="${{ steps.bump.outputs.new }}"
79+
80+
# Update VERSION file
81+
echo "$NEW_VERSION" > VERSION
82+
83+
# Update package.json
84+
jq --arg v "$NEW_VERSION" '.version = $v' package.json > package.json.tmp
85+
mv package.json.tmp package.json
86+
87+
# Update installers
88+
sed -i "s/^VERSION=.*/VERSION=\"$NEW_VERSION\"/" installers/install.sh
89+
sed -i "s/^\$Version = .*/\$Version = \"$NEW_VERSION\"/" installers/install.ps1
90+
91+
- name: Publish to npm
92+
env:
93+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
94+
run: npm publish --tag dev
95+
96+
- name: Commit version bump
97+
run: |
98+
git config user.name "github-actions[bot]"
99+
git config user.email "github-actions[bot]@users.noreply.github.com"
100+
git add VERSION package.json installers/install.sh installers/install.ps1
101+
git commit -m "chore(release): ${{ steps.bump.outputs.new }} [skip ci]"
102+
git push origin dev

.github/workflows/release.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ name: Release
22

33
on:
44
push:
5-
branches:
6-
- main
7-
- dev
5+
branches: [main]
86
workflow_dispatch:
97

108
jobs:
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: Sync Dev After Main Release
2+
3+
on:
4+
workflow_run:
5+
workflows: ["Release"]
6+
types: [completed]
7+
branches: [main]
8+
9+
jobs:
10+
sync-dev:
11+
# Only run if Release workflow succeeded on main branch
12+
if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'main' }}
13+
runs-on: ubuntu-latest
14+
15+
permissions:
16+
contents: write
17+
18+
steps:
19+
- name: Checkout
20+
uses: actions/checkout@v4
21+
with:
22+
fetch-depth: 0
23+
token: ${{ secrets.PAT_TOKEN }}
24+
25+
- name: Configure Git
26+
run: |
27+
git config user.name "github-actions[bot]"
28+
git config user.email "github-actions[bot]@users.noreply.github.com"
29+
30+
- name: Sync dev with main
31+
run: |
32+
git fetch origin main dev
33+
git checkout dev
34+
35+
# Attempt merge, handle version conflicts by taking main's versions
36+
if ! git merge origin/main --no-edit -m "chore(sync): merge main into dev after release [skip ci]"; then
37+
echo "Merge conflicts detected, resolving version files..."
38+
39+
# For version files, take main's version (new stable base)
40+
for file in VERSION package.json installers/install.sh installers/install.ps1 CHANGELOG.md; do
41+
if git diff --name-only --diff-filter=U | grep -q "^${file}$"; then
42+
git checkout --theirs "$file"
43+
git add "$file"
44+
fi
45+
done
46+
47+
# Complete the merge
48+
git commit --no-edit
49+
fi
50+
51+
git push origin dev

.releaserc.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
{
2-
"branches": [
3-
"main",
4-
{ "name": "dev", "prerelease": true }
5-
],
2+
"branches": ["main"],
63
"plugins": [
74
"@semantic-release/commit-analyzer",
85
"@semantic-release/release-notes-generator",

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
5.5.0
1+
5.5.0-dev.11

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@kaitranntt/ccs",
3-
"version": "5.5.0",
3+
"version": "5.5.0-dev.11",
44
"description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
55
"keywords": [
66
"cli",

src/ccs.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,27 @@ async function main(): Promise<void> {
241241
// Special case: update command
242242
if (firstArg === 'update' || firstArg === '--update') {
243243
const updateArgs = args.slice(1);
244+
245+
// Handle --help for update command
246+
if (updateArgs.includes('--help') || updateArgs.includes('-h')) {
247+
console.log('');
248+
console.log('Usage: ccs update [options]');
249+
console.log('');
250+
console.log('Options:');
251+
console.log(' --force Force reinstall current version');
252+
console.log(' --beta, --dev Install from dev channel (unstable)');
253+
console.log(' --help, -h Show this help message');
254+
console.log('');
255+
console.log('Examples:');
256+
console.log(' ccs update Update to latest stable');
257+
console.log(' ccs update --force Force reinstall');
258+
console.log(' ccs update --beta Install dev channel');
259+
console.log('');
260+
return;
261+
}
262+
244263
const forceFlag = updateArgs.includes('--force');
245-
const betaFlag = updateArgs.includes('--beta');
264+
const betaFlag = updateArgs.includes('--beta') || updateArgs.includes('--dev');
246265
await handleUpdateCommand({ force: forceFlag, beta: betaFlag });
247266
return;
248267
}
@@ -263,6 +282,13 @@ async function main(): Promise<void> {
263282
return;
264283
}
265284

285+
// Special case: cliproxy command (manages CLIProxyAPI binary)
286+
if (firstArg === 'cliproxy') {
287+
const { handleCliproxyCommand } = await import('./commands/cliproxy-command');
288+
await handleCliproxyCommand(args.slice(1));
289+
return;
290+
}
291+
266292
// Special case: headless delegation (-p flag)
267293
if (args.includes('-p') || args.includes('--prompt')) {
268294
const { DelegationHandler } = await import('./delegation/delegation-handler');

src/cliproxy/binary-manager.ts

Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ const DEFAULT_CONFIG: BinaryManagerConfig = {
6363
binPath: getBinDir(),
6464
maxRetries: 3,
6565
verbose: false,
66+
forceVersion: false,
6667
};
6768

6869
/**
@@ -88,6 +89,12 @@ export class BinaryManager {
8889
if (fs.existsSync(binaryPath)) {
8990
this.log(`Binary exists: ${binaryPath}`);
9091

92+
// Skip auto-update if forceVersion is set (user requested specific version)
93+
if (this.config.forceVersion) {
94+
this.log(`Force version mode: skipping auto-update`);
95+
return this.getBinaryPath();
96+
}
97+
9198
// Check for updates in background (non-blocking for UX)
9299
try {
93100
const updateResult = await this.checkForUpdates();
@@ -114,16 +121,21 @@ export class BinaryManager {
114121
// Download, verify, extract
115122
this.log('Binary not found, downloading...');
116123

117-
// Check latest version before first download
118-
try {
119-
const latestVersion = await this.fetchLatestVersion();
120-
if (latestVersion && this.isNewerVersion(latestVersion, this.config.version)) {
121-
this.log(`Using latest version: ${latestVersion} (instead of ${this.config.version})`);
122-
this.config.version = latestVersion;
124+
// Skip auto-upgrade to latest if forceVersion is set
125+
if (!this.config.forceVersion) {
126+
// Check latest version before first download
127+
try {
128+
const latestVersion = await this.fetchLatestVersion();
129+
if (latestVersion && this.isNewerVersion(latestVersion, this.config.version)) {
130+
this.log(`Using latest version: ${latestVersion} (instead of ${this.config.version})`);
131+
this.config.version = latestVersion;
132+
}
133+
} catch {
134+
// Use pinned version if API fails
135+
this.log(`Using pinned version: ${this.config.version}`);
123136
}
124-
} catch {
125-
// Use pinned version if API fails
126-
this.log(`Using pinned version: ${this.config.version}`);
137+
} else {
138+
this.log(`Force version mode: using specified version ${this.config.version}`);
127139
}
128140

129141
await this.downloadAndInstall();
@@ -900,4 +912,54 @@ export function getCLIProxyPath(): string {
900912
return manager.getBinaryPath();
901913
}
902914

915+
/**
916+
* Get installed CLIProxyAPI version from .version file
917+
* Returns the fallback version if not installed or version file missing
918+
*/
919+
export function getInstalledCliproxyVersion(): string {
920+
const versionFile = path.join(getBinDir(), '.version');
921+
if (fs.existsSync(versionFile)) {
922+
try {
923+
return fs.readFileSync(versionFile, 'utf8').trim();
924+
} catch {
925+
return CLIPROXY_FALLBACK_VERSION;
926+
}
927+
}
928+
return CLIPROXY_FALLBACK_VERSION;
929+
}
930+
931+
/**
932+
* Install a specific version of CLIProxyAPI
933+
* Deletes existing binary and downloads the specified version
934+
*
935+
* @param version Version to install (e.g., "6.5.40")
936+
* @param verbose Enable verbose logging
937+
*/
938+
export async function installCliproxyVersion(version: string, verbose = false): Promise<void> {
939+
// Use forceVersion to prevent auto-upgrade to latest
940+
const manager = new BinaryManager({ version, verbose, forceVersion: true });
941+
942+
// Delete existing binary if present
943+
if (manager.isBinaryInstalled()) {
944+
const currentVersion = getInstalledCliproxyVersion();
945+
if (verbose) {
946+
console.log(`[i] Removing existing CLIProxyAPI v${currentVersion}`);
947+
}
948+
manager.deleteBinary();
949+
}
950+
951+
// Install specified version (forceVersion prevents auto-upgrade)
952+
await manager.ensureBinary();
953+
}
954+
955+
/**
956+
* Fetch the latest CLIProxyAPI version from GitHub API
957+
* @returns Latest version string (e.g., "6.5.40")
958+
*/
959+
export async function fetchLatestCliproxyVersion(): Promise<string> {
960+
const manager = new BinaryManager();
961+
const result = await manager.checkForUpdates();
962+
return result.latestVersion;
963+
}
964+
903965
export default BinaryManager;

0 commit comments

Comments
 (0)