@plexor-dev/claude-code-plugin-staging 0.1.0-beta.23 → 0.1.0-beta.25

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.
@@ -92,6 +92,32 @@ function getGatewayLabel(apiUrl) {
92
92
  return apiUrl.includes('staging') ? 'staging' : 'production';
93
93
  }
94
94
 
95
+ function isRunningInsideClaudeSession(env = process.env) {
96
+ return Boolean(env.CLAUDECODE);
97
+ }
98
+
99
+ function createSkipVerifyResult() {
100
+ return { ok: true, reason: '', stdout: EXPECTED_VERIFY_RESPONSE, stderr: '', code: 0, pendingRestart: false };
101
+ }
102
+
103
+ function createPendingRestartVerifyResult() {
104
+ return {
105
+ ok: false,
106
+ reason: 'Restart Claude to finish Plexor activation in your current session.',
107
+ stdout: '',
108
+ stderr: '',
109
+ code: 0,
110
+ pendingRestart: true
111
+ };
112
+ }
113
+
114
+ function getVerifyLabel(verifyResult) {
115
+ if (verifyResult.pendingRestart) {
116
+ return 'Restart Claude';
117
+ }
118
+ return verifyResult.ok ? 'OK' : 'FAILED';
119
+ }
120
+
95
121
  function updateHealth(config, state) {
96
122
  config.health = {
97
123
  installed: true,
@@ -102,6 +128,7 @@ function updateHealth(config, state) {
102
128
  verify_prompt: VERIFY_PROMPT,
103
129
  verify_expected: EXPECTED_VERIFY_RESPONSE,
104
130
  verify_error: state.verifyResult.ok ? null : state.verifyResult.reason,
131
+ activation_pending_restart: Boolean(state.verifyResult.pendingRestart),
105
132
  gateway: state.gateway,
106
133
  previous_auth_preserved: state.previousAuthPreserved
107
134
  };
@@ -124,10 +151,14 @@ function printReceipt({ user, gateway, previousAuthPreserved, verifyResult }) {
124
151
  console.log('├─────────────────────────────────────────────┤');
125
152
  console.log(line(`Connected: OK (${user.email || 'Unknown'})`));
126
153
  console.log(line(`Routing Active: OK (${gateway})`));
127
- console.log(line(`Verified: ${verifyResult.ok ? 'OK' : 'FAILED'}`));
154
+ console.log(line(`Verified: ${getVerifyLabel(verifyResult)}`));
128
155
  console.log(line(`Previous Claude auth: ${previousAuthPreserved ? 'Saved' : 'None found'}`));
129
156
  console.log('├─────────────────────────────────────────────┤');
130
- if (verifyResult.ok) {
157
+ if (verifyResult.pendingRestart) {
158
+ console.log(line('Plexor routing is configured'));
159
+ console.log(line('Restart Claude to activate Plexor'));
160
+ console.log(line('Then run /plexor-status'));
161
+ } else if (verifyResult.ok) {
131
162
  console.log(line('Plexor routing is configured'));
132
163
  console.log(line('Restart Claude, then send a prompt'));
133
164
  console.log(line('Logout/uninstall restores prior auth'));
@@ -221,9 +252,9 @@ async function main() {
221
252
  currentSettings.env?.PLEXOR_PREVIOUS_CLAUDE_PRIMARY_API_KEY
222
253
  );
223
254
 
224
- const verifyResult = skipVerify
225
- ? { ok: true, reason: '', stdout: EXPECTED_VERIFY_RESPONSE, stderr: '', code: 0 }
226
- : runClaudeRouteVerification();
255
+ const verifyResult = isRunningInsideClaudeSession()
256
+ ? createPendingRestartVerifyResult()
257
+ : (skipVerify ? createSkipVerifyResult() : runClaudeRouteVerification());
227
258
 
228
259
  updateHealth(updatedConfig, {
229
260
  gateway: getGatewayLabel(apiUrl),
@@ -243,12 +274,22 @@ async function main() {
243
274
  verifyResult
244
275
  });
245
276
 
246
- if (!verifyResult.ok) {
277
+ if (!verifyResult.ok && !verifyResult.pendingRestart) {
247
278
  process.exit(1);
248
279
  }
249
280
  }
250
281
 
251
- main().catch((err) => {
252
- console.error(`Error: ${err.message}`);
253
- process.exit(1);
254
- });
282
+ if (require.main === module) {
283
+ main().catch((err) => {
284
+ console.error(`Error: ${err.message}`);
285
+ process.exit(1);
286
+ });
287
+ } else {
288
+ module.exports = {
289
+ createPendingRestartVerifyResult,
290
+ createSkipVerifyResult,
291
+ getVerifyLabel,
292
+ isRunningInsideClaudeSession,
293
+ updateHealth
294
+ };
295
+ }
@@ -58,7 +58,7 @@ function getDirectClaudeAuthState() {
58
58
  try {
59
59
  const data = fs.readFileSync(CLAUDE_STATE_PATH, 'utf8');
60
60
  const state = JSON.parse(data);
61
- if (state.primaryApiKey) {
61
+ if (state.primaryApiKey && !state.primaryApiKey.startsWith('plx_')) {
62
62
  return { present: true, source: '/login managed key' };
63
63
  }
64
64
  } catch {
@@ -172,6 +172,47 @@ function loadConfig() {
172
172
  }
173
173
  }
174
174
 
175
+ function saveConfig(config) {
176
+ try {
177
+ const configDir = path.dirname(CONFIG_PATH);
178
+ if (!fs.existsSync(configDir)) {
179
+ fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
180
+ }
181
+ const tempPath = path.join(configDir, `.config.${Date.now()}.tmp`);
182
+ fs.writeFileSync(tempPath, JSON.stringify(config, null, 2), { mode: 0o600 });
183
+ fs.renameSync(tempPath, CONFIG_PATH);
184
+ return true;
185
+ } catch {
186
+ return false;
187
+ }
188
+ }
189
+
190
+ function isActivationPendingRestart(verification = {}) {
191
+ return verification.activation_pending_restart === true;
192
+ }
193
+
194
+ function shouldPromotePendingRestart(verification = {}, routing = {}, userFetchWorked = false) {
195
+ return isActivationPendingRestart(verification) && routing.active && userFetchWorked;
196
+ }
197
+
198
+ function getVerificationSummary(verification = {}, options = {}) {
199
+ const { routingActive = false, userFetchWorked = false } = options;
200
+
201
+ if (shouldPromotePendingRestart(verification, { active: routingActive }, userFetchWorked)) {
202
+ return { verified: 'OK', nextAction: '/plexor-status', shouldPromote: true };
203
+ }
204
+
205
+ if (isActivationPendingRestart(verification)) {
206
+ return { verified: 'Restart Claude', nextAction: 'Restart Claude', shouldPromote: false };
207
+ }
208
+
209
+ return {
210
+ verified: verification.verified ? 'OK' : (verification.verify_error ? 'Failed' : 'Not yet'),
211
+ nextAction: null,
212
+ shouldPromote: false
213
+ };
214
+ }
215
+
175
216
  /**
176
217
  * Check for environment mismatch between config and routing
177
218
  */
@@ -385,12 +426,33 @@ ${line(`└── Cost saved: $${sessionCostSaved}`)}
385
426
  const envLabel = routing.isStaging ? '(staging)' : '(production)';
386
427
  const stateMismatch = checkStateMismatch(enabled, routing.active);
387
428
  const connectedState = userFetchWorked ? 'OK' : 'Unknown';
388
- const verifiedState = verification.verified
429
+ const verificationSummary = getVerificationSummary(verification, {
430
+ routingActive: routing.active,
431
+ userFetchWorked
432
+ });
433
+
434
+ if (verificationSummary.shouldPromote) {
435
+ config.health = {
436
+ ...verification,
437
+ verified: true,
438
+ verified_at: new Date().toISOString(),
439
+ verify_error: null,
440
+ activation_pending_restart: false
441
+ };
442
+ saveConfig(config);
443
+ verification.verified = true;
444
+ verification.verified_at = config.health.verified_at;
445
+ verification.verify_error = null;
446
+ verification.activation_pending_restart = false;
447
+ }
448
+
449
+ const verifiedState = verificationSummary.shouldPromote
389
450
  ? 'OK'
390
- : (verification.verify_error ? 'Failed' : 'Not yet');
391
- const nextAction = !routing.active || partialState.partial || stateMismatch || !verification.verified
392
- ? '/plexor-setup'
393
- : '/plexor-status';
451
+ : verificationSummary.verified;
452
+ const nextAction = verificationSummary.nextAction ||
453
+ (!routing.active || partialState.partial || stateMismatch || !verification.verified
454
+ ? '/plexor-setup'
455
+ : '/plexor-status');
394
456
 
395
457
  // Note: Environment mismatch warning removed - it caused false positives during
396
458
  // concurrent operations and transient states. The partial state and config/routing
@@ -521,7 +583,15 @@ function fetchJson(apiUrl, endpoint, apiKey) {
521
583
  });
522
584
  }
523
585
 
524
- main().catch(err => {
525
- console.error('Error:', err.message);
526
- process.exit(1);
527
- });
586
+ if (require.main === module) {
587
+ main().catch(err => {
588
+ console.error('Error:', err.message);
589
+ process.exit(1);
590
+ });
591
+ } else {
592
+ module.exports = {
593
+ getVerificationSummary,
594
+ isActivationPendingRestart,
595
+ shouldPromotePendingRestart
596
+ };
597
+ }
@@ -18,6 +18,9 @@
18
18
  const fs = require('fs');
19
19
  const path = require('path');
20
20
  const os = require('os');
21
+ const { removeManagedStatusLine } = require('../lib/statusline-manager');
22
+ const { removeManagedHooks, cleanupLegacyManagedHooksFile } = require('../lib/hooks-manager');
23
+ const { removeManagedClaudeCustomHeadersFromEnv } = require('../lib/config-utils');
21
24
 
22
25
  // Get home directory, handling sudo case
23
26
  function getHomeDir() {
@@ -36,22 +39,12 @@ const HOME_DIR = getHomeDir();
36
39
  const CLAUDE_DIR = path.join(HOME_DIR, '.claude');
37
40
  const CLAUDE_COMMANDS_DIR = path.join(CLAUDE_DIR, 'commands');
38
41
  const CLAUDE_PLUGINS_DIR = path.join(CLAUDE_DIR, 'plugins', 'plexor');
42
+ const CLAUDE_SETTINGS_PATH = path.join(CLAUDE_DIR, 'settings.json');
43
+ const CLAUDE_HOOKS_PATH = path.join(CLAUDE_DIR, 'settings.json');
44
+ const CLAUDE_LEGACY_HOOKS_PATH = path.join(CLAUDE_DIR, 'hooks.json');
39
45
  const PLEXOR_CONFIG_DIR = path.join(HOME_DIR, '.plexor');
40
46
 
41
47
  // All Plexor slash command files
42
- const PLEXOR_COMMANDS = [
43
- 'plexor-enabled.md',
44
- 'plexor-login.md',
45
- 'plexor-logout.md',
46
- 'plexor-setup.md',
47
- 'plexor-status.md',
48
- 'plexor-uninstall.md',
49
- 'plexor-mode.md',
50
- 'plexor-provider.md',
51
- 'plexor-settings.md',
52
- 'plexor-config.md'
53
- ];
54
-
55
48
  /**
56
49
  * Load settings manager if available
57
50
  */
@@ -115,10 +108,11 @@ function disableRoutingManually() {
115
108
  const hasManagedBaseUrl = isManagedGatewayUrl(settings.env.ANTHROPIC_BASE_URL || '');
116
109
  const hasPlexorAuthToken = isPlexorApiKey(settings.env.ANTHROPIC_AUTH_TOKEN || '');
117
110
  const hasPlexorApiKey = isPlexorApiKey(settings.env.ANTHROPIC_API_KEY || '');
111
+ const removedManagedHeaders = removeManagedClaudeCustomHeadersFromEnv(settings.env);
118
112
 
119
113
  const hasPreviousPrimaryApiKey = Boolean(settings.env[previousPrimaryApiKeyEnv]);
120
114
 
121
- if (!hasManagedBaseUrl && !hasPlexorAuthToken && !hasPlexorApiKey && !hasPreviousPrimaryApiKey) {
115
+ if (!hasManagedBaseUrl && !hasPlexorAuthToken && !hasPlexorApiKey && !hasPreviousPrimaryApiKey && !removedManagedHeaders) {
122
116
  return { success: true, message: 'Plexor routing not active' };
123
117
  }
124
118
 
@@ -191,7 +185,12 @@ function removeSlashCommands() {
191
185
  let removed = 0;
192
186
  let restored = 0;
193
187
 
194
- for (const cmd of PLEXOR_COMMANDS) {
188
+ if (!fs.existsSync(CLAUDE_COMMANDS_DIR)) {
189
+ return { removed, restored };
190
+ }
191
+
192
+ const plexorCommands = fs.readdirSync(CLAUDE_COMMANDS_DIR).filter((entry) => /^plexor-.*\.md$/i.test(entry));
193
+ for (const cmd of plexorCommands) {
195
194
  const cmdPath = path.join(CLAUDE_COMMANDS_DIR, cmd);
196
195
  const backupPath = cmdPath + '.backup';
197
196
 
@@ -229,6 +228,30 @@ function removePluginDirectory() {
229
228
  return false;
230
229
  }
231
230
 
231
+ function removeManagedStatusLineConfig() {
232
+ try {
233
+ return removeManagedStatusLine(CLAUDE_SETTINGS_PATH, HOME_DIR);
234
+ } catch (err) {
235
+ return { changed: false, restored: false, error: err.message };
236
+ }
237
+ }
238
+
239
+ function removeManagedHooksConfig() {
240
+ try {
241
+ return removeManagedHooks(CLAUDE_HOOKS_PATH);
242
+ } catch (err) {
243
+ return { changed: false, removed: 0, error: err.message };
244
+ }
245
+ }
246
+
247
+ function removeLegacyManagedHooksConfig() {
248
+ try {
249
+ return cleanupLegacyManagedHooksFile(CLAUDE_LEGACY_HOOKS_PATH);
250
+ } catch (err) {
251
+ return { changed: false, removed: 0, error: err.message };
252
+ }
253
+ }
254
+
232
255
  /**
233
256
  * Remove config directory
234
257
  */
@@ -255,7 +278,8 @@ function main() {
255
278
  console.log(' Cleans up Plexor integration before npm uninstall.');
256
279
  console.log('');
257
280
  console.log(' Options:');
258
- console.log(' --remove-config, -c Also remove ~/.plexor/ config directory');
281
+ console.log(' --keep-config, -k Preserve ~/.plexor/ config directory');
282
+ console.log(' --remove-config, -c Legacy alias (config is removed by default)');
259
283
  console.log(' --quiet, -q Suppress output messages');
260
284
  console.log(' --help, -h Show this help message');
261
285
  console.log('');
@@ -265,7 +289,7 @@ function main() {
265
289
  process.exit(0);
266
290
  }
267
291
 
268
- const removeConfig = args.includes('--remove-config') || args.includes('-c');
292
+ const removeConfig = !(args.includes('--keep-config') || args.includes('-k'));
269
293
  const quiet = args.includes('--quiet') || args.includes('-q');
270
294
 
271
295
  if (!quiet) {
@@ -295,7 +319,36 @@ function main() {
295
319
  : ` ✗ Failed to remove routing: ${routingResult.message}`);
296
320
  }
297
321
 
298
- // 2. Remove slash command .md files
322
+ // 2. Remove managed Plexor status line
323
+ const statusLineResult = removeManagedStatusLineConfig();
324
+ if (!quiet) {
325
+ if (statusLineResult.error) {
326
+ console.log(` ✗ Failed to clean Plexor status line: ${statusLineResult.error}`);
327
+ } else if (statusLineResult.changed) {
328
+ console.log(' ✓ Restored Claude status line configuration');
329
+ } else {
330
+ console.log(' ○ Claude status line already clean');
331
+ }
332
+ }
333
+
334
+ const hooksResult = removeManagedHooksConfig();
335
+ const legacyHooksResult = removeLegacyManagedHooksConfig();
336
+ if (!quiet) {
337
+ if (hooksResult.error) {
338
+ console.log(` ✗ Failed to clean Plexor hooks: ${hooksResult.error}`);
339
+ } else if (hooksResult.changed) {
340
+ console.log(' ✓ Restored Claude hook configuration');
341
+ } else {
342
+ console.log(' ○ Claude hooks already clean');
343
+ }
344
+ if (legacyHooksResult.error) {
345
+ console.log(` ✗ Failed to clean legacy ~/.claude/hooks.json: ${legacyHooksResult.error}`);
346
+ } else if (legacyHooksResult.changed) {
347
+ console.log(' ✓ Cleaned legacy ~/.claude/hooks.json');
348
+ }
349
+ }
350
+
351
+ // 3. Remove slash command .md files
299
352
  const cmdResult = removeSlashCommands();
300
353
  if (!quiet) {
301
354
  console.log(` ✓ Removed ${cmdResult.removed} slash command files`);
@@ -304,7 +357,7 @@ function main() {
304
357
  }
305
358
  }
306
359
 
307
- // 3. Remove plugin directory
360
+ // 4. Remove plugin directory
308
361
  const pluginRemoved = removePluginDirectory();
309
362
  if (!quiet) {
310
363
  console.log(pluginRemoved
@@ -312,7 +365,7 @@ function main() {
312
365
  : ' ○ Plugin directory not found (already clean)');
313
366
  }
314
367
 
315
- // 4. Optionally remove config directory
368
+ // 5. Remove config directory unless explicitly preserved
316
369
  if (removeConfig) {
317
370
  const configRemoved = removeConfigDirectory();
318
371
  if (!quiet) {
@@ -337,8 +390,7 @@ function main() {
337
390
 
338
391
  if (!removeConfig) {
339
392
  console.log(' Note: ~/.plexor/ config directory was preserved.');
340
- console.log(' To also remove it: plexor-uninstall --remove-config');
341
- console.log(' Or manually: rm -rf ~/.plexor');
393
+ console.log(' To remove it manually: rm -rf ~/.plexor');
342
394
  console.log('');
343
395
  }
344
396
  }
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const https = require('https');
6
+ const http = require('http');
7
+ const { CONFIG_PATH, SESSION_PATH, PLEXOR_DIR } = require('../lib/constants');
8
+
9
+ function readJson(filePath) {
10
+ try {
11
+ if (!fs.existsSync(filePath)) return null;
12
+ const raw = fs.readFileSync(filePath, 'utf8');
13
+ return raw && raw.trim() ? JSON.parse(raw) : null;
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
18
+
19
+ function writeJsonAtomically(filePath, value) {
20
+ fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
21
+ const tempPath = `${filePath}.tmp.${Date.now()}`;
22
+ fs.writeFileSync(tempPath, JSON.stringify(value, null, 2), { mode: 0o600 });
23
+ fs.renameSync(tempPath, filePath);
24
+ }
25
+
26
+ function readStdin() {
27
+ return new Promise((resolve) => {
28
+ let data = '';
29
+ process.stdin.setEncoding('utf8');
30
+ process.stdin.on('data', (chunk) => { data += chunk; });
31
+ process.stdin.on('end', () => resolve(data));
32
+ process.stdin.resume();
33
+ });
34
+ }
35
+
36
+ function clampNonNegative(value) {
37
+ return Number.isFinite(value) ? Math.max(0, value) : 0;
38
+ }
39
+
40
+ function finiteOrZero(value) {
41
+ return Number.isFinite(value) ? value : 0;
42
+ }
43
+
44
+ function parseStats(payload) {
45
+ const summary = payload?.summary || payload || {};
46
+ return {
47
+ total_requests: Number(summary.total_requests ?? payload?.total_requests ?? 0),
48
+ total_optimizations: Number(summary.total_optimizations ?? payload?.total_optimizations ?? 0),
49
+ original_tokens: Number(summary.original_tokens ?? payload?.original_tokens ?? 0),
50
+ optimized_tokens: Number(summary.optimized_tokens ?? payload?.optimized_tokens ?? 0),
51
+ tokens_saved: Number(summary.tokens_saved ?? payload?.tokens_saved ?? 0),
52
+ total_cost: Number(summary.total_cost ?? payload?.total_cost ?? 0),
53
+ baseline_cost: Number(summary.baseline_cost ?? payload?.baseline_cost ?? payload?.total_baseline_cost ?? 0),
54
+ cost_saved: Number(summary.cost_saved ?? payload?.cost_saved ?? payload?.total_savings ?? 0)
55
+ };
56
+ }
57
+
58
+ function fetchJson(apiUrl, endpoint, apiKey) {
59
+ return new Promise((resolve, reject) => {
60
+ const url = new URL(endpoint, apiUrl.replace(/\/$/, '') + '/');
61
+ const client = url.protocol === 'https:' ? https : http;
62
+ const req = client.request({
63
+ hostname: url.hostname,
64
+ port: url.port || (url.protocol === 'https:' ? 443 : 80),
65
+ path: `${url.pathname}${url.search}`,
66
+ method: 'GET',
67
+ headers: {
68
+ 'X-API-Key': apiKey,
69
+ 'X-Plexor-Key': apiKey,
70
+ 'User-Agent': 'plexor-session-sync/0.1.0-beta.24'
71
+ },
72
+ timeout: 5000
73
+ }, (res) => {
74
+ let body = '';
75
+ res.on('data', (chunk) => { body += chunk; });
76
+ res.on('end', () => {
77
+ try {
78
+ const parsed = JSON.parse(body);
79
+ if (res.statusCode >= 200 && res.statusCode < 300) {
80
+ resolve(parsed);
81
+ } else {
82
+ reject(new Error(parsed.message || `HTTP ${res.statusCode}`));
83
+ }
84
+ } catch {
85
+ reject(new Error(`Invalid JSON response: ${body.slice(0, 120)}`));
86
+ }
87
+ });
88
+ });
89
+ req.on('error', reject);
90
+ req.on('timeout', () => {
91
+ req.destroy(new Error('Request timeout'));
92
+ });
93
+ req.end();
94
+ });
95
+ }
96
+
97
+ function loadConfig() {
98
+ const config = readJson(CONFIG_PATH);
99
+ const apiKey = config?.auth?.api_key || '';
100
+ const apiUrl = config?.settings?.apiUrl || 'https://staging.api.plexor.dev';
101
+ const enabled = config?.settings?.enabled !== false;
102
+ if (!enabled || !apiKey.startsWith('plx_')) {
103
+ return null;
104
+ }
105
+ return { config, apiKey, apiUrl };
106
+ }
107
+
108
+ function buildSessionRecord(sessionId, baseline, nowIso) {
109
+ return {
110
+ session_id: sessionId || `session_${Date.now()}`,
111
+ started_at: nowIso,
112
+ last_activity: Date.now(),
113
+ requests: 0,
114
+ optimizations: 0,
115
+ cache_hits: 0,
116
+ passthroughs: 0,
117
+ original_tokens: 0,
118
+ optimized_tokens: 0,
119
+ tokens_saved: 0,
120
+ output_tokens: 0,
121
+ baseline_cost: 0,
122
+ actual_cost: 0,
123
+ cost_delta: 0,
124
+ cost_saved: 0,
125
+ _baseline: baseline
126
+ };
127
+ }
128
+
129
+ async function main() {
130
+ const mode = process.argv[2] || 'stop';
131
+ const inputRaw = await readStdin();
132
+ let input = {};
133
+ try {
134
+ input = inputRaw && inputRaw.trim() ? JSON.parse(inputRaw) : {};
135
+ } catch {
136
+ input = {};
137
+ }
138
+
139
+ if (mode === 'end') {
140
+ try {
141
+ fs.rmSync(SESSION_PATH, { force: true });
142
+ } catch {
143
+ // Session cleanup must not interrupt Claude shutdown.
144
+ }
145
+ process.exit(0);
146
+ }
147
+
148
+ const loaded = loadConfig();
149
+ if (!loaded) {
150
+ process.exit(0);
151
+ }
152
+
153
+ try {
154
+ const statsPayload = await fetchJson(loaded.apiUrl, '/v1/stats', loaded.apiKey);
155
+ const current = parseStats(statsPayload);
156
+ const nowIso = new Date().toISOString();
157
+ const sessionId = input.session_id || input.sessionId || null;
158
+ const existing = readJson(SESSION_PATH);
159
+
160
+ if (mode === 'start' || !existing || !existing._baseline || (sessionId && existing.session_id !== sessionId)) {
161
+ const baseline = { ...current };
162
+ writeJsonAtomically(SESSION_PATH, buildSessionRecord(sessionId, baseline, nowIso));
163
+ process.exit(0);
164
+ }
165
+
166
+ const baseline = existing._baseline || {};
167
+ const baselineCost = clampNonNegative(current.baseline_cost - Number(baseline.baseline_cost || 0));
168
+ const actualCost = clampNonNegative(current.total_cost - Number(baseline.total_cost || 0));
169
+ const next = {
170
+ ...existing,
171
+ session_id: sessionId || existing.session_id,
172
+ last_activity: Date.now(),
173
+ requests: clampNonNegative(current.total_requests - Number(baseline.total_requests || 0)),
174
+ optimizations: clampNonNegative(current.total_optimizations - Number(baseline.total_optimizations || 0)),
175
+ original_tokens: clampNonNegative(current.original_tokens - Number(baseline.original_tokens || 0)),
176
+ optimized_tokens: clampNonNegative(current.optimized_tokens - Number(baseline.optimized_tokens || 0)),
177
+ tokens_saved: clampNonNegative(current.tokens_saved - Number(baseline.tokens_saved || 0)),
178
+ baseline_cost: baselineCost,
179
+ actual_cost: actualCost,
180
+ cost_delta: finiteOrZero(baselineCost - actualCost),
181
+ cost_saved: clampNonNegative(current.cost_saved - Number(baseline.cost_saved || 0)),
182
+ _baseline: baseline
183
+ };
184
+
185
+ if (!fs.existsSync(PLEXOR_DIR)) {
186
+ fs.mkdirSync(PLEXOR_DIR, { recursive: true, mode: 0o700 });
187
+ }
188
+ writeJsonAtomically(SESSION_PATH, next);
189
+ } catch {
190
+ // Hook failures must not interrupt Claude.
191
+ }
192
+ }
193
+
194
+ main();