replit-tools 1.0.10 → 1.1.1
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 +61 -37
- package/index.js +193 -97
- package/package.json +1 -1
- package/scripts/claude-auth-refresh.sh +10 -2
- package/scripts/claude-session-manager.sh +2 -1
- package/scripts/setup-claude-code.sh +23 -13
package/README.md
CHANGED
|
@@ -32,14 +32,18 @@ Both are installed only if not already present. Existing installations are prese
|
|
|
32
32
|
|
|
33
33
|
## What Gets Persisted
|
|
34
34
|
|
|
35
|
+
Everything is stored in a single `.replit-tools/` directory:
|
|
36
|
+
|
|
35
37
|
| Data | Location | Survives Restart? |
|
|
36
38
|
|------|----------|-------------------|
|
|
37
|
-
| Claude conversations | `.claude-persistent/` | Yes |
|
|
38
|
-
| Claude credentials | `.claude-persistent/` | Yes |
|
|
39
|
-
| Claude binary | `.
|
|
40
|
-
| Codex data | `.codex-persistent/` | Yes |
|
|
41
|
-
| Bash history | `.persistent-home/` | Yes |
|
|
42
|
-
| Per-terminal sessions | `.claude-sessions/` | Yes |
|
|
39
|
+
| Claude conversations | `.replit-tools/.claude-persistent/` | Yes |
|
|
40
|
+
| Claude credentials | `.replit-tools/.claude-persistent/` | Yes |
|
|
41
|
+
| Claude binary | `.replit-tools/.claude-versions/` | Yes |
|
|
42
|
+
| Codex data | `.replit-tools/.codex-persistent/` | Yes |
|
|
43
|
+
| Bash history | `.replit-tools/.persistent-home/` | Yes |
|
|
44
|
+
| Per-terminal sessions | `.replit-tools/.claude-sessions/` | Yes |
|
|
45
|
+
| Auth logs | `.replit-tools/.logs/` | Yes |
|
|
46
|
+
| Scripts | `.replit-tools/scripts/` | Yes |
|
|
43
47
|
|
|
44
48
|
## Automatic Token Refresh
|
|
45
49
|
|
|
@@ -55,10 +59,10 @@ This means you can leave overnight and come back to a working session - no more
|
|
|
55
59
|
|
|
56
60
|
```bash
|
|
57
61
|
# Check token status
|
|
58
|
-
/home/runner/workspace/scripts/claude-auth-refresh.sh --status
|
|
62
|
+
/home/runner/workspace/.replit-tools/scripts/claude-auth-refresh.sh --status
|
|
59
63
|
|
|
60
64
|
# Force refresh now
|
|
61
|
-
/home/runner/workspace/scripts/claude-auth-refresh.sh --force
|
|
65
|
+
/home/runner/workspace/.replit-tools/scripts/claude-auth-refresh.sh --force
|
|
62
66
|
|
|
63
67
|
# Or use a permanent API token (never expires)
|
|
64
68
|
claude setup-token
|
|
@@ -122,19 +126,19 @@ Press `c` to continue YOUR terminal's last session. Other terminals are unaffect
|
|
|
122
126
|
|
|
123
127
|
## How It Works
|
|
124
128
|
|
|
125
|
-
The installer creates symlinks from ephemeral locations to persistent
|
|
129
|
+
The installer creates symlinks from ephemeral locations to persistent `.replit-tools/` storage:
|
|
126
130
|
|
|
127
131
|
```
|
|
128
|
-
~/.claude
|
|
129
|
-
~/.codex
|
|
130
|
-
~/.local/share/claude
|
|
131
|
-
~/.local/bin/claude
|
|
132
|
+
~/.claude → .replit-tools/.claude-persistent/
|
|
133
|
+
~/.codex → .replit-tools/.codex-persistent/
|
|
134
|
+
~/.local/share/claude/versions/ → .replit-tools/.claude-versions/
|
|
135
|
+
~/.local/bin/claude → .replit-tools/.claude-versions/X.X.X
|
|
132
136
|
```
|
|
133
137
|
|
|
134
138
|
Three layers ensure setup runs on every restart:
|
|
135
139
|
1. `.replit` onBoot hook (runs at container boot)
|
|
136
140
|
2. `.config/bashrc` (runs on every shell start)
|
|
137
|
-
3. Scripts in
|
|
141
|
+
3. Scripts in `.replit-tools/scripts/` (called by above)
|
|
138
142
|
|
|
139
143
|
## Smart Detection
|
|
140
144
|
|
|
@@ -142,7 +146,7 @@ The installer checks for:
|
|
|
142
146
|
|
|
143
147
|
- **`CLAUDE_CONFIG_DIR`** - Respects custom Claude config directory if set in Replit Secrets
|
|
144
148
|
- **`CODEX_HOME`** - Respects custom Codex config directory
|
|
145
|
-
- **Existing persistent config** - Uses your existing config if present
|
|
149
|
+
- **Existing persistent config** - Uses your existing config if present (won't migrate if custom dir set)
|
|
146
150
|
- **Replit Secrets** - Detects `ANTHROPIC_API_KEY` and `OPENAI_API_KEY`
|
|
147
151
|
- **Existing installations** - Won't reinstall Claude or Codex if already present
|
|
148
152
|
- **Existing data in ~/.claude** - Moves it to persistent storage instead of overwriting
|
|
@@ -168,7 +172,7 @@ The installer checks for:
|
|
|
168
172
|
| `CODEX_DATA_DIR` | Alternative name |
|
|
169
173
|
| `OPENAI_API_KEY` | Codex API authentication |
|
|
170
174
|
|
|
171
|
-
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.
|
|
175
|
+
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. **Your custom directories will NOT be migrated** - we respect your configuration.
|
|
172
176
|
|
|
173
177
|
## Installation Options
|
|
174
178
|
|
|
@@ -224,26 +228,46 @@ Creates a long-lived API token that never expires (recommended for unattended us
|
|
|
224
228
|
|
|
225
229
|
```
|
|
226
230
|
workspace/
|
|
227
|
-
├── .
|
|
228
|
-
├── .
|
|
229
|
-
├── .
|
|
230
|
-
├── .
|
|
231
|
-
├── .
|
|
232
|
-
├── .
|
|
233
|
-
├── logs/
|
|
234
|
-
|
|
235
|
-
│
|
|
236
|
-
│
|
|
237
|
-
│
|
|
238
|
-
|
|
231
|
+
├── .replit-tools/ # All DATA Tools data (gitignored)
|
|
232
|
+
│ ├── .claude-persistent/ # Claude conversations & credentials
|
|
233
|
+
│ ├── .codex-persistent/ # Codex CLI data
|
|
234
|
+
│ ├── .claude-sessions/ # Per-terminal session tracking
|
|
235
|
+
│ ├── .claude-versions/ # Claude binary versions
|
|
236
|
+
│ ├── .persistent-home/ # Bash history
|
|
237
|
+
│ ├── .logs/ # Auth refresh logs
|
|
238
|
+
│ └── scripts/ # Setup & management scripts
|
|
239
|
+
│ ├── setup-claude-code.sh
|
|
240
|
+
│ ├── claude-session-manager.sh
|
|
241
|
+
│ └── claude-auth-refresh.sh
|
|
242
|
+
├── .config/bashrc # Shell startup config (sources scripts)
|
|
243
|
+
└── .gitignore # Updated to ignore .replit-tools/
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Upgrading from v1.x
|
|
247
|
+
|
|
248
|
+
If you used DATA Tools v1.x (before the `.replit-tools/` consolidation), your data will be automatically migrated:
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
Old Location → New Location
|
|
252
|
+
.claude-persistent/ → .replit-tools/.claude-persistent/
|
|
253
|
+
.codex-persistent/ → .replit-tools/.codex-persistent/
|
|
254
|
+
.claude-sessions/ → .replit-tools/.claude-sessions/
|
|
255
|
+
.persistent-home/ → .replit-tools/.persistent-home/
|
|
256
|
+
.local/share/claude/versions/ → .replit-tools/.claude-versions/
|
|
239
257
|
```
|
|
240
258
|
|
|
259
|
+
Migration only happens if:
|
|
260
|
+
1. Old location exists AND new location doesn't
|
|
261
|
+
2. You don't have custom `CLAUDE_CONFIG_DIR` or `CODEX_HOME` set
|
|
262
|
+
|
|
263
|
+
Your original data is copied (not moved), so nothing is lost.
|
|
264
|
+
|
|
241
265
|
## Troubleshooting
|
|
242
266
|
|
|
243
267
|
### Claude or Codex not found after restart
|
|
244
268
|
|
|
245
269
|
```bash
|
|
246
|
-
source /home/runner/workspace/scripts/setup-claude-code.sh
|
|
270
|
+
source /home/runner/workspace/.replit-tools/scripts/setup-claude-code.sh
|
|
247
271
|
```
|
|
248
272
|
|
|
249
273
|
### Session picker not appearing
|
|
@@ -258,10 +282,10 @@ The auto-refresh should handle this, but if it fails:
|
|
|
258
282
|
|
|
259
283
|
```bash
|
|
260
284
|
# Check why refresh failed
|
|
261
|
-
cat /home/runner/workspace
|
|
285
|
+
cat /home/runner/workspace/.replit-tools/.logs/auth-refresh.log
|
|
262
286
|
|
|
263
287
|
# Manual refresh
|
|
264
|
-
/home/runner/workspace/scripts/claude-auth-refresh.sh --force
|
|
288
|
+
/home/runner/workspace/.replit-tools/scripts/claude-auth-refresh.sh --force
|
|
265
289
|
|
|
266
290
|
# Or use permanent token (recommended)
|
|
267
291
|
claude setup-token
|
|
@@ -277,15 +301,15 @@ Running the installer again is safe - it preserves existing data.
|
|
|
277
301
|
|
|
278
302
|
## Security
|
|
279
303
|
|
|
280
|
-
The installer adds
|
|
304
|
+
The installer adds `.replit-tools/` to `.gitignore`, which protects:
|
|
281
305
|
|
|
282
306
|
| Path | Contains | Why Protected |
|
|
283
307
|
|------|----------|---------------|
|
|
284
|
-
| `.claude-persistent/` | OAuth tokens, refresh tokens, conversations | **Critical** - full account access |
|
|
285
|
-
| `.codex-persistent/` | API keys in `auth.json`, conversations | **Critical** - full account access |
|
|
286
|
-
| `.claude-sessions/` | Session UUIDs, terminal mappings | Session metadata |
|
|
287
|
-
| `.persistent-home/` | Bash history | May contain typed secrets |
|
|
288
|
-
|
|
|
308
|
+
| `.replit-tools/.claude-persistent/` | OAuth tokens, refresh tokens, conversations | **Critical** - full account access |
|
|
309
|
+
| `.replit-tools/.codex-persistent/` | API keys in `auth.json`, conversations | **Critical** - full account access |
|
|
310
|
+
| `.replit-tools/.claude-sessions/` | Session UUIDs, terminal mappings | Session metadata |
|
|
311
|
+
| `.replit-tools/.persistent-home/` | Bash history | May contain typed secrets |
|
|
312
|
+
| `.replit-tools/.logs/` | Token refresh timestamps | Auth timing info |
|
|
289
313
|
|
|
290
314
|
Your API keys, OAuth tokens, and conversation history won't be committed to git.
|
|
291
315
|
|
package/index.js
CHANGED
|
@@ -7,6 +7,7 @@ const os = require('os');
|
|
|
7
7
|
|
|
8
8
|
const WORKSPACE = '/home/runner/workspace';
|
|
9
9
|
const HOME = os.homedir();
|
|
10
|
+
const REPLIT_TOOLS = path.join(WORKSPACE, '.replit-tools');
|
|
10
11
|
|
|
11
12
|
// Helper to run commands safely without crashing the installer
|
|
12
13
|
function safeExec(cmd, options = {}) {
|
|
@@ -20,7 +21,6 @@ function safeExec(cmd, options = {}) {
|
|
|
20
21
|
});
|
|
21
22
|
|
|
22
23
|
if (options.showOutput && result.stdout) {
|
|
23
|
-
// Show condensed output
|
|
24
24
|
const lines = result.stdout.trim().split('\n');
|
|
25
25
|
if (lines.length <= 5) {
|
|
26
26
|
lines.forEach(l => console.log(` ${l}`));
|
|
@@ -47,6 +47,21 @@ function safeExec(cmd, options = {}) {
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
// Helper to migrate data from old location to new
|
|
51
|
+
function migrateDirectory(oldPath, newPath, description) {
|
|
52
|
+
if (fs.existsSync(oldPath) && !fs.existsSync(newPath)) {
|
|
53
|
+
try {
|
|
54
|
+
console.log(` Migrating ${description}...`);
|
|
55
|
+
execSync(`cp -rp "${oldPath}" "${newPath}"`, { shell: '/bin/bash' });
|
|
56
|
+
return true;
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.log(` ⚠️ Could not migrate ${description}: ${err.message}`);
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
50
65
|
// Wrap everything in try-catch to prevent crashes
|
|
51
66
|
try {
|
|
52
67
|
main();
|
|
@@ -89,11 +104,34 @@ function main() {
|
|
|
89
104
|
return process.env[name] || null;
|
|
90
105
|
}
|
|
91
106
|
|
|
107
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
108
|
+
// DEFINE DIRECTORY STRUCTURE
|
|
109
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
110
|
+
|
|
111
|
+
// Default locations inside .replit-tools (all hidden with dots)
|
|
112
|
+
const defaultClaudeDir = path.join(REPLIT_TOOLS, '.claude-persistent');
|
|
113
|
+
const defaultCodexDir = path.join(REPLIT_TOOLS, '.codex-persistent');
|
|
114
|
+
const sessionsDir = path.join(REPLIT_TOOLS, '.claude-sessions');
|
|
115
|
+
const persistentHomeDir = path.join(REPLIT_TOOLS, '.persistent-home');
|
|
116
|
+
const claudeVersionsDir = path.join(REPLIT_TOOLS, '.claude-versions');
|
|
117
|
+
const logsDir = path.join(REPLIT_TOOLS, '.logs');
|
|
118
|
+
const scriptsDir = path.join(REPLIT_TOOLS, 'scripts');
|
|
119
|
+
|
|
120
|
+
// Old locations (for migration)
|
|
121
|
+
const oldLocations = {
|
|
122
|
+
claude: path.join(WORKSPACE, '.claude-persistent'),
|
|
123
|
+
codex: path.join(WORKSPACE, '.codex-persistent'),
|
|
124
|
+
sessions: path.join(WORKSPACE, '.claude-sessions'),
|
|
125
|
+
home: path.join(WORKSPACE, '.persistent-home'),
|
|
126
|
+
versions: path.join(WORKSPACE, '.local/share/claude/versions'),
|
|
127
|
+
logs: path.join(WORKSPACE, 'logs'),
|
|
128
|
+
scripts: path.join(WORKSPACE, 'scripts')
|
|
129
|
+
};
|
|
130
|
+
|
|
92
131
|
// ═══════════════════════════════════════════════════════════════════
|
|
93
132
|
// CLAUDE CONFIG DIRECTORY DETECTION
|
|
94
133
|
// ═══════════════════════════════════════════════════════════════════
|
|
95
134
|
|
|
96
|
-
// Check for all possible Claude config env vars
|
|
97
135
|
const claudeConfigEnvVars = [
|
|
98
136
|
'CLAUDE_CONFIG_DIR',
|
|
99
137
|
'CLAUDE_WORKSPACE_DIR',
|
|
@@ -115,14 +153,16 @@ function main() {
|
|
|
115
153
|
}
|
|
116
154
|
|
|
117
155
|
// Determine Claude persistent directory
|
|
118
|
-
let claudePersistentDir =
|
|
156
|
+
let claudePersistentDir = defaultClaudeDir;
|
|
157
|
+
let usingCustomClaudeDir = false;
|
|
119
158
|
|
|
120
159
|
if (customClaudeDir) {
|
|
121
160
|
if (customClaudeDir.startsWith(WORKSPACE)) {
|
|
122
161
|
claudePersistentDir = customClaudeDir;
|
|
123
|
-
|
|
162
|
+
usingCustomClaudeDir = true;
|
|
163
|
+
console.log(` Using custom Claude directory (not migrating)`);
|
|
124
164
|
} else {
|
|
125
|
-
console.log(` ⚠️ Custom dir outside workspace -
|
|
165
|
+
console.log(` ⚠️ Custom dir outside workspace - using .replit-tools for persistence`);
|
|
126
166
|
}
|
|
127
167
|
}
|
|
128
168
|
|
|
@@ -130,7 +170,6 @@ function main() {
|
|
|
130
170
|
// CODEX CONFIG DIRECTORY DETECTION
|
|
131
171
|
// ═══════════════════════════════════════════════════════════════════
|
|
132
172
|
|
|
133
|
-
// Check for Codex config env vars
|
|
134
173
|
const codexConfigEnvVars = [
|
|
135
174
|
'CODEX_HOME',
|
|
136
175
|
'CODEX_CONFIG_DIR',
|
|
@@ -151,32 +190,23 @@ function main() {
|
|
|
151
190
|
}
|
|
152
191
|
|
|
153
192
|
// Determine Codex persistent directory
|
|
154
|
-
let codexPersistentDir =
|
|
193
|
+
let codexPersistentDir = defaultCodexDir;
|
|
194
|
+
let usingCustomCodexDir = false;
|
|
155
195
|
|
|
156
196
|
if (customCodexDir) {
|
|
157
197
|
if (customCodexDir.startsWith(WORKSPACE)) {
|
|
158
198
|
codexPersistentDir = customCodexDir;
|
|
159
|
-
|
|
199
|
+
usingCustomCodexDir = true;
|
|
200
|
+
console.log(` Using custom Codex directory (not migrating)`);
|
|
160
201
|
} else {
|
|
161
|
-
console.log(` ⚠️ Custom dir outside workspace -
|
|
202
|
+
console.log(` ⚠️ Custom dir outside workspace - using .replit-tools for persistence`);
|
|
162
203
|
}
|
|
163
204
|
}
|
|
164
205
|
|
|
165
206
|
// ═══════════════════════════════════════════════════════════════════
|
|
166
|
-
// CHECK FOR
|
|
207
|
+
// CHECK FOR API KEYS
|
|
167
208
|
// ═══════════════════════════════════════════════════════════════════
|
|
168
209
|
|
|
169
|
-
const existingClaudeConfig = fs.existsSync(claudePersistentDir);
|
|
170
|
-
const existingCodexConfig = fs.existsSync(codexPersistentDir);
|
|
171
|
-
|
|
172
|
-
if (existingClaudeConfig) {
|
|
173
|
-
console.log(`✅ Found existing Claude config`);
|
|
174
|
-
}
|
|
175
|
-
if (existingCodexConfig) {
|
|
176
|
-
console.log('✅ Found existing Codex config');
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Check for API key secrets
|
|
180
210
|
if (getEnvVar('ANTHROPIC_API_KEY')) {
|
|
181
211
|
console.log('✅ Found ANTHROPIC_API_KEY');
|
|
182
212
|
}
|
|
@@ -188,44 +218,77 @@ function main() {
|
|
|
188
218
|
// CREATE DIRECTORIES
|
|
189
219
|
// ═══════════════════════════════════════════════════════════════════
|
|
190
220
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
'.local/share/claude/versions',
|
|
194
|
-
'.persistent-home',
|
|
195
|
-
'.config',
|
|
196
|
-
'scripts',
|
|
197
|
-
'logs'
|
|
198
|
-
];
|
|
221
|
+
console.log('');
|
|
222
|
+
console.log('📁 Creating directories...');
|
|
199
223
|
|
|
200
|
-
//
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
if (!dirs.includes(relativePath)) {
|
|
204
|
-
dirs.unshift(relativePath);
|
|
205
|
-
}
|
|
224
|
+
// Create base .replit-tools directory
|
|
225
|
+
if (!fs.existsSync(REPLIT_TOOLS)) {
|
|
226
|
+
fs.mkdirSync(REPLIT_TOOLS, { recursive: true });
|
|
206
227
|
}
|
|
207
228
|
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
229
|
+
// Create all subdirectories
|
|
230
|
+
const dirsToCreate = [
|
|
231
|
+
claudePersistentDir,
|
|
232
|
+
codexPersistentDir,
|
|
233
|
+
sessionsDir,
|
|
234
|
+
persistentHomeDir,
|
|
235
|
+
claudeVersionsDir,
|
|
236
|
+
logsDir,
|
|
237
|
+
scriptsDir,
|
|
238
|
+
path.join(WORKSPACE, '.config') // Keep .config in workspace for Replit auto-sourcing
|
|
239
|
+
];
|
|
215
240
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
dirs.forEach(dir => {
|
|
219
|
-
const fullPath = path.join(WORKSPACE, dir);
|
|
220
|
-
if (!fs.existsSync(fullPath)) {
|
|
241
|
+
dirsToCreate.forEach(dir => {
|
|
242
|
+
if (!fs.existsSync(dir)) {
|
|
221
243
|
try {
|
|
222
|
-
fs.mkdirSync(
|
|
244
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
223
245
|
} catch (err) {
|
|
224
246
|
console.log(` ⚠️ Could not create ${dir}: ${err.message}`);
|
|
225
247
|
}
|
|
226
248
|
}
|
|
227
249
|
});
|
|
228
250
|
|
|
251
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
252
|
+
// MIGRATE FROM OLD LOCATIONS
|
|
253
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
254
|
+
|
|
255
|
+
console.log('');
|
|
256
|
+
console.log('🔄 Checking for data migration...');
|
|
257
|
+
|
|
258
|
+
let migrated = false;
|
|
259
|
+
|
|
260
|
+
// Migrate Claude data (only if not using custom dir)
|
|
261
|
+
if (!usingCustomClaudeDir) {
|
|
262
|
+
if (migrateDirectory(oldLocations.claude, claudePersistentDir, 'Claude config')) {
|
|
263
|
+
migrated = true;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Migrate Codex data (only if not using custom dir)
|
|
268
|
+
if (!usingCustomCodexDir) {
|
|
269
|
+
if (migrateDirectory(oldLocations.codex, codexPersistentDir, 'Codex config')) {
|
|
270
|
+
migrated = true;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Always migrate these (no custom dir options)
|
|
275
|
+
if (migrateDirectory(oldLocations.sessions, sessionsDir, 'session data')) migrated = true;
|
|
276
|
+
if (migrateDirectory(oldLocations.home, persistentHomeDir, 'bash history')) migrated = true;
|
|
277
|
+
if (migrateDirectory(oldLocations.versions, claudeVersionsDir, 'Claude versions')) migrated = true;
|
|
278
|
+
|
|
279
|
+
// Migrate logs (just auth-refresh.log)
|
|
280
|
+
if (fs.existsSync(path.join(oldLocations.logs, 'auth-refresh.log')) && !fs.existsSync(path.join(logsDir, 'auth-refresh.log'))) {
|
|
281
|
+
try {
|
|
282
|
+
fs.copyFileSync(path.join(oldLocations.logs, 'auth-refresh.log'), path.join(logsDir, 'auth-refresh.log'));
|
|
283
|
+
migrated = true;
|
|
284
|
+
console.log(' Migrating auth logs...');
|
|
285
|
+
} catch {}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!migrated) {
|
|
289
|
+
console.log(' No migration needed');
|
|
290
|
+
}
|
|
291
|
+
|
|
229
292
|
// ═══════════════════════════════════════════════════════════════════
|
|
230
293
|
// INSTALL CLAUDE CODE
|
|
231
294
|
// ═══════════════════════════════════════════════════════════════════
|
|
@@ -234,10 +297,16 @@ function main() {
|
|
|
234
297
|
|
|
235
298
|
let claudeVersions = [];
|
|
236
299
|
try {
|
|
237
|
-
claudeVersions = fs.readdirSync(
|
|
238
|
-
.filter(f => !f.startsWith('.'));
|
|
300
|
+
claudeVersions = fs.readdirSync(claudeVersionsDir).filter(f => !f.startsWith('.'));
|
|
239
301
|
} catch {}
|
|
240
302
|
|
|
303
|
+
// Also check old location
|
|
304
|
+
if (claudeVersions.length === 0) {
|
|
305
|
+
try {
|
|
306
|
+
claudeVersions = fs.readdirSync(oldLocations.versions).filter(f => !f.startsWith('.'));
|
|
307
|
+
} catch {}
|
|
308
|
+
}
|
|
309
|
+
|
|
241
310
|
const claudeInstalled = commandExists('claude') ||
|
|
242
311
|
fs.existsSync(path.join(HOME, '.local/bin/claude')) ||
|
|
243
312
|
claudeVersions.length > 0;
|
|
@@ -251,12 +320,27 @@ function main() {
|
|
|
251
320
|
};
|
|
252
321
|
const result = safeExec('curl -fsSL https://claude.ai/install.sh | bash', {
|
|
253
322
|
env: installEnv,
|
|
254
|
-
timeout: 180000,
|
|
323
|
+
timeout: 180000,
|
|
255
324
|
showOutput: true
|
|
256
325
|
});
|
|
257
326
|
|
|
258
327
|
if (result.success) {
|
|
259
328
|
console.log('✅ Claude Code installed');
|
|
329
|
+
// Move installed version to our directory
|
|
330
|
+
try {
|
|
331
|
+
const defaultVersionsDir = path.join(HOME, '.local/share/claude/versions');
|
|
332
|
+
if (fs.existsSync(defaultVersionsDir)) {
|
|
333
|
+
const versions = fs.readdirSync(defaultVersionsDir).filter(f => !f.startsWith('.'));
|
|
334
|
+
versions.forEach(v => {
|
|
335
|
+
const src = path.join(defaultVersionsDir, v);
|
|
336
|
+
const dest = path.join(claudeVersionsDir, v);
|
|
337
|
+
if (!fs.existsSync(dest)) {
|
|
338
|
+
fs.copyFileSync(src, dest);
|
|
339
|
+
fs.chmodSync(dest, '755');
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
} catch {}
|
|
260
344
|
} else {
|
|
261
345
|
console.log('⚠️ Claude Code installation had issues (may still work)');
|
|
262
346
|
if (result.stderr && result.stderr.length < 200) {
|
|
@@ -282,7 +366,7 @@ function main() {
|
|
|
282
366
|
};
|
|
283
367
|
const result = safeExec('npm i -g @openai/codex', {
|
|
284
368
|
env: installEnv,
|
|
285
|
-
timeout: 180000,
|
|
369
|
+
timeout: 180000,
|
|
286
370
|
showOutput: true
|
|
287
371
|
});
|
|
288
372
|
|
|
@@ -305,7 +389,7 @@ function main() {
|
|
|
305
389
|
console.log('');
|
|
306
390
|
console.log('🔗 Setting up symlinks...');
|
|
307
391
|
|
|
308
|
-
// Claude symlink
|
|
392
|
+
// Claude config symlink (~/.claude -> our persistent dir)
|
|
309
393
|
const claudeLink = path.join(HOME, '.claude');
|
|
310
394
|
try {
|
|
311
395
|
let needsLink = false;
|
|
@@ -330,12 +414,13 @@ function main() {
|
|
|
330
414
|
if (needsLink) {
|
|
331
415
|
fs.symlinkSync(claudePersistentDir, claudeLink);
|
|
332
416
|
}
|
|
333
|
-
|
|
417
|
+
const displayPath = claudePersistentDir.replace(WORKSPACE + '/', '');
|
|
418
|
+
console.log(` ~/.claude → ${displayPath}/`);
|
|
334
419
|
} catch (err) {
|
|
335
420
|
console.log(` ⚠️ Could not create Claude symlink: ${err.message}`);
|
|
336
421
|
}
|
|
337
422
|
|
|
338
|
-
// Codex symlink
|
|
423
|
+
// Codex config symlink (~/.codex -> our persistent dir)
|
|
339
424
|
const codexLink = path.join(HOME, '.codex');
|
|
340
425
|
try {
|
|
341
426
|
let needsLink = false;
|
|
@@ -360,7 +445,8 @@ function main() {
|
|
|
360
445
|
if (needsLink) {
|
|
361
446
|
fs.symlinkSync(codexPersistentDir, codexLink);
|
|
362
447
|
}
|
|
363
|
-
|
|
448
|
+
const displayPath = codexPersistentDir.replace(WORKSPACE + '/', '');
|
|
449
|
+
console.log(` ~/.codex → ${displayPath}/`);
|
|
364
450
|
} catch (err) {
|
|
365
451
|
console.log(` ⚠️ Could not create Codex symlink: ${err.message}`);
|
|
366
452
|
}
|
|
@@ -368,45 +454,51 @@ function main() {
|
|
|
368
454
|
// Claude binary symlinks
|
|
369
455
|
const localBin = path.join(HOME, '.local/bin');
|
|
370
456
|
const localShare = path.join(HOME, '.local/share');
|
|
371
|
-
const claudeShareTarget = path.join(WORKSPACE, '.local/share/claude');
|
|
372
457
|
|
|
373
458
|
try { fs.mkdirSync(localBin, { recursive: true }); } catch {}
|
|
374
459
|
try { fs.mkdirSync(localShare, { recursive: true }); } catch {}
|
|
375
460
|
|
|
376
|
-
// Link .local/share/claude
|
|
461
|
+
// Link .local/share/claude to our versions directory's parent
|
|
462
|
+
const claudeShareTarget = path.join(REPLIT_TOOLS, '.claude-versions');
|
|
377
463
|
try {
|
|
378
|
-
|
|
464
|
+
// Create a wrapper directory structure for compatibility
|
|
465
|
+
const shareClaudeDir = path.join(localShare, 'claude');
|
|
466
|
+
const shareVersionsDir = path.join(shareClaudeDir, 'versions');
|
|
467
|
+
|
|
468
|
+
if (!fs.existsSync(shareClaudeDir)) {
|
|
469
|
+
fs.mkdirSync(shareClaudeDir, { recursive: true });
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Symlink versions dir
|
|
379
473
|
try {
|
|
380
|
-
const stat = fs.lstatSync(
|
|
474
|
+
const stat = fs.lstatSync(shareVersionsDir);
|
|
381
475
|
if (stat.isSymbolicLink()) {
|
|
382
|
-
const current = fs.readlinkSync(
|
|
383
|
-
if (current !==
|
|
384
|
-
fs.unlinkSync(
|
|
385
|
-
|
|
476
|
+
const current = fs.readlinkSync(shareVersionsDir);
|
|
477
|
+
if (current !== claudeVersionsDir) {
|
|
478
|
+
fs.unlinkSync(shareVersionsDir);
|
|
479
|
+
fs.symlinkSync(claudeVersionsDir, shareVersionsDir);
|
|
386
480
|
}
|
|
387
481
|
}
|
|
388
482
|
} catch {
|
|
389
|
-
|
|
483
|
+
fs.symlinkSync(claudeVersionsDir, shareVersionsDir);
|
|
390
484
|
}
|
|
391
|
-
|
|
392
|
-
if (needsLink) {
|
|
393
|
-
fs.symlinkSync(claudeShareTarget, path.join(localShare, 'claude'));
|
|
394
|
-
}
|
|
395
|
-
console.log(' ~/.local/share/claude → .local/share/claude/');
|
|
485
|
+
console.log(` ~/.local/share/claude/versions → .replit-tools/.claude-versions/`);
|
|
396
486
|
} catch {}
|
|
397
487
|
|
|
398
488
|
// Link binary to latest version
|
|
399
489
|
try {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
.sort();
|
|
490
|
+
let versions = [];
|
|
491
|
+
try {
|
|
492
|
+
versions = fs.readdirSync(claudeVersionsDir).filter(f => !f.startsWith('.')).sort();
|
|
493
|
+
} catch {}
|
|
494
|
+
|
|
403
495
|
if (versions.length > 0) {
|
|
404
496
|
const latest = versions[versions.length - 1];
|
|
405
|
-
const binaryPath = path.join(
|
|
497
|
+
const binaryPath = path.join(claudeVersionsDir, latest);
|
|
406
498
|
const binLink = path.join(localBin, 'claude');
|
|
407
499
|
try { fs.unlinkSync(binLink); } catch {}
|
|
408
500
|
fs.symlinkSync(binaryPath, binLink);
|
|
409
|
-
console.log(` ~/.local/bin/claude → versions/${latest}`);
|
|
501
|
+
console.log(` ~/.local/bin/claude → .replit-tools/.claude-versions/${latest}`);
|
|
410
502
|
}
|
|
411
503
|
} catch {}
|
|
412
504
|
|
|
@@ -414,16 +506,15 @@ function main() {
|
|
|
414
506
|
// COPY SCRIPTS
|
|
415
507
|
// ═══════════════════════════════════════════════════════════════════
|
|
416
508
|
|
|
417
|
-
const
|
|
418
|
-
const targetScriptsDir = path.join(WORKSPACE, 'scripts');
|
|
509
|
+
const packageScriptsDir = path.join(__dirname, 'scripts');
|
|
419
510
|
|
|
420
511
|
console.log('');
|
|
421
512
|
console.log('📝 Installing scripts...');
|
|
422
513
|
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
const srcPath = path.join(
|
|
426
|
-
const destPath = path.join(
|
|
514
|
+
const scriptFiles = ['setup-claude-code.sh', 'claude-session-manager.sh', 'claude-auth-refresh.sh'];
|
|
515
|
+
scriptFiles.forEach(script => {
|
|
516
|
+
const srcPath = path.join(packageScriptsDir, script);
|
|
517
|
+
const destPath = path.join(scriptsDir, script);
|
|
427
518
|
|
|
428
519
|
if (fs.existsSync(srcPath)) {
|
|
429
520
|
try {
|
|
@@ -445,7 +536,10 @@ function main() {
|
|
|
445
536
|
|
|
446
537
|
const bashrcContent = `#!/bin/bash
|
|
447
538
|
# DATA Tools - Replit Claude & Codex Persistence
|
|
448
|
-
# Auto-generated bashrc
|
|
539
|
+
# Auto-generated bashrc - v2.0 (.replit-tools structure)
|
|
540
|
+
|
|
541
|
+
# Base directory for all DATA Tools data
|
|
542
|
+
export REPLIT_TOOLS_DIR="${REPLIT_TOOLS}"
|
|
449
543
|
|
|
450
544
|
# Claude Config Directory (tells Claude where to store data)
|
|
451
545
|
export CLAUDE_CONFIG_DIR="${claudePersistentDir}"
|
|
@@ -455,7 +549,7 @@ export CLAUDE_WORKSPACE_DIR="${claudePersistentDir}"
|
|
|
455
549
|
export CODEX_HOME="${codexPersistentDir}"
|
|
456
550
|
|
|
457
551
|
# Claude Code Setup
|
|
458
|
-
SETUP_SCRIPT="/
|
|
552
|
+
SETUP_SCRIPT="${scriptsDir}/setup-claude-code.sh"
|
|
459
553
|
[ -f "\${SETUP_SCRIPT}" ] && source "\${SETUP_SCRIPT}"
|
|
460
554
|
|
|
461
555
|
# Codex Persistence
|
|
@@ -463,7 +557,7 @@ mkdir -p "${codexPersistentDir}"
|
|
|
463
557
|
[ ! -L "\${HOME}/.codex" ] && ln -sf "${codexPersistentDir}" "\${HOME}/.codex"
|
|
464
558
|
|
|
465
559
|
# Bash History Persistence
|
|
466
|
-
PERSISTENT_HOME="
|
|
560
|
+
PERSISTENT_HOME="${persistentHomeDir}"
|
|
467
561
|
mkdir -p "\${PERSISTENT_HOME}"
|
|
468
562
|
export HISTFILE="\${PERSISTENT_HOME}/.bash_history"
|
|
469
563
|
export HISTSIZE=10000
|
|
@@ -472,7 +566,7 @@ export HISTCONTROL=ignoredups
|
|
|
472
566
|
[ -f "\${HISTFILE}" ] && history -r "\${HISTFILE}"
|
|
473
567
|
|
|
474
568
|
# Session Manager (interactive menu)
|
|
475
|
-
SESSION_MANAGER="/
|
|
569
|
+
SESSION_MANAGER="${scriptsDir}/claude-session-manager.sh"
|
|
476
570
|
[ -f "\${SESSION_MANAGER}" ] && source "\${SESSION_MANAGER}"
|
|
477
571
|
|
|
478
572
|
# Aliases
|
|
@@ -493,15 +587,17 @@ alias claude-pick='claude -r --dangerously-skip-permissions'
|
|
|
493
587
|
|
|
494
588
|
console.log('📝 Updating .replit configuration...');
|
|
495
589
|
const replitPath = path.join(WORKSPACE, '.replit');
|
|
496
|
-
const onBootLine =
|
|
590
|
+
const onBootLine = `onBoot = "source ${scriptsDir}/setup-claude-code.sh 2>/dev/null || true"`;
|
|
497
591
|
|
|
498
592
|
try {
|
|
499
593
|
if (fs.existsSync(replitPath)) {
|
|
500
594
|
let content = fs.readFileSync(replitPath, 'utf8');
|
|
595
|
+
// Remove old onBoot line if present
|
|
596
|
+
content = content.replace(/onBoot\s*=\s*"[^"]*setup-claude-code\.sh[^"]*"\n?/g, '');
|
|
501
597
|
if (!content.includes('setup-claude-code.sh')) {
|
|
502
598
|
content += '\n\n# Claude persistence (added by DATA Tools)\n' + onBootLine + '\n';
|
|
503
|
-
fs.writeFileSync(replitPath, content);
|
|
504
599
|
}
|
|
600
|
+
fs.writeFileSync(replitPath, content);
|
|
505
601
|
} else {
|
|
506
602
|
fs.writeFileSync(replitPath, '# Claude persistence (DATA Tools)\n' + onBootLine + '\n');
|
|
507
603
|
}
|
|
@@ -516,18 +612,14 @@ alias claude-pick='claude -r --dangerously-skip-permissions'
|
|
|
516
612
|
console.log('📝 Updating .gitignore...');
|
|
517
613
|
const gitignorePath = path.join(WORKSPACE, '.gitignore');
|
|
518
614
|
const gitignoreEntries = `
|
|
519
|
-
#
|
|
520
|
-
.
|
|
521
|
-
.codex-persistent/
|
|
522
|
-
.claude-sessions/
|
|
523
|
-
.persistent-home/
|
|
524
|
-
logs/auth-refresh.log
|
|
615
|
+
# DATA Tools - All sensitive data in one place (added by DATA Tools)
|
|
616
|
+
.replit-tools/
|
|
525
617
|
`;
|
|
526
618
|
|
|
527
619
|
try {
|
|
528
620
|
if (fs.existsSync(gitignorePath)) {
|
|
529
621
|
let content = fs.readFileSync(gitignorePath, 'utf8');
|
|
530
|
-
if (!content.includes('.
|
|
622
|
+
if (!content.includes('.replit-tools/')) {
|
|
531
623
|
fs.writeFileSync(gitignorePath, content + gitignoreEntries);
|
|
532
624
|
}
|
|
533
625
|
} else {
|
|
@@ -545,6 +637,7 @@ logs/auth-refresh.log
|
|
|
545
637
|
process.env.CLAUDE_CONFIG_DIR = claudePersistentDir;
|
|
546
638
|
process.env.CLAUDE_WORKSPACE_DIR = claudePersistentDir;
|
|
547
639
|
process.env.CODEX_HOME = codexPersistentDir;
|
|
640
|
+
process.env.REPLIT_TOOLS_DIR = REPLIT_TOOLS;
|
|
548
641
|
|
|
549
642
|
// ═══════════════════════════════════════════════════════════════════
|
|
550
643
|
// SHOW COMPLETION MESSAGE
|
|
@@ -566,8 +659,12 @@ logs/auth-refresh.log
|
|
|
566
659
|
console.log('║ ║');
|
|
567
660
|
console.log('╠═════════════════════════════════════════════════════════════╣');
|
|
568
661
|
console.log('║ ║');
|
|
569
|
-
console.log(
|
|
570
|
-
console.log(
|
|
662
|
+
console.log('║ All data stored in: .replit-tools/ ║');
|
|
663
|
+
console.log('║ ║');
|
|
664
|
+
const claudeDisplay = claudePersistentDir.replace(WORKSPACE + '/', '').padEnd(40);
|
|
665
|
+
const codexDisplay = codexPersistentDir.replace(WORKSPACE + '/', '').padEnd(40);
|
|
666
|
+
console.log(`║ Claude: ${claudeDisplay} ║`);
|
|
667
|
+
console.log(`║ Codex: ${codexDisplay} ║`);
|
|
571
668
|
console.log('║ ║');
|
|
572
669
|
console.log('╚═════════════════════════════════════════════════════════════╝');
|
|
573
670
|
console.log('');
|
|
@@ -598,7 +695,6 @@ logs/auth-refresh.log
|
|
|
598
695
|
console.log('Launching session manager...');
|
|
599
696
|
console.log('');
|
|
600
697
|
|
|
601
|
-
// Use spawn to run bash interactively with our session manager
|
|
602
698
|
const sessionManager = spawn('bash', ['--rcfile', path.join(WORKSPACE, '.config/bashrc'), '-i'], {
|
|
603
699
|
stdio: 'inherit',
|
|
604
700
|
cwd: WORKSPACE,
|
package/package.json
CHANGED
|
@@ -3,11 +3,19 @@
|
|
|
3
3
|
# Automatically refreshes Claude Code OAuth tokens before expiration
|
|
4
4
|
# Part of DATA Tools - https://github.com/stevemoraco/DATAtools
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
# Use .replit-tools structure
|
|
7
|
+
WORKSPACE="/home/runner/workspace"
|
|
8
|
+
REPLIT_TOOLS="${WORKSPACE}/.replit-tools"
|
|
9
|
+
CREDENTIALS_FILE="${CLAUDE_CONFIG_DIR:-${REPLIT_TOOLS}/.claude-persistent}/.credentials.json"
|
|
10
|
+
LOG_FILE="${REPLIT_TOOLS}/.logs/auth-refresh.log"
|
|
11
|
+
|
|
12
|
+
# OAuth configuration
|
|
7
13
|
OAUTH_ENDPOINT="https://console.anthropic.com/v1/oauth/token"
|
|
8
14
|
CLIENT_ID="9d1c250a-e61b-44d9-88ed-5944d1962f5e"
|
|
9
15
|
REFRESH_THRESHOLD_HOURS=2 # Refresh when less than 2 hours remaining
|
|
10
|
-
|
|
16
|
+
|
|
17
|
+
# Ensure log directory exists
|
|
18
|
+
mkdir -p "${REPLIT_TOOLS}/.logs" 2>/dev/null
|
|
11
19
|
|
|
12
20
|
# Logging function
|
|
13
21
|
log() {
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
# =============================================================================
|
|
8
8
|
|
|
9
9
|
WORKSPACE="/home/runner/workspace"
|
|
10
|
-
|
|
10
|
+
REPLIT_TOOLS="${WORKSPACE}/.replit-tools"
|
|
11
|
+
SESSIONS_DIR="${REPLIT_TOOLS}/.claude-sessions"
|
|
11
12
|
LOCK_DIR="/tmp/.claude-locks"
|
|
12
13
|
|
|
13
14
|
mkdir -p "${SESSIONS_DIR}" "${LOCK_DIR}" 2>/dev/null
|
|
@@ -15,12 +15,16 @@
|
|
|
15
15
|
|
|
16
16
|
set -e
|
|
17
17
|
|
|
18
|
-
# Configuration
|
|
18
|
+
# Configuration - use .replit-tools structure
|
|
19
19
|
WORKSPACE="/home/runner/workspace"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
REPLIT_TOOLS="${WORKSPACE}/.replit-tools"
|
|
21
|
+
|
|
22
|
+
# Allow env vars to override (for custom config locations)
|
|
23
|
+
CLAUDE_PERSISTENT="${CLAUDE_CONFIG_DIR:-${REPLIT_TOOLS}/.claude-persistent}"
|
|
24
|
+
CLAUDE_VERSIONS="${REPLIT_TOOLS}/.claude-versions"
|
|
25
|
+
LOGS_DIR="${REPLIT_TOOLS}/.logs"
|
|
26
|
+
SCRIPTS_DIR="${REPLIT_TOOLS}/scripts"
|
|
27
|
+
AUTH_REFRESH_SCRIPT="${SCRIPTS_DIR}/claude-auth-refresh.sh"
|
|
24
28
|
|
|
25
29
|
# Target locations (ephemeral, need symlinks)
|
|
26
30
|
CLAUDE_SYMLINK="${HOME}/.claude"
|
|
@@ -41,7 +45,7 @@ mkdir -p "${CLAUDE_PERSISTENT}"
|
|
|
41
45
|
mkdir -p "${CLAUDE_VERSIONS}"
|
|
42
46
|
mkdir -p "${LOCAL_BIN}"
|
|
43
47
|
mkdir -p "${HOME}/.local/share"
|
|
44
|
-
mkdir -p "${
|
|
48
|
+
mkdir -p "${LOGS_DIR}"
|
|
45
49
|
|
|
46
50
|
# =============================================================================
|
|
47
51
|
# Step 2: Create ~/.claude symlink for conversation history & credentials
|
|
@@ -55,10 +59,13 @@ fi
|
|
|
55
59
|
# =============================================================================
|
|
56
60
|
# Step 3: Create ~/.local/share/claude symlink for installed versions
|
|
57
61
|
# =============================================================================
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
SHARE_VERSIONS="${LOCAL_SHARE_CLAUDE}/versions"
|
|
63
|
+
mkdir -p "${LOCAL_SHARE_CLAUDE}"
|
|
64
|
+
|
|
65
|
+
if [ ! -L "${SHARE_VERSIONS}" ] || [ "$(readlink -f "${SHARE_VERSIONS}")" != "${CLAUDE_VERSIONS}" ]; then
|
|
66
|
+
rm -rf "${SHARE_VERSIONS}" 2>/dev/null || true
|
|
67
|
+
ln -sf "${CLAUDE_VERSIONS}" "${SHARE_VERSIONS}"
|
|
68
|
+
log "✅ Claude versions symlink: ~/.local/share/claude/versions -> ${CLAUDE_VERSIONS}"
|
|
62
69
|
fi
|
|
63
70
|
|
|
64
71
|
# =============================================================================
|
|
@@ -84,10 +91,13 @@ else
|
|
|
84
91
|
|
|
85
92
|
# Install Claude Code using the official installer
|
|
86
93
|
if curl -fsSL https://claude.ai/install.sh | bash 2>/dev/null; then
|
|
87
|
-
# After install,
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
# After install, copy to our versions directory
|
|
95
|
+
DEFAULT_VERSIONS="${HOME}/.local/share/claude/versions"
|
|
96
|
+
if [ -d "${DEFAULT_VERSIONS}" ]; then
|
|
97
|
+
LATEST_VERSION=$(ls -1 "${DEFAULT_VERSIONS}" 2>/dev/null | grep -v '^\.' | sort -V | tail -n1)
|
|
90
98
|
if [ -n "${LATEST_VERSION}" ]; then
|
|
99
|
+
cp -p "${DEFAULT_VERSIONS}/${LATEST_VERSION}" "${CLAUDE_VERSIONS}/${LATEST_VERSION}" 2>/dev/null || true
|
|
100
|
+
chmod 755 "${CLAUDE_VERSIONS}/${LATEST_VERSION}" 2>/dev/null || true
|
|
91
101
|
ln -sf "${CLAUDE_VERSIONS}/${LATEST_VERSION}" "${LOCAL_BIN}/claude"
|
|
92
102
|
log "✅ Claude Code ${LATEST_VERSION} installed"
|
|
93
103
|
fi
|