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.
Files changed (3) hide show
  1. package/README.md +14 -0
  2. package/index.js +100 -41
  3. 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('│ Replit Tools - Claude & Codex Persistence │');
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 check for Replit secret
35
- function hasReplitSecret(name) {
36
- return process.env[name] !== undefined;
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(path.join(WORKSPACE, '.claude-persistent'));
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('✅ Found existing Claude config in workspace');
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 Replit secrets
51
- if (hasReplitSecret('ANTHROPIC_API_KEY')) {
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 (hasReplitSecret('OPENAI_API_KEY')) {
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
- try {
132
- const stat = fs.lstatSync(claudeLink);
133
- if (stat.isSymbolicLink()) {
134
- const current = fs.readlinkSync(claudeLink);
135
- if (current !== claudeTarget) {
136
- fs.unlinkSync(claudeLink);
137
- fs.symlinkSync(claudeTarget, claudeLink);
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
- } else if (stat.isDirectory()) {
140
- // Move existing data to persistent location
141
- console.log(' Moving existing ~/.claude data to persistent storage...');
142
- execSync(`cp -rn ${claudeLink}/* ${claudeTarget}/ 2>/dev/null || true`, { shell: '/bin/bash' });
143
- execSync(`rm -rf ${claudeLink}`, { shell: '/bin/bash' });
144
- fs.symlinkSync(claudeTarget, claudeLink);
191
+ } catch {
192
+ // Doesn't exist, create it
193
+ fs.symlinkSync(claudePersistentDir, claudeLink);
145
194
  }
146
- } catch {
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')).sort();
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 Persistence - Auto-generated bashrc
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 installer)\n' + onBootLine + '\n';
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 replit-tools)\n.claude-persistent/\n.codex-persistent/\n';
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 for session manager
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', { encoding: 'utf8', shell: '/bin/bash' });
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 && !hasReplitSecret('ANTHROPIC_API_KEY')) {
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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replit-tools",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
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": {