replit-tools 1.2.40 → 1.2.41

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replit-tools",
3
- "version": "1.2.40",
3
+ "version": "1.2.41",
4
4
  "description": "DATA Tools - One command to set up Claude Code and Codex CLI on Replit with full persistence",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -301,16 +301,25 @@ get_terminal_last_session() {
301
301
  fi
302
302
  }
303
303
 
304
- # Recent sessions within last 24h (max 9). Prints "NUM|TOOL|ID|SNIPPET" per line.
304
+ # Recent sessions within configured window (max 9). Prints "NUM|TOOL|ID|AGO|SNIPPET" per line.
305
+ # Window read from ${REPLIT_TOOLS}/config.json: recentWindowHours (default 48)
305
306
  get_recent_24h_sessions() {
306
307
  local history="${HOME}/.claude/history.jsonl"
307
308
  local projects_dir="${HOME}/.claude/projects/-home-runner-workspace"
308
309
  local codex_sessions_dir="${HOME}/.codex/sessions"
310
+ local config_path="${REPLIT_TOOLS}/config.json"
309
311
 
310
312
  node -e "
311
313
  const fs = require('fs');
312
314
  const path = require('path');
313
- const cutoff = Date.now() - 24*60*60*1000;
315
+ let hours = 48;
316
+ try {
317
+ if (fs.existsSync('${config_path}')) {
318
+ const cfg = JSON.parse(fs.readFileSync('${config_path}', 'utf8'));
319
+ if (typeof cfg.recentWindowHours === 'number' && cfg.recentWindowHours > 0) hours = cfg.recentWindowHours;
320
+ }
321
+ } catch(e) {}
322
+ const cutoff = Date.now() - hours*60*60*1000;
314
323
  const cwd = '/home/runner/workspace';
315
324
  const sessions = new Map();
316
325
 
@@ -563,8 +572,25 @@ claude_prompt() {
563
572
  local recent_tools=()
564
573
  local recent_ids=()
565
574
  if [ -n "$recent_24h" ]; then
575
+ # Read configured window for label
576
+ local window_hours=48
577
+ if [ -f "${REPLIT_TOOLS}/config.json" ] && command -v node &>/dev/null; then
578
+ window_hours=$(node -e "try{const c=require('${REPLIT_TOOLS}/config.json');console.log(c.recentWindowHours||48)}catch(e){console.log(48)}" 2>/dev/null)
579
+ fi
580
+ local window_label
581
+ if [ "$window_hours" -ge 8760 ]; then
582
+ window_label="$((window_hours / 8760))y"
583
+ elif [ "$window_hours" -ge 720 ]; then
584
+ window_label="$((window_hours / 720))mo"
585
+ elif [ "$window_hours" -ge 168 ]; then
586
+ window_label="$((window_hours / 168))w"
587
+ elif [ "$window_hours" -ge 24 ]; then
588
+ window_label="$((window_hours / 24))d"
589
+ else
590
+ window_label="${window_hours}h"
591
+ fi
566
592
  echo ""
567
- echo -e " \033[1mRecent (last 24h):\033[0m"
593
+ echo -e " \033[1mRecent (last $window_label):\033[0m"
568
594
  while IFS='|' read -r num tool id when snippet; do
569
595
  [ -z "$num" ] && continue
570
596
  recent_tools[$num]="$tool"
@@ -39,7 +39,7 @@ LOCAL_SHARE_CLAUDE="${HOME}/.local/share/claude"
39
39
 
40
40
  # Version file
41
41
  VERSION_FILE="${REPLIT_TOOLS}/.version"
42
- PACKAGE_NAME="replit-tools"
42
+ PACKAGE_NAME="data-remote"
43
43
 
44
44
  # Logging helper
45
45
  log() {
@@ -171,54 +171,121 @@ for f in "${SSH_PERSISTENT}"/*; do
171
171
  done
172
172
 
173
173
  # =============================================================================
174
- # Step 2.6: Force forever-persistence settings for Claude + Codex
174
+ # Step 2.6: Apply user-config persistence + sync append-only mirror archive
175
175
  # =============================================================================
176
- # Claude: cleanupPeriodDays = 365250 (~1000 years) so sessions never auto-delete
177
- # Codex: history.max_bytes = 1 TiB so history.jsonl never rotates
178
- # (session rollouts under ~/.codex/sessions/ are already kept forever)
176
+ # Config at ${REPLIT_TOOLS}/config.json:
177
+ # {
178
+ # "recentWindowHours": 48, // recent sessions list window
179
+ # "persistenceDays": 365250, // Claude cleanupPeriodDays + Codex history bytes
180
+ # "mirror": { "enabled": true } // append-only backup mirror of sessions
181
+ # }
182
+ # The mirror is at ${REPLIT_TOOLS}/.session-archive/ — append-only: files only grow,
183
+ # never shrink. If Claude/Codex deletes a session, the mirror still has it.
179
184
  if command -v node &>/dev/null; then
180
- PERSIST_OUTPUT=$(CLAUDE_PERSISTENT_DIR="${CLAUDE_PERSISTENT}" CODEX_PERSISTENT_DIR="${CODEX_PERSISTENT}" node -e '
185
+ PERSIST_OUTPUT=$(REPLIT_TOOLS_DIR="${REPLIT_TOOLS}" CLAUDE_PERSISTENT_DIR="${CLAUDE_PERSISTENT}" CODEX_PERSISTENT_DIR="${CODEX_PERSISTENT}" node -e '
181
186
  const fs = require("fs");
187
+ const path = require("path");
188
+
189
+ // Load config with defaults
190
+ const configPath = process.env.REPLIT_TOOLS_DIR + "/config.json";
191
+ const defaults = { recentWindowHours: 48, persistenceDays: 365250, mirror: { enabled: true } };
192
+ let config = defaults;
193
+ try {
194
+ if (fs.existsSync(configPath)) {
195
+ const loaded = JSON.parse(fs.readFileSync(configPath, "utf8"));
196
+ config = { ...defaults, ...loaded, mirror: { ...defaults.mirror, ...(loaded.mirror || {}) } };
197
+ } else {
198
+ fs.writeFileSync(configPath, JSON.stringify(defaults, null, 2) + "\n");
199
+ console.log("Created config at " + configPath);
200
+ }
201
+ } catch(e) { config = defaults; }
202
+
203
+ const persistDays = Math.max(1, parseInt(config.persistenceDays, 10) || 365250);
204
+ // Claude needs days; Codex max_bytes scales with days (rough: 1 MiB per day, min 100 MiB)
205
+ const codexMaxBytes = Math.max(104857600, persistDays * 1048576);
206
+
207
+ // --- Claude settings.json ---
182
208
  const claudeSettingsPath = process.env.CLAUDE_PERSISTENT_DIR + "/settings.json";
183
209
  try {
184
210
  let s = {};
185
211
  if (fs.existsSync(claudeSettingsPath)) {
186
212
  try { s = JSON.parse(fs.readFileSync(claudeSettingsPath, "utf8")); } catch(e) { s = {}; }
187
213
  }
188
- if (s.cleanupPeriodDays !== 365250) {
189
- s.cleanupPeriodDays = 365250;
214
+ if (s.cleanupPeriodDays !== persistDays) {
215
+ s.cleanupPeriodDays = persistDays;
190
216
  fs.writeFileSync(claudeSettingsPath, JSON.stringify(s, null, 2) + "\n");
191
- console.log("Claude cleanupPeriodDays set to 365250 (~1000 years)");
217
+ console.log("Claude cleanupPeriodDays = " + persistDays);
192
218
  }
193
219
  } catch(e) { console.error("Could not update Claude settings: " + e.message); }
194
220
 
221
+ // --- Codex config.toml ---
195
222
  const codexConfigPath = process.env.CODEX_PERSISTENT_DIR + "/config.toml";
196
223
  try {
197
224
  let c = "";
198
225
  if (fs.existsSync(codexConfigPath)) c = fs.readFileSync(codexConfigPath, "utf8");
199
- const desired = "1099511627776"; // 1 TiB
200
- const hasHistory = /\[history\]/.test(c);
226
+ const desired = String(codexMaxBytes);
201
227
  let updated = false;
202
- if (!hasHistory) {
228
+ if (!/\[history\]/.test(c)) {
203
229
  c = (c.trimEnd() + "\n\n[history]\nmax_bytes = " + desired + "\n").trimStart();
204
230
  updated = true;
205
- } else {
206
- const m = c.match(/(\[history\][\s\S]*?)max_bytes\s*=\s*(\d+)/);
207
- if (m) {
208
- if (m[2] !== desired) {
209
- c = c.replace(/(\[history\][\s\S]*?max_bytes\s*=\s*)\d+/, "$1" + desired);
210
- updated = true;
211
- }
212
- } else {
213
- c = c.replace(/\[history\](\s*)/, "[history]$1max_bytes = " + desired + "\n");
231
+ } else if (/max_bytes\s*=\s*(\d+)/.test(c)) {
232
+ const cur = c.match(/max_bytes\s*=\s*(\d+)/)[1];
233
+ if (cur !== desired) {
234
+ c = c.replace(/(\[history\][\s\S]*?max_bytes\s*=\s*)\d+/, "$1" + desired);
214
235
  updated = true;
215
236
  }
237
+ } else {
238
+ c = c.replace(/\[history\](\s*)/, "[history]$1max_bytes = " + desired + "\n");
239
+ updated = true;
216
240
  }
217
241
  if (updated) {
218
242
  fs.writeFileSync(codexConfigPath, c);
219
- console.log("Codex history.max_bytes set to 1 TiB");
243
+ console.log("Codex history.max_bytes = " + desired);
220
244
  }
221
245
  } catch(e) { console.error("Could not update Codex config: " + e.message); }
246
+
247
+ // --- Append-only mirror sync ---
248
+ if (config.mirror && config.mirror.enabled) {
249
+ const mirrorBase = process.env.REPLIT_TOOLS_DIR + "/.session-archive";
250
+ const syncTree = (srcDir, mirrorDir) => {
251
+ if (!fs.existsSync(srcDir)) return { copied: 0, grew: 0 };
252
+ let copied = 0, grew = 0;
253
+ const walk = (rel) => {
254
+ const srcPath = rel ? path.join(srcDir, rel) : srcDir;
255
+ const mirrorPath = rel ? path.join(mirrorDir, rel) : mirrorDir;
256
+ let stat;
257
+ try { stat = fs.statSync(srcPath); } catch(e) { return; }
258
+ if (stat.isDirectory()) {
259
+ try { fs.mkdirSync(mirrorPath, { recursive: true }); } catch(e){}
260
+ let entries = [];
261
+ try { entries = fs.readdirSync(srcPath); } catch(e) { return; }
262
+ for (const e of entries) walk(rel ? path.join(rel, e) : e);
263
+ } else if (stat.isFile()) {
264
+ let mirrorSize = 0;
265
+ if (fs.existsSync(mirrorPath)) mirrorSize = fs.statSync(mirrorPath).size;
266
+ if (stat.size > mirrorSize) {
267
+ try {
268
+ fs.mkdirSync(path.dirname(mirrorPath), { recursive: true });
269
+ fs.copyFileSync(srcPath, mirrorPath);
270
+ if (mirrorSize === 0) copied++; else grew++;
271
+ } catch(e){}
272
+ }
273
+ }
274
+ };
275
+ walk("");
276
+ return { copied, grew };
277
+ };
278
+ try {
279
+ fs.mkdirSync(mirrorBase, { recursive: true });
280
+ const c1 = syncTree(process.env.CLAUDE_PERSISTENT_DIR + "/projects", mirrorBase + "/claude/projects");
281
+ const c2 = syncTree(process.env.CLAUDE_PERSISTENT_DIR + "/history.jsonl", mirrorBase + "/claude/history.jsonl");
282
+ const c3 = syncTree(process.env.CODEX_PERSISTENT_DIR + "/sessions", mirrorBase + "/codex/sessions");
283
+ const c4 = syncTree(process.env.CODEX_PERSISTENT_DIR + "/history.jsonl", mirrorBase + "/codex/history.jsonl");
284
+ const total = c1.copied + c2.copied + c3.copied + c4.copied;
285
+ const grew = c1.grew + c2.grew + c3.grew + c4.grew;
286
+ if (total > 0 || grew > 0) console.log("Archive mirror: +" + total + " new, " + grew + " updated");
287
+ } catch(e) { console.error("Mirror sync failed: " + e.message); }
288
+ }
222
289
  ' 2>&1)
223
290
  if [ -n "${PERSIST_OUTPUT}" ]; then
224
291
  while IFS= read -r line; do log "✅ ${line}"; done <<< "${PERSIST_OUTPUT}"