New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details → →
Socket
Book a DemoSign in
Socket

codecli-hostapi

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

codecli-hostapi - npm Package Compare versions

Comparing version
1.0.0
to
1.1.0
+288
-235
bin/activate.js

@@ -9,4 +9,13 @@ #!/usr/bin/env node

const PROXY_PORT = 18080;
const PROXY_BASE_URL = `http://127.0.0.1:${PROXY_PORT}`;
const PROXY_HOST = '127.0.0.1';
const PROXY_BASE_URL = `http://${PROXY_HOST}:${PROXY_PORT}`;
// XDG-compliant paths
const HOME = process.env.HOME || process.env.USERPROFILE;
const DATA_DIR = path.join(HOME, '.local', 'share', 'codecli-hostapi');
const CONFIG_DIR = path.join(HOME, '.config', 'codecli-hostapi');
const BACKUP_DIR = path.join(DATA_DIR, 'backups');
const PROXY_SCRIPT_PATH = path.join(DATA_DIR, 'proxy.py');
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
const PROXY_SCRIPT = `#!/usr/bin/env python3

@@ -16,3 +25,3 @@ import json, subprocess, time, sys, uuid

HOST = "127.0.0.1"
HOST = "${PROXY_HOST}"
PORT = ${PROXY_PORT}

@@ -66,3 +75,3 @@

self.end_headers()
self.wfile.write(json.dumps({"ok": True}).encode())
self.wfile.write(json.dumps({"ok": True, "endpoint": f"http://{HOST}:{PORT}"}).encode())
return

@@ -176,2 +185,3 @@ self.send_response(404)

log(f"anthropic-cli-proxy listening on http://{HOST}:{PORT}")
log(f"API Endpoint: http://{HOST}:{PORT}/v1/messages")
httpd.serve_forever()

@@ -185,2 +195,8 @@

function ensureDir(dir) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
}
function run(cmd, opts = {}) {

@@ -196,78 +212,78 @@ if (!opts.silent) console.log(`> ${cmd}`);

function findClawdbotConfig() {
const home = process.env.HOME || process.env.USERPROFILE;
const candidates = [
path.join(home, 'clawd', 'clawdbot.json'),
path.join(home, '.config', 'clawdbot', 'clawdbot.json'),
path.join(home, 'clawdbot.json'),
];
for (const p of candidates) {
if (fs.existsSync(p)) return p;
async function prompt(question) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise(resolve => {
rl.question(question, answer => {
rl.close();
resolve(answer.trim());
});
});
}
function loadConfig() {
if (fs.existsSync(CONFIG_PATH)) {
return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
}
return null;
return { installed: false, apps: {} };
}
function getBackupDir(configPath) {
return path.join(path.dirname(configPath), 'backups');
function saveConfig(config) {
ensureDir(CONFIG_DIR);
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
}
function ensureBackupDir(configPath) {
const backupDir = getBackupDir(configPath);
if (!fs.existsSync(backupDir)) {
fs.mkdirSync(backupDir, { recursive: true });
function isProxyRunning() {
try {
const result = run(`lsof -i :${PROXY_PORT} -t`, { silent: true, ignoreError: true });
return result.trim().length > 0;
} catch {
return false;
}
return backupDir;
}
function hasHostProxy(config) {
const baseUrl = config?.models?.providers?.anthropic?.baseUrl || '';
return baseUrl.includes('127.0.0.1') || baseUrl.includes('localhost');
function getProxyPid() {
try {
return run(`lsof -i :${PROXY_PORT} -t`, { silent: true }).trim().split('\n')[0];
} catch {
return null;
}
}
function isOurProxy(config) {
const baseUrl = config?.models?.providers?.anthropic?.baseUrl || '';
return baseUrl === PROXY_BASE_URL;
}
// ============ App Configurations ============
function listBackups(configPath) {
const backupDir = getBackupDir(configPath);
if (!fs.existsSync(backupDir)) return [];
return fs.readdirSync(backupDir)
.filter(f => f.endsWith('.json'))
.map(f => {
const fullPath = path.join(backupDir, f);
const stat = fs.statSync(fullPath);
return {
name: f.replace('.json', ''),
path: fullPath,
time: stat.mtime
};
})
.sort((a, b) => b.time - a.time);
function findClawdbotConfig() {
const candidates = [
path.join(HOME, 'clawd', 'clawdbot.json'),
path.join(HOME, '.config', 'clawdbot', 'clawdbot.json'),
path.join(HOME, 'clawdbot.json'),
];
for (const p of candidates) {
if (fs.existsSync(p)) return p;
}
return null;
}
function createBackup(configPath, backupName) {
const backupDir = ensureBackupDir(configPath);
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
const fileName = `${backupName}_${timestamp}.json`;
const backupPath = path.join(backupDir, fileName);
function configureClawdbot(configPath) {
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
// Backup first
const backupPath = path.join(BACKUP_DIR, `clawdbot_${Date.now()}.json`);
ensureDir(BACKUP_DIR);
fs.copyFileSync(configPath, backupPath);
return backupPath;
}
console.log(` šŸ’¾ Backup: ${path.basename(backupPath)}`);
function restoreBackup(configPath, backupPath) {
fs.copyFileSync(backupPath, configPath);
}
// Update config
if (!config.models) config.models = {};
if (!config.models.providers) config.models.providers = {};
config.models.providers.anthropic = {
baseUrl: PROXY_BASE_URL,
models: []
};
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
console.log(` āœ“ Updated: ${configPath}`);
async function prompt(question) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise(resolve => {
rl.question(question, answer => {
rl.close();
resolve(answer.trim());
});
});
return backupPath;
}

@@ -277,4 +293,4 @@

async function doActivate(configPath) {
console.log('\nšŸ”§ Activating codecli-hostapi...\n');
async function doInstall() {
console.log('\nšŸ”§ Installing codecli-hostapi...\n');

@@ -299,48 +315,17 @@ // Check prerequisites

// Read current config
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
// Auto backup before activation
if (!isOurProxy(config)) {
const backupName = hasHostProxy(config) ? 'hostbak' : 'initbak';
const backupPath = createBackup(configPath, backupName);
console.log(`\nšŸ’¾ Auto backup created: ${path.basename(backupPath)}`);
}
// Update config
console.log('\nšŸ“ Configuring clawdbot...');
if (!config.models) config.models = {};
if (!config.models.providers) config.models.providers = {};
config.models.providers.anthropic = {
baseUrl: PROXY_BASE_URL,
models: []
};
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
console.log(' āœ“ Updated clawdbot.json with proxy settings');
// Install proxy script
console.log('\nšŸ“¦ Installing proxy...');
const proxyPath = '/usr/local/bin/anthropic-cli-proxy.py';
const tempPath = '/tmp/anthropic-cli-proxy.py';
fs.writeFileSync(tempPath, PROXY_SCRIPT);
console.log('\nšŸ“¦ Installing proxy script...');
ensureDir(DATA_DIR);
fs.writeFileSync(PROXY_SCRIPT_PATH, PROXY_SCRIPT);
fs.chmodSync(PROXY_SCRIPT_PATH, '755');
console.log(` āœ“ Installed: ${PROXY_SCRIPT_PATH}`);
let installedProxyPath = tempPath;
try {
run(`sudo cp ${tempPath} ${proxyPath}`, { silent: true });
run(`sudo chmod +x ${proxyPath}`, { silent: true });
installedProxyPath = proxyPath;
console.log(` āœ“ Proxy installed to ${proxyPath}`);
} catch {
console.log(` ⚠ Could not install to ${proxyPath}, using ${tempPath}`);
}
// Setup systemd services (Linux only)
// Setup systemd service (Linux only)
if (process.platform === 'linux') {
console.log('\nāš™ļø Setting up systemd services...');
console.log('\nāš™ļø Setting up systemd service...');
const user = process.env.USER || 'ubuntu';
const home = process.env.HOME;
const proxyService = `[Unit]
Description=Anthropic CLI Proxy for Clawdbot
Description=Anthropic CLI Proxy (codecli-hostapi)
After=network.target

@@ -351,7 +336,7 @@

User=${user}
ExecStart=/usr/bin/python3 ${installedProxyPath}
ExecStart=/usr/bin/python3 ${PROXY_SCRIPT_PATH}
Restart=always
RestartSec=5
StandardOutput=append:/var/log/anthropic-proxy.log
StandardError=append:/var/log/anthropic-proxy.log
StandardOutput=append:/var/log/codecli-hostapi.log
StandardError=append:/var/log/codecli-hostapi.log

@@ -362,51 +347,85 @@ [Install]

let clawdbotPath = '';
try {
clawdbotPath = run('which clawdbot', { silent: true }).trim();
} catch {
clawdbotPath = '/usr/local/bin/clawdbot';
fs.writeFileSync('/tmp/codecli-hostapi.service', proxyService);
run('sudo cp /tmp/codecli-hostapi.service /etc/systemd/system/', { ignoreError: true, silent: true });
run('sudo systemctl daemon-reload', { ignoreError: true, silent: true });
run('sudo systemctl enable codecli-hostapi.service', { ignoreError: true, silent: true });
run('sudo systemctl restart codecli-hostapi.service', { ignoreError: true, silent: true });
console.log(' āœ“ Systemd service installed and started');
} else {
// Non-Linux: start in background
console.log('\nšŸš€ Starting proxy...');
spawn('python3', [PROXY_SCRIPT_PATH], {
detached: true,
stdio: 'ignore'
}).unref();
console.log(' āœ“ Proxy started in background');
}
// Save config
const config = loadConfig();
config.installed = true;
config.installedAt = new Date().toISOString();
saveConfig(config);
console.log('\nāœ… Installation complete!');
console.log(`\nšŸ“” API Endpoint: ${PROXY_BASE_URL}/v1/messages`);
console.log(' Use this as your Anthropic API base URL');
// Check for known apps
const clawdbotConfig = findClawdbotConfig();
if (clawdbotConfig) {
console.log('\n────────────────────────────────────────');
console.log('šŸ” Detected: clawdbot');
const configure = await prompt(' Configure clawdbot to use this proxy? (y/n): ');
if (configure.toLowerCase() === 'y') {
configureClawdbot(clawdbotConfig);
// Restart clawdbot service if exists
if (process.platform === 'linux') {
run('sudo systemctl restart clawdbot.service 2>/dev/null || true', { ignoreError: true, silent: true });
console.log(' āœ“ Clawdbot service restarted');
}
}
}
const clawdbotService = `[Unit]
Description=Clawdbot Gateway
After=network.target anthropic-proxy.service
Wants=anthropic-proxy.service
return true;
}
[Service]
Type=simple
User=${user}
Environment=HOME=${home}
WorkingDirectory=${home}
ExecStart=${clawdbotPath} gateway
Restart=always
RestartSec=10
StandardOutput=append:/var/log/clawdbot.log
StandardError=append:/var/log/clawdbot.log
async function doStop() {
console.log('\nšŸ›‘ Stopping proxy...\n');
[Install]
WantedBy=multi-user.target
`;
if (process.platform === 'linux') {
run('sudo systemctl stop codecli-hostapi.service', { ignoreError: true, silent: true });
console.log(' āœ“ Systemd service stopped');
} else {
const pid = getProxyPid();
if (pid) {
run(`kill ${pid}`, { ignoreError: true, silent: true });
console.log(` āœ“ Killed process ${pid}`);
} else {
console.log(' ā—‹ Proxy not running');
}
}
fs.writeFileSync('/tmp/anthropic-proxy.service', proxyService);
fs.writeFileSync('/tmp/clawdbot.service', clawdbotService);
return true;
}
run('sudo cp /tmp/anthropic-proxy.service /etc/systemd/system/', { ignoreError: true, silent: true });
run('sudo cp /tmp/clawdbot.service /etc/systemd/system/', { ignoreError: true, silent: true });
run('sudo systemctl daemon-reload', { ignoreError: true, silent: true });
run('sudo systemctl enable anthropic-proxy.service', { ignoreError: true, silent: true });
run('sudo systemctl enable clawdbot.service', { ignoreError: true, silent: true });
async function doStart() {
console.log('\nšŸš€ Starting proxy...\n');
console.log(' āœ“ Systemd services created and enabled');
if (!fs.existsSync(PROXY_SCRIPT_PATH)) {
console.log(' āœ— Proxy not installed. Run "Install" first.');
return false;
}
// Start services
console.log('\nšŸš€ Starting services...');
run('sudo systemctl restart anthropic-proxy.service', { ignoreError: true, silent: true });
run('sleep 2', { silent: true });
run('sudo systemctl restart clawdbot.service', { ignoreError: true, silent: true });
if (isProxyRunning()) {
console.log(' ā—‹ Proxy already running');
return true;
}
console.log(' āœ“ Services started');
if (process.platform === 'linux') {
run('sudo systemctl start codecli-hostapi.service', { ignoreError: true, silent: true });
console.log(' āœ“ Systemd service started');
} else {
// Non-Linux: just start proxy in background
console.log('\nšŸš€ Starting proxy...');
spawn('python3', [installedProxyPath], {
spawn('python3', [PROXY_SCRIPT_PATH], {
detached: true,

@@ -416,14 +435,39 @@ stdio: 'ignore'

console.log(' āœ“ Proxy started in background');
console.log('\n To start clawdbot, run: clawdbot gateway');
}
console.log('\nāœ… Activation complete!');
console.log(`\nšŸ“” API Endpoint: ${PROXY_BASE_URL}/v1/messages`);
return true;
}
async function doRestore(configPath) {
async function doBackup() {
console.log('\nšŸ’¾ Creating backup...\n');
ensureDir(BACKUP_DIR);
// Backup our own config
if (fs.existsSync(CONFIG_PATH)) {
const backupPath = path.join(BACKUP_DIR, `config_${Date.now()}.json`);
fs.copyFileSync(CONFIG_PATH, backupPath);
console.log(` āœ“ Config: ${path.basename(backupPath)}`);
}
// Backup clawdbot if exists
const clawdbotConfig = findClawdbotConfig();
if (clawdbotConfig) {
const backupPath = path.join(BACKUP_DIR, `clawdbot_${Date.now()}.json`);
fs.copyFileSync(clawdbotConfig, backupPath);
console.log(` āœ“ Clawdbot: ${path.basename(backupPath)}`);
}
console.log(`\nšŸ“‚ Backups stored in: ${BACKUP_DIR}`);
return true;
}
async function doRestore() {
console.log('\nšŸ“‚ Available backups:\n');
const backups = listBackups(configPath);
if (backups.length === 0) {
ensureDir(BACKUP_DIR);
const files = fs.readdirSync(BACKUP_DIR).filter(f => f.endsWith('.json'));
if (files.length === 0) {
console.log(' No backups found.');

@@ -433,13 +477,35 @@ return false;

backups.forEach((b, i) => {
const timeStr = b.time.toLocaleString();
console.log(` ${i + 1}. ${b.name} (${timeStr})`);
});
// Group by type
const clawdbotBackups = files.filter(f => f.startsWith('clawdbot_'));
const configBackups = files.filter(f => f.startsWith('config_'));
let options = [];
let idx = 1;
if (clawdbotBackups.length > 0) {
console.log(' Clawdbot backups:');
clawdbotBackups.slice(0, 5).forEach(f => {
const stat = fs.statSync(path.join(BACKUP_DIR, f));
console.log(` ${idx}. ${f} (${stat.mtime.toLocaleString()})`);
options.push({ type: 'clawdbot', file: f });
idx++;
});
}
if (configBackups.length > 0) {
console.log(' Config backups:');
configBackups.slice(0, 5).forEach(f => {
const stat = fs.statSync(path.join(BACKUP_DIR, f));
console.log(` ${idx}. ${f} (${stat.mtime.toLocaleString()})`);
options.push({ type: 'config', file: f });
idx++;
});
}
console.log(` 0. Cancel`);
const choice = await prompt('\nSelect backup to restore: ');
const idx = parseInt(choice, 10);
const choiceIdx = parseInt(choice, 10);
if (idx === 0 || isNaN(idx) || idx < 1 || idx > backups.length) {
if (choiceIdx === 0 || isNaN(choiceIdx) || choiceIdx < 1 || choiceIdx > options.length) {
console.log('Cancelled.');

@@ -449,21 +515,24 @@ return false;

const selected = backups[idx - 1];
const selected = options[choiceIdx - 1];
const backupPath = path.join(BACKUP_DIR, selected.file);
// Backup current config before restore
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
if (isOurProxy(config)) {
createBackup(configPath, 'hostbak');
console.log(' šŸ’¾ Current config backed up as hostbak');
}
if (selected.type === 'clawdbot') {
const clawdbotConfig = findClawdbotConfig();
if (clawdbotConfig) {
fs.copyFileSync(backupPath, clawdbotConfig);
console.log(`\nāœ… Restored clawdbot config from: ${selected.file}`);
restoreBackup(configPath, selected.path);
console.log(`\nāœ… Restored from: ${selected.name}`);
// Restart services if on Linux
if (process.platform === 'linux') {
const restart = await prompt('\nRestart clawdbot service? (y/n): ');
if (restart.toLowerCase() === 'y') {
run('sudo systemctl restart clawdbot.service', { ignoreError: true, silent: true });
console.log(' āœ“ Clawdbot restarted');
if (process.platform === 'linux') {
const restart = await prompt('Restart clawdbot service? (y/n): ');
if (restart.toLowerCase() === 'y') {
run('sudo systemctl restart clawdbot.service', { ignoreError: true, silent: true });
console.log(' āœ“ Clawdbot restarted');
}
}
} else {
console.log(' āœ— Clawdbot config not found on this system');
}
} else if (selected.type === 'config') {
fs.copyFileSync(backupPath, CONFIG_PATH);
console.log(`\nāœ… Restored config from: ${selected.file}`);
}

@@ -474,54 +543,36 @@

async function doBackup(configPath) {
console.log('\nšŸ’¾ Manual Backup\n');
async function showStatus() {
console.log('\nšŸ“Š Status\n');
const defaultName = 'manual';
const name = await prompt(`Backup name (default: ${defaultName}): `) || defaultName;
const config = loadConfig();
const running = isProxyRunning();
const backupPath = createBackup(configPath, name);
console.log(`\nāœ… Backup created: ${path.basename(backupPath)}`);
return true;
}
console.log(` Installed: ${config.installed ? 'āœ“ Yes' : 'ā—‹ No'}`);
console.log(` Running: ${running ? 'āœ“ Yes' : 'ā—‹ No'}`);
console.log(` Endpoint: ${PROXY_BASE_URL}/v1/messages`);
async function showStatus(configPath) {
console.log('\nšŸ“Š Current Status\n');
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
const baseUrl = config?.models?.providers?.anthropic?.baseUrl || '(not set)';
console.log(` Config: ${configPath}`);
console.log(` Anthropic baseUrl: ${baseUrl}`);
if (isOurProxy(config)) {
console.log(` Status: āœ“ codecli-hostapi is ACTIVE`);
} else if (hasHostProxy(config)) {
console.log(` Status: ⚠ Another local proxy is configured`);
} else {
console.log(` Status: ā—‹ Using default/remote API`);
}
const backups = listBackups(configPath);
console.log(`\n Backups: ${backups.length} found`);
if (backups.length > 0) {
console.log(' Recent:');
backups.slice(0, 3).forEach(b => {
console.log(` - ${b.name}`);
});
}
if (process.platform === 'linux') {
console.log('\n Services:');
try {
const proxyStatus = run('systemctl is-active anthropic-proxy.service', { silent: true }).trim();
console.log(` - anthropic-proxy: ${proxyStatus}`);
const status = run('systemctl is-active codecli-hostapi.service', { silent: true }).trim();
console.log(` Service: ${status}`);
} catch {
console.log(` - anthropic-proxy: not installed`);
console.log(` Service: not installed`);
}
try {
const clawdbotStatus = run('systemctl is-active clawdbot.service', { silent: true }).trim();
console.log(` - clawdbot: ${clawdbotStatus}`);
} catch {
console.log(` - clawdbot: not installed`);
}
}
// Check clawdbot
const clawdbotConfig = findClawdbotConfig();
if (clawdbotConfig) {
const cfg = JSON.parse(fs.readFileSync(clawdbotConfig, 'utf8'));
const baseUrl = cfg?.models?.providers?.anthropic?.baseUrl || '(default)';
const usingProxy = baseUrl === PROXY_BASE_URL;
console.log(`\n Clawdbot: ${clawdbotConfig}`);
console.log(` BaseURL: ${baseUrl}`);
console.log(` Using proxy: ${usingProxy ? 'āœ“ Yes' : 'ā—‹ No'}`);
}
// List backups
ensureDir(BACKUP_DIR);
const backups = fs.readdirSync(BACKUP_DIR).filter(f => f.endsWith('.json'));
console.log(`\n Backups: ${backups.length} files in ${BACKUP_DIR}`);
}

@@ -533,19 +584,15 @@

console.log('\n╔════════════════════════════════════════╗');
console.log('ā•‘ codecli-hostapi - Setup Tool ā•‘');
console.log('ā•‘ Proxy Anthropic API via Claude CLI ā•‘');
console.log('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•\n');
console.log('ā•‘ codecli-hostapi v1.0.0 ā•‘');
console.log('ā•‘ Turn Claude CLI into a local API ā•‘');
console.log('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•');
const configPath = findClawdbotConfig();
if (!configPath) {
console.error('āœ— clawdbot.json not found. Please run "clawdbot init" first.');
process.exit(1);
}
await showStatus();
await showStatus(configPath);
console.log('\n────────────────────────────────────────');
console.log(' 1. Activate - Enable codecli-hostapi');
console.log(' 2. Restore - Restore from backup');
console.log(' 3. Backup - Create manual backup');
console.log(' 4. Status - Show current status');
console.log(' 1. Install - Install & start proxy');
console.log(' 2. Start - Start proxy service');
console.log(' 3. Stop - Stop proxy service');
console.log(' 4. Backup - Create backup');
console.log(' 5. Restore - Restore from backup');
console.log(' 6. Status - Show current status');
console.log(' 0. Exit');

@@ -558,13 +605,19 @@ console.log('────────────────────────────────────────');

case '1':
await doActivate(configPath);
await doInstall();
break;
case '2':
await doRestore(configPath);
await doStart();
break;
case '3':
await doBackup(configPath);
await doStop();
break;
case '4':
await showStatus(configPath);
await doBackup();
break;
case '5':
await doRestore();
break;
case '6':
await showStatus();
break;
case '0':

@@ -571,0 +624,0 @@ case '':

{
"name": "codecli-hostapi",
"version": "1.0.0",
"version": "1.1.0",
"description": "One-command setup to proxy Anthropic API requests through Claude Code CLI",

@@ -5,0 +5,0 @@ "bin": {