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.
- package/README.md +24 -0
- package/index.js +476 -245
- 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
|
|
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
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
console.
|
|
22
|
-
console.
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
}
|
|
48
|
+
// Helper to get env var value
|
|
49
|
+
function getEnvVar(name) {
|
|
50
|
+
return process.env[name] || null;
|
|
51
|
+
}
|
|
38
52
|
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
//
|
|
80
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
//
|
|
107
|
-
|
|
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
|
-
|
|
110
|
-
console.log('
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
console.log(
|
|
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
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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.
|
|
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
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
217
|
-
|
|
394
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
395
|
+
// CREATE BASHRC
|
|
396
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
218
397
|
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
#
|
|
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
|
-
|
|
243
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
//
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
//
|
|
297
|
-
|
|
488
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
489
|
+
// SET UP ENVIRONMENT
|
|
490
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
298
491
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
//
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
//
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
//
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
cwd: WORKSPACE
|
|
329
|
-
});
|
|
542
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
543
|
+
// LAUNCH SESSION MANAGER
|
|
544
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
330
545
|
|
|
331
|
-
|
|
332
|
-
|
|
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
|
+
}
|