replit-tools 1.0.10 → 1.1.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/README.md +61 -37
- package/index.js +215 -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 +37 -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) {
|
|
@@ -266,6 +350,28 @@ function main() {
|
|
|
266
350
|
} else {
|
|
267
351
|
const version = claudeVersions.sort().pop() || 'installed';
|
|
268
352
|
console.log(`✅ Claude Code already installed (${version})`);
|
|
353
|
+
|
|
354
|
+
// Ensure binary is in our versions directory
|
|
355
|
+
const defaultVersionsDir = path.join(HOME, '.local/share/claude/versions');
|
|
356
|
+
const sourceDirs = [defaultVersionsDir, oldLocations.versions];
|
|
357
|
+
|
|
358
|
+
for (const sourceDir of sourceDirs) {
|
|
359
|
+
try {
|
|
360
|
+
if (fs.existsSync(sourceDir)) {
|
|
361
|
+
const versions = fs.readdirSync(sourceDir).filter(f => !f.startsWith('.'));
|
|
362
|
+
versions.forEach(v => {
|
|
363
|
+
const src = path.join(sourceDir, v);
|
|
364
|
+
const dest = path.join(claudeVersionsDir, v);
|
|
365
|
+
if (!fs.existsSync(dest) && fs.existsSync(src)) {
|
|
366
|
+
try {
|
|
367
|
+
fs.copyFileSync(src, dest);
|
|
368
|
+
fs.chmodSync(dest, '755');
|
|
369
|
+
} catch {}
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
} catch {}
|
|
374
|
+
}
|
|
269
375
|
}
|
|
270
376
|
|
|
271
377
|
// ═══════════════════════════════════════════════════════════════════
|
|
@@ -282,7 +388,7 @@ function main() {
|
|
|
282
388
|
};
|
|
283
389
|
const result = safeExec('npm i -g @openai/codex', {
|
|
284
390
|
env: installEnv,
|
|
285
|
-
timeout: 180000,
|
|
391
|
+
timeout: 180000,
|
|
286
392
|
showOutput: true
|
|
287
393
|
});
|
|
288
394
|
|
|
@@ -305,7 +411,7 @@ function main() {
|
|
|
305
411
|
console.log('');
|
|
306
412
|
console.log('🔗 Setting up symlinks...');
|
|
307
413
|
|
|
308
|
-
// Claude symlink
|
|
414
|
+
// Claude config symlink (~/.claude -> our persistent dir)
|
|
309
415
|
const claudeLink = path.join(HOME, '.claude');
|
|
310
416
|
try {
|
|
311
417
|
let needsLink = false;
|
|
@@ -330,12 +436,13 @@ function main() {
|
|
|
330
436
|
if (needsLink) {
|
|
331
437
|
fs.symlinkSync(claudePersistentDir, claudeLink);
|
|
332
438
|
}
|
|
333
|
-
|
|
439
|
+
const displayPath = claudePersistentDir.replace(WORKSPACE + '/', '');
|
|
440
|
+
console.log(` ~/.claude → ${displayPath}/`);
|
|
334
441
|
} catch (err) {
|
|
335
442
|
console.log(` ⚠️ Could not create Claude symlink: ${err.message}`);
|
|
336
443
|
}
|
|
337
444
|
|
|
338
|
-
// Codex symlink
|
|
445
|
+
// Codex config symlink (~/.codex -> our persistent dir)
|
|
339
446
|
const codexLink = path.join(HOME, '.codex');
|
|
340
447
|
try {
|
|
341
448
|
let needsLink = false;
|
|
@@ -360,7 +467,8 @@ function main() {
|
|
|
360
467
|
if (needsLink) {
|
|
361
468
|
fs.symlinkSync(codexPersistentDir, codexLink);
|
|
362
469
|
}
|
|
363
|
-
|
|
470
|
+
const displayPath = codexPersistentDir.replace(WORKSPACE + '/', '');
|
|
471
|
+
console.log(` ~/.codex → ${displayPath}/`);
|
|
364
472
|
} catch (err) {
|
|
365
473
|
console.log(` ⚠️ Could not create Codex symlink: ${err.message}`);
|
|
366
474
|
}
|
|
@@ -368,45 +476,51 @@ function main() {
|
|
|
368
476
|
// Claude binary symlinks
|
|
369
477
|
const localBin = path.join(HOME, '.local/bin');
|
|
370
478
|
const localShare = path.join(HOME, '.local/share');
|
|
371
|
-
const claudeShareTarget = path.join(WORKSPACE, '.local/share/claude');
|
|
372
479
|
|
|
373
480
|
try { fs.mkdirSync(localBin, { recursive: true }); } catch {}
|
|
374
481
|
try { fs.mkdirSync(localShare, { recursive: true }); } catch {}
|
|
375
482
|
|
|
376
|
-
// Link .local/share/claude
|
|
483
|
+
// Link .local/share/claude to our versions directory's parent
|
|
484
|
+
const claudeShareTarget = path.join(REPLIT_TOOLS, '.claude-versions');
|
|
377
485
|
try {
|
|
378
|
-
|
|
486
|
+
// Create a wrapper directory structure for compatibility
|
|
487
|
+
const shareClaudeDir = path.join(localShare, 'claude');
|
|
488
|
+
const shareVersionsDir = path.join(shareClaudeDir, 'versions');
|
|
489
|
+
|
|
490
|
+
if (!fs.existsSync(shareClaudeDir)) {
|
|
491
|
+
fs.mkdirSync(shareClaudeDir, { recursive: true });
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Symlink versions dir
|
|
379
495
|
try {
|
|
380
|
-
const stat = fs.lstatSync(
|
|
496
|
+
const stat = fs.lstatSync(shareVersionsDir);
|
|
381
497
|
if (stat.isSymbolicLink()) {
|
|
382
|
-
const current = fs.readlinkSync(
|
|
383
|
-
if (current !==
|
|
384
|
-
fs.unlinkSync(
|
|
385
|
-
|
|
498
|
+
const current = fs.readlinkSync(shareVersionsDir);
|
|
499
|
+
if (current !== claudeVersionsDir) {
|
|
500
|
+
fs.unlinkSync(shareVersionsDir);
|
|
501
|
+
fs.symlinkSync(claudeVersionsDir, shareVersionsDir);
|
|
386
502
|
}
|
|
387
503
|
}
|
|
388
504
|
} catch {
|
|
389
|
-
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
if (needsLink) {
|
|
393
|
-
fs.symlinkSync(claudeShareTarget, path.join(localShare, 'claude'));
|
|
505
|
+
fs.symlinkSync(claudeVersionsDir, shareVersionsDir);
|
|
394
506
|
}
|
|
395
|
-
console.log(
|
|
507
|
+
console.log(` ~/.local/share/claude/versions → .replit-tools/.claude-versions/`);
|
|
396
508
|
} catch {}
|
|
397
509
|
|
|
398
510
|
// Link binary to latest version
|
|
399
511
|
try {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
.sort();
|
|
512
|
+
let versions = [];
|
|
513
|
+
try {
|
|
514
|
+
versions = fs.readdirSync(claudeVersionsDir).filter(f => !f.startsWith('.')).sort();
|
|
515
|
+
} catch {}
|
|
516
|
+
|
|
403
517
|
if (versions.length > 0) {
|
|
404
518
|
const latest = versions[versions.length - 1];
|
|
405
|
-
const binaryPath = path.join(
|
|
519
|
+
const binaryPath = path.join(claudeVersionsDir, latest);
|
|
406
520
|
const binLink = path.join(localBin, 'claude');
|
|
407
521
|
try { fs.unlinkSync(binLink); } catch {}
|
|
408
522
|
fs.symlinkSync(binaryPath, binLink);
|
|
409
|
-
console.log(` ~/.local/bin/claude → versions/${latest}`);
|
|
523
|
+
console.log(` ~/.local/bin/claude → .replit-tools/.claude-versions/${latest}`);
|
|
410
524
|
}
|
|
411
525
|
} catch {}
|
|
412
526
|
|
|
@@ -414,16 +528,15 @@ function main() {
|
|
|
414
528
|
// COPY SCRIPTS
|
|
415
529
|
// ═══════════════════════════════════════════════════════════════════
|
|
416
530
|
|
|
417
|
-
const
|
|
418
|
-
const targetScriptsDir = path.join(WORKSPACE, 'scripts');
|
|
531
|
+
const packageScriptsDir = path.join(__dirname, 'scripts');
|
|
419
532
|
|
|
420
533
|
console.log('');
|
|
421
534
|
console.log('📝 Installing scripts...');
|
|
422
535
|
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
const srcPath = path.join(
|
|
426
|
-
const destPath = path.join(
|
|
536
|
+
const scriptFiles = ['setup-claude-code.sh', 'claude-session-manager.sh', 'claude-auth-refresh.sh'];
|
|
537
|
+
scriptFiles.forEach(script => {
|
|
538
|
+
const srcPath = path.join(packageScriptsDir, script);
|
|
539
|
+
const destPath = path.join(scriptsDir, script);
|
|
427
540
|
|
|
428
541
|
if (fs.existsSync(srcPath)) {
|
|
429
542
|
try {
|
|
@@ -445,7 +558,10 @@ function main() {
|
|
|
445
558
|
|
|
446
559
|
const bashrcContent = `#!/bin/bash
|
|
447
560
|
# DATA Tools - Replit Claude & Codex Persistence
|
|
448
|
-
# Auto-generated bashrc
|
|
561
|
+
# Auto-generated bashrc - v2.0 (.replit-tools structure)
|
|
562
|
+
|
|
563
|
+
# Base directory for all DATA Tools data
|
|
564
|
+
export REPLIT_TOOLS_DIR="${REPLIT_TOOLS}"
|
|
449
565
|
|
|
450
566
|
# Claude Config Directory (tells Claude where to store data)
|
|
451
567
|
export CLAUDE_CONFIG_DIR="${claudePersistentDir}"
|
|
@@ -455,7 +571,7 @@ export CLAUDE_WORKSPACE_DIR="${claudePersistentDir}"
|
|
|
455
571
|
export CODEX_HOME="${codexPersistentDir}"
|
|
456
572
|
|
|
457
573
|
# Claude Code Setup
|
|
458
|
-
SETUP_SCRIPT="/
|
|
574
|
+
SETUP_SCRIPT="${scriptsDir}/setup-claude-code.sh"
|
|
459
575
|
[ -f "\${SETUP_SCRIPT}" ] && source "\${SETUP_SCRIPT}"
|
|
460
576
|
|
|
461
577
|
# Codex Persistence
|
|
@@ -463,7 +579,7 @@ mkdir -p "${codexPersistentDir}"
|
|
|
463
579
|
[ ! -L "\${HOME}/.codex" ] && ln -sf "${codexPersistentDir}" "\${HOME}/.codex"
|
|
464
580
|
|
|
465
581
|
# Bash History Persistence
|
|
466
|
-
PERSISTENT_HOME="
|
|
582
|
+
PERSISTENT_HOME="${persistentHomeDir}"
|
|
467
583
|
mkdir -p "\${PERSISTENT_HOME}"
|
|
468
584
|
export HISTFILE="\${PERSISTENT_HOME}/.bash_history"
|
|
469
585
|
export HISTSIZE=10000
|
|
@@ -472,7 +588,7 @@ export HISTCONTROL=ignoredups
|
|
|
472
588
|
[ -f "\${HISTFILE}" ] && history -r "\${HISTFILE}"
|
|
473
589
|
|
|
474
590
|
# Session Manager (interactive menu)
|
|
475
|
-
SESSION_MANAGER="/
|
|
591
|
+
SESSION_MANAGER="${scriptsDir}/claude-session-manager.sh"
|
|
476
592
|
[ -f "\${SESSION_MANAGER}" ] && source "\${SESSION_MANAGER}"
|
|
477
593
|
|
|
478
594
|
# Aliases
|
|
@@ -493,15 +609,17 @@ alias claude-pick='claude -r --dangerously-skip-permissions'
|
|
|
493
609
|
|
|
494
610
|
console.log('📝 Updating .replit configuration...');
|
|
495
611
|
const replitPath = path.join(WORKSPACE, '.replit');
|
|
496
|
-
const onBootLine =
|
|
612
|
+
const onBootLine = `onBoot = "source ${scriptsDir}/setup-claude-code.sh 2>/dev/null || true"`;
|
|
497
613
|
|
|
498
614
|
try {
|
|
499
615
|
if (fs.existsSync(replitPath)) {
|
|
500
616
|
let content = fs.readFileSync(replitPath, 'utf8');
|
|
617
|
+
// Remove old onBoot line if present
|
|
618
|
+
content = content.replace(/onBoot\s*=\s*"[^"]*setup-claude-code\.sh[^"]*"\n?/g, '');
|
|
501
619
|
if (!content.includes('setup-claude-code.sh')) {
|
|
502
620
|
content += '\n\n# Claude persistence (added by DATA Tools)\n' + onBootLine + '\n';
|
|
503
|
-
fs.writeFileSync(replitPath, content);
|
|
504
621
|
}
|
|
622
|
+
fs.writeFileSync(replitPath, content);
|
|
505
623
|
} else {
|
|
506
624
|
fs.writeFileSync(replitPath, '# Claude persistence (DATA Tools)\n' + onBootLine + '\n');
|
|
507
625
|
}
|
|
@@ -516,18 +634,14 @@ alias claude-pick='claude -r --dangerously-skip-permissions'
|
|
|
516
634
|
console.log('📝 Updating .gitignore...');
|
|
517
635
|
const gitignorePath = path.join(WORKSPACE, '.gitignore');
|
|
518
636
|
const gitignoreEntries = `
|
|
519
|
-
#
|
|
520
|
-
.
|
|
521
|
-
.codex-persistent/
|
|
522
|
-
.claude-sessions/
|
|
523
|
-
.persistent-home/
|
|
524
|
-
logs/auth-refresh.log
|
|
637
|
+
# DATA Tools - All sensitive data in one place (added by DATA Tools)
|
|
638
|
+
.replit-tools/
|
|
525
639
|
`;
|
|
526
640
|
|
|
527
641
|
try {
|
|
528
642
|
if (fs.existsSync(gitignorePath)) {
|
|
529
643
|
let content = fs.readFileSync(gitignorePath, 'utf8');
|
|
530
|
-
if (!content.includes('.
|
|
644
|
+
if (!content.includes('.replit-tools/')) {
|
|
531
645
|
fs.writeFileSync(gitignorePath, content + gitignoreEntries);
|
|
532
646
|
}
|
|
533
647
|
} else {
|
|
@@ -545,6 +659,7 @@ logs/auth-refresh.log
|
|
|
545
659
|
process.env.CLAUDE_CONFIG_DIR = claudePersistentDir;
|
|
546
660
|
process.env.CLAUDE_WORKSPACE_DIR = claudePersistentDir;
|
|
547
661
|
process.env.CODEX_HOME = codexPersistentDir;
|
|
662
|
+
process.env.REPLIT_TOOLS_DIR = REPLIT_TOOLS;
|
|
548
663
|
|
|
549
664
|
// ═══════════════════════════════════════════════════════════════════
|
|
550
665
|
// SHOW COMPLETION MESSAGE
|
|
@@ -566,8 +681,12 @@ logs/auth-refresh.log
|
|
|
566
681
|
console.log('║ ║');
|
|
567
682
|
console.log('╠═════════════════════════════════════════════════════════════╣');
|
|
568
683
|
console.log('║ ║');
|
|
569
|
-
console.log(
|
|
570
|
-
console.log(
|
|
684
|
+
console.log('║ All data stored in: .replit-tools/ ║');
|
|
685
|
+
console.log('║ ║');
|
|
686
|
+
const claudeDisplay = claudePersistentDir.replace(WORKSPACE + '/', '').padEnd(40);
|
|
687
|
+
const codexDisplay = codexPersistentDir.replace(WORKSPACE + '/', '').padEnd(40);
|
|
688
|
+
console.log(`║ Claude: ${claudeDisplay} ║`);
|
|
689
|
+
console.log(`║ Codex: ${codexDisplay} ║`);
|
|
571
690
|
console.log('║ ║');
|
|
572
691
|
console.log('╚═════════════════════════════════════════════════════════════╝');
|
|
573
692
|
console.log('');
|
|
@@ -598,7 +717,6 @@ logs/auth-refresh.log
|
|
|
598
717
|
console.log('Launching session manager...');
|
|
599
718
|
console.log('');
|
|
600
719
|
|
|
601
|
-
// Use spawn to run bash interactively with our session manager
|
|
602
720
|
const sessionManager = spawn('bash', ['--rcfile', path.join(WORKSPACE, '.config/bashrc'), '-i'], {
|
|
603
721
|
stdio: 'inherit',
|
|
604
722
|
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,15 +59,33 @@ 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
|
# =============================================================================
|
|
65
72
|
# Step 4: Find latest Claude version and create binary symlink
|
|
66
73
|
# =============================================================================
|
|
74
|
+
|
|
75
|
+
# First, sync any versions from default location to our directory
|
|
76
|
+
DEFAULT_VERSIONS="${HOME}/.local/share/claude/versions"
|
|
77
|
+
if [ -d "${DEFAULT_VERSIONS}" ]; then
|
|
78
|
+
for version_file in "${DEFAULT_VERSIONS}"/*; do
|
|
79
|
+
if [ -f "${version_file}" ]; then
|
|
80
|
+
version_name=$(basename "${version_file}")
|
|
81
|
+
if [ ! -f "${CLAUDE_VERSIONS}/${version_name}" ]; then
|
|
82
|
+
cp -p "${version_file}" "${CLAUDE_VERSIONS}/${version_name}" 2>/dev/null || true
|
|
83
|
+
chmod 755 "${CLAUDE_VERSIONS}/${version_name}" 2>/dev/null || true
|
|
84
|
+
fi
|
|
85
|
+
fi
|
|
86
|
+
done
|
|
87
|
+
fi
|
|
88
|
+
|
|
67
89
|
LATEST_VERSION=""
|
|
68
90
|
if [ -d "${CLAUDE_VERSIONS}" ]; then
|
|
69
91
|
LATEST_VERSION=$(ls -1 "${CLAUDE_VERSIONS}" 2>/dev/null | grep -v '^\.' | sort -V | tail -n1)
|
|
@@ -84,10 +106,12 @@ else
|
|
|
84
106
|
|
|
85
107
|
# Install Claude Code using the official installer
|
|
86
108
|
if curl -fsSL https://claude.ai/install.sh | bash 2>/dev/null; then
|
|
87
|
-
# After install,
|
|
88
|
-
if [ -d "${
|
|
89
|
-
LATEST_VERSION=$(ls -1 "${
|
|
109
|
+
# After install, copy to our versions directory
|
|
110
|
+
if [ -d "${DEFAULT_VERSIONS}" ]; then
|
|
111
|
+
LATEST_VERSION=$(ls -1 "${DEFAULT_VERSIONS}" 2>/dev/null | grep -v '^\.' | sort -V | tail -n1)
|
|
90
112
|
if [ -n "${LATEST_VERSION}" ]; then
|
|
113
|
+
cp -p "${DEFAULT_VERSIONS}/${LATEST_VERSION}" "${CLAUDE_VERSIONS}/${LATEST_VERSION}" 2>/dev/null || true
|
|
114
|
+
chmod 755 "${CLAUDE_VERSIONS}/${LATEST_VERSION}" 2>/dev/null || true
|
|
91
115
|
ln -sf "${CLAUDE_VERSIONS}/${LATEST_VERSION}" "${LOCAL_BIN}/claude"
|
|
92
116
|
log "✅ Claude Code ${LATEST_VERSION} installed"
|
|
93
117
|
fi
|