replit-tools 1.0.3 → 1.0.5

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.
Files changed (3) hide show
  1. package/README.md +24 -0
  2. package/index.js +476 -245
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -116,11 +116,35 @@ Three layers ensure setup runs on every restart:
116
116
 
117
117
  The installer checks for:
118
118
 
119
+ - **`CLAUDE_CONFIG_DIR`** - Respects custom Claude config directory if set in Replit Secrets
119
120
  - **Existing persistent config** - Uses your existing `.claude-persistent/` if present
120
121
  - **Replit Secrets** - Detects `ANTHROPIC_API_KEY` and `OPENAI_API_KEY`
121
122
  - **Existing installations** - Won't reinstall Claude or Codex if already present
122
123
  - **Existing data in ~/.claude** - Moves it to persistent storage instead of overwriting
123
124
 
125
+ ### Supported Environment Variables
126
+
127
+ **Claude Code:**
128
+
129
+ | Variable | Purpose |
130
+ |----------|---------|
131
+ | `CLAUDE_CONFIG_DIR` | Custom directory for Claude config/data |
132
+ | `CLAUDE_WORKSPACE_DIR` | Alternative name (both are valid) |
133
+ | `CLAUDE_DATA_DIR` | Alternative name |
134
+ | `CLAUDE_HOME` | Alternative name |
135
+ | `ANTHROPIC_API_KEY` | Claude API authentication |
136
+
137
+ **OpenAI Codex CLI:**
138
+
139
+ | Variable | Purpose |
140
+ |----------|---------|
141
+ | `CODEX_HOME` | Custom directory for Codex config/data (official) |
142
+ | `CODEX_CONFIG_DIR` | Alternative name |
143
+ | `CODEX_DATA_DIR` | Alternative name |
144
+ | `OPENAI_API_KEY` | Codex API authentication |
145
+
146
+ If you set these in your Replit Secrets to paths inside `/home/runner/workspace/`, DATA Tools will use those directories for persistence instead of the defaults.
147
+
124
148
  ## Installation Options
125
149
 
126
150
  ### Option 1: npx (recommended)
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,240 +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('│ Replit Tools - Claude & Codex Persistence │');
21
- console.log('╰─────────────────────────────────────────────────────────╯');
22
- console.log('');
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);
30
+ }
23
31
 
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;
32
+ console.log('');
33
+ console.log('╭─────────────────────────────────────────────────────────╮');
34
+ console.log('│ DATA Tools - Claude & Codex Persistence │');
35
+ console.log('╰─────────────────────────────────────────────────────────╯');
36
+ console.log('');
37
+
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
+ }
31
46
  }
32
- }
33
47
 
34
- // Helper to check for Replit secret
35
- function hasReplitSecret(name) {
36
- return process.env[name] !== undefined;
37
- }
48
+ // Helper to get env var value
49
+ function getEnvVar(name) {
50
+ return process.env[name] || null;
51
+ }
38
52
 
39
- // Check for existing persistent config
40
- const existingClaudeConfig = fs.existsSync(path.join(WORKSPACE, '.claude-persistent'));
41
- const existingCodexConfig = fs.existsSync(path.join(WORKSPACE, '.codex-persistent'));
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
+ }
76
+ }
42
77
 
43
- if (existingClaudeConfig) {
44
- console.log('✅ Found existing Claude config in workspace');
45
- }
46
- if (existingCodexConfig) {
47
- console.log('✅ Found existing Codex config in workspace');
48
- }
78
+ // Determine Claude persistent directory
79
+ let claudePersistentDir = path.join(WORKSPACE, '.claude-persistent');
49
80
 
50
- // Check for Replit secrets
51
- if (hasReplitSecret('ANTHROPIC_API_KEY')) {
52
- console.log('✅ Found ANTHROPIC_API_KEY in Replit secrets');
53
- }
54
- if (hasReplitSecret('OPENAI_API_KEY')) {
55
- console.log('✅ Found OPENAI_API_KEY in Replit secrets');
56
- }
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
+ }
57
89
 
58
- // Create directories (preserving existing data)
59
- const dirs = [
60
- '.claude-persistent',
61
- '.codex-persistent',
62
- '.claude-sessions',
63
- '.local/share/claude/versions',
64
- '.persistent-home',
65
- '.config',
66
- 'scripts',
67
- 'logs'
68
- ];
69
-
70
- console.log('');
71
- console.log('📁 Creating directories...');
72
- dirs.forEach(dir => {
73
- const fullPath = path.join(WORKSPACE, dir);
74
- if (!fs.existsSync(fullPath)) {
75
- fs.mkdirSync(fullPath, { recursive: true });
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
+ }
76
112
  }
77
- });
78
113
 
79
- // Check and install Claude Code if needed
80
- console.log('');
81
- const claudeInstalled = commandExists('claude') ||
82
- fs.existsSync(path.join(HOME, '.local/bin/claude')) ||
83
- fs.existsSync(path.join(WORKSPACE, '.local/share/claude/versions'));
114
+ // Determine Codex persistent directory
115
+ let codexPersistentDir = path.join(WORKSPACE, '.codex-persistent');
84
116
 
85
- let claudeVersions = [];
86
- try {
87
- claudeVersions = fs.readdirSync(path.join(WORKSPACE, '.local/share/claude/versions'));
88
- } catch {}
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
+ }
124
+ }
89
125
 
90
- if (!claudeInstalled || claudeVersions.length === 0) {
91
- console.log('📦 Installing Claude Code...');
92
- try {
93
- execSync('curl -fsSL https://claude.ai/install.sh | bash', {
94
- stdio: 'inherit',
95
- shell: '/bin/bash'
96
- });
97
- console.log('✅ Claude Code installed');
98
- } catch (err) {
99
- console.log('⚠️ Claude Code installation failed (you can install manually later)');
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');
138
+ }
139
+
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
+ }
147
+
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
+ }
100
167
  }
101
- } else {
102
- const version = claudeVersions.sort().pop() || 'unknown';
103
- console.log(`✅ Claude Code already installed (${version})`);
104
- }
105
168
 
106
- // Check and install Codex if needed
107
- const codexInstalled = commandExists('codex');
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
+ }
175
+ }
108
176
 
109
- if (!codexInstalled) {
110
- console.log('📦 Installing OpenAI Codex CLI...');
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
+ // ═══════════════════════════════════════════════════════════════════
193
+
194
+ console.log('');
195
+
196
+ let claudeVersions = [];
111
197
  try {
112
- execSync('npm i -g @openai/codex', {
113
- stdio: 'inherit',
114
- shell: '/bin/bash'
115
- });
116
- console.log('✅ Codex CLI installed');
117
- } catch (err) {
118
- 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})`);
119
227
  }
120
- } else {
121
- console.log('✅ Codex CLI already installed');
122
- }
123
228
 
124
- // Set up symlinks
125
- console.log('');
126
- 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
+ }
127
255
 
128
- // Claude symlink
129
- const claudeTarget = path.join(WORKSPACE, '.claude-persistent');
130
- const claudeLink = path.join(HOME, '.claude');
131
- try {
132
- const stat = fs.lstatSync(claudeLink);
133
- if (stat.isSymbolicLink()) {
134
- const current = fs.readlinkSync(claudeLink);
135
- if (current !== claudeTarget) {
136
- fs.unlinkSync(claudeLink);
137
- fs.symlinkSync(claudeTarget, claudeLink);
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');
265
+ try {
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;
280
+ }
281
+ } catch {
282
+ needsLink = true;
283
+ }
284
+
285
+ if (needsLink) {
286
+ fs.symlinkSync(claudePersistentDir, claudeLink);
138
287
  }
139
- } else if (stat.isDirectory()) {
140
- // Move existing data to persistent location
141
- console.log(' Moving existing ~/.claude data to persistent storage...');
142
- execSync(`cp -rn ${claudeLink}/* ${claudeTarget}/ 2>/dev/null || true`, { shell: '/bin/bash' });
143
- execSync(`rm -rf ${claudeLink}`, { shell: '/bin/bash' });
144
- fs.symlinkSync(claudeTarget, claudeLink);
288
+ console.log(` ~/.claude ${claudePersistentDir.replace(WORKSPACE + '/', '')}/`);
289
+ } catch (err) {
290
+ console.log(` ⚠️ Could not create Claude symlink: ${err.message}`);
145
291
  }
146
- } catch {
147
- // Doesn't exist, create it
148
- fs.symlinkSync(claudeTarget, claudeLink);
149
- }
150
- console.log(' ~/.claude → .claude-persistent/');
151
292
 
152
- // Codex symlink
153
- const codexTarget = path.join(WORKSPACE, '.codex-persistent');
154
- const codexLink = path.join(HOME, '.codex');
155
- try {
156
- const stat = fs.lstatSync(codexLink);
157
- if (stat.isSymbolicLink()) {
158
- const current = fs.readlinkSync(codexLink);
159
- if (current !== codexTarget) {
160
- fs.unlinkSync(codexLink);
161
- 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;
162
313
  }
163
- } else if (stat.isDirectory()) {
164
- console.log(' Moving existing ~/.codex data to persistent storage...');
165
- execSync(`cp -rn ${codexLink}/* ${codexTarget}/ 2>/dev/null || true`, { shell: '/bin/bash' });
166
- execSync(`rm -rf ${codexLink}`, { shell: '/bin/bash' });
167
- fs.symlinkSync(codexTarget, codexLink);
314
+
315
+ if (needsLink) {
316
+ fs.symlinkSync(codexPersistentDir, codexLink);
317
+ }
318
+ console.log(` ~/.codex → ${codexPersistentDir.replace(WORKSPACE + '/', '')}/`);
319
+ } catch (err) {
320
+ console.log(` ⚠️ Could not create Codex symlink: ${err.message}`);
168
321
  }
169
- } catch {
170
- fs.symlinkSync(codexTarget, codexLink);
171
- }
172
- console.log(' ~/.codex → .codex-persistent/');
173
322
 
174
- // Claude binary symlinks
175
- const localBin = path.join(HOME, '.local/bin');
176
- const localShare = path.join(HOME, '.local/share');
177
- 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');
178
327
 
179
- try { fs.mkdirSync(localBin, { recursive: true }); } catch {}
180
- try { fs.mkdirSync(localShare, { recursive: true }); } catch {}
328
+ try { fs.mkdirSync(localBin, { recursive: true }); } catch {}
329
+ try { fs.mkdirSync(localShare, { recursive: true }); } catch {}
181
330
 
182
- // Link .local/share/claude
183
- try {
184
- const stat = fs.lstatSync(path.join(localShare, 'claude'));
185
- if (stat.isSymbolicLink()) {
186
- const current = fs.readlinkSync(path.join(localShare, 'claude'));
187
- if (current !== claudeShareTarget) {
188
- 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) {
189
348
  fs.symlinkSync(claudeShareTarget, path.join(localShare, 'claude'));
190
349
  }
191
- }
192
- } catch {
350
+ console.log(' ~/.local/share/claude → .local/share/claude/');
351
+ } catch {}
352
+
353
+ // Link binary to latest version
193
354
  try {
194
- 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
+ }
195
366
  } catch {}
196
- }
197
- console.log(' ~/.local/share/claude → .local/share/claude/');
198
367
 
199
- // Link binary to latest version
200
- try {
201
- const versions = fs.readdirSync(path.join(WORKSPACE, '.local/share/claude/versions')).sort();
202
- if (versions.length > 0) {
203
- const latest = versions[versions.length - 1];
204
- const binaryPath = path.join(WORKSPACE, '.local/share/claude/versions', latest);
205
- const binLink = path.join(localBin, 'claude');
206
- try { fs.unlinkSync(binLink); } catch {}
207
- fs.symlinkSync(binaryPath, binLink);
208
- console.log(` ~/.local/bin/claude → versions/${latest}`);
209
- }
210
- } catch {}
368
+ // ═══════════════════════════════════════════════════════════════════
369
+ // COPY SCRIPTS
370
+ // ═══════════════════════════════════════════════════════════════════
371
+
372
+ const scriptsDir = path.join(__dirname, 'scripts');
373
+ const targetScriptsDir = path.join(WORKSPACE, 'scripts');
211
374
 
212
- // Copy scripts from the package
213
- const scriptsDir = path.join(__dirname, 'scripts');
214
- const targetScriptsDir = path.join(WORKSPACE, 'scripts');
375
+ console.log('');
376
+ console.log('📝 Installing scripts...');
377
+
378
+ const scripts = ['setup-claude-code.sh', 'claude-session-manager.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
+ });
215
393
 
216
- console.log('');
217
- console.log('📝 Installing scripts...');
394
+ // ═══════════════════════════════════════════════════════════════════
395
+ // CREATE BASHRC
396
+ // ═══════════════════════════════════════════════════════════════════
218
397
 
219
- const scripts = ['setup-claude-code.sh', 'claude-session-manager.sh'];
220
- scripts.forEach(script => {
221
- const srcPath = path.join(scriptsDir, script);
222
- const destPath = path.join(targetScriptsDir, script);
398
+ console.log('');
399
+ console.log('📝 Creating .config/bashrc...');
223
400
 
224
- if (fs.existsSync(srcPath)) {
225
- fs.copyFileSync(srcPath, destPath);
226
- fs.chmodSync(destPath, '755');
227
- console.log(` ${script}`);
228
- }
229
- });
401
+ const bashrcContent = `#!/bin/bash
402
+ # DATA Tools - Replit Claude & Codex Persistence
403
+ # Auto-generated bashrc
230
404
 
231
- // Create/update .config/bashrc
232
- console.log('');
233
- console.log('📝 Creating .config/bashrc...');
234
- const bashrcContent = `#!/bin/bash
235
- # Replit Claude Persistence - Auto-generated bashrc
405
+ # Claude Config Directory (tells Claude where to store data)
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}"
236
411
 
237
412
  # Claude Code Setup
238
413
  SETUP_SCRIPT="/home/runner/workspace/scripts/setup-claude-code.sh"
239
414
  [ -f "\${SETUP_SCRIPT}" ] && source "\${SETUP_SCRIPT}"
240
415
 
241
416
  # Codex Persistence
242
- CODEX_PERSISTENT="/home/runner/workspace/.codex-persistent"
243
- mkdir -p "\${CODEX_PERSISTENT}"
244
- [ ! -L "\${HOME}/.codex" ] && ln -sf "\${CODEX_PERSISTENT}" "\${HOME}/.codex"
417
+ mkdir -p "${codexPersistentDir}"
418
+ [ ! -L "\${HOME}/.codex" ] && ln -sf "${codexPersistentDir}" "\${HOME}/.codex"
245
419
 
246
420
  # Bash History Persistence
247
421
  PERSISTENT_HOME="/home/runner/workspace/.persistent-home"
@@ -262,72 +436,129 @@ alias claude-resume='claude -c --dangerously-skip-permissions'
262
436
  alias claude-pick='claude -r --dangerously-skip-permissions'
263
437
  `;
264
438
 
265
- 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
+ }
266
444
 
267
- // Update .replit
268
- console.log('📝 Updating .replit configuration...');
269
- const replitPath = path.join(WORKSPACE, '.replit');
270
- const onBootLine = 'onBoot = "source /home/runner/workspace/scripts/setup-claude-code.sh 2>/dev/null || true"';
445
+ // ═══════════════════════════════════════════════════════════════════
446
+ // UPDATE .replit
447
+ // ═══════════════════════════════════════════════════════════════════
271
448
 
272
- if (fs.existsSync(replitPath)) {
273
- let content = fs.readFileSync(replitPath, 'utf8');
274
- if (!content.includes('setup-claude-code.sh')) {
275
- content += '\n\n# Claude persistence (added by installer)\n' + onBootLine + '\n';
276
- fs.writeFileSync(replitPath, content);
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"';
452
+
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}`);
277
465
  }
278
- } else {
279
- fs.writeFileSync(replitPath, '# Claude persistence\n' + onBootLine + '\n');
280
- }
281
466
 
282
- // Update .gitignore (only essential sensitive dirs)
283
- console.log('📝 Updating .gitignore...');
284
- const gitignorePath = path.join(WORKSPACE, '.gitignore');
285
- const gitignoreEntries = '\n# Claude/Codex credentials (added by replit-tools)\n.claude-persistent/\n.codex-persistent/\n';
467
+ // ═══════════════════════════════════════════════════════════════════
468
+ // UPDATE .gitignore
469
+ // ═══════════════════════════════════════════════════════════════════
286
470
 
287
- if (fs.existsSync(gitignorePath)) {
288
- let content = fs.readFileSync(gitignorePath, 'utf8');
289
- if (!content.includes('.claude-persistent')) {
290
- 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}`);
291
486
  }
292
- } else {
293
- fs.writeFileSync(gitignorePath, gitignoreEntries.trim() + '\n');
294
- }
295
487
 
296
- // Add PATH to current process for session manager
297
- process.env.PATH = `${localBin}:${process.env.PATH}`;
488
+ // ═══════════════════════════════════════════════════════════════════
489
+ // SET UP ENVIRONMENT
490
+ // ═══════════════════════════════════════════════════════════════════
298
491
 
299
- console.log('');
300
- console.log('╭─────────────────────────────────────────────────────────╮');
301
- console.log('│ ✅ Installation complete! │');
302
- console.log('╰─────────────────────────────────────────────────────────╯');
303
- console.log('');
304
- console.log('Your conversations and credentials now persist across restarts.');
305
- console.log('');
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;
306
496
 
307
- // Check if Claude needs login
308
- let needsLogin = true;
309
- try {
310
- const authCheck = execSync('claude auth status 2>&1 || true', { encoding: 'utf8', shell: '/bin/bash' });
311
- if (authCheck.includes('Logged in') || authCheck.includes('valid')) {
312
- needsLogin = false;
313
- }
314
- } catch {}
497
+ // ═══════════════════════════════════════════════════════════════════
498
+ // SHOW COMPLETION MESSAGE
499
+ // ═══════════════════════════════════════════════════════════════════
315
500
 
316
- if (needsLogin && !hasReplitSecret('ANTHROPIC_API_KEY')) {
317
- console.log('⚠️ Claude needs authentication. Run: claude login');
318
501
  console.log('');
319
- }
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('');
320
522
 
321
- // Launch the session picker
322
- console.log('Launching session manager...');
323
- console.log('');
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('');
540
+ }
324
541
 
325
- // Use spawn to run bash interactively with our session manager
326
- const sessionManager = spawn('bash', ['--rcfile', path.join(WORKSPACE, '.config/bashrc'), '-i'], {
327
- stdio: 'inherit',
328
- cwd: WORKSPACE
329
- });
542
+ // ═══════════════════════════════════════════════════════════════════
543
+ // LAUNCH SESSION MANAGER
544
+ // ═══════════════════════════════════════════════════════════════════
330
545
 
331
- sessionManager.on('exit', (code) => {
332
- process.exit(code || 0);
333
- });
546
+ console.log('Launching session manager...');
547
+ console.log('');
548
+
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
+ });
555
+
556
+ sessionManager.on('error', (err) => {
557
+ console.error('Failed to launch session manager:', err.message);
558
+ process.exit(1);
559
+ });
560
+
561
+ sessionManager.on('exit', (code) => {
562
+ process.exit(code || 0);
563
+ });
564
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replit-tools",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "DATA Tools - One command to set up Claude Code and Codex CLI on Replit with full persistence",
5
5
  "main": "index.js",
6
6
  "bin": {