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.
- package/index.js +210 -20
- 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
|
|
20
|
+
console.log('│ Replit Tools - Claude & Codex Persistence │');
|
|
19
21
|
console.log('╰─────────────────────────────────────────────────────────╯');
|
|
20
22
|
console.log('');
|
|
21
23
|
|
|
22
|
-
//
|
|
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('
|
|
126
|
-
console.log('');
|
|
127
|
-
console.log('
|
|
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('
|
|
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
|
-
|
|
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
|
+
});
|