@kaitranntt/ccs 6.7.0-dev.2 → 6.7.0-dev.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -40,8 +40,10 @@ const child_process_1 = require("child_process");
40
40
  const net = __importStar(require("net"));
41
41
  const binary_manager_1 = require("./binary-manager");
42
42
  const config_generator_1 = require("./config-generator");
43
- const stats_fetcher_1 = require("./stats-fetcher");
44
43
  const session_tracker_1 = require("./session-tracker");
44
+ const proxy_detector_1 = require("./proxy-detector");
45
+ const startup_lock_1 = require("./startup-lock");
46
+ const stats_fetcher_1 = require("./stats-fetcher");
45
47
  /** Background proxy process reference */
46
48
  let proxyProcess = null;
47
49
  /** Cleanup registered flag */
@@ -108,9 +110,6 @@ async function ensureCliproxyService(port = config_generator_1.CLIPROXY_DEFAULT_
108
110
  console.error(`[cliproxy-service] ${msg}`);
109
111
  }
110
112
  };
111
- // Check if already running (from another process or previous start)
112
- log(`Checking if CLIProxy is running on port ${port}...`);
113
- const running = await (0, stats_fetcher_1.isCliproxyRunning)(port);
114
113
  // Check if config needs update (even if running)
115
114
  let configRegenerated = false;
116
115
  if ((0, config_generator_1.configNeedsRegeneration)()) {
@@ -118,92 +117,120 @@ async function ensureCliproxyService(port = config_generator_1.CLIPROXY_DEFAULT_
118
117
  (0, config_generator_1.regenerateConfig)(port);
119
118
  configRegenerated = true;
120
119
  }
121
- if (running) {
122
- log('CLIProxy already running');
123
- if (configRegenerated) {
124
- log('Config was updated - running instance will use new config on next restart');
120
+ // Use startup lock to coordinate with other CCS processes (ccs agy, ccs config, etc.)
121
+ return await (0, startup_lock_1.withStartupLock)(async () => {
122
+ // Use unified detection (HTTP check + session-lock + port-process)
123
+ log(`Checking if CLIProxy is running on port ${port}...`);
124
+ const proxyStatus = await (0, proxy_detector_1.detectRunningProxy)(port);
125
+ log(`Proxy detection: ${JSON.stringify(proxyStatus)}`);
126
+ if (proxyStatus.running && proxyStatus.verified) {
127
+ // Already running and healthy
128
+ log('CLIProxy already running');
129
+ if (configRegenerated) {
130
+ log('Config was updated - running instance will use new config on next restart');
131
+ }
132
+ return { started: true, alreadyRunning: true, port, configRegenerated };
125
133
  }
126
- return { started: true, alreadyRunning: true, port, configRegenerated };
127
- }
128
- // Need to start new instance
129
- log('CLIProxy not running, starting background instance...');
130
- // 1. Ensure binary exists
131
- let binaryPath;
132
- try {
133
- binaryPath = await (0, binary_manager_1.ensureCLIProxyBinary)(verbose);
134
- log(`Binary ready: ${binaryPath}`);
135
- }
136
- catch (error) {
137
- const err = error;
138
- return {
139
- started: false,
140
- alreadyRunning: false,
141
- port,
142
- error: `Failed to prepare binary: ${err.message}`,
143
- };
144
- }
145
- // 2. Ensure/regenerate config if needed
146
- let configPath;
147
- if ((0, config_generator_1.configNeedsRegeneration)()) {
148
- log('Config needs regeneration, updating...');
149
- configPath = (0, config_generator_1.regenerateConfig)(port);
150
- }
151
- else {
152
- // generateConfig only creates if doesn't exist
153
- configPath = (0, config_generator_1.generateConfig)('gemini', port); // Provider doesn't matter for unified config
154
- }
155
- log(`Config ready: ${configPath}`);
156
- // 3. Spawn background process
157
- const proxyArgs = ['--config', configPath];
158
- log(`Spawning: ${binaryPath} ${proxyArgs.join(' ')}`);
159
- proxyProcess = (0, child_process_1.spawn)(binaryPath, proxyArgs, {
160
- stdio: ['ignore', verbose ? 'pipe' : 'ignore', verbose ? 'pipe' : 'ignore'],
161
- detached: true, // Allow process to run independently
162
- env: {
163
- ...process.env,
164
- WRITABLE_PATH: (0, config_generator_1.getCliproxyWritablePath)(), // Logs stored in ~/.ccs/cliproxy/logs/
165
- },
166
- });
167
- // Forward output in verbose mode
168
- if (verbose) {
169
- proxyProcess.stdout?.on('data', (data) => {
170
- process.stderr.write(`[cliproxy] ${data.toString()}`);
134
+ if (proxyStatus.running && !proxyStatus.verified) {
135
+ // Proxy detected but not ready yet (another process is starting it)
136
+ log(`Proxy starting up (detected via ${proxyStatus.method}), waiting...`);
137
+ const becameHealthy = await (0, proxy_detector_1.waitForProxyHealthy)(port, 5000);
138
+ if (becameHealthy) {
139
+ log('Proxy became healthy');
140
+ return { started: true, alreadyRunning: true, port, configRegenerated };
141
+ }
142
+ // Proxy didn't become healthy - will try to start fresh below
143
+ log('Proxy detected but not responding, will start fresh');
144
+ }
145
+ if (proxyStatus.blocked) {
146
+ // Port blocked by non-CLIProxy process - try HTTP as last resort
147
+ const isActuallyOurs = await (0, proxy_detector_1.waitForProxyHealthy)(port, 1000);
148
+ if (isActuallyOurs) {
149
+ log('Reclaimed CLIProxy with unrecognized process name');
150
+ return { started: true, alreadyRunning: true, port, configRegenerated };
151
+ }
152
+ // Truly blocked
153
+ return {
154
+ started: false,
155
+ alreadyRunning: false,
156
+ port,
157
+ error: `Port ${port} is blocked by ${proxyStatus.blocker?.processName}`,
158
+ };
159
+ }
160
+ // Need to start new instance
161
+ log('CLIProxy not running, starting background instance...');
162
+ // 1. Ensure binary exists
163
+ let binaryPath;
164
+ try {
165
+ binaryPath = await (0, binary_manager_1.ensureCLIProxyBinary)(verbose);
166
+ log(`Binary ready: ${binaryPath}`);
167
+ }
168
+ catch (error) {
169
+ const err = error;
170
+ return {
171
+ started: false,
172
+ alreadyRunning: false,
173
+ port,
174
+ error: `Failed to prepare binary: ${err.message}`,
175
+ };
176
+ }
177
+ // 2. Ensure/regenerate config if needed
178
+ let configPath;
179
+ if ((0, config_generator_1.configNeedsRegeneration)()) {
180
+ log('Config needs regeneration, updating...');
181
+ configPath = (0, config_generator_1.regenerateConfig)(port);
182
+ }
183
+ else {
184
+ configPath = (0, config_generator_1.generateConfig)('gemini', port);
185
+ }
186
+ log(`Config ready: ${configPath}`);
187
+ // 3. Spawn background process
188
+ const proxyArgs = ['--config', configPath];
189
+ log(`Spawning: ${binaryPath} ${proxyArgs.join(' ')}`);
190
+ proxyProcess = (0, child_process_1.spawn)(binaryPath, proxyArgs, {
191
+ stdio: ['ignore', verbose ? 'pipe' : 'ignore', verbose ? 'pipe' : 'ignore'],
192
+ detached: true,
193
+ env: {
194
+ ...process.env,
195
+ WRITABLE_PATH: (0, config_generator_1.getCliproxyWritablePath)(),
196
+ },
171
197
  });
172
- proxyProcess.stderr?.on('data', (data) => {
173
- process.stderr.write(`[cliproxy-err] ${data.toString()}`);
198
+ if (verbose) {
199
+ proxyProcess.stdout?.on('data', (data) => {
200
+ process.stderr.write(`[cliproxy] ${data.toString()}`);
201
+ });
202
+ proxyProcess.stderr?.on('data', (data) => {
203
+ process.stderr.write(`[cliproxy-err] ${data.toString()}`);
204
+ });
205
+ }
206
+ proxyProcess.unref();
207
+ proxyProcess.on('error', (error) => {
208
+ log(`Spawn error: ${error.message}`);
174
209
  });
175
- }
176
- // Don't let this process prevent parent from exiting
177
- proxyProcess.unref();
178
- // Handle spawn errors
179
- proxyProcess.on('error', (error) => {
180
- log(`Spawn error: ${error.message}`);
181
- });
182
- // Register cleanup handlers
183
- registerCleanup();
184
- // 4. Wait for proxy to be ready
185
- log(`Waiting for CLIProxy on port ${port}...`);
186
- const ready = await waitForPort(port, 5000);
187
- if (!ready) {
188
- // Kill failed process
189
- if (proxyProcess && !proxyProcess.killed) {
190
- proxyProcess.kill('SIGTERM');
191
- proxyProcess = null;
210
+ registerCleanup();
211
+ // 4. Wait for proxy to be ready
212
+ log(`Waiting for CLIProxy on port ${port}...`);
213
+ const ready = await waitForPort(port, 5000);
214
+ if (!ready) {
215
+ if (proxyProcess && !proxyProcess.killed) {
216
+ proxyProcess.kill('SIGTERM');
217
+ proxyProcess = null;
218
+ }
219
+ return {
220
+ started: false,
221
+ alreadyRunning: false,
222
+ port,
223
+ error: `CLIProxy failed to start within 5s on port ${port}`,
224
+ };
192
225
  }
193
- return {
194
- started: false,
195
- alreadyRunning: false,
196
- port,
197
- error: `CLIProxy failed to start within 5s on port ${port}`,
198
- };
199
- }
200
- log(`CLIProxy service started on port ${port}`);
201
- // 5. Register session so stopProxy() can find and kill this process
202
- if (proxyProcess.pid) {
203
- (0, session_tracker_1.registerSession)(port, proxyProcess.pid);
204
- log(`Session registered for PID ${proxyProcess.pid}`);
205
- }
206
- return { started: true, alreadyRunning: false, port };
226
+ log(`CLIProxy service started on port ${port}`);
227
+ // 5. Register session
228
+ if (proxyProcess.pid) {
229
+ (0, session_tracker_1.registerSession)(port, proxyProcess.pid);
230
+ log(`Session registered for PID ${proxyProcess.pid}`);
231
+ }
232
+ return { started: true, alreadyRunning: false, port };
233
+ });
207
234
  }
208
235
  exports.ensureCliproxyService = ensureCliproxyService;
209
236
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"service-manager.js","sourceRoot":"","sources":["../../src/cliproxy/service-manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service-manager.js","sourceRoot":"","sources":["../../src/cliproxy/service-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Startup Lock for CLIProxy
3
+ *
4
+ * File-based mutex to prevent race conditions when multiple
5
+ * CCS processes try to start CLIProxy simultaneously.
6
+ *
7
+ * Uses a lock file with PID and timestamp to coordinate startup.
8
+ * Lock is automatically released after timeout or process exit.
9
+ *
10
+ * Lock Timeout Rationale (10 seconds):
11
+ * - CLIProxy startup typically takes 1-3s (binary spawn + port bind)
12
+ * - HTTP health check takes ~1s timeout
13
+ * - Session registration takes <100ms
14
+ * - Total expected lock hold time: 2-5s
15
+ * - 10s provides 2x safety margin for slow systems/disk I/O
16
+ * - Too short: legitimate startups fail on slow systems
17
+ * - Too long: dead processes block other terminals unnecessarily
18
+ *
19
+ * Why file-based instead of port-based:
20
+ * - Works before port is bound (prevents duplicate spawn attempts)
21
+ * - Survives process crashes (stale detection via PID check)
22
+ * - Cross-platform (Windows, macOS, Linux)
23
+ */
24
+ /** Lock acquisition result */
25
+ export interface LockResult {
26
+ acquired: boolean;
27
+ lockPath: string;
28
+ release: () => void;
29
+ }
30
+ /**
31
+ * Acquire the startup lock with retries.
32
+ *
33
+ * @param options.retries Number of retry attempts (default: 20)
34
+ * @param options.retryInterval Ms between retries (default: 250)
35
+ * @param options.verbose Enable verbose logging (default: false)
36
+ * @returns LockResult
37
+ * @throws Error if lock cannot be acquired after all retries
38
+ */
39
+ export declare function acquireStartupLock(options?: {
40
+ retries?: number;
41
+ retryInterval?: number;
42
+ verbose?: boolean;
43
+ }): Promise<LockResult>;
44
+ /**
45
+ * Execute a function while holding the startup lock.
46
+ * Lock is automatically released after function completes or throws.
47
+ *
48
+ * @param fn Function to execute
49
+ * @param options Lock acquisition options (retries, retryInterval, verbose)
50
+ * @returns Result of fn
51
+ */
52
+ export declare function withStartupLock<T>(fn: () => Promise<T>, options?: {
53
+ retries?: number;
54
+ retryInterval?: number;
55
+ verbose?: boolean;
56
+ }): Promise<T>;
57
+ //# sourceMappingURL=startup-lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startup-lock.d.ts","sourceRoot":"","sources":["../../src/cliproxy/startup-lock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAaH,8BAA8B;AAC9B,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAkID;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,CAAC,EAAE;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,GAAG,OAAO,CAAC,UAAU,CAAC,CAwBtB;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,CAAC,EACrC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACxE,OAAO,CAAC,CAAC,CAAC,CAOZ"}
@@ -0,0 +1,216 @@
1
+ "use strict";
2
+ /**
3
+ * Startup Lock for CLIProxy
4
+ *
5
+ * File-based mutex to prevent race conditions when multiple
6
+ * CCS processes try to start CLIProxy simultaneously.
7
+ *
8
+ * Uses a lock file with PID and timestamp to coordinate startup.
9
+ * Lock is automatically released after timeout or process exit.
10
+ *
11
+ * Lock Timeout Rationale (10 seconds):
12
+ * - CLIProxy startup typically takes 1-3s (binary spawn + port bind)
13
+ * - HTTP health check takes ~1s timeout
14
+ * - Session registration takes <100ms
15
+ * - Total expected lock hold time: 2-5s
16
+ * - 10s provides 2x safety margin for slow systems/disk I/O
17
+ * - Too short: legitimate startups fail on slow systems
18
+ * - Too long: dead processes block other terminals unnecessarily
19
+ *
20
+ * Why file-based instead of port-based:
21
+ * - Works before port is bound (prevents duplicate spawn attempts)
22
+ * - Survives process crashes (stale detection via PID check)
23
+ * - Cross-platform (Windows, macOS, Linux)
24
+ */
25
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ var desc = Object.getOwnPropertyDescriptor(m, k);
28
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
29
+ desc = { enumerable: true, get: function() { return m[k]; } };
30
+ }
31
+ Object.defineProperty(o, k2, desc);
32
+ }) : (function(o, m, k, k2) {
33
+ if (k2 === undefined) k2 = k;
34
+ o[k2] = m[k];
35
+ }));
36
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
37
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
38
+ }) : function(o, v) {
39
+ o["default"] = v;
40
+ });
41
+ var __importStar = (this && this.__importStar) || function (mod) {
42
+ if (mod && mod.__esModule) return mod;
43
+ var result = {};
44
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
45
+ __setModuleDefault(result, mod);
46
+ return result;
47
+ };
48
+ Object.defineProperty(exports, "__esModule", { value: true });
49
+ exports.withStartupLock = exports.acquireStartupLock = void 0;
50
+ const fs = __importStar(require("fs"));
51
+ const path = __importStar(require("path"));
52
+ const config_generator_1 = require("./config-generator");
53
+ /** Lock file name */
54
+ const LOCK_FILE = '.startup.lock';
55
+ /** Lock timeout in ms (stale lock auto-released) */
56
+ const LOCK_TIMEOUT_MS = 10000; // 10 seconds - see module docstring for rationale
57
+ /** No-op logger for when verbose is disabled */
58
+ const noopLog = () => { };
59
+ /**
60
+ * Get path to startup lock file
61
+ */
62
+ function getLockPath() {
63
+ return path.join((0, config_generator_1.getCliproxyDir)(), LOCK_FILE);
64
+ }
65
+ /**
66
+ * Check if a lock is stale (old or from dead process)
67
+ */
68
+ function isLockStale(lockData) {
69
+ // Check timestamp
70
+ if (Date.now() - lockData.timestamp > LOCK_TIMEOUT_MS) {
71
+ return true;
72
+ }
73
+ // Check if PID is still running
74
+ try {
75
+ process.kill(lockData.pid, 0);
76
+ return false; // Process exists
77
+ }
78
+ catch {
79
+ return true; // Process dead
80
+ }
81
+ }
82
+ /**
83
+ * Try to acquire the startup lock once.
84
+ *
85
+ * @param log Logger function for verbose output
86
+ * @returns LockResult with acquired=true if lock obtained
87
+ */
88
+ function tryAcquireLockOnce(log) {
89
+ const lockPath = getLockPath();
90
+ const dir = path.dirname(lockPath);
91
+ // Ensure directory exists
92
+ if (!fs.existsSync(dir)) {
93
+ log(`Creating lock directory: ${dir}`);
94
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
95
+ }
96
+ // Check for existing lock
97
+ if (fs.existsSync(lockPath)) {
98
+ try {
99
+ const content = fs.readFileSync(lockPath, 'utf-8');
100
+ const lockData = JSON.parse(content);
101
+ if (!isLockStale(lockData)) {
102
+ // Lock is held by another active process
103
+ log(`Lock held by PID ${lockData.pid} (age: ${Date.now() - lockData.timestamp}ms)`);
104
+ return {
105
+ acquired: false,
106
+ lockPath,
107
+ release: () => { },
108
+ };
109
+ }
110
+ // Lock is stale - remove and continue
111
+ log(`Removing stale lock from PID ${lockData.pid}`);
112
+ }
113
+ catch {
114
+ // Invalid lock file - remove and continue
115
+ log('Removing invalid lock file');
116
+ }
117
+ try {
118
+ fs.unlinkSync(lockPath);
119
+ }
120
+ catch {
121
+ // Ignore removal errors
122
+ }
123
+ }
124
+ // Try to create lock atomically
125
+ const lockData = {
126
+ pid: process.pid,
127
+ timestamp: Date.now(),
128
+ hostname: require('os').hostname(),
129
+ };
130
+ try {
131
+ // Use 'wx' flag for exclusive creation (fails if exists)
132
+ fs.writeFileSync(lockPath, JSON.stringify(lockData), { flag: 'wx', mode: 0o600 });
133
+ log(`Lock acquired by PID ${process.pid}`);
134
+ }
135
+ catch (err) {
136
+ const error = err;
137
+ if (error.code === 'EEXIST') {
138
+ // Another process created lock between our check and write
139
+ log('Lock acquisition race - another process won');
140
+ return {
141
+ acquired: false,
142
+ lockPath,
143
+ release: () => { },
144
+ };
145
+ }
146
+ throw error;
147
+ }
148
+ // Lock acquired - return release function
149
+ const release = () => {
150
+ try {
151
+ // Only release if we still own it
152
+ const content = fs.readFileSync(lockPath, 'utf-8');
153
+ const currentLock = JSON.parse(content);
154
+ if (currentLock.pid === process.pid) {
155
+ fs.unlinkSync(lockPath);
156
+ log('Lock released');
157
+ }
158
+ }
159
+ catch {
160
+ // Ignore release errors
161
+ }
162
+ };
163
+ return {
164
+ acquired: true,
165
+ lockPath,
166
+ release,
167
+ };
168
+ }
169
+ /**
170
+ * Acquire the startup lock with retries.
171
+ *
172
+ * @param options.retries Number of retry attempts (default: 20)
173
+ * @param options.retryInterval Ms between retries (default: 250)
174
+ * @param options.verbose Enable verbose logging (default: false)
175
+ * @returns LockResult
176
+ * @throws Error if lock cannot be acquired after all retries
177
+ */
178
+ async function acquireStartupLock(options) {
179
+ const retries = options?.retries ?? 20;
180
+ const retryInterval = options?.retryInterval ?? 250;
181
+ const log = options?.verbose ? (msg) => console.error(`[startup-lock] ${msg}`) : noopLog;
182
+ log(`Attempting to acquire startup lock (max ${retries} retries, ${retryInterval}ms interval)`);
183
+ for (let attempt = 0; attempt <= retries; attempt++) {
184
+ const result = tryAcquireLockOnce(log);
185
+ if (result.acquired) {
186
+ return result;
187
+ }
188
+ if (attempt < retries) {
189
+ log(`Retry ${attempt + 1}/${retries} in ${retryInterval}ms...`);
190
+ await new Promise((r) => setTimeout(r, retryInterval));
191
+ }
192
+ }
193
+ log(`Failed to acquire lock after ${retries} attempts`);
194
+ throw new Error(`Failed to acquire startup lock after ${retries} attempts. ` +
195
+ `Another CCS process may be starting CLIProxy.`);
196
+ }
197
+ exports.acquireStartupLock = acquireStartupLock;
198
+ /**
199
+ * Execute a function while holding the startup lock.
200
+ * Lock is automatically released after function completes or throws.
201
+ *
202
+ * @param fn Function to execute
203
+ * @param options Lock acquisition options (retries, retryInterval, verbose)
204
+ * @returns Result of fn
205
+ */
206
+ async function withStartupLock(fn, options) {
207
+ const lock = await acquireStartupLock(options);
208
+ try {
209
+ return await fn();
210
+ }
211
+ finally {
212
+ lock.release();
213
+ }
214
+ }
215
+ exports.withStartupLock = withStartupLock;
216
+ //# sourceMappingURL=startup-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startup-lock.js","sourceRoot":"","sources":["../../src/cliproxy/startup-lock.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,7 @@
1
1
  /**
2
2
  * Auto-recovery for missing or corrupted configuration
3
+ * Lazy initialization: Creates ~/.ccs/ structure on first CLI run
4
+ * Mirrors postinstall.js behavior for package managers that skip lifecycle scripts (e.g., bun)
3
5
  */
4
6
  /**
5
7
  * Recovery Manager Class
@@ -8,6 +10,8 @@ declare class RecoveryManager {
8
10
  private readonly homedir;
9
11
  private readonly ccsDir;
10
12
  private readonly claudeDir;
13
+ private readonly sharedDir;
14
+ private readonly completionsDir;
11
15
  private recovered;
12
16
  constructor();
13
17
  /**
@@ -23,7 +27,28 @@ declare class RecoveryManager {
23
27
  */
24
28
  ensureClaudeSettings(): boolean;
25
29
  /**
26
- * Run all recovery operations
30
+ * Ensure ~/.ccs/shared/ directory structure exists
31
+ */
32
+ ensureSharedDirectories(): boolean;
33
+ /**
34
+ * Ensure GLM settings file exists
35
+ */
36
+ ensureGlmSettings(): boolean;
37
+ /**
38
+ * Ensure GLMT settings file exists
39
+ */
40
+ ensureGlmtSettings(): boolean;
41
+ /**
42
+ * Ensure Kimi settings file exists
43
+ */
44
+ ensureKimiSettings(): boolean;
45
+ /**
46
+ * Install shell completion files
47
+ */
48
+ ensureShellCompletions(): boolean;
49
+ /**
50
+ * Run all recovery operations (lazy initialization)
51
+ * Mirrors postinstall.js behavior
27
52
  */
28
53
  recoverAll(): boolean;
29
54
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"recovery-manager.d.ts","sourceRoot":"","sources":["../../src/management/recovery-manager.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH;;GAEG;AACH,cAAM,eAAe;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,SAAS,CAAW;;IAS5B;;OAEG;IACH,kBAAkB,IAAI,OAAO;IAS7B;;OAEG;IACH,gBAAgB,IAAI,OAAO;IAkC3B;;OAEG;IACH,oBAAoB,IAAI,OAAO;IAsB/B;;OAEG;IACH,UAAU,IAAI,OAAO;IAUrB;;OAEG;IACH,kBAAkB,IAAI,MAAM,EAAE;IAI9B;;OAEG;IACH,iBAAiB,IAAI,IAAI;CAgB1B;AAED,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"recovery-manager.d.ts","sourceRoot":"","sources":["../../src/management/recovery-manager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAcH;;GAEG;AACH,cAAM,eAAe;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,SAAS,CAAW;;IAW5B;;OAEG;IACH,kBAAkB,IAAI,OAAO;IAS7B;;OAEG;IACH,gBAAgB,IAAI,OAAO;IAoC3B;;OAEG;IACH,oBAAoB,IAAI,OAAO;IAsB/B;;OAEG;IACH,uBAAuB,IAAI,OAAO;IAuBlC;;OAEG;IACH,iBAAiB,IAAI,OAAO;IAsB5B;;OAEG;IACH,kBAAkB,IAAI,OAAO;IA6B7B;;OAEG;IACH,kBAAkB,IAAI,OAAO;IAuB7B;;OAEG;IACH,sBAAsB,IAAI,OAAO;IA+CjC;;;OAGG;IACH,UAAU,IAAI,OAAO;IAoBrB;;OAEG;IACH,kBAAkB,IAAI,MAAM,EAAE;IAI9B;;OAEG;IACH,iBAAiB,IAAI,IAAI;CAiC1B;AAED,eAAe,eAAe,CAAC"}