replit-tools 1.0.4 → 1.0.6

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.
package/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { execSync, spawn, spawnSync } = require('child_process');
3
+ const { execSync, spawn } = require('child_process');
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
6
  const os = require('os');
@@ -8,292 +8,414 @@ const os = require('os');
8
8
  const WORKSPACE = '/home/runner/workspace';
9
9
  const HOME = os.homedir();
10
10
 
11
- // Check if we're on Replit
12
- if (!fs.existsSync(WORKSPACE)) {
13
- console.error('❌ This tool must be run on Replit');
14
- console.error(' /home/runner/workspace not found');
11
+ // Wrap everything in try-catch to prevent crashes
12
+ try {
13
+ main();
14
+ } catch (err) {
15
+ console.error('');
16
+ console.error('❌ Installation error:', err.message);
17
+ console.error('');
18
+ console.error('You can try running manually:');
19
+ console.error(' curl -fsSL https://claude.ai/install.sh | bash');
20
+ console.error(' npm i -g @openai/codex');
15
21
  process.exit(1);
16
22
  }
17
23
 
18
- console.log('');
19
- console.log('╭─────────────────────────────────────────────────────────╮');
20
- console.log('│ DATA Tools - Claude & Codex Persistence │');
21
- console.log('╰─────────────────────────────────────────────────────────╯');
22
- console.log('');
23
-
24
- // Helper to check if command exists
25
- function commandExists(cmd) {
26
- try {
27
- execSync(`which ${cmd}`, { stdio: 'pipe' });
28
- return true;
29
- } catch {
30
- return false;
24
+ function main() {
25
+ // Check if we're on Replit
26
+ if (!fs.existsSync(WORKSPACE)) {
27
+ console.error('❌ This tool must be run on Replit');
28
+ console.error(' /home/runner/workspace not found');
29
+ process.exit(1);
31
30
  }
32
- }
33
31
 
34
- // Helper to get Replit secret value
35
- function getReplitSecret(name) {
36
- return process.env[name] || null;
37
- }
32
+ console.log('');
33
+ console.log('╭─────────────────────────────────────────────────────────╮');
34
+ console.log('│ DATA Tools - Claude & Codex Persistence │');
35
+ console.log('╰─────────────────────────────────────────────────────────╯');
36
+ console.log('');
38
37
 
39
- // Check for CLAUDE_CONFIG_DIR or similar env vars that specify custom Claude location
40
- const claudeConfigEnvVars = [
41
- 'CLAUDE_CONFIG_DIR',
42
- 'CLAUDE_WORKSPACE_DIR',
43
- 'CLAUDE_DATA_DIR',
44
- 'CLAUDE_HOME'
45
- ];
46
-
47
- let customClaudeDir = null;
48
- for (const envVar of claudeConfigEnvVars) {
49
- const value = getReplitSecret(envVar);
50
- if (value) {
51
- customClaudeDir = value;
52
- console.log(`✅ Found ${envVar} = ${value}`);
53
- break;
38
+ // Helper to check if command exists
39
+ function commandExists(cmd) {
40
+ try {
41
+ execSync(`which ${cmd}`, { stdio: 'pipe' });
42
+ return true;
43
+ } catch {
44
+ return false;
45
+ }
54
46
  }
55
- }
56
47
 
57
- // Determine Claude persistent directory
58
- // Priority: 1) CLAUDE_CONFIG_DIR from env, 2) existing .claude-persistent, 3) default
59
- let claudePersistentDir = path.join(WORKSPACE, '.claude-persistent');
48
+ // Helper to get env var value
49
+ function getEnvVar(name) {
50
+ return process.env[name] || null;
51
+ }
60
52
 
61
- if (customClaudeDir) {
62
- // User has specified a custom directory via env var
63
- if (customClaudeDir.startsWith(WORKSPACE)) {
64
- // It's already in workspace - use it directly
65
- claudePersistentDir = customClaudeDir;
66
- console.log(` Using custom Claude directory: ${claudePersistentDir}`);
67
- } else {
68
- // It's outside workspace - we'll symlink to it but also ensure persistence
69
- console.log(` Custom dir outside workspace - will set up persistence`);
53
+ // ═══════════════════════════════════════════════════════════════════
54
+ // CLAUDE CONFIG DIRECTORY DETECTION
55
+ // ═══════════════════════════════════════════════════════════════════
56
+
57
+ // Check for all possible Claude config env vars
58
+ const claudeConfigEnvVars = [
59
+ 'CLAUDE_CONFIG_DIR',
60
+ 'CLAUDE_WORKSPACE_DIR',
61
+ 'CLAUDE_DATA_DIR',
62
+ 'CLAUDE_HOME'
63
+ ];
64
+
65
+ let claudeEnvVarUsed = null;
66
+ let customClaudeDir = null;
67
+
68
+ for (const envVar of claudeConfigEnvVars) {
69
+ const value = getEnvVar(envVar);
70
+ if (value) {
71
+ customClaudeDir = value;
72
+ claudeEnvVarUsed = envVar;
73
+ console.log(`✅ Found ${envVar} = ${value}`);
74
+ break;
75
+ }
70
76
  }
71
- }
72
77
 
73
- // Check for existing persistent config
74
- const existingClaudeConfig = fs.existsSync(claudePersistentDir);
75
- const existingCodexConfig = fs.existsSync(path.join(WORKSPACE, '.codex-persistent'));
78
+ // Determine Claude persistent directory
79
+ let claudePersistentDir = path.join(WORKSPACE, '.claude-persistent');
76
80
 
77
- if (existingClaudeConfig) {
78
- console.log(`✅ Found existing Claude config at ${claudePersistentDir}`);
79
- }
80
- if (existingCodexConfig) {
81
- console.log('✅ Found existing Codex config in workspace');
82
- }
81
+ if (customClaudeDir) {
82
+ if (customClaudeDir.startsWith(WORKSPACE)) {
83
+ claudePersistentDir = customClaudeDir;
84
+ console.log(` Using custom Claude directory`);
85
+ } else {
86
+ console.log(` ⚠️ Custom dir outside workspace - will redirect to workspace for persistence`);
87
+ }
88
+ }
83
89
 
84
- // Check for API key secrets
85
- if (getReplitSecret('ANTHROPIC_API_KEY')) {
86
- console.log('✅ Found ANTHROPIC_API_KEY in Replit secrets');
87
- }
88
- if (getReplitSecret('OPENAI_API_KEY')) {
89
- console.log('✅ Found OPENAI_API_KEY in Replit secrets');
90
- }
90
+ // ═══════════════════════════════════════════════════════════════════
91
+ // CODEX CONFIG DIRECTORY DETECTION
92
+ // ═══════════════════════════════════════════════════════════════════
93
+
94
+ // Check for Codex config env vars
95
+ const codexConfigEnvVars = [
96
+ 'CODEX_HOME',
97
+ 'CODEX_CONFIG_DIR',
98
+ 'CODEX_DATA_DIR'
99
+ ];
100
+
101
+ let codexEnvVarUsed = null;
102
+ let customCodexDir = null;
103
+
104
+ for (const envVar of codexConfigEnvVars) {
105
+ const value = getEnvVar(envVar);
106
+ if (value) {
107
+ customCodexDir = value;
108
+ codexEnvVarUsed = envVar;
109
+ console.log(`✅ Found ${envVar} = ${value}`);
110
+ break;
111
+ }
112
+ }
113
+
114
+ // Determine Codex persistent directory
115
+ let codexPersistentDir = path.join(WORKSPACE, '.codex-persistent');
91
116
 
92
- // Create directories (preserving existing data)
93
- const dirs = [
94
- '.codex-persistent',
95
- '.claude-sessions',
96
- '.local/share/claude/versions',
97
- '.persistent-home',
98
- '.config',
99
- 'scripts',
100
- 'logs'
101
- ];
102
-
103
- // Add Claude persistent dir if it's relative to workspace
104
- if (claudePersistentDir.startsWith(WORKSPACE)) {
105
- const relativePath = claudePersistentDir.replace(WORKSPACE + '/', '');
106
- if (!dirs.includes(relativePath)) {
107
- dirs.unshift(relativePath);
117
+ if (customCodexDir) {
118
+ if (customCodexDir.startsWith(WORKSPACE)) {
119
+ codexPersistentDir = customCodexDir;
120
+ console.log(` Using custom Codex directory`);
121
+ } else {
122
+ console.log(` ⚠️ Custom dir outside workspace - will redirect to workspace for persistence`);
123
+ }
108
124
  }
109
- }
110
125
 
111
- console.log('');
112
- console.log('📁 Creating directories...');
113
- dirs.forEach(dir => {
114
- const fullPath = path.join(WORKSPACE, dir);
115
- if (!fs.existsSync(fullPath)) {
116
- fs.mkdirSync(fullPath, { recursive: true });
126
+ // ═══════════════════════════════════════════════════════════════════
127
+ // CHECK FOR EXISTING CONFIG
128
+ // ═══════════════════════════════════════════════════════════════════
129
+
130
+ const existingClaudeConfig = fs.existsSync(claudePersistentDir);
131
+ const existingCodexConfig = fs.existsSync(codexPersistentDir);
132
+
133
+ if (existingClaudeConfig) {
134
+ console.log(`✅ Found existing Claude config`);
135
+ }
136
+ if (existingCodexConfig) {
137
+ console.log('✅ Found existing Codex config');
117
138
  }
118
- });
119
139
 
120
- // Check and install Claude Code if needed
121
- console.log('');
122
- const claudeInstalled = commandExists('claude') ||
123
- fs.existsSync(path.join(HOME, '.local/bin/claude')) ||
124
- fs.existsSync(path.join(WORKSPACE, '.local/share/claude/versions'));
140
+ // Check for API key secrets
141
+ if (getEnvVar('ANTHROPIC_API_KEY')) {
142
+ console.log('✅ Found ANTHROPIC_API_KEY');
143
+ }
144
+ if (getEnvVar('OPENAI_API_KEY')) {
145
+ console.log('✅ Found OPENAI_API_KEY');
146
+ }
125
147
 
126
- let claudeVersions = [];
127
- try {
128
- claudeVersions = fs.readdirSync(path.join(WORKSPACE, '.local/share/claude/versions'))
129
- .filter(f => !f.startsWith('.'));
130
- } catch {}
148
+ // ═══════════════════════════════════════════════════════════════════
149
+ // CREATE DIRECTORIES
150
+ // ═══════════════════════════════════════════════════════════════════
151
+
152
+ const dirs = [
153
+ '.claude-sessions',
154
+ '.local/share/claude/versions',
155
+ '.persistent-home',
156
+ '.config',
157
+ 'scripts',
158
+ 'logs'
159
+ ];
160
+
161
+ // Add Claude persistent dir
162
+ if (claudePersistentDir.startsWith(WORKSPACE)) {
163
+ const relativePath = claudePersistentDir.replace(WORKSPACE + '/', '');
164
+ if (!dirs.includes(relativePath)) {
165
+ dirs.unshift(relativePath);
166
+ }
167
+ }
131
168
 
132
- if (!claudeInstalled || claudeVersions.length === 0) {
133
- console.log('📦 Installing Claude Code...');
134
- try {
135
- // Set CLAUDE_CONFIG_DIR before install so it installs to the right place
136
- const installEnv = { ...process.env, CLAUDE_CONFIG_DIR: claudePersistentDir };
137
- execSync('curl -fsSL https://claude.ai/install.sh | bash', {
138
- stdio: 'inherit',
139
- shell: '/bin/bash',
140
- env: installEnv
141
- });
142
- console.log('✅ Claude Code installed');
143
- } catch (err) {
144
- console.log('⚠️ Claude Code installation failed (you can install manually later)');
169
+ // Add Codex persistent dir
170
+ if (codexPersistentDir.startsWith(WORKSPACE)) {
171
+ const relativePath = codexPersistentDir.replace(WORKSPACE + '/', '');
172
+ if (!dirs.includes(relativePath)) {
173
+ dirs.unshift(relativePath);
174
+ }
145
175
  }
146
- } else {
147
- const version = claudeVersions.sort().pop() || 'unknown';
148
- console.log(`✅ Claude Code already installed (${version})`);
149
- }
150
176
 
151
- // Check and install Codex if needed
152
- const codexInstalled = commandExists('codex');
177
+ console.log('');
178
+ console.log('📁 Creating directories...');
179
+ dirs.forEach(dir => {
180
+ const fullPath = path.join(WORKSPACE, dir);
181
+ if (!fs.existsSync(fullPath)) {
182
+ try {
183
+ fs.mkdirSync(fullPath, { recursive: true });
184
+ } catch (err) {
185
+ console.log(` ⚠️ Could not create ${dir}: ${err.message}`);
186
+ }
187
+ }
188
+ });
189
+
190
+ // ═══════════════════════════════════════════════════════════════════
191
+ // INSTALL CLAUDE CODE
192
+ // ═══════════════════════════════════════════════════════════════════
153
193
 
154
- if (!codexInstalled) {
155
- console.log('📦 Installing OpenAI Codex CLI...');
194
+ console.log('');
195
+
196
+ let claudeVersions = [];
156
197
  try {
157
- execSync('npm i -g @openai/codex', {
158
- stdio: 'inherit',
159
- shell: '/bin/bash'
160
- });
161
- console.log('✅ Codex CLI installed');
162
- } catch (err) {
163
- console.log('⚠️ Codex installation failed (you can install manually later)');
198
+ claudeVersions = fs.readdirSync(path.join(WORKSPACE, '.local/share/claude/versions'))
199
+ .filter(f => !f.startsWith('.'));
200
+ } catch {}
201
+
202
+ const claudeInstalled = commandExists('claude') ||
203
+ fs.existsSync(path.join(HOME, '.local/bin/claude')) ||
204
+ claudeVersions.length > 0;
205
+
206
+ if (!claudeInstalled) {
207
+ console.log('📦 Installing Claude Code...');
208
+ try {
209
+ const installEnv = {
210
+ ...process.env,
211
+ CLAUDE_CONFIG_DIR: claudePersistentDir,
212
+ CLAUDE_WORKSPACE_DIR: claudePersistentDir
213
+ };
214
+ execSync('curl -fsSL https://claude.ai/install.sh | bash', {
215
+ stdio: 'inherit',
216
+ shell: '/bin/bash',
217
+ env: installEnv,
218
+ timeout: 120000 // 2 minute timeout
219
+ });
220
+ console.log('✅ Claude Code installed');
221
+ } catch (err) {
222
+ console.log('⚠️ Claude Code installation had issues (may still work)');
223
+ }
224
+ } else {
225
+ const version = claudeVersions.sort().pop() || 'installed';
226
+ console.log(`✅ Claude Code already installed (${version})`);
164
227
  }
165
- } else {
166
- console.log('✅ Codex CLI already installed');
167
- }
168
228
 
169
- // Set up symlinks
170
- console.log('');
171
- console.log('🔗 Setting up symlinks...');
229
+ // ═══════════════════════════════════════════════════════════════════
230
+ // INSTALL CODEX CLI
231
+ // ═══════════════════════════════════════════════════════════════════
232
+
233
+ const codexInstalled = commandExists('codex');
234
+
235
+ if (!codexInstalled) {
236
+ console.log('📦 Installing OpenAI Codex CLI...');
237
+ try {
238
+ const installEnv = {
239
+ ...process.env,
240
+ CODEX_HOME: codexPersistentDir
241
+ };
242
+ execSync('npm i -g @openai/codex', {
243
+ stdio: 'inherit',
244
+ shell: '/bin/bash',
245
+ env: installEnv,
246
+ timeout: 120000 // 2 minute timeout
247
+ });
248
+ console.log('✅ Codex CLI installed');
249
+ } catch (err) {
250
+ console.log('⚠️ Codex installation had issues (may still work)');
251
+ }
252
+ } else {
253
+ console.log('✅ Codex CLI already installed');
254
+ }
172
255
 
173
- // Claude symlink - only if we're using default location
174
- const claudeLink = path.join(HOME, '.claude');
175
- if (!customClaudeDir || customClaudeDir.startsWith(WORKSPACE)) {
256
+ // ═══════════════════════════════════════════════════════════════════
257
+ // SET UP SYMLINKS
258
+ // ═══════════════════════════════════════════════════════════════════
259
+
260
+ console.log('');
261
+ console.log('🔗 Setting up symlinks...');
262
+
263
+ // Claude symlink
264
+ const claudeLink = path.join(HOME, '.claude');
176
265
  try {
177
- const stat = fs.lstatSync(claudeLink);
178
- if (stat.isSymbolicLink()) {
179
- const current = fs.readlinkSync(claudeLink);
180
- if (current !== claudePersistentDir) {
181
- fs.unlinkSync(claudeLink);
182
- fs.symlinkSync(claudePersistentDir, claudeLink);
266
+ let needsLink = false;
267
+ try {
268
+ const stat = fs.lstatSync(claudeLink);
269
+ if (stat.isSymbolicLink()) {
270
+ const current = fs.readlinkSync(claudeLink);
271
+ if (current !== claudePersistentDir) {
272
+ fs.unlinkSync(claudeLink);
273
+ needsLink = true;
274
+ }
275
+ } else if (stat.isDirectory()) {
276
+ console.log(' Moving existing ~/.claude data to persistent storage...');
277
+ execSync(`cp -rn "${claudeLink}"/* "${claudePersistentDir}/" 2>/dev/null || true`, { shell: '/bin/bash' });
278
+ execSync(`rm -rf "${claudeLink}"`, { shell: '/bin/bash' });
279
+ needsLink = true;
183
280
  }
184
- } else if (stat.isDirectory()) {
185
- // Move existing data to persistent location
186
- console.log(' Moving existing ~/.claude data to persistent storage...');
187
- execSync(`cp -rn ${claudeLink}/* ${claudePersistentDir}/ 2>/dev/null || true`, { shell: '/bin/bash' });
188
- execSync(`rm -rf ${claudeLink}`, { shell: '/bin/bash' });
281
+ } catch {
282
+ needsLink = true;
283
+ }
284
+
285
+ if (needsLink) {
189
286
  fs.symlinkSync(claudePersistentDir, claudeLink);
190
287
  }
191
- } catch {
192
- // Doesn't exist, create it
193
- fs.symlinkSync(claudePersistentDir, claudeLink);
288
+ console.log(` ~/.claude ${claudePersistentDir.replace(WORKSPACE + '/', '')}/`);
289
+ } catch (err) {
290
+ console.log(` ⚠️ Could not create Claude symlink: ${err.message}`);
194
291
  }
195
- console.log(` ~/.claude → ${claudePersistentDir.replace(WORKSPACE + '/', '')}/`);
196
- }
197
292
 
198
- // Codex symlink
199
- const codexTarget = path.join(WORKSPACE, '.codex-persistent');
200
- const codexLink = path.join(HOME, '.codex');
201
- try {
202
- const stat = fs.lstatSync(codexLink);
203
- if (stat.isSymbolicLink()) {
204
- const current = fs.readlinkSync(codexLink);
205
- if (current !== codexTarget) {
206
- fs.unlinkSync(codexLink);
207
- fs.symlinkSync(codexTarget, codexLink);
293
+ // Codex symlink
294
+ const codexLink = path.join(HOME, '.codex');
295
+ try {
296
+ let needsLink = false;
297
+ try {
298
+ const stat = fs.lstatSync(codexLink);
299
+ if (stat.isSymbolicLink()) {
300
+ const current = fs.readlinkSync(codexLink);
301
+ if (current !== codexPersistentDir) {
302
+ fs.unlinkSync(codexLink);
303
+ needsLink = true;
304
+ }
305
+ } else if (stat.isDirectory()) {
306
+ console.log(' Moving existing ~/.codex data to persistent storage...');
307
+ execSync(`cp -rn "${codexLink}"/* "${codexPersistentDir}/" 2>/dev/null || true`, { shell: '/bin/bash' });
308
+ execSync(`rm -rf "${codexLink}"`, { shell: '/bin/bash' });
309
+ needsLink = true;
310
+ }
311
+ } catch {
312
+ needsLink = true;
313
+ }
314
+
315
+ if (needsLink) {
316
+ fs.symlinkSync(codexPersistentDir, codexLink);
208
317
  }
209
- } else if (stat.isDirectory()) {
210
- console.log(' Moving existing ~/.codex data to persistent storage...');
211
- execSync(`cp -rn ${codexLink}/* ${codexTarget}/ 2>/dev/null || true`, { shell: '/bin/bash' });
212
- execSync(`rm -rf ${codexLink}`, { shell: '/bin/bash' });
213
- fs.symlinkSync(codexTarget, codexLink);
318
+ console.log(` ~/.codex ${codexPersistentDir.replace(WORKSPACE + '/', '')}/`);
319
+ } catch (err) {
320
+ console.log(` ⚠️ Could not create Codex symlink: ${err.message}`);
214
321
  }
215
- } catch {
216
- fs.symlinkSync(codexTarget, codexLink);
217
- }
218
- console.log(' ~/.codex → .codex-persistent/');
219
322
 
220
- // Claude binary symlinks
221
- const localBin = path.join(HOME, '.local/bin');
222
- const localShare = path.join(HOME, '.local/share');
223
- const claudeShareTarget = path.join(WORKSPACE, '.local/share/claude');
323
+ // Claude binary symlinks
324
+ const localBin = path.join(HOME, '.local/bin');
325
+ const localShare = path.join(HOME, '.local/share');
326
+ const claudeShareTarget = path.join(WORKSPACE, '.local/share/claude');
224
327
 
225
- try { fs.mkdirSync(localBin, { recursive: true }); } catch {}
226
- try { fs.mkdirSync(localShare, { recursive: true }); } catch {}
328
+ try { fs.mkdirSync(localBin, { recursive: true }); } catch {}
329
+ try { fs.mkdirSync(localShare, { recursive: true }); } catch {}
227
330
 
228
- // Link .local/share/claude
229
- try {
230
- const stat = fs.lstatSync(path.join(localShare, 'claude'));
231
- if (stat.isSymbolicLink()) {
232
- const current = fs.readlinkSync(path.join(localShare, 'claude'));
233
- if (current !== claudeShareTarget) {
234
- fs.unlinkSync(path.join(localShare, 'claude'));
331
+ // Link .local/share/claude
332
+ try {
333
+ let needsLink = false;
334
+ try {
335
+ const stat = fs.lstatSync(path.join(localShare, 'claude'));
336
+ if (stat.isSymbolicLink()) {
337
+ const current = fs.readlinkSync(path.join(localShare, 'claude'));
338
+ if (current !== claudeShareTarget) {
339
+ fs.unlinkSync(path.join(localShare, 'claude'));
340
+ needsLink = true;
341
+ }
342
+ }
343
+ } catch {
344
+ needsLink = true;
345
+ }
346
+
347
+ if (needsLink) {
235
348
  fs.symlinkSync(claudeShareTarget, path.join(localShare, 'claude'));
236
349
  }
237
- }
238
- } catch {
350
+ console.log(' ~/.local/share/claude → .local/share/claude/');
351
+ } catch {}
352
+
353
+ // Link binary to latest version
239
354
  try {
240
- fs.symlinkSync(claudeShareTarget, path.join(localShare, 'claude'));
355
+ const versions = fs.readdirSync(path.join(WORKSPACE, '.local/share/claude/versions'))
356
+ .filter(f => !f.startsWith('.'))
357
+ .sort();
358
+ if (versions.length > 0) {
359
+ const latest = versions[versions.length - 1];
360
+ const binaryPath = path.join(WORKSPACE, '.local/share/claude/versions', latest);
361
+ const binLink = path.join(localBin, 'claude');
362
+ try { fs.unlinkSync(binLink); } catch {}
363
+ fs.symlinkSync(binaryPath, binLink);
364
+ console.log(` ~/.local/bin/claude → versions/${latest}`);
365
+ }
241
366
  } catch {}
242
- }
243
- console.log(' ~/.local/share/claude → .local/share/claude/');
244
367
 
245
- // Link binary to latest version
246
- try {
247
- const versions = fs.readdirSync(path.join(WORKSPACE, '.local/share/claude/versions'))
248
- .filter(f => !f.startsWith('.'))
249
- .sort();
250
- if (versions.length > 0) {
251
- const latest = versions[versions.length - 1];
252
- const binaryPath = path.join(WORKSPACE, '.local/share/claude/versions', latest);
253
- const binLink = path.join(localBin, 'claude');
254
- try { fs.unlinkSync(binLink); } catch {}
255
- fs.symlinkSync(binaryPath, binLink);
256
- console.log(` ~/.local/bin/claude → versions/${latest}`);
257
- }
258
- } catch {}
368
+ // ═══════════════════════════════════════════════════════════════════
369
+ // COPY SCRIPTS
370
+ // ═══════════════════════════════════════════════════════════════════
259
371
 
260
- // Copy scripts from the package
261
- const scriptsDir = path.join(__dirname, 'scripts');
262
- const targetScriptsDir = path.join(WORKSPACE, 'scripts');
372
+ const scriptsDir = path.join(__dirname, 'scripts');
373
+ const targetScriptsDir = path.join(WORKSPACE, 'scripts');
263
374
 
264
- console.log('');
265
- console.log('📝 Installing scripts...');
375
+ console.log('');
376
+ console.log('📝 Installing scripts...');
377
+
378
+ const scripts = ['setup-claude-code.sh', 'claude-session-manager.sh', 'claude-auth-refresh.sh'];
379
+ scripts.forEach(script => {
380
+ const srcPath = path.join(scriptsDir, script);
381
+ const destPath = path.join(targetScriptsDir, script);
382
+
383
+ if (fs.existsSync(srcPath)) {
384
+ try {
385
+ fs.copyFileSync(srcPath, destPath);
386
+ fs.chmodSync(destPath, '755');
387
+ console.log(` ${script}`);
388
+ } catch (err) {
389
+ console.log(` ⚠️ Could not copy ${script}: ${err.message}`);
390
+ }
391
+ }
392
+ });
266
393
 
267
- const scripts = ['setup-claude-code.sh', 'claude-session-manager.sh'];
268
- scripts.forEach(script => {
269
- const srcPath = path.join(scriptsDir, script);
270
- const destPath = path.join(targetScriptsDir, script);
394
+ // ═══════════════════════════════════════════════════════════════════
395
+ // CREATE BASHRC
396
+ // ═══════════════════════════════════════════════════════════════════
271
397
 
272
- if (fs.existsSync(srcPath)) {
273
- fs.copyFileSync(srcPath, destPath);
274
- fs.chmodSync(destPath, '755');
275
- console.log(` ${script}`);
276
- }
277
- });
398
+ console.log('');
399
+ console.log('📝 Creating .config/bashrc...');
278
400
 
279
- // Create/update .config/bashrc with CLAUDE_CONFIG_DIR export
280
- console.log('');
281
- console.log('📝 Creating .config/bashrc...');
282
- const bashrcContent = `#!/bin/bash
401
+ const bashrcContent = `#!/bin/bash
283
402
  # DATA Tools - Replit Claude & Codex Persistence
284
403
  # Auto-generated bashrc
285
404
 
286
405
  # Claude Config Directory (tells Claude where to store data)
287
406
  export CLAUDE_CONFIG_DIR="${claudePersistentDir}"
407
+ export CLAUDE_WORKSPACE_DIR="${claudePersistentDir}"
408
+
409
+ # Codex Config Directory (tells Codex where to store data)
410
+ export CODEX_HOME="${codexPersistentDir}"
288
411
 
289
412
  # Claude Code Setup
290
413
  SETUP_SCRIPT="/home/runner/workspace/scripts/setup-claude-code.sh"
291
414
  [ -f "\${SETUP_SCRIPT}" ] && source "\${SETUP_SCRIPT}"
292
415
 
293
416
  # Codex Persistence
294
- CODEX_PERSISTENT="/home/runner/workspace/.codex-persistent"
295
- mkdir -p "\${CODEX_PERSISTENT}"
296
- [ ! -L "\${HOME}/.codex" ] && ln -sf "\${CODEX_PERSISTENT}" "\${HOME}/.codex"
417
+ mkdir -p "${codexPersistentDir}"
418
+ [ ! -L "\${HOME}/.codex" ] && ln -sf "${codexPersistentDir}" "\${HOME}/.codex"
297
419
 
298
420
  # Bash History Persistence
299
421
  PERSISTENT_HOME="/home/runner/workspace/.persistent-home"
@@ -314,79 +436,129 @@ alias claude-resume='claude -c --dangerously-skip-permissions'
314
436
  alias claude-pick='claude -r --dangerously-skip-permissions'
315
437
  `;
316
438
 
317
- fs.writeFileSync(path.join(WORKSPACE, '.config/bashrc'), bashrcContent);
439
+ try {
440
+ fs.writeFileSync(path.join(WORKSPACE, '.config/bashrc'), bashrcContent);
441
+ } catch (err) {
442
+ console.log(` ⚠️ Could not write bashrc: ${err.message}`);
443
+ }
444
+
445
+ // ═══════════════════════════════════════════════════════════════════
446
+ // UPDATE .replit
447
+ // ═══════════════════════════════════════════════════════════════════
318
448
 
319
- // Update .replit
320
- console.log('📝 Updating .replit configuration...');
321
- const replitPath = path.join(WORKSPACE, '.replit');
322
- const onBootLine = 'onBoot = "source /home/runner/workspace/scripts/setup-claude-code.sh 2>/dev/null || true"';
449
+ console.log('📝 Updating .replit configuration...');
450
+ const replitPath = path.join(WORKSPACE, '.replit');
451
+ const onBootLine = 'onBoot = "source /home/runner/workspace/scripts/setup-claude-code.sh 2>/dev/null || true"';
323
452
 
324
- if (fs.existsSync(replitPath)) {
325
- let content = fs.readFileSync(replitPath, 'utf8');
326
- if (!content.includes('setup-claude-code.sh')) {
327
- content += '\n\n# Claude persistence (added by DATA Tools)\n' + onBootLine + '\n';
328
- fs.writeFileSync(replitPath, content);
453
+ try {
454
+ if (fs.existsSync(replitPath)) {
455
+ let content = fs.readFileSync(replitPath, 'utf8');
456
+ if (!content.includes('setup-claude-code.sh')) {
457
+ content += '\n\n# Claude persistence (added by DATA Tools)\n' + onBootLine + '\n';
458
+ fs.writeFileSync(replitPath, content);
459
+ }
460
+ } else {
461
+ fs.writeFileSync(replitPath, '# Claude persistence (DATA Tools)\n' + onBootLine + '\n');
462
+ }
463
+ } catch (err) {
464
+ console.log(` ⚠️ Could not update .replit: ${err.message}`);
329
465
  }
330
- } else {
331
- fs.writeFileSync(replitPath, '# Claude persistence (DATA Tools)\n' + onBootLine + '\n');
332
- }
333
466
 
334
- // Update .gitignore (only essential sensitive dirs)
335
- console.log('📝 Updating .gitignore...');
336
- const gitignorePath = path.join(WORKSPACE, '.gitignore');
337
- const gitignoreEntries = '\n# Claude/Codex credentials (added by DATA Tools)\n.claude-persistent/\n.codex-persistent/\n';
467
+ // ═══════════════════════════════════════════════════════════════════
468
+ // UPDATE .gitignore
469
+ // ═══════════════════════════════════════════════════════════════════
338
470
 
339
- if (fs.existsSync(gitignorePath)) {
340
- let content = fs.readFileSync(gitignorePath, 'utf8');
341
- if (!content.includes('.claude-persistent')) {
342
- fs.writeFileSync(gitignorePath, content + gitignoreEntries);
471
+ console.log('📝 Updating .gitignore...');
472
+ const gitignorePath = path.join(WORKSPACE, '.gitignore');
473
+ const gitignoreEntries = '\n# Claude/Codex credentials (added by DATA Tools)\n.claude-persistent/\n.codex-persistent/\n';
474
+
475
+ try {
476
+ if (fs.existsSync(gitignorePath)) {
477
+ let content = fs.readFileSync(gitignorePath, 'utf8');
478
+ if (!content.includes('.claude-persistent')) {
479
+ fs.writeFileSync(gitignorePath, content + gitignoreEntries);
480
+ }
481
+ } else {
482
+ fs.writeFileSync(gitignorePath, gitignoreEntries.trim() + '\n');
483
+ }
484
+ } catch (err) {
485
+ console.log(` ⚠️ Could not update .gitignore: ${err.message}`);
343
486
  }
344
- } else {
345
- fs.writeFileSync(gitignorePath, gitignoreEntries.trim() + '\n');
346
- }
347
487
 
348
- // Add PATH and CLAUDE_CONFIG_DIR to current process
349
- process.env.PATH = `${localBin}:${process.env.PATH}`;
350
- process.env.CLAUDE_CONFIG_DIR = claudePersistentDir;
351
-
352
- console.log('');
353
- console.log('╭─────────────────────────────────────────────────────────╮');
354
- console.log('│ ✅ Installation complete! │');
355
- console.log('╰─────────────────────────────────────────────────────────╯');
356
- console.log('');
357
- console.log('Your conversations and credentials now persist across restarts.');
358
- console.log(`Claude config directory: ${claudePersistentDir}`);
359
- console.log('');
360
-
361
- // Check if Claude needs login
362
- let needsLogin = true;
363
- try {
364
- const authCheck = execSync('claude auth status 2>&1 || true', {
365
- encoding: 'utf8',
366
- shell: '/bin/bash',
367
- env: { ...process.env, CLAUDE_CONFIG_DIR: claudePersistentDir }
368
- });
369
- if (authCheck.includes('Logged in') || authCheck.includes('valid')) {
370
- needsLogin = false;
488
+ // ═══════════════════════════════════════════════════════════════════
489
+ // SET UP ENVIRONMENT
490
+ // ═══════════════════════════════════════════════════════════════════
491
+
492
+ process.env.PATH = `${localBin}:${process.env.PATH}`;
493
+ process.env.CLAUDE_CONFIG_DIR = claudePersistentDir;
494
+ process.env.CLAUDE_WORKSPACE_DIR = claudePersistentDir;
495
+ process.env.CODEX_HOME = codexPersistentDir;
496
+
497
+ // ═══════════════════════════════════════════════════════════════════
498
+ // SHOW COMPLETION MESSAGE
499
+ // ═══════════════════════════════════════════════════════════════════
500
+
501
+ console.log('');
502
+ console.log('╔═════════════════════════════════════════════════════════════╗');
503
+ console.log('║ ║');
504
+ console.log('║ ✅ DATA Tools Installation Complete! ║');
505
+ console.log('║ ║');
506
+ console.log('╠═════════════════════════════════════════════════════════════╣');
507
+ console.log('║ ║');
508
+ console.log('║ Your setup now persists across container restarts: ║');
509
+ console.log('║ ║');
510
+ console.log('║ • Claude Code conversations & credentials ║');
511
+ console.log('║ • Codex CLI data & authentication ║');
512
+ console.log('║ • Command history (bash) ║');
513
+ console.log('║ • Per-terminal session tracking ║');
514
+ console.log('║ ║');
515
+ console.log('╠═════════════════════════════════════════════════════════════╣');
516
+ console.log('║ ║');
517
+ console.log(`║ Claude config: ${claudePersistentDir.replace(WORKSPACE + '/', '').padEnd(38)} ║`);
518
+ console.log(`║ Codex config: ${codexPersistentDir.replace(WORKSPACE + '/', '').padEnd(38)} ║`);
519
+ console.log('║ ║');
520
+ console.log('╚═════════════════════════════════════════════════════════════╝');
521
+ console.log('');
522
+
523
+ // Check if Claude needs login
524
+ let needsLogin = true;
525
+ try {
526
+ const authCheck = execSync('claude auth status 2>&1 || true', {
527
+ encoding: 'utf8',
528
+ shell: '/bin/bash',
529
+ env: process.env,
530
+ timeout: 10000
531
+ });
532
+ if (authCheck.includes('Logged in') || authCheck.includes('valid') || authCheck.includes('authenticated')) {
533
+ needsLogin = false;
534
+ }
535
+ } catch {}
536
+
537
+ if (needsLogin && !getEnvVar('ANTHROPIC_API_KEY')) {
538
+ console.log('⚠️ Claude needs authentication. Run: claude login');
539
+ console.log('');
371
540
  }
372
- } catch {}
373
541
 
374
- if (needsLogin && !getReplitSecret('ANTHROPIC_API_KEY')) {
375
- console.log('⚠️ Claude needs authentication. Run: claude login');
542
+ // ═══════════════════════════════════════════════════════════════════
543
+ // LAUNCH SESSION MANAGER
544
+ // ═══════════════════════════════════════════════════════════════════
545
+
546
+ console.log('Launching session manager...');
376
547
  console.log('');
377
- }
378
548
 
379
- // Launch the session picker
380
- console.log('Launching session manager...');
381
- console.log('');
549
+ // Use spawn to run bash interactively with our session manager
550
+ const sessionManager = spawn('bash', ['--rcfile', path.join(WORKSPACE, '.config/bashrc'), '-i'], {
551
+ stdio: 'inherit',
552
+ cwd: WORKSPACE,
553
+ env: process.env
554
+ });
382
555
 
383
- // Use spawn to run bash interactively with our session manager
384
- const sessionManager = spawn('bash', ['--rcfile', path.join(WORKSPACE, '.config/bashrc'), '-i'], {
385
- stdio: 'inherit',
386
- cwd: WORKSPACE,
387
- env: { ...process.env, CLAUDE_CONFIG_DIR: claudePersistentDir }
388
- });
556
+ sessionManager.on('error', (err) => {
557
+ console.error('Failed to launch session manager:', err.message);
558
+ process.exit(1);
559
+ });
389
560
 
390
- sessionManager.on('exit', (code) => {
391
- process.exit(code || 0);
392
- });
561
+ sessionManager.on('exit', (code) => {
562
+ process.exit(code || 0);
563
+ });
564
+ }