replit-tools 1.0.10 → 1.1.1

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/README.md CHANGED
@@ -32,14 +32,18 @@ Both are installed only if not already present. Existing installations are prese
32
32
 
33
33
  ## What Gets Persisted
34
34
 
35
+ Everything is stored in a single `.replit-tools/` directory:
36
+
35
37
  | Data | Location | Survives Restart? |
36
38
  |------|----------|-------------------|
37
- | Claude conversations | `.claude-persistent/` | Yes |
38
- | Claude credentials | `.claude-persistent/` | Yes |
39
- | Claude binary | `.local/share/claude/versions/` | Yes |
40
- | Codex data | `.codex-persistent/` | Yes |
41
- | Bash history | `.persistent-home/` | Yes |
42
- | Per-terminal sessions | `.claude-sessions/` | Yes |
39
+ | Claude conversations | `.replit-tools/.claude-persistent/` | Yes |
40
+ | Claude credentials | `.replit-tools/.claude-persistent/` | Yes |
41
+ | Claude binary | `.replit-tools/.claude-versions/` | Yes |
42
+ | Codex data | `.replit-tools/.codex-persistent/` | Yes |
43
+ | Bash history | `.replit-tools/.persistent-home/` | Yes |
44
+ | Per-terminal sessions | `.replit-tools/.claude-sessions/` | Yes |
45
+ | Auth logs | `.replit-tools/.logs/` | Yes |
46
+ | Scripts | `.replit-tools/scripts/` | Yes |
43
47
 
44
48
  ## Automatic Token Refresh
45
49
 
@@ -55,10 +59,10 @@ This means you can leave overnight and come back to a working session - no more
55
59
 
56
60
  ```bash
57
61
  # Check token status
58
- /home/runner/workspace/scripts/claude-auth-refresh.sh --status
62
+ /home/runner/workspace/.replit-tools/scripts/claude-auth-refresh.sh --status
59
63
 
60
64
  # Force refresh now
61
- /home/runner/workspace/scripts/claude-auth-refresh.sh --force
65
+ /home/runner/workspace/.replit-tools/scripts/claude-auth-refresh.sh --force
62
66
 
63
67
  # Or use a permanent API token (never expires)
64
68
  claude setup-token
@@ -122,19 +126,19 @@ Press `c` to continue YOUR terminal's last session. Other terminals are unaffect
122
126
 
123
127
  ## How It Works
124
128
 
125
- The installer creates symlinks from ephemeral locations to persistent workspace storage:
129
+ The installer creates symlinks from ephemeral locations to persistent `.replit-tools/` storage:
126
130
 
127
131
  ```
128
- ~/.claude /workspace/.claude-persistent/
129
- ~/.codex /workspace/.codex-persistent/
130
- ~/.local/share/claude /workspace/.local/share/claude/
131
- ~/.local/bin/claude /workspace/.local/share/claude/versions/X.X.X
132
+ ~/.claude .replit-tools/.claude-persistent/
133
+ ~/.codex .replit-tools/.codex-persistent/
134
+ ~/.local/share/claude/versions/ .replit-tools/.claude-versions/
135
+ ~/.local/bin/claude .replit-tools/.claude-versions/X.X.X
132
136
  ```
133
137
 
134
138
  Three layers ensure setup runs on every restart:
135
139
  1. `.replit` onBoot hook (runs at container boot)
136
140
  2. `.config/bashrc` (runs on every shell start)
137
- 3. Scripts in `workspace/scripts/` (called by above)
141
+ 3. Scripts in `.replit-tools/scripts/` (called by above)
138
142
 
139
143
  ## Smart Detection
140
144
 
@@ -142,7 +146,7 @@ The installer checks for:
142
146
 
143
147
  - **`CLAUDE_CONFIG_DIR`** - Respects custom Claude config directory if set in Replit Secrets
144
148
  - **`CODEX_HOME`** - Respects custom Codex config directory
145
- - **Existing persistent config** - Uses your existing config if present
149
+ - **Existing persistent config** - Uses your existing config if present (won't migrate if custom dir set)
146
150
  - **Replit Secrets** - Detects `ANTHROPIC_API_KEY` and `OPENAI_API_KEY`
147
151
  - **Existing installations** - Won't reinstall Claude or Codex if already present
148
152
  - **Existing data in ~/.claude** - Moves it to persistent storage instead of overwriting
@@ -168,7 +172,7 @@ The installer checks for:
168
172
  | `CODEX_DATA_DIR` | Alternative name |
169
173
  | `OPENAI_API_KEY` | Codex API authentication |
170
174
 
171
- 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.
175
+ 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. **Your custom directories will NOT be migrated** - we respect your configuration.
172
176
 
173
177
  ## Installation Options
174
178
 
@@ -224,26 +228,46 @@ Creates a long-lived API token that never expires (recommended for unattended us
224
228
 
225
229
  ```
226
230
  workspace/
227
- ├── .claude-persistent/ # Claude conversations & credentials
228
- ├── .codex-persistent/ # Codex CLI data
229
- ├── .claude-sessions/ # Per-terminal session tracking
230
- ├── .local/share/claude/ # Claude binary versions
231
- ├── .persistent-home/ # Bash history
232
- ├── .config/bashrc # Shell startup config
233
- ├── logs/ # Auth refresh logs
234
- ├── scripts/
235
- ├── setup-claude-code.sh # Main setup script
236
- ├── claude-session-manager.sh # Interactive session picker
237
- └── claude-auth-refresh.sh # OAuth token auto-refresh
238
- └── .gitignore # Updated to ignore credential dirs
231
+ ├── .replit-tools/ # All DATA Tools data (gitignored)
232
+ ├── .claude-persistent/ # Claude conversations & credentials
233
+ ├── .codex-persistent/ # Codex CLI data
234
+ ├── .claude-sessions/ # Per-terminal session tracking
235
+ ├── .claude-versions/ # Claude binary versions
236
+ ├── .persistent-home/ # Bash history
237
+ ├── .logs/ # Auth refresh logs
238
+ │ └── scripts/ # Setup & management scripts
239
+ ├── setup-claude-code.sh
240
+ ├── claude-session-manager.sh
241
+ └── claude-auth-refresh.sh
242
+ ├── .config/bashrc # Shell startup config (sources scripts)
243
+ └── .gitignore # Updated to ignore .replit-tools/
244
+ ```
245
+
246
+ ## Upgrading from v1.x
247
+
248
+ If you used DATA Tools v1.x (before the `.replit-tools/` consolidation), your data will be automatically migrated:
249
+
250
+ ```
251
+ Old Location → New Location
252
+ .claude-persistent/ → .replit-tools/.claude-persistent/
253
+ .codex-persistent/ → .replit-tools/.codex-persistent/
254
+ .claude-sessions/ → .replit-tools/.claude-sessions/
255
+ .persistent-home/ → .replit-tools/.persistent-home/
256
+ .local/share/claude/versions/ → .replit-tools/.claude-versions/
239
257
  ```
240
258
 
259
+ Migration only happens if:
260
+ 1. Old location exists AND new location doesn't
261
+ 2. You don't have custom `CLAUDE_CONFIG_DIR` or `CODEX_HOME` set
262
+
263
+ Your original data is copied (not moved), so nothing is lost.
264
+
241
265
  ## Troubleshooting
242
266
 
243
267
  ### Claude or Codex not found after restart
244
268
 
245
269
  ```bash
246
- source /home/runner/workspace/scripts/setup-claude-code.sh
270
+ source /home/runner/workspace/.replit-tools/scripts/setup-claude-code.sh
247
271
  ```
248
272
 
249
273
  ### Session picker not appearing
@@ -258,10 +282,10 @@ The auto-refresh should handle this, but if it fails:
258
282
 
259
283
  ```bash
260
284
  # Check why refresh failed
261
- cat /home/runner/workspace/logs/auth-refresh.log
285
+ cat /home/runner/workspace/.replit-tools/.logs/auth-refresh.log
262
286
 
263
287
  # Manual refresh
264
- /home/runner/workspace/scripts/claude-auth-refresh.sh --force
288
+ /home/runner/workspace/.replit-tools/scripts/claude-auth-refresh.sh --force
265
289
 
266
290
  # Or use permanent token (recommended)
267
291
  claude setup-token
@@ -277,15 +301,15 @@ Running the installer again is safe - it preserves existing data.
277
301
 
278
302
  ## Security
279
303
 
280
- The installer adds these to `.gitignore`:
304
+ The installer adds `.replit-tools/` to `.gitignore`, which protects:
281
305
 
282
306
  | Path | Contains | Why Protected |
283
307
  |------|----------|---------------|
284
- | `.claude-persistent/` | OAuth tokens, refresh tokens, conversations | **Critical** - full account access |
285
- | `.codex-persistent/` | API keys in `auth.json`, conversations | **Critical** - full account access |
286
- | `.claude-sessions/` | Session UUIDs, terminal mappings | Session metadata |
287
- | `.persistent-home/` | Bash history | May contain typed secrets |
288
- | `logs/auth-refresh.log` | Token refresh timestamps | Auth timing info |
308
+ | `.replit-tools/.claude-persistent/` | OAuth tokens, refresh tokens, conversations | **Critical** - full account access |
309
+ | `.replit-tools/.codex-persistent/` | API keys in `auth.json`, conversations | **Critical** - full account access |
310
+ | `.replit-tools/.claude-sessions/` | Session UUIDs, terminal mappings | Session metadata |
311
+ | `.replit-tools/.persistent-home/` | Bash history | May contain typed secrets |
312
+ | `.replit-tools/.logs/` | Token refresh timestamps | Auth timing info |
289
313
 
290
314
  Your API keys, OAuth tokens, and conversation history won't be committed to git.
291
315
 
package/index.js CHANGED
@@ -7,6 +7,7 @@ const os = require('os');
7
7
 
8
8
  const WORKSPACE = '/home/runner/workspace';
9
9
  const HOME = os.homedir();
10
+ const REPLIT_TOOLS = path.join(WORKSPACE, '.replit-tools');
10
11
 
11
12
  // Helper to run commands safely without crashing the installer
12
13
  function safeExec(cmd, options = {}) {
@@ -20,7 +21,6 @@ function safeExec(cmd, options = {}) {
20
21
  });
21
22
 
22
23
  if (options.showOutput && result.stdout) {
23
- // Show condensed output
24
24
  const lines = result.stdout.trim().split('\n');
25
25
  if (lines.length <= 5) {
26
26
  lines.forEach(l => console.log(` ${l}`));
@@ -47,6 +47,21 @@ function safeExec(cmd, options = {}) {
47
47
  }
48
48
  }
49
49
 
50
+ // Helper to migrate data from old location to new
51
+ function migrateDirectory(oldPath, newPath, description) {
52
+ if (fs.existsSync(oldPath) && !fs.existsSync(newPath)) {
53
+ try {
54
+ console.log(` Migrating ${description}...`);
55
+ execSync(`cp -rp "${oldPath}" "${newPath}"`, { shell: '/bin/bash' });
56
+ return true;
57
+ } catch (err) {
58
+ console.log(` ⚠️ Could not migrate ${description}: ${err.message}`);
59
+ return false;
60
+ }
61
+ }
62
+ return false;
63
+ }
64
+
50
65
  // Wrap everything in try-catch to prevent crashes
51
66
  try {
52
67
  main();
@@ -89,11 +104,34 @@ function main() {
89
104
  return process.env[name] || null;
90
105
  }
91
106
 
107
+ // ═══════════════════════════════════════════════════════════════════
108
+ // DEFINE DIRECTORY STRUCTURE
109
+ // ═══════════════════════════════════════════════════════════════════
110
+
111
+ // Default locations inside .replit-tools (all hidden with dots)
112
+ const defaultClaudeDir = path.join(REPLIT_TOOLS, '.claude-persistent');
113
+ const defaultCodexDir = path.join(REPLIT_TOOLS, '.codex-persistent');
114
+ const sessionsDir = path.join(REPLIT_TOOLS, '.claude-sessions');
115
+ const persistentHomeDir = path.join(REPLIT_TOOLS, '.persistent-home');
116
+ const claudeVersionsDir = path.join(REPLIT_TOOLS, '.claude-versions');
117
+ const logsDir = path.join(REPLIT_TOOLS, '.logs');
118
+ const scriptsDir = path.join(REPLIT_TOOLS, 'scripts');
119
+
120
+ // Old locations (for migration)
121
+ const oldLocations = {
122
+ claude: path.join(WORKSPACE, '.claude-persistent'),
123
+ codex: path.join(WORKSPACE, '.codex-persistent'),
124
+ sessions: path.join(WORKSPACE, '.claude-sessions'),
125
+ home: path.join(WORKSPACE, '.persistent-home'),
126
+ versions: path.join(WORKSPACE, '.local/share/claude/versions'),
127
+ logs: path.join(WORKSPACE, 'logs'),
128
+ scripts: path.join(WORKSPACE, 'scripts')
129
+ };
130
+
92
131
  // ═══════════════════════════════════════════════════════════════════
93
132
  // CLAUDE CONFIG DIRECTORY DETECTION
94
133
  // ═══════════════════════════════════════════════════════════════════
95
134
 
96
- // Check for all possible Claude config env vars
97
135
  const claudeConfigEnvVars = [
98
136
  'CLAUDE_CONFIG_DIR',
99
137
  'CLAUDE_WORKSPACE_DIR',
@@ -115,14 +153,16 @@ function main() {
115
153
  }
116
154
 
117
155
  // Determine Claude persistent directory
118
- let claudePersistentDir = path.join(WORKSPACE, '.claude-persistent');
156
+ let claudePersistentDir = defaultClaudeDir;
157
+ let usingCustomClaudeDir = false;
119
158
 
120
159
  if (customClaudeDir) {
121
160
  if (customClaudeDir.startsWith(WORKSPACE)) {
122
161
  claudePersistentDir = customClaudeDir;
123
- console.log(` Using custom Claude directory`);
162
+ usingCustomClaudeDir = true;
163
+ console.log(` Using custom Claude directory (not migrating)`);
124
164
  } else {
125
- console.log(` ⚠️ Custom dir outside workspace - will redirect to workspace for persistence`);
165
+ console.log(` ⚠️ Custom dir outside workspace - using .replit-tools for persistence`);
126
166
  }
127
167
  }
128
168
 
@@ -130,7 +170,6 @@ function main() {
130
170
  // CODEX CONFIG DIRECTORY DETECTION
131
171
  // ═══════════════════════════════════════════════════════════════════
132
172
 
133
- // Check for Codex config env vars
134
173
  const codexConfigEnvVars = [
135
174
  'CODEX_HOME',
136
175
  'CODEX_CONFIG_DIR',
@@ -151,32 +190,23 @@ function main() {
151
190
  }
152
191
 
153
192
  // Determine Codex persistent directory
154
- let codexPersistentDir = path.join(WORKSPACE, '.codex-persistent');
193
+ let codexPersistentDir = defaultCodexDir;
194
+ let usingCustomCodexDir = false;
155
195
 
156
196
  if (customCodexDir) {
157
197
  if (customCodexDir.startsWith(WORKSPACE)) {
158
198
  codexPersistentDir = customCodexDir;
159
- console.log(` Using custom Codex directory`);
199
+ usingCustomCodexDir = true;
200
+ console.log(` Using custom Codex directory (not migrating)`);
160
201
  } else {
161
- console.log(` ⚠️ Custom dir outside workspace - will redirect to workspace for persistence`);
202
+ console.log(` ⚠️ Custom dir outside workspace - using .replit-tools for persistence`);
162
203
  }
163
204
  }
164
205
 
165
206
  // ═══════════════════════════════════════════════════════════════════
166
- // CHECK FOR EXISTING CONFIG
207
+ // CHECK FOR API KEYS
167
208
  // ═══════════════════════════════════════════════════════════════════
168
209
 
169
- const existingClaudeConfig = fs.existsSync(claudePersistentDir);
170
- const existingCodexConfig = fs.existsSync(codexPersistentDir);
171
-
172
- if (existingClaudeConfig) {
173
- console.log(`✅ Found existing Claude config`);
174
- }
175
- if (existingCodexConfig) {
176
- console.log('✅ Found existing Codex config');
177
- }
178
-
179
- // Check for API key secrets
180
210
  if (getEnvVar('ANTHROPIC_API_KEY')) {
181
211
  console.log('✅ Found ANTHROPIC_API_KEY');
182
212
  }
@@ -188,44 +218,77 @@ function main() {
188
218
  // CREATE DIRECTORIES
189
219
  // ═══════════════════════════════════════════════════════════════════
190
220
 
191
- const dirs = [
192
- '.claude-sessions',
193
- '.local/share/claude/versions',
194
- '.persistent-home',
195
- '.config',
196
- 'scripts',
197
- 'logs'
198
- ];
221
+ console.log('');
222
+ console.log('📁 Creating directories...');
199
223
 
200
- // Add Claude persistent dir
201
- if (claudePersistentDir.startsWith(WORKSPACE)) {
202
- const relativePath = claudePersistentDir.replace(WORKSPACE + '/', '');
203
- if (!dirs.includes(relativePath)) {
204
- dirs.unshift(relativePath);
205
- }
224
+ // Create base .replit-tools directory
225
+ if (!fs.existsSync(REPLIT_TOOLS)) {
226
+ fs.mkdirSync(REPLIT_TOOLS, { recursive: true });
206
227
  }
207
228
 
208
- // Add Codex persistent dir
209
- if (codexPersistentDir.startsWith(WORKSPACE)) {
210
- const relativePath = codexPersistentDir.replace(WORKSPACE + '/', '');
211
- if (!dirs.includes(relativePath)) {
212
- dirs.unshift(relativePath);
213
- }
214
- }
229
+ // Create all subdirectories
230
+ const dirsToCreate = [
231
+ claudePersistentDir,
232
+ codexPersistentDir,
233
+ sessionsDir,
234
+ persistentHomeDir,
235
+ claudeVersionsDir,
236
+ logsDir,
237
+ scriptsDir,
238
+ path.join(WORKSPACE, '.config') // Keep .config in workspace for Replit auto-sourcing
239
+ ];
215
240
 
216
- console.log('');
217
- console.log('📁 Creating directories...');
218
- dirs.forEach(dir => {
219
- const fullPath = path.join(WORKSPACE, dir);
220
- if (!fs.existsSync(fullPath)) {
241
+ dirsToCreate.forEach(dir => {
242
+ if (!fs.existsSync(dir)) {
221
243
  try {
222
- fs.mkdirSync(fullPath, { recursive: true });
244
+ fs.mkdirSync(dir, { recursive: true });
223
245
  } catch (err) {
224
246
  console.log(` ⚠️ Could not create ${dir}: ${err.message}`);
225
247
  }
226
248
  }
227
249
  });
228
250
 
251
+ // ═══════════════════════════════════════════════════════════════════
252
+ // MIGRATE FROM OLD LOCATIONS
253
+ // ═══════════════════════════════════════════════════════════════════
254
+
255
+ console.log('');
256
+ console.log('🔄 Checking for data migration...');
257
+
258
+ let migrated = false;
259
+
260
+ // Migrate Claude data (only if not using custom dir)
261
+ if (!usingCustomClaudeDir) {
262
+ if (migrateDirectory(oldLocations.claude, claudePersistentDir, 'Claude config')) {
263
+ migrated = true;
264
+ }
265
+ }
266
+
267
+ // Migrate Codex data (only if not using custom dir)
268
+ if (!usingCustomCodexDir) {
269
+ if (migrateDirectory(oldLocations.codex, codexPersistentDir, 'Codex config')) {
270
+ migrated = true;
271
+ }
272
+ }
273
+
274
+ // Always migrate these (no custom dir options)
275
+ if (migrateDirectory(oldLocations.sessions, sessionsDir, 'session data')) migrated = true;
276
+ if (migrateDirectory(oldLocations.home, persistentHomeDir, 'bash history')) migrated = true;
277
+ if (migrateDirectory(oldLocations.versions, claudeVersionsDir, 'Claude versions')) migrated = true;
278
+
279
+ // Migrate logs (just auth-refresh.log)
280
+ if (fs.existsSync(path.join(oldLocations.logs, 'auth-refresh.log')) && !fs.existsSync(path.join(logsDir, 'auth-refresh.log'))) {
281
+ try {
282
+ fs.copyFileSync(path.join(oldLocations.logs, 'auth-refresh.log'), path.join(logsDir, 'auth-refresh.log'));
283
+ migrated = true;
284
+ console.log(' Migrating auth logs...');
285
+ } catch {}
286
+ }
287
+
288
+ if (!migrated) {
289
+ console.log(' No migration needed');
290
+ }
291
+
229
292
  // ═══════════════════════════════════════════════════════════════════
230
293
  // INSTALL CLAUDE CODE
231
294
  // ═══════════════════════════════════════════════════════════════════
@@ -234,10 +297,16 @@ function main() {
234
297
 
235
298
  let claudeVersions = [];
236
299
  try {
237
- claudeVersions = fs.readdirSync(path.join(WORKSPACE, '.local/share/claude/versions'))
238
- .filter(f => !f.startsWith('.'));
300
+ claudeVersions = fs.readdirSync(claudeVersionsDir).filter(f => !f.startsWith('.'));
239
301
  } catch {}
240
302
 
303
+ // Also check old location
304
+ if (claudeVersions.length === 0) {
305
+ try {
306
+ claudeVersions = fs.readdirSync(oldLocations.versions).filter(f => !f.startsWith('.'));
307
+ } catch {}
308
+ }
309
+
241
310
  const claudeInstalled = commandExists('claude') ||
242
311
  fs.existsSync(path.join(HOME, '.local/bin/claude')) ||
243
312
  claudeVersions.length > 0;
@@ -251,12 +320,27 @@ function main() {
251
320
  };
252
321
  const result = safeExec('curl -fsSL https://claude.ai/install.sh | bash', {
253
322
  env: installEnv,
254
- timeout: 180000, // 3 minute timeout
323
+ timeout: 180000,
255
324
  showOutput: true
256
325
  });
257
326
 
258
327
  if (result.success) {
259
328
  console.log('✅ Claude Code installed');
329
+ // Move installed version to our directory
330
+ try {
331
+ const defaultVersionsDir = path.join(HOME, '.local/share/claude/versions');
332
+ if (fs.existsSync(defaultVersionsDir)) {
333
+ const versions = fs.readdirSync(defaultVersionsDir).filter(f => !f.startsWith('.'));
334
+ versions.forEach(v => {
335
+ const src = path.join(defaultVersionsDir, v);
336
+ const dest = path.join(claudeVersionsDir, v);
337
+ if (!fs.existsSync(dest)) {
338
+ fs.copyFileSync(src, dest);
339
+ fs.chmodSync(dest, '755');
340
+ }
341
+ });
342
+ }
343
+ } catch {}
260
344
  } else {
261
345
  console.log('⚠️ Claude Code installation had issues (may still work)');
262
346
  if (result.stderr && result.stderr.length < 200) {
@@ -282,7 +366,7 @@ function main() {
282
366
  };
283
367
  const result = safeExec('npm i -g @openai/codex', {
284
368
  env: installEnv,
285
- timeout: 180000, // 3 minute timeout
369
+ timeout: 180000,
286
370
  showOutput: true
287
371
  });
288
372
 
@@ -305,7 +389,7 @@ function main() {
305
389
  console.log('');
306
390
  console.log('🔗 Setting up symlinks...');
307
391
 
308
- // Claude symlink
392
+ // Claude config symlink (~/.claude -> our persistent dir)
309
393
  const claudeLink = path.join(HOME, '.claude');
310
394
  try {
311
395
  let needsLink = false;
@@ -330,12 +414,13 @@ function main() {
330
414
  if (needsLink) {
331
415
  fs.symlinkSync(claudePersistentDir, claudeLink);
332
416
  }
333
- console.log(` ~/.claude ${claudePersistentDir.replace(WORKSPACE + '/', '')}/`);
417
+ const displayPath = claudePersistentDir.replace(WORKSPACE + '/', '');
418
+ console.log(` ~/.claude → ${displayPath}/`);
334
419
  } catch (err) {
335
420
  console.log(` ⚠️ Could not create Claude symlink: ${err.message}`);
336
421
  }
337
422
 
338
- // Codex symlink
423
+ // Codex config symlink (~/.codex -> our persistent dir)
339
424
  const codexLink = path.join(HOME, '.codex');
340
425
  try {
341
426
  let needsLink = false;
@@ -360,7 +445,8 @@ function main() {
360
445
  if (needsLink) {
361
446
  fs.symlinkSync(codexPersistentDir, codexLink);
362
447
  }
363
- console.log(` ~/.codex ${codexPersistentDir.replace(WORKSPACE + '/', '')}/`);
448
+ const displayPath = codexPersistentDir.replace(WORKSPACE + '/', '');
449
+ console.log(` ~/.codex → ${displayPath}/`);
364
450
  } catch (err) {
365
451
  console.log(` ⚠️ Could not create Codex symlink: ${err.message}`);
366
452
  }
@@ -368,45 +454,51 @@ function main() {
368
454
  // Claude binary symlinks
369
455
  const localBin = path.join(HOME, '.local/bin');
370
456
  const localShare = path.join(HOME, '.local/share');
371
- const claudeShareTarget = path.join(WORKSPACE, '.local/share/claude');
372
457
 
373
458
  try { fs.mkdirSync(localBin, { recursive: true }); } catch {}
374
459
  try { fs.mkdirSync(localShare, { recursive: true }); } catch {}
375
460
 
376
- // Link .local/share/claude
461
+ // Link .local/share/claude to our versions directory's parent
462
+ const claudeShareTarget = path.join(REPLIT_TOOLS, '.claude-versions');
377
463
  try {
378
- let needsLink = false;
464
+ // Create a wrapper directory structure for compatibility
465
+ const shareClaudeDir = path.join(localShare, 'claude');
466
+ const shareVersionsDir = path.join(shareClaudeDir, 'versions');
467
+
468
+ if (!fs.existsSync(shareClaudeDir)) {
469
+ fs.mkdirSync(shareClaudeDir, { recursive: true });
470
+ }
471
+
472
+ // Symlink versions dir
379
473
  try {
380
- const stat = fs.lstatSync(path.join(localShare, 'claude'));
474
+ const stat = fs.lstatSync(shareVersionsDir);
381
475
  if (stat.isSymbolicLink()) {
382
- const current = fs.readlinkSync(path.join(localShare, 'claude'));
383
- if (current !== claudeShareTarget) {
384
- fs.unlinkSync(path.join(localShare, 'claude'));
385
- needsLink = true;
476
+ const current = fs.readlinkSync(shareVersionsDir);
477
+ if (current !== claudeVersionsDir) {
478
+ fs.unlinkSync(shareVersionsDir);
479
+ fs.symlinkSync(claudeVersionsDir, shareVersionsDir);
386
480
  }
387
481
  }
388
482
  } catch {
389
- needsLink = true;
483
+ fs.symlinkSync(claudeVersionsDir, shareVersionsDir);
390
484
  }
391
-
392
- if (needsLink) {
393
- fs.symlinkSync(claudeShareTarget, path.join(localShare, 'claude'));
394
- }
395
- console.log(' ~/.local/share/claude → .local/share/claude/');
485
+ console.log(` ~/.local/share/claude/versions → .replit-tools/.claude-versions/`);
396
486
  } catch {}
397
487
 
398
488
  // Link binary to latest version
399
489
  try {
400
- const versions = fs.readdirSync(path.join(WORKSPACE, '.local/share/claude/versions'))
401
- .filter(f => !f.startsWith('.'))
402
- .sort();
490
+ let versions = [];
491
+ try {
492
+ versions = fs.readdirSync(claudeVersionsDir).filter(f => !f.startsWith('.')).sort();
493
+ } catch {}
494
+
403
495
  if (versions.length > 0) {
404
496
  const latest = versions[versions.length - 1];
405
- const binaryPath = path.join(WORKSPACE, '.local/share/claude/versions', latest);
497
+ const binaryPath = path.join(claudeVersionsDir, latest);
406
498
  const binLink = path.join(localBin, 'claude');
407
499
  try { fs.unlinkSync(binLink); } catch {}
408
500
  fs.symlinkSync(binaryPath, binLink);
409
- console.log(` ~/.local/bin/claude → versions/${latest}`);
501
+ console.log(` ~/.local/bin/claude → .replit-tools/.claude-versions/${latest}`);
410
502
  }
411
503
  } catch {}
412
504
 
@@ -414,16 +506,15 @@ function main() {
414
506
  // COPY SCRIPTS
415
507
  // ═══════════════════════════════════════════════════════════════════
416
508
 
417
- const scriptsDir = path.join(__dirname, 'scripts');
418
- const targetScriptsDir = path.join(WORKSPACE, 'scripts');
509
+ const packageScriptsDir = path.join(__dirname, 'scripts');
419
510
 
420
511
  console.log('');
421
512
  console.log('📝 Installing scripts...');
422
513
 
423
- const scripts = ['setup-claude-code.sh', 'claude-session-manager.sh', 'claude-auth-refresh.sh'];
424
- scripts.forEach(script => {
425
- const srcPath = path.join(scriptsDir, script);
426
- const destPath = path.join(targetScriptsDir, script);
514
+ const scriptFiles = ['setup-claude-code.sh', 'claude-session-manager.sh', 'claude-auth-refresh.sh'];
515
+ scriptFiles.forEach(script => {
516
+ const srcPath = path.join(packageScriptsDir, script);
517
+ const destPath = path.join(scriptsDir, script);
427
518
 
428
519
  if (fs.existsSync(srcPath)) {
429
520
  try {
@@ -445,7 +536,10 @@ function main() {
445
536
 
446
537
  const bashrcContent = `#!/bin/bash
447
538
  # DATA Tools - Replit Claude & Codex Persistence
448
- # Auto-generated bashrc
539
+ # Auto-generated bashrc - v2.0 (.replit-tools structure)
540
+
541
+ # Base directory for all DATA Tools data
542
+ export REPLIT_TOOLS_DIR="${REPLIT_TOOLS}"
449
543
 
450
544
  # Claude Config Directory (tells Claude where to store data)
451
545
  export CLAUDE_CONFIG_DIR="${claudePersistentDir}"
@@ -455,7 +549,7 @@ export CLAUDE_WORKSPACE_DIR="${claudePersistentDir}"
455
549
  export CODEX_HOME="${codexPersistentDir}"
456
550
 
457
551
  # Claude Code Setup
458
- SETUP_SCRIPT="/home/runner/workspace/scripts/setup-claude-code.sh"
552
+ SETUP_SCRIPT="${scriptsDir}/setup-claude-code.sh"
459
553
  [ -f "\${SETUP_SCRIPT}" ] && source "\${SETUP_SCRIPT}"
460
554
 
461
555
  # Codex Persistence
@@ -463,7 +557,7 @@ mkdir -p "${codexPersistentDir}"
463
557
  [ ! -L "\${HOME}/.codex" ] && ln -sf "${codexPersistentDir}" "\${HOME}/.codex"
464
558
 
465
559
  # Bash History Persistence
466
- PERSISTENT_HOME="/home/runner/workspace/.persistent-home"
560
+ PERSISTENT_HOME="${persistentHomeDir}"
467
561
  mkdir -p "\${PERSISTENT_HOME}"
468
562
  export HISTFILE="\${PERSISTENT_HOME}/.bash_history"
469
563
  export HISTSIZE=10000
@@ -472,7 +566,7 @@ export HISTCONTROL=ignoredups
472
566
  [ -f "\${HISTFILE}" ] && history -r "\${HISTFILE}"
473
567
 
474
568
  # Session Manager (interactive menu)
475
- SESSION_MANAGER="/home/runner/workspace/scripts/claude-session-manager.sh"
569
+ SESSION_MANAGER="${scriptsDir}/claude-session-manager.sh"
476
570
  [ -f "\${SESSION_MANAGER}" ] && source "\${SESSION_MANAGER}"
477
571
 
478
572
  # Aliases
@@ -493,15 +587,17 @@ alias claude-pick='claude -r --dangerously-skip-permissions'
493
587
 
494
588
  console.log('📝 Updating .replit configuration...');
495
589
  const replitPath = path.join(WORKSPACE, '.replit');
496
- const onBootLine = 'onBoot = "source /home/runner/workspace/scripts/setup-claude-code.sh 2>/dev/null || true"';
590
+ const onBootLine = `onBoot = "source ${scriptsDir}/setup-claude-code.sh 2>/dev/null || true"`;
497
591
 
498
592
  try {
499
593
  if (fs.existsSync(replitPath)) {
500
594
  let content = fs.readFileSync(replitPath, 'utf8');
595
+ // Remove old onBoot line if present
596
+ content = content.replace(/onBoot\s*=\s*"[^"]*setup-claude-code\.sh[^"]*"\n?/g, '');
501
597
  if (!content.includes('setup-claude-code.sh')) {
502
598
  content += '\n\n# Claude persistence (added by DATA Tools)\n' + onBootLine + '\n';
503
- fs.writeFileSync(replitPath, content);
504
599
  }
600
+ fs.writeFileSync(replitPath, content);
505
601
  } else {
506
602
  fs.writeFileSync(replitPath, '# Claude persistence (DATA Tools)\n' + onBootLine + '\n');
507
603
  }
@@ -516,18 +612,14 @@ alias claude-pick='claude -r --dangerously-skip-permissions'
516
612
  console.log('📝 Updating .gitignore...');
517
613
  const gitignorePath = path.join(WORKSPACE, '.gitignore');
518
614
  const gitignoreEntries = `
519
- # Claude/Codex data (added by DATA Tools)
520
- .claude-persistent/
521
- .codex-persistent/
522
- .claude-sessions/
523
- .persistent-home/
524
- logs/auth-refresh.log
615
+ # DATA Tools - All sensitive data in one place (added by DATA Tools)
616
+ .replit-tools/
525
617
  `;
526
618
 
527
619
  try {
528
620
  if (fs.existsSync(gitignorePath)) {
529
621
  let content = fs.readFileSync(gitignorePath, 'utf8');
530
- if (!content.includes('.claude-persistent')) {
622
+ if (!content.includes('.replit-tools/')) {
531
623
  fs.writeFileSync(gitignorePath, content + gitignoreEntries);
532
624
  }
533
625
  } else {
@@ -545,6 +637,7 @@ logs/auth-refresh.log
545
637
  process.env.CLAUDE_CONFIG_DIR = claudePersistentDir;
546
638
  process.env.CLAUDE_WORKSPACE_DIR = claudePersistentDir;
547
639
  process.env.CODEX_HOME = codexPersistentDir;
640
+ process.env.REPLIT_TOOLS_DIR = REPLIT_TOOLS;
548
641
 
549
642
  // ═══════════════════════════════════════════════════════════════════
550
643
  // SHOW COMPLETION MESSAGE
@@ -566,8 +659,12 @@ logs/auth-refresh.log
566
659
  console.log('║ ║');
567
660
  console.log('╠═════════════════════════════════════════════════════════════╣');
568
661
  console.log('║ ║');
569
- console.log(`║ Claude config: ${claudePersistentDir.replace(WORKSPACE + '/', '').padEnd(38)} ║`);
570
- console.log(`║ Codex config: ${codexPersistentDir.replace(WORKSPACE + '/', '').padEnd(38)} ║`);
662
+ console.log('║ All data stored in: .replit-tools/');
663
+ console.log('║ ║');
664
+ const claudeDisplay = claudePersistentDir.replace(WORKSPACE + '/', '').padEnd(40);
665
+ const codexDisplay = codexPersistentDir.replace(WORKSPACE + '/', '').padEnd(40);
666
+ console.log(`║ Claude: ${claudeDisplay} ║`);
667
+ console.log(`║ Codex: ${codexDisplay} ║`);
571
668
  console.log('║ ║');
572
669
  console.log('╚═════════════════════════════════════════════════════════════╝');
573
670
  console.log('');
@@ -598,7 +695,6 @@ logs/auth-refresh.log
598
695
  console.log('Launching session manager...');
599
696
  console.log('');
600
697
 
601
- // Use spawn to run bash interactively with our session manager
602
698
  const sessionManager = spawn('bash', ['--rcfile', path.join(WORKSPACE, '.config/bashrc'), '-i'], {
603
699
  stdio: 'inherit',
604
700
  cwd: WORKSPACE,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replit-tools",
3
- "version": "1.0.10",
3
+ "version": "1.1.1",
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": {
@@ -3,11 +3,19 @@
3
3
  # Automatically refreshes Claude Code OAuth tokens before expiration
4
4
  # Part of DATA Tools - https://github.com/stevemoraco/DATAtools
5
5
 
6
- CREDENTIALS_FILE="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.credentials.json"
6
+ # Use .replit-tools structure
7
+ WORKSPACE="/home/runner/workspace"
8
+ REPLIT_TOOLS="${WORKSPACE}/.replit-tools"
9
+ CREDENTIALS_FILE="${CLAUDE_CONFIG_DIR:-${REPLIT_TOOLS}/.claude-persistent}/.credentials.json"
10
+ LOG_FILE="${REPLIT_TOOLS}/.logs/auth-refresh.log"
11
+
12
+ # OAuth configuration
7
13
  OAUTH_ENDPOINT="https://console.anthropic.com/v1/oauth/token"
8
14
  CLIENT_ID="9d1c250a-e61b-44d9-88ed-5944d1962f5e"
9
15
  REFRESH_THRESHOLD_HOURS=2 # Refresh when less than 2 hours remaining
10
- LOG_FILE="/home/runner/workspace/logs/auth-refresh.log"
16
+
17
+ # Ensure log directory exists
18
+ mkdir -p "${REPLIT_TOOLS}/.logs" 2>/dev/null
11
19
 
12
20
  # Logging function
13
21
  log() {
@@ -7,7 +7,8 @@
7
7
  # =============================================================================
8
8
 
9
9
  WORKSPACE="/home/runner/workspace"
10
- SESSIONS_DIR="${WORKSPACE}/.claude-sessions"
10
+ REPLIT_TOOLS="${WORKSPACE}/.replit-tools"
11
+ SESSIONS_DIR="${REPLIT_TOOLS}/.claude-sessions"
11
12
  LOCK_DIR="/tmp/.claude-locks"
12
13
 
13
14
  mkdir -p "${SESSIONS_DIR}" "${LOCK_DIR}" 2>/dev/null
@@ -15,12 +15,16 @@
15
15
 
16
16
  set -e
17
17
 
18
- # Configuration
18
+ # Configuration - use .replit-tools structure
19
19
  WORKSPACE="/home/runner/workspace"
20
- CLAUDE_PERSISTENT="${CLAUDE_CONFIG_DIR:-${WORKSPACE}/.claude-persistent}"
21
- CLAUDE_LOCAL_SHARE="${WORKSPACE}/.local/share/claude"
22
- CLAUDE_VERSIONS="${CLAUDE_LOCAL_SHARE}/versions"
23
- AUTH_REFRESH_SCRIPT="${WORKSPACE}/scripts/claude-auth-refresh.sh"
20
+ REPLIT_TOOLS="${WORKSPACE}/.replit-tools"
21
+
22
+ # Allow env vars to override (for custom config locations)
23
+ CLAUDE_PERSISTENT="${CLAUDE_CONFIG_DIR:-${REPLIT_TOOLS}/.claude-persistent}"
24
+ CLAUDE_VERSIONS="${REPLIT_TOOLS}/.claude-versions"
25
+ LOGS_DIR="${REPLIT_TOOLS}/.logs"
26
+ SCRIPTS_DIR="${REPLIT_TOOLS}/scripts"
27
+ AUTH_REFRESH_SCRIPT="${SCRIPTS_DIR}/claude-auth-refresh.sh"
24
28
 
25
29
  # Target locations (ephemeral, need symlinks)
26
30
  CLAUDE_SYMLINK="${HOME}/.claude"
@@ -41,7 +45,7 @@ mkdir -p "${CLAUDE_PERSISTENT}"
41
45
  mkdir -p "${CLAUDE_VERSIONS}"
42
46
  mkdir -p "${LOCAL_BIN}"
43
47
  mkdir -p "${HOME}/.local/share"
44
- mkdir -p "${WORKSPACE}/logs"
48
+ mkdir -p "${LOGS_DIR}"
45
49
 
46
50
  # =============================================================================
47
51
  # Step 2: Create ~/.claude symlink for conversation history & credentials
@@ -55,10 +59,13 @@ fi
55
59
  # =============================================================================
56
60
  # Step 3: Create ~/.local/share/claude symlink for installed versions
57
61
  # =============================================================================
58
- if [ ! -L "${LOCAL_SHARE_CLAUDE}" ] || [ "$(readlink -f "${LOCAL_SHARE_CLAUDE}")" != "${CLAUDE_LOCAL_SHARE}" ]; then
59
- rm -rf "${LOCAL_SHARE_CLAUDE}" 2>/dev/null || true
60
- ln -sf "${CLAUDE_LOCAL_SHARE}" "${LOCAL_SHARE_CLAUDE}"
61
- log " Claude versions symlink: ~/.local/share/claude -> ${CLAUDE_LOCAL_SHARE}"
62
+ SHARE_VERSIONS="${LOCAL_SHARE_CLAUDE}/versions"
63
+ mkdir -p "${LOCAL_SHARE_CLAUDE}"
64
+
65
+ if [ ! -L "${SHARE_VERSIONS}" ] || [ "$(readlink -f "${SHARE_VERSIONS}")" != "${CLAUDE_VERSIONS}" ]; then
66
+ rm -rf "${SHARE_VERSIONS}" 2>/dev/null || true
67
+ ln -sf "${CLAUDE_VERSIONS}" "${SHARE_VERSIONS}"
68
+ log "✅ Claude versions symlink: ~/.local/share/claude/versions -> ${CLAUDE_VERSIONS}"
62
69
  fi
63
70
 
64
71
  # =============================================================================
@@ -84,10 +91,13 @@ else
84
91
 
85
92
  # Install Claude Code using the official installer
86
93
  if curl -fsSL https://claude.ai/install.sh | bash 2>/dev/null; then
87
- # After install, find the new version
88
- if [ -d "${CLAUDE_VERSIONS}" ]; then
89
- LATEST_VERSION=$(ls -1 "${CLAUDE_VERSIONS}" 2>/dev/null | grep -v '^\.' | sort -V | tail -n1)
94
+ # After install, copy to our versions directory
95
+ DEFAULT_VERSIONS="${HOME}/.local/share/claude/versions"
96
+ if [ -d "${DEFAULT_VERSIONS}" ]; then
97
+ LATEST_VERSION=$(ls -1 "${DEFAULT_VERSIONS}" 2>/dev/null | grep -v '^\.' | sort -V | tail -n1)
90
98
  if [ -n "${LATEST_VERSION}" ]; then
99
+ cp -p "${DEFAULT_VERSIONS}/${LATEST_VERSION}" "${CLAUDE_VERSIONS}/${LATEST_VERSION}" 2>/dev/null || true
100
+ chmod 755 "${CLAUDE_VERSIONS}/${LATEST_VERSION}" 2>/dev/null || true
91
101
  ln -sf "${CLAUDE_VERSIONS}/${LATEST_VERSION}" "${LOCAL_BIN}/claude"
92
102
  log "✅ Claude Code ${LATEST_VERSION} installed"
93
103
  fi