replit-tools 1.0.3 → 1.0.4
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 +14 -0
- package/index.js +100 -41
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -116,11 +116,25 @@ 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
|
+
| Variable | Purpose |
|
|
128
|
+
|----------|---------|
|
|
129
|
+
| `CLAUDE_CONFIG_DIR` | Custom directory for Claude config/data (recommended) |
|
|
130
|
+
| `CLAUDE_WORKSPACE_DIR` | Alternative name for config directory |
|
|
131
|
+
| `CLAUDE_DATA_DIR` | Alternative name for config directory |
|
|
132
|
+
| `CLAUDE_HOME` | Alternative name for config directory |
|
|
133
|
+
| `ANTHROPIC_API_KEY` | Claude API authentication |
|
|
134
|
+
| `OPENAI_API_KEY` | Codex API authentication |
|
|
135
|
+
|
|
136
|
+
If you set `CLAUDE_CONFIG_DIR` in your Replit Secrets to a path inside `/home/runner/workspace/`, DATA Tools will use that directory for persistence instead of the default `.claude-persistent/`.
|
|
137
|
+
|
|
124
138
|
## Installation Options
|
|
125
139
|
|
|
126
140
|
### Option 1: npx (recommended)
|
package/index.js
CHANGED
|
@@ -17,7 +17,7 @@ if (!fs.existsSync(WORKSPACE)) {
|
|
|
17
17
|
|
|
18
18
|
console.log('');
|
|
19
19
|
console.log('╭─────────────────────────────────────────────────────────╮');
|
|
20
|
-
console.log('│
|
|
20
|
+
console.log('│ DATA Tools - Claude & Codex Persistence │');
|
|
21
21
|
console.log('╰─────────────────────────────────────────────────────────╯');
|
|
22
22
|
console.log('');
|
|
23
23
|
|
|
@@ -31,33 +31,66 @@ function commandExists(cmd) {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
// Helper to
|
|
35
|
-
function
|
|
36
|
-
return process.env[name]
|
|
34
|
+
// Helper to get Replit secret value
|
|
35
|
+
function getReplitSecret(name) {
|
|
36
|
+
return process.env[name] || null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Check for CLAUDE_CONFIG_DIR or similar env vars that specify custom Claude location
|
|
40
|
+
const claudeConfigEnvVars = [
|
|
41
|
+
'CLAUDE_CONFIG_DIR',
|
|
42
|
+
'CLAUDE_WORKSPACE_DIR',
|
|
43
|
+
'CLAUDE_DATA_DIR',
|
|
44
|
+
'CLAUDE_HOME'
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
let customClaudeDir = null;
|
|
48
|
+
for (const envVar of claudeConfigEnvVars) {
|
|
49
|
+
const value = getReplitSecret(envVar);
|
|
50
|
+
if (value) {
|
|
51
|
+
customClaudeDir = value;
|
|
52
|
+
console.log(`✅ Found ${envVar} = ${value}`);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Determine Claude persistent directory
|
|
58
|
+
// Priority: 1) CLAUDE_CONFIG_DIR from env, 2) existing .claude-persistent, 3) default
|
|
59
|
+
let claudePersistentDir = path.join(WORKSPACE, '.claude-persistent');
|
|
60
|
+
|
|
61
|
+
if (customClaudeDir) {
|
|
62
|
+
// User has specified a custom directory via env var
|
|
63
|
+
if (customClaudeDir.startsWith(WORKSPACE)) {
|
|
64
|
+
// It's already in workspace - use it directly
|
|
65
|
+
claudePersistentDir = customClaudeDir;
|
|
66
|
+
console.log(` Using custom Claude directory: ${claudePersistentDir}`);
|
|
67
|
+
} else {
|
|
68
|
+
// It's outside workspace - we'll symlink to it but also ensure persistence
|
|
69
|
+
console.log(` Custom dir outside workspace - will set up persistence`);
|
|
70
|
+
}
|
|
37
71
|
}
|
|
38
72
|
|
|
39
73
|
// Check for existing persistent config
|
|
40
|
-
const existingClaudeConfig = fs.existsSync(
|
|
74
|
+
const existingClaudeConfig = fs.existsSync(claudePersistentDir);
|
|
41
75
|
const existingCodexConfig = fs.existsSync(path.join(WORKSPACE, '.codex-persistent'));
|
|
42
76
|
|
|
43
77
|
if (existingClaudeConfig) {
|
|
44
|
-
console.log(
|
|
78
|
+
console.log(`✅ Found existing Claude config at ${claudePersistentDir}`);
|
|
45
79
|
}
|
|
46
80
|
if (existingCodexConfig) {
|
|
47
81
|
console.log('✅ Found existing Codex config in workspace');
|
|
48
82
|
}
|
|
49
83
|
|
|
50
|
-
// Check for
|
|
51
|
-
if (
|
|
84
|
+
// Check for API key secrets
|
|
85
|
+
if (getReplitSecret('ANTHROPIC_API_KEY')) {
|
|
52
86
|
console.log('✅ Found ANTHROPIC_API_KEY in Replit secrets');
|
|
53
87
|
}
|
|
54
|
-
if (
|
|
88
|
+
if (getReplitSecret('OPENAI_API_KEY')) {
|
|
55
89
|
console.log('✅ Found OPENAI_API_KEY in Replit secrets');
|
|
56
90
|
}
|
|
57
91
|
|
|
58
92
|
// Create directories (preserving existing data)
|
|
59
93
|
const dirs = [
|
|
60
|
-
'.claude-persistent',
|
|
61
94
|
'.codex-persistent',
|
|
62
95
|
'.claude-sessions',
|
|
63
96
|
'.local/share/claude/versions',
|
|
@@ -67,6 +100,14 @@ const dirs = [
|
|
|
67
100
|
'logs'
|
|
68
101
|
];
|
|
69
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);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
70
111
|
console.log('');
|
|
71
112
|
console.log('📁 Creating directories...');
|
|
72
113
|
dirs.forEach(dir => {
|
|
@@ -84,15 +125,19 @@ const claudeInstalled = commandExists('claude') ||
|
|
|
84
125
|
|
|
85
126
|
let claudeVersions = [];
|
|
86
127
|
try {
|
|
87
|
-
claudeVersions = fs.readdirSync(path.join(WORKSPACE, '.local/share/claude/versions'))
|
|
128
|
+
claudeVersions = fs.readdirSync(path.join(WORKSPACE, '.local/share/claude/versions'))
|
|
129
|
+
.filter(f => !f.startsWith('.'));
|
|
88
130
|
} catch {}
|
|
89
131
|
|
|
90
132
|
if (!claudeInstalled || claudeVersions.length === 0) {
|
|
91
133
|
console.log('📦 Installing Claude Code...');
|
|
92
134
|
try {
|
|
135
|
+
// Set CLAUDE_CONFIG_DIR before install so it installs to the right place
|
|
136
|
+
const installEnv = { ...process.env, CLAUDE_CONFIG_DIR: claudePersistentDir };
|
|
93
137
|
execSync('curl -fsSL https://claude.ai/install.sh | bash', {
|
|
94
138
|
stdio: 'inherit',
|
|
95
|
-
shell: '/bin/bash'
|
|
139
|
+
shell: '/bin/bash',
|
|
140
|
+
env: installEnv
|
|
96
141
|
});
|
|
97
142
|
console.log('✅ Claude Code installed');
|
|
98
143
|
} catch (err) {
|
|
@@ -125,29 +170,30 @@ if (!codexInstalled) {
|
|
|
125
170
|
console.log('');
|
|
126
171
|
console.log('🔗 Setting up symlinks...');
|
|
127
172
|
|
|
128
|
-
// Claude symlink
|
|
129
|
-
const claudeTarget = path.join(WORKSPACE, '.claude-persistent');
|
|
173
|
+
// Claude symlink - only if we're using default location
|
|
130
174
|
const claudeLink = path.join(HOME, '.claude');
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
175
|
+
if (!customClaudeDir || customClaudeDir.startsWith(WORKSPACE)) {
|
|
176
|
+
try {
|
|
177
|
+
const stat = fs.lstatSync(claudeLink);
|
|
178
|
+
if (stat.isSymbolicLink()) {
|
|
179
|
+
const current = fs.readlinkSync(claudeLink);
|
|
180
|
+
if (current !== claudePersistentDir) {
|
|
181
|
+
fs.unlinkSync(claudeLink);
|
|
182
|
+
fs.symlinkSync(claudePersistentDir, claudeLink);
|
|
183
|
+
}
|
|
184
|
+
} else if (stat.isDirectory()) {
|
|
185
|
+
// Move existing data to persistent location
|
|
186
|
+
console.log(' Moving existing ~/.claude data to persistent storage...');
|
|
187
|
+
execSync(`cp -rn ${claudeLink}/* ${claudePersistentDir}/ 2>/dev/null || true`, { shell: '/bin/bash' });
|
|
188
|
+
execSync(`rm -rf ${claudeLink}`, { shell: '/bin/bash' });
|
|
189
|
+
fs.symlinkSync(claudePersistentDir, claudeLink);
|
|
138
190
|
}
|
|
139
|
-
}
|
|
140
|
-
//
|
|
141
|
-
|
|
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);
|
|
191
|
+
} catch {
|
|
192
|
+
// Doesn't exist, create it
|
|
193
|
+
fs.symlinkSync(claudePersistentDir, claudeLink);
|
|
145
194
|
}
|
|
146
|
-
|
|
147
|
-
// Doesn't exist, create it
|
|
148
|
-
fs.symlinkSync(claudeTarget, claudeLink);
|
|
195
|
+
console.log(` ~/.claude → ${claudePersistentDir.replace(WORKSPACE + '/', '')}/`);
|
|
149
196
|
}
|
|
150
|
-
console.log(' ~/.claude → .claude-persistent/');
|
|
151
197
|
|
|
152
198
|
// Codex symlink
|
|
153
199
|
const codexTarget = path.join(WORKSPACE, '.codex-persistent');
|
|
@@ -198,7 +244,9 @@ console.log(' ~/.local/share/claude → .local/share/claude/');
|
|
|
198
244
|
|
|
199
245
|
// Link binary to latest version
|
|
200
246
|
try {
|
|
201
|
-
const versions = fs.readdirSync(path.join(WORKSPACE, '.local/share/claude/versions'))
|
|
247
|
+
const versions = fs.readdirSync(path.join(WORKSPACE, '.local/share/claude/versions'))
|
|
248
|
+
.filter(f => !f.startsWith('.'))
|
|
249
|
+
.sort();
|
|
202
250
|
if (versions.length > 0) {
|
|
203
251
|
const latest = versions[versions.length - 1];
|
|
204
252
|
const binaryPath = path.join(WORKSPACE, '.local/share/claude/versions', latest);
|
|
@@ -228,11 +276,15 @@ scripts.forEach(script => {
|
|
|
228
276
|
}
|
|
229
277
|
});
|
|
230
278
|
|
|
231
|
-
// Create/update .config/bashrc
|
|
279
|
+
// Create/update .config/bashrc with CLAUDE_CONFIG_DIR export
|
|
232
280
|
console.log('');
|
|
233
281
|
console.log('📝 Creating .config/bashrc...');
|
|
234
282
|
const bashrcContent = `#!/bin/bash
|
|
235
|
-
# Replit Claude
|
|
283
|
+
# DATA Tools - Replit Claude & Codex Persistence
|
|
284
|
+
# Auto-generated bashrc
|
|
285
|
+
|
|
286
|
+
# Claude Config Directory (tells Claude where to store data)
|
|
287
|
+
export CLAUDE_CONFIG_DIR="${claudePersistentDir}"
|
|
236
288
|
|
|
237
289
|
# Claude Code Setup
|
|
238
290
|
SETUP_SCRIPT="/home/runner/workspace/scripts/setup-claude-code.sh"
|
|
@@ -272,17 +324,17 @@ const onBootLine = 'onBoot = "source /home/runner/workspace/scripts/setup-claude
|
|
|
272
324
|
if (fs.existsSync(replitPath)) {
|
|
273
325
|
let content = fs.readFileSync(replitPath, 'utf8');
|
|
274
326
|
if (!content.includes('setup-claude-code.sh')) {
|
|
275
|
-
content += '\n\n# Claude persistence (added by
|
|
327
|
+
content += '\n\n# Claude persistence (added by DATA Tools)\n' + onBootLine + '\n';
|
|
276
328
|
fs.writeFileSync(replitPath, content);
|
|
277
329
|
}
|
|
278
330
|
} else {
|
|
279
|
-
fs.writeFileSync(replitPath, '# Claude persistence\n' + onBootLine + '\n');
|
|
331
|
+
fs.writeFileSync(replitPath, '# Claude persistence (DATA Tools)\n' + onBootLine + '\n');
|
|
280
332
|
}
|
|
281
333
|
|
|
282
334
|
// Update .gitignore (only essential sensitive dirs)
|
|
283
335
|
console.log('📝 Updating .gitignore...');
|
|
284
336
|
const gitignorePath = path.join(WORKSPACE, '.gitignore');
|
|
285
|
-
const gitignoreEntries = '\n# Claude/Codex credentials (added by
|
|
337
|
+
const gitignoreEntries = '\n# Claude/Codex credentials (added by DATA Tools)\n.claude-persistent/\n.codex-persistent/\n';
|
|
286
338
|
|
|
287
339
|
if (fs.existsSync(gitignorePath)) {
|
|
288
340
|
let content = fs.readFileSync(gitignorePath, 'utf8');
|
|
@@ -293,8 +345,9 @@ if (fs.existsSync(gitignorePath)) {
|
|
|
293
345
|
fs.writeFileSync(gitignorePath, gitignoreEntries.trim() + '\n');
|
|
294
346
|
}
|
|
295
347
|
|
|
296
|
-
// Add PATH to current process
|
|
348
|
+
// Add PATH and CLAUDE_CONFIG_DIR to current process
|
|
297
349
|
process.env.PATH = `${localBin}:${process.env.PATH}`;
|
|
350
|
+
process.env.CLAUDE_CONFIG_DIR = claudePersistentDir;
|
|
298
351
|
|
|
299
352
|
console.log('');
|
|
300
353
|
console.log('╭─────────────────────────────────────────────────────────╮');
|
|
@@ -302,18 +355,23 @@ console.log('│ ✅ Installation complete! │');
|
|
|
302
355
|
console.log('╰─────────────────────────────────────────────────────────╯');
|
|
303
356
|
console.log('');
|
|
304
357
|
console.log('Your conversations and credentials now persist across restarts.');
|
|
358
|
+
console.log(`Claude config directory: ${claudePersistentDir}`);
|
|
305
359
|
console.log('');
|
|
306
360
|
|
|
307
361
|
// Check if Claude needs login
|
|
308
362
|
let needsLogin = true;
|
|
309
363
|
try {
|
|
310
|
-
const authCheck = execSync('claude auth status 2>&1 || true', {
|
|
364
|
+
const authCheck = execSync('claude auth status 2>&1 || true', {
|
|
365
|
+
encoding: 'utf8',
|
|
366
|
+
shell: '/bin/bash',
|
|
367
|
+
env: { ...process.env, CLAUDE_CONFIG_DIR: claudePersistentDir }
|
|
368
|
+
});
|
|
311
369
|
if (authCheck.includes('Logged in') || authCheck.includes('valid')) {
|
|
312
370
|
needsLogin = false;
|
|
313
371
|
}
|
|
314
372
|
} catch {}
|
|
315
373
|
|
|
316
|
-
if (needsLogin && !
|
|
374
|
+
if (needsLogin && !getReplitSecret('ANTHROPIC_API_KEY')) {
|
|
317
375
|
console.log('⚠️ Claude needs authentication. Run: claude login');
|
|
318
376
|
console.log('');
|
|
319
377
|
}
|
|
@@ -325,7 +383,8 @@ console.log('');
|
|
|
325
383
|
// Use spawn to run bash interactively with our session manager
|
|
326
384
|
const sessionManager = spawn('bash', ['--rcfile', path.join(WORKSPACE, '.config/bashrc'), '-i'], {
|
|
327
385
|
stdio: 'inherit',
|
|
328
|
-
cwd: WORKSPACE
|
|
386
|
+
cwd: WORKSPACE,
|
|
387
|
+
env: { ...process.env, CLAUDE_CONFIG_DIR: claudePersistentDir }
|
|
329
388
|
});
|
|
330
389
|
|
|
331
390
|
sessionManager.on('exit', (code) => {
|