replit-tools 1.0.1 → 1.0.2

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 (2) hide show
  1. package/index.js +210 -20
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { execSync, spawn } = require('child_process');
3
+ const { execSync, spawn, spawnSync } = require('child_process');
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
+ const os = require('os');
6
7
 
7
8
  const WORKSPACE = '/home/runner/workspace';
9
+ const HOME = os.homedir();
8
10
 
9
11
  // Check if we're on Replit
10
12
  if (!fs.existsSync(WORKSPACE)) {
@@ -15,11 +17,45 @@ if (!fs.existsSync(WORKSPACE)) {
15
17
 
16
18
  console.log('');
17
19
  console.log('╭─────────────────────────────────────────────────────────╮');
18
- console.log('│ Replit Claude Persistence Installer │');
20
+ console.log('│ Replit Tools - Claude & Codex Persistence │');
19
21
  console.log('╰─────────────────────────────────────────────────────────╯');
20
22
  console.log('');
21
23
 
22
- // Create directories
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;
31
+ }
32
+ }
33
+
34
+ // Helper to check for Replit secret
35
+ function hasReplitSecret(name) {
36
+ return process.env[name] !== undefined;
37
+ }
38
+
39
+ // Check for existing persistent config
40
+ const existingClaudeConfig = fs.existsSync(path.join(WORKSPACE, '.claude-persistent'));
41
+ const existingCodexConfig = fs.existsSync(path.join(WORKSPACE, '.codex-persistent'));
42
+
43
+ if (existingClaudeConfig) {
44
+ console.log('✅ Found existing Claude config in workspace');
45
+ }
46
+ if (existingCodexConfig) {
47
+ console.log('✅ Found existing Codex config in workspace');
48
+ }
49
+
50
+ // Check for Replit secrets
51
+ if (hasReplitSecret('ANTHROPIC_API_KEY')) {
52
+ console.log('✅ Found ANTHROPIC_API_KEY in Replit secrets');
53
+ }
54
+ if (hasReplitSecret('OPENAI_API_KEY')) {
55
+ console.log('✅ Found OPENAI_API_KEY in Replit secrets');
56
+ }
57
+
58
+ // Create directories (preserving existing data)
23
59
  const dirs = [
24
60
  '.claude-persistent',
25
61
  '.codex-persistent',
@@ -31,6 +67,7 @@ const dirs = [
31
67
  'logs'
32
68
  ];
33
69
 
70
+ console.log('');
34
71
  console.log('📁 Creating directories...');
35
72
  dirs.forEach(dir => {
36
73
  const fullPath = path.join(WORKSPACE, dir);
@@ -39,13 +76,146 @@ dirs.forEach(dir => {
39
76
  }
40
77
  });
41
78
 
79
+ // Check and install Claude Code if needed
80
+ console.log('');
81
+ const claudeInstalled = commandExists('claude') ||
82
+ fs.existsSync(path.join(HOME, '.local/bin/claude')) ||
83
+ fs.existsSync(path.join(WORKSPACE, '.local/share/claude/versions'));
84
+
85
+ let claudeVersions = [];
86
+ try {
87
+ claudeVersions = fs.readdirSync(path.join(WORKSPACE, '.local/share/claude/versions'));
88
+ } catch {}
89
+
90
+ if (!claudeInstalled || claudeVersions.length === 0) {
91
+ console.log('📦 Installing Claude Code...');
92
+ try {
93
+ execSync('curl -fsSL https://claude.ai/install.sh | bash', {
94
+ stdio: 'inherit',
95
+ shell: '/bin/bash'
96
+ });
97
+ console.log('✅ Claude Code installed');
98
+ } catch (err) {
99
+ console.log('⚠️ Claude Code installation failed (you can install manually later)');
100
+ }
101
+ } else {
102
+ const version = claudeVersions.sort().pop() || 'unknown';
103
+ console.log(`✅ Claude Code already installed (${version})`);
104
+ }
105
+
106
+ // Check and install Codex if needed
107
+ const codexInstalled = commandExists('codex');
108
+
109
+ if (!codexInstalled) {
110
+ console.log('📦 Installing OpenAI Codex CLI...');
111
+ try {
112
+ execSync('npm i -g @openai/codex', {
113
+ stdio: 'inherit',
114
+ shell: '/bin/bash'
115
+ });
116
+ console.log('✅ Codex CLI installed');
117
+ } catch (err) {
118
+ console.log('⚠️ Codex installation failed (you can install manually later)');
119
+ }
120
+ } else {
121
+ console.log('✅ Codex CLI already installed');
122
+ }
123
+
124
+ // Set up symlinks
125
+ console.log('');
126
+ console.log('🔗 Setting up symlinks...');
127
+
128
+ // Claude symlink
129
+ const claudeTarget = path.join(WORKSPACE, '.claude-persistent');
130
+ 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);
138
+ }
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);
145
+ }
146
+ } catch {
147
+ // Doesn't exist, create it
148
+ fs.symlinkSync(claudeTarget, claudeLink);
149
+ }
150
+ console.log(' ~/.claude → .claude-persistent/');
151
+
152
+ // Codex symlink
153
+ const codexTarget = path.join(WORKSPACE, '.codex-persistent');
154
+ const codexLink = path.join(HOME, '.codex');
155
+ try {
156
+ const stat = fs.lstatSync(codexLink);
157
+ if (stat.isSymbolicLink()) {
158
+ const current = fs.readlinkSync(codexLink);
159
+ if (current !== codexTarget) {
160
+ fs.unlinkSync(codexLink);
161
+ fs.symlinkSync(codexTarget, codexLink);
162
+ }
163
+ } else if (stat.isDirectory()) {
164
+ console.log(' Moving existing ~/.codex data to persistent storage...');
165
+ execSync(`cp -rn ${codexLink}/* ${codexTarget}/ 2>/dev/null || true`, { shell: '/bin/bash' });
166
+ execSync(`rm -rf ${codexLink}`, { shell: '/bin/bash' });
167
+ fs.symlinkSync(codexTarget, codexLink);
168
+ }
169
+ } catch {
170
+ fs.symlinkSync(codexTarget, codexLink);
171
+ }
172
+ console.log(' ~/.codex → .codex-persistent/');
173
+
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');
178
+
179
+ try { fs.mkdirSync(localBin, { recursive: true }); } catch {}
180
+ try { fs.mkdirSync(localShare, { recursive: true }); } catch {}
181
+
182
+ // Link .local/share/claude
183
+ try {
184
+ const stat = fs.lstatSync(path.join(localShare, 'claude'));
185
+ if (stat.isSymbolicLink()) {
186
+ const current = fs.readlinkSync(path.join(localShare, 'claude'));
187
+ if (current !== claudeShareTarget) {
188
+ fs.unlinkSync(path.join(localShare, 'claude'));
189
+ fs.symlinkSync(claudeShareTarget, path.join(localShare, 'claude'));
190
+ }
191
+ }
192
+ } catch {
193
+ try {
194
+ fs.symlinkSync(claudeShareTarget, path.join(localShare, 'claude'));
195
+ } catch {}
196
+ }
197
+ console.log(' ~/.local/share/claude → .local/share/claude/');
198
+
199
+ // Link binary to latest version
200
+ try {
201
+ const versions = fs.readdirSync(path.join(WORKSPACE, '.local/share/claude/versions')).sort();
202
+ if (versions.length > 0) {
203
+ const latest = versions[versions.length - 1];
204
+ const binaryPath = path.join(WORKSPACE, '.local/share/claude/versions', latest);
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 {}
211
+
42
212
  // Copy scripts from the package
43
213
  const scriptsDir = path.join(__dirname, 'scripts');
44
214
  const targetScriptsDir = path.join(WORKSPACE, 'scripts');
45
215
 
216
+ console.log('');
46
217
  console.log('📝 Installing scripts...');
47
218
 
48
- // Read and write each script
49
219
  const scripts = ['setup-claude-code.sh', 'claude-session-manager.sh'];
50
220
  scripts.forEach(script => {
51
221
  const srcPath = path.join(scriptsDir, script);
@@ -54,10 +224,12 @@ scripts.forEach(script => {
54
224
  if (fs.existsSync(srcPath)) {
55
225
  fs.copyFileSync(srcPath, destPath);
56
226
  fs.chmodSync(destPath, '755');
227
+ console.log(` ${script}`);
57
228
  }
58
229
  });
59
230
 
60
231
  // Create/update .config/bashrc
232
+ console.log('');
61
233
  console.log('📝 Creating .config/bashrc...');
62
234
  const bashrcContent = `#!/bin/bash
63
235
  # Replit Claude Persistence - Auto-generated bashrc
@@ -121,23 +293,41 @@ if (fs.existsSync(gitignorePath)) {
121
293
  fs.writeFileSync(gitignorePath, gitignoreEntries.trim() + '\n');
122
294
  }
123
295
 
296
+ // Add PATH to current process for session manager
297
+ process.env.PATH = `${localBin}:${process.env.PATH}`;
298
+
124
299
  console.log('');
125
- console.log('✅ Installation complete!');
126
- console.log('');
127
- console.log('What happens now:');
128
- console.log(' • New shells will show the Claude session picker');
129
- console.log(' • Your conversations persist across container restarts');
130
- console.log(' • Claude binary is cached (faster startup)');
131
- console.log(' • Bash history is preserved');
132
- console.log('');
133
- console.log('To test, open a new shell or run:');
134
- console.log(' source ~/.config/bashrc');
300
+ console.log('╭─────────────────────────────────────────────────────────╮');
301
+ console.log('│ ✅ Installation complete! │');
302
+ console.log('╰─────────────────────────────────────────────────────────╯');
135
303
  console.log('');
136
- console.log('Options:');
137
- console.log(" Press 'c' - Continue last session");
138
- console.log(" Press 'r' - Pick from session list");
139
- console.log(" Press 'n' - New session");
140
- console.log(" Press 's' - Skip (just a shell)");
304
+ console.log('Your conversations and credentials now persist across restarts.');
141
305
  console.log('');
142
- console.log('To disable the menu: export CLAUDE_NO_PROMPT=true');
306
+
307
+ // Check if Claude needs login
308
+ let needsLogin = true;
309
+ try {
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 {}
315
+
316
+ if (needsLogin && !hasReplitSecret('ANTHROPIC_API_KEY')) {
317
+ console.log('⚠️ Claude needs authentication. Run: claude login');
318
+ console.log('');
319
+ }
320
+
321
+ // Launch the session picker
322
+ console.log('Launching session manager...');
143
323
  console.log('');
324
+
325
+ // Use spawn to run bash interactively with our session manager
326
+ const sessionManager = spawn('bash', ['--rcfile', path.join(WORKSPACE, '.config/bashrc'), '-i'], {
327
+ stdio: 'inherit',
328
+ cwd: WORKSPACE
329
+ });
330
+
331
+ sessionManager.on('exit', (code) => {
332
+ process.exit(code || 0);
333
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replit-tools",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Persist Claude Code sessions, auth, and history across Replit container restarts",
5
5
  "main": "index.js",
6
6
  "bin": {