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