replit-tools 1.2.39 → 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.39",
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() {
@@ -170,6 +170,128 @@ for f in "${SSH_PERSISTENT}"/*; do
170
170
  esac
171
171
  done
172
172
 
173
+ # =============================================================================
174
+ # Step 2.6: Apply user-config persistence + sync append-only mirror archive
175
+ # =============================================================================
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.
184
+ if command -v node &>/dev/null; then
185
+ PERSIST_OUTPUT=$(REPLIT_TOOLS_DIR="${REPLIT_TOOLS}" CLAUDE_PERSISTENT_DIR="${CLAUDE_PERSISTENT}" CODEX_PERSISTENT_DIR="${CODEX_PERSISTENT}" node -e '
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 ---
208
+ const claudeSettingsPath = process.env.CLAUDE_PERSISTENT_DIR + "/settings.json";
209
+ try {
210
+ let s = {};
211
+ if (fs.existsSync(claudeSettingsPath)) {
212
+ try { s = JSON.parse(fs.readFileSync(claudeSettingsPath, "utf8")); } catch(e) { s = {}; }
213
+ }
214
+ if (s.cleanupPeriodDays !== persistDays) {
215
+ s.cleanupPeriodDays = persistDays;
216
+ fs.writeFileSync(claudeSettingsPath, JSON.stringify(s, null, 2) + "\n");
217
+ console.log("Claude cleanupPeriodDays = " + persistDays);
218
+ }
219
+ } catch(e) { console.error("Could not update Claude settings: " + e.message); }
220
+
221
+ // --- Codex config.toml ---
222
+ const codexConfigPath = process.env.CODEX_PERSISTENT_DIR + "/config.toml";
223
+ try {
224
+ let c = "";
225
+ if (fs.existsSync(codexConfigPath)) c = fs.readFileSync(codexConfigPath, "utf8");
226
+ const desired = String(codexMaxBytes);
227
+ let updated = false;
228
+ if (!/\[history\]/.test(c)) {
229
+ c = (c.trimEnd() + "\n\n[history]\nmax_bytes = " + desired + "\n").trimStart();
230
+ updated = true;
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);
235
+ updated = true;
236
+ }
237
+ } else {
238
+ c = c.replace(/\[history\](\s*)/, "[history]$1max_bytes = " + desired + "\n");
239
+ updated = true;
240
+ }
241
+ if (updated) {
242
+ fs.writeFileSync(codexConfigPath, c);
243
+ console.log("Codex history.max_bytes = " + desired);
244
+ }
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
+ }
289
+ ' 2>&1)
290
+ if [ -n "${PERSIST_OUTPUT}" ]; then
291
+ while IFS= read -r line; do log "✅ ${line}"; done <<< "${PERSIST_OUTPUT}"
292
+ fi
293
+ fi
294
+
173
295
  # =============================================================================
174
296
  # Step 3: Create ~/.local/share/claude symlink for installed versions
175
297
  # =============================================================================