mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 04:18:05 +00:00
* fix(cliproxy): use double-dash flags for cliproxyapi auth (#24) CLIProxyAPI updated from single-dash to double-dash CLI flags. Changed -login to --login, -codex-login to --codex-login, -antigravity-login to --antigravity-login, -config to --config, and -no-browser to --no-browser. * chore(release): 5.1.1-beta.1 [skip ci] ## [5.1.1-beta.1](https://github.com/kaitranntt/ccs/compare/v5.1.0...v5.1.1-beta.1) (2025-12-01) ### Bug Fixes * **cliproxy:** use double-dash flags for cliproxyapi auth ([#24](https://github.com/kaitranntt/ccs/issues/24)) ([4c81f28](https://github.com/kaitranntt/ccs/commit/4c81f28f0b67ef92cf74d0f5c13a5943ff0a7f00)) * chore(release): 5.1.1-beta.2 [skip ci] ## [5.1.1-beta.2](https://github.com/kaitranntt/ccs/compare/v5.1.1-beta.1...v5.1.1-beta.2) (2025-12-01) ### Bug Fixes * **cliproxy:** use double-dash flags for cliproxyapi auth ([9489884](https://github.com/kaitranntt/ccs/commit/94898848ea4533dcfc142e1b6c9bf939ba655537)) * fix(glmt): handle 401 errors and headers-already-sent exception (#30) - add res.headersSent check before writing error headers - write error as SSE event when headers already sent (streaming mode) - add parseUpstreamError() for user-friendly error messages - return proper HTTP status codes (401, 403, 429, 502, 504) - clear auth error: check ANTHROPIC_AUTH_TOKEN in config Fixes #26 * chore(release): 5.1.1-beta.3 [skip ci] ## [5.1.1-beta.3](https://github.com/kaitranntt/ccs/compare/v5.1.1-beta.2...v5.1.1-beta.3) (2025-12-01) ### Bug Fixes * **glmt:** handle 401 errors and headers-already-sent exception ([#30](https://github.com/kaitranntt/ccs/issues/30)) ([c953382](https://github.com/kaitranntt/ccs/commit/c95338232a37981b95b785b47185ce18d6d94b7a)), closes [#26](https://github.com/kaitranntt/ccs/issues/26) * chore(release): enable success comments and released labels in semantic-release * feat(cliproxy): add qwen code oauth provider support (#31) Add 'qwen' as new CLIProxy OAuth provider with --qwen-login flag. Support qwen3-coder model with base config at config/base-qwen.settings.json. Register qwen OAuth endpoints and token prefix handling. Update help documentation across all CLI entry points. Implements issue #29 * chore(release): 5.2.0-beta.1 [skip ci] # [5.2.0-beta.1](https://github.com/kaitranntt/ccs/compare/v5.1.1-beta.3...v5.2.0-beta.1) (2025-12-01) ### Features * **cliproxy:** add qwen code oauth provider support ([#31](https://github.com/kaitranntt/ccs/issues/31)) ([a3f1e52](https://github.com/kaitranntt/ccs/commit/a3f1e52ac68600ba0806d67aacceb6477ffa3543)), closes [#29](https://github.com/kaitranntt/ccs/issues/29) * feat(cliproxy): auto-update cliproxyapi to latest release on startup - add github api integration to fetch latest release version - check for updates on every startup (cached for 1 hour) - auto-download newer versions when available - rename CLIPROXY_VERSION to CLIPROXY_FALLBACK_VERSION (safety net) - add version tracking (.version file) and caching (.version-cache.json) - non-blocking: startup continues if update check fails * chore(release): 5.2.0-beta.2 [skip ci] # [5.2.0-beta.2](https://github.com/kaitranntt/ccs/compare/v5.2.0-beta.1...v5.2.0-beta.2) (2025-12-01) ### Features * **cliproxy:** auto-update cliproxyapi to latest release on startup ([8873ccd](https://github.com/kaitranntt/ccs/commit/8873ccd981679e8acff8965accdc22215c6e4aa2)) * chore(release): 5.3.0-beta.1 [skip ci] # [5.3.0-beta.1](https://github.com/kaitranntt/ccs/compare/v5.2.0...v5.3.0-beta.1) (2025-12-01) ### Bug Fixes * **cliproxy:** use double-dash flags for cliproxyapi auth ([#24](https://github.com/kaitranntt/ccs/issues/24)) ([4c81f28](https://github.com/kaitranntt/ccs/commit/4c81f28f0b67ef92cf74d0f5c13a5943ff0a7f00)) * **glmt:** handle 401 errors and headers-already-sent exception ([#30](https://github.com/kaitranntt/ccs/issues/30)) ([c953382](https://github.com/kaitranntt/ccs/commit/c95338232a37981b95b785b47185ce18d6d94b7a)), closes [#26](https://github.com/kaitranntt/ccs/issues/26) ### Features * **cliproxy:** add qwen code oauth provider support ([#31](https://github.com/kaitranntt/ccs/issues/31)) ([a3f1e52](https://github.com/kaitranntt/ccs/commit/a3f1e52ac68600ba0806d67aacceb6477ffa3543)), closes [#29](https://github.com/kaitranntt/ccs/issues/29) * **cliproxy:** auto-update cliproxyapi to latest release on startup ([8873ccd](https://github.com/kaitranntt/ccs/commit/8873ccd981679e8acff8965accdc22215c6e4aa2)) * fix(cliproxy): improve qwen oauth error handling - Updated OAuth port documentation with correct flow types per provider - Removed Qwen from port cleanup logic (Device Code Flow has no callback) - Fixed Qwen error messages to show Device Code Flow troubleshooting tips Fixes #29 * chore(release): 5.3.0-beta.2 [skip ci] # [5.3.0-beta.2](https://github.com/kaitranntt/ccs/compare/v5.3.0-beta.1...v5.3.0-beta.2) (2025-12-01) ### Bug Fixes * **cliproxy:** improve qwen oauth error handling ([7e0b0fe](https://github.com/kaitranntt/ccs/commit/7e0b0feca8ce2ed5d505c5bf6c84e54c6df8839e)), closes [#29](https://github.com/kaitranntt/ccs/issues/29) * feat(profile): add profile command with configuration display * feat(shell-completion): add --force flag and fix zsh profile coloring * fix(prompt): improve password input handling with raw mode and buffer support * chore(release): 5.3.0-beta.3 [skip ci] # [5.3.0-beta.3](https://github.com/kaitranntt/ccs/compare/v5.3.0-beta.2...v5.3.0-beta.3) (2025-12-01) ### Bug Fixes * **prompt:** improve password input handling with raw mode and buffer support ([bc56d2e](https://github.com/kaitranntt/ccs/commit/bc56d2e135532b2ae443144dd42217b26bcba951)) ### Features * **profile:** add profile command with configuration display ([53d7a15](https://github.com/kaitranntt/ccs/commit/53d7a15c047e760723e051dc0f7be3c0dd42d087)) * **shell-completion:** add --force flag and fix zsh profile coloring ([7faed1d](https://github.com/kaitranntt/ccs/commit/7faed1d84ba29ba02bf687bae5b3458617512e67)) * chore(release): 5.3.0-beta.4 [skip ci] # [5.3.0-beta.4](https://github.com/kaitranntt/ccs/compare/v5.3.0-beta.3...v5.3.0-beta.4) (2025-12-01) ### Bug Fixes * **cliproxy:** improve qwen oauth error handling ([#33](https://github.com/kaitranntt/ccs/issues/33)) ([1c3374f](https://github.com/kaitranntt/ccs/commit/1c3374f6a7e4440e299d49b58808c6454b4547c2)), closes [#29](https://github.com/kaitranntt/ccs/issues/29) --------- Co-authored-by: semantic-release-bot <semantic-release-bot@martynus.net>
291 lines
8.2 KiB
TypeScript
291 lines
8.2 KiB
TypeScript
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import * as os from 'os';
|
|
// import { execSync } from 'child_process'; // Currently unused
|
|
|
|
type ShellType = 'bash' | 'zsh' | 'fish' | 'powershell' | null;
|
|
|
|
interface InstallResult {
|
|
success: boolean;
|
|
alreadyInstalled?: boolean;
|
|
message?: string;
|
|
reload?: string;
|
|
}
|
|
|
|
/**
|
|
* Shell Completion Installer
|
|
* Auto-configures shell completion for bash, zsh, fish, PowerShell
|
|
*/
|
|
export class ShellCompletionInstaller {
|
|
private homeDir: string;
|
|
private ccsDir: string;
|
|
private completionDir: string;
|
|
private scriptsDir: string;
|
|
|
|
constructor() {
|
|
this.homeDir = os.homedir();
|
|
this.ccsDir = path.join(this.homeDir, '.ccs');
|
|
this.completionDir = path.join(this.ccsDir, 'completions');
|
|
this.scriptsDir = path.join(__dirname, '../../scripts/completion');
|
|
}
|
|
|
|
/**
|
|
* Detect current shell
|
|
*/
|
|
detectShell(): ShellType {
|
|
const shell = process.env.SHELL || '';
|
|
|
|
if (shell.includes('bash')) return 'bash';
|
|
if (shell.includes('zsh')) return 'zsh';
|
|
if (shell.includes('fish')) return 'fish';
|
|
if (process.platform === 'win32') return 'powershell';
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Ensure completion files are in ~/.ccs/completions/
|
|
*/
|
|
ensureCompletionFiles(): void {
|
|
if (!fs.existsSync(this.completionDir)) {
|
|
fs.mkdirSync(this.completionDir, { recursive: true });
|
|
}
|
|
|
|
// Copy completion scripts
|
|
const files = ['ccs.bash', 'ccs.zsh', 'ccs.fish', 'ccs.ps1'];
|
|
files.forEach((file) => {
|
|
const src = path.join(this.scriptsDir, file);
|
|
const dest = path.join(this.completionDir, file);
|
|
|
|
if (fs.existsSync(src)) {
|
|
fs.copyFileSync(src, dest);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Safely create directory, checking for file conflicts
|
|
*/
|
|
private ensureDirectory(dirPath: string): void {
|
|
if (fs.existsSync(dirPath)) {
|
|
const stat = fs.statSync(dirPath);
|
|
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.`
|
|
);
|
|
}
|
|
// Directory exists, nothing to do
|
|
return;
|
|
}
|
|
|
|
// Check parent directories recursively
|
|
const parentDir = path.dirname(dirPath);
|
|
if (parentDir !== dirPath) {
|
|
this.ensureDirectory(parentDir);
|
|
}
|
|
|
|
// Create the directory
|
|
fs.mkdirSync(dirPath);
|
|
}
|
|
|
|
/**
|
|
* Install bash completion
|
|
*/
|
|
private installBash(force = false): InstallResult {
|
|
const rcFile = path.join(this.homeDir, '.bashrc');
|
|
const completionPath = path.join(this.completionDir, 'ccs.bash');
|
|
|
|
if (!fs.existsSync(completionPath)) {
|
|
throw new Error('Completion file not found. Please reinstall CCS.');
|
|
}
|
|
|
|
const marker = '# CCS shell completion';
|
|
const sourceCmd = `source "${completionPath}"`;
|
|
const block = `\n${marker}\n${sourceCmd}\n`;
|
|
|
|
// Check if already installed
|
|
if (fs.existsSync(rcFile)) {
|
|
const content = fs.readFileSync(rcFile, 'utf8');
|
|
if (content.includes(marker)) {
|
|
if (!force) {
|
|
return { success: true, alreadyInstalled: true };
|
|
}
|
|
// Force: completion files already updated by ensureCompletionFiles()
|
|
return {
|
|
success: true,
|
|
message: `Updated completion files in ${this.completionDir}`,
|
|
reload: 'source ~/.bashrc',
|
|
};
|
|
}
|
|
}
|
|
|
|
// Append to .bashrc
|
|
fs.appendFileSync(rcFile, block);
|
|
|
|
return {
|
|
success: true,
|
|
message: `Added to ${rcFile}`,
|
|
reload: 'source ~/.bashrc',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Install zsh completion
|
|
*/
|
|
private installZsh(force = false): InstallResult {
|
|
const rcFile = path.join(this.homeDir, '.zshrc');
|
|
const completionPath = path.join(this.completionDir, 'ccs.zsh');
|
|
const zshCompDir = path.join(this.homeDir, '.zsh', 'completion');
|
|
|
|
if (!fs.existsSync(completionPath)) {
|
|
throw new Error('Completion file not found. Please reinstall CCS.');
|
|
}
|
|
|
|
// Create zsh completion directory (with file conflict checking)
|
|
this.ensureDirectory(zshCompDir);
|
|
|
|
// Copy to zsh completion directory (always update for force)
|
|
const destFile = path.join(zshCompDir, '_ccs');
|
|
fs.copyFileSync(completionPath, destFile);
|
|
|
|
const marker = '# CCS shell completion';
|
|
const setupCmds = ['fpath=(~/.zsh/completion $fpath)', 'autoload -Uz compinit && compinit'];
|
|
const block = `\n${marker}\n${setupCmds.join('\n')}\n`;
|
|
|
|
// Check if already installed
|
|
if (fs.existsSync(rcFile)) {
|
|
const content = fs.readFileSync(rcFile, 'utf8');
|
|
if (content.includes(marker)) {
|
|
if (!force) {
|
|
return { success: true, alreadyInstalled: true };
|
|
}
|
|
// Force: files already updated above
|
|
return {
|
|
success: true,
|
|
message: `Updated ${destFile}`,
|
|
reload: 'source ~/.zshrc',
|
|
};
|
|
}
|
|
}
|
|
|
|
// Append to .zshrc
|
|
fs.appendFileSync(rcFile, block);
|
|
|
|
return {
|
|
success: true,
|
|
message: `Added to ${rcFile}`,
|
|
reload: 'source ~/.zshrc',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Install fish completion
|
|
*/
|
|
private installFish(force = false): InstallResult {
|
|
const completionPath = path.join(this.completionDir, 'ccs.fish');
|
|
const fishCompDir = path.join(this.homeDir, '.config', 'fish', 'completions');
|
|
|
|
if (!fs.existsSync(completionPath)) {
|
|
throw new Error('Completion file not found. Please reinstall CCS.');
|
|
}
|
|
|
|
// Create fish completion directory (with file conflict checking)
|
|
this.ensureDirectory(fishCompDir);
|
|
|
|
// Copy to fish completion directory (fish auto-loads from here)
|
|
const destFile = path.join(fishCompDir, 'ccs.fish');
|
|
|
|
// Check if already installed
|
|
if (fs.existsSync(destFile) && !force) {
|
|
return { success: true, alreadyInstalled: true };
|
|
}
|
|
|
|
fs.copyFileSync(completionPath, destFile);
|
|
|
|
return {
|
|
success: true,
|
|
message: `Installed to ${destFile}`,
|
|
reload: 'Fish auto-loads completions (no reload needed)',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Install PowerShell completion
|
|
*/
|
|
private installPowerShell(force = false): InstallResult {
|
|
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)) {
|
|
throw new Error('Completion file not found. Please reinstall CCS.');
|
|
}
|
|
|
|
const marker = '# CCS shell completion';
|
|
const sourceCmd = `. "${completionPath.replace(/\\/g, '\\\\')}"`;
|
|
const block = `\n${marker}\n${sourceCmd}\n`;
|
|
|
|
// Create profile directory if needed (with file conflict checking)
|
|
const profileDir = path.dirname(profilePath);
|
|
this.ensureDirectory(profileDir);
|
|
|
|
// Check if already installed
|
|
if (fs.existsSync(profilePath)) {
|
|
const content = fs.readFileSync(profilePath, 'utf8');
|
|
if (content.includes(marker)) {
|
|
if (!force) {
|
|
return { success: true, alreadyInstalled: true };
|
|
}
|
|
// Force: completion files already updated by ensureCompletionFiles()
|
|
return {
|
|
success: true,
|
|
message: `Updated completion files in ${this.completionDir}`,
|
|
reload: '. $PROFILE',
|
|
};
|
|
}
|
|
}
|
|
|
|
// Append to PowerShell profile
|
|
fs.appendFileSync(profilePath, block);
|
|
|
|
return {
|
|
success: true,
|
|
message: `Added to ${profilePath}`,
|
|
reload: '. $PROFILE',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Install for detected or specified shell
|
|
*/
|
|
install(shell: ShellType = null, options: { force?: boolean } = {}): InstallResult {
|
|
const targetShell = shell || this.detectShell();
|
|
const { force = false } = options;
|
|
|
|
if (!targetShell) {
|
|
throw new Error(
|
|
'Could not detect shell. Please specify: --bash, --zsh, --fish, or --powershell'
|
|
);
|
|
}
|
|
|
|
// Ensure completion files exist (always copy latest)
|
|
this.ensureCompletionFiles();
|
|
|
|
// Install for target shell
|
|
switch (targetShell) {
|
|
case 'bash':
|
|
return this.installBash(force);
|
|
case 'zsh':
|
|
return this.installZsh(force);
|
|
case 'fish':
|
|
return this.installFish(force);
|
|
case 'powershell':
|
|
return this.installPowerShell(force);
|
|
default:
|
|
throw new Error(`Unsupported shell: ${targetShell}`);
|
|
}
|
|
}
|
|
}
|