aegiscode 6.1.0 → 6.2.0

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.
@@ -0,0 +1,85 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Transcript checkpoints for /rewind. The session loop snapshots the
5
+ * transcript after every exchange into ~/.aegiscode/checkpoints/<session>.jsonl
6
+ * (bounded to the last CHECKPOINT_LIMIT), and /rewind lists or restores them.
7
+ * Checkpoint 0 is the session start (empty transcript).
8
+ *
9
+ * Ported from aegiscodex-dev/src/checkpoint.js (ESM → CommonJS). The data dir
10
+ * now comes from config.js's shared `aegisDir()` helper.
11
+ */
12
+
13
+ const fs = require('node:fs');
14
+ const path = require('node:path');
15
+ const { aegisDir } = require('./config.js');
16
+
17
+ const CHECKPOINT_LIMIT = 8;
18
+
19
+ function checkpointsPath(sessionId) {
20
+ return path.join(aegisDir(), 'checkpoints', `${sessionId}.jsonl`);
21
+ }
22
+
23
+ /**
24
+ * Snapshot the current transcript. Returns the new checkpoint index.
25
+ * Cheap: transcripts are small arrays of messages.
26
+ */
27
+ function snapshotCheckpoint(sessionId, transcript) {
28
+ try {
29
+ fs.mkdirSync(path.dirname(checkpointsPath(sessionId)), { recursive: true });
30
+ const entry = {
31
+ ts: new Date().toISOString(),
32
+ depth: transcript.length,
33
+ words: transcript.reduce((a, m) => a + (m.text || '').split(/\s+/).length, 0),
34
+ transcript,
35
+ };
36
+ const p = checkpointsPath(sessionId);
37
+ const prev = fs.existsSync(p) ? fs.readFileSync(p, 'utf8').split('\n').filter(Boolean) : [];
38
+ const lines = [...prev, JSON.stringify(entry)];
39
+ const trimmed = lines.slice(Math.max(0, lines.length - CHECKPOINT_LIMIT));
40
+ fs.writeFileSync(p, trimmed.join('\n') + '\n');
41
+ return lines.length - 1; // index into the trimmed list
42
+ } catch {
43
+ return -1;
44
+ }
45
+ }
46
+
47
+ /** Newest-first checkpoint list: [{ idx, ts, depth, words }]. */
48
+ function listCheckpoints(sessionId) {
49
+ try {
50
+ const raw = fs.readFileSync(checkpointsPath(sessionId), 'utf8');
51
+ return raw
52
+ .split('\n')
53
+ .filter(Boolean)
54
+ .map((l, idx) => {
55
+ try {
56
+ const e = JSON.parse(l);
57
+ return { idx, ts: e.ts, depth: e.depth || 0, words: e.words || 0 };
58
+ } catch { return null; }
59
+ })
60
+ .filter(Boolean)
61
+ .reverse();
62
+ } catch {
63
+ return [];
64
+ }
65
+ }
66
+
67
+ /** Restore a checkpoint by its stored index. Returns the transcript or null. */
68
+ function loadCheckpoint(sessionId, idx) {
69
+ try {
70
+ const raw = fs.readFileSync(checkpointsPath(sessionId), 'utf8').split('\n').filter(Boolean);
71
+ const e = JSON.parse(raw[idx]);
72
+ if (!e || !Array.isArray(e.transcript)) return null;
73
+ return e.transcript;
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+
79
+ module.exports = {
80
+ CHECKPOINT_LIMIT,
81
+ checkpointsPath,
82
+ snapshotCheckpoint,
83
+ listCheckpoints,
84
+ loadCheckpoint,
85
+ };
@@ -0,0 +1,62 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Cross-platform clipboard write. Tries the platform's native tool first
5
+ * (pbcopy / wl-copy / xclip / xsel / clip.exe), then falls back to writing a
6
+ * file in the data dir and reporting its path — the CLI never errors out over
7
+ * clipboard availability. Set AEGISCODE_NO_CLIPBOARD=1 to force the file
8
+ * fallback (used by tests and headless setups).
9
+ *
10
+ * Ported from aegiscodex-dev/src/clipboard.js (ESM → CommonJS). The data dir
11
+ * comes from the shared `aegisDir()` helper and the force-fallback env var was
12
+ * renamed AEGISCODEX_NO_CLIPBOARD → AEGISCODE_NO_CLIPBOARD.
13
+ */
14
+
15
+ const { spawnSync } = require('node:child_process');
16
+ const fs = require('node:fs');
17
+ const path = require('node:path');
18
+ const { aegisDir } = require('./config.js');
19
+
20
+ const TOOLS = [
21
+ { name: 'pbcopy', cmd: 'pbcopy', args: [], test: () => process.platform === 'darwin' },
22
+ { name: 'wl-copy', cmd: 'wl-copy', args: [], test: () => process.platform === 'linux' },
23
+ { name: 'xclip', cmd: 'xclip', args: ['-selection', 'clipboard'], test: () => process.platform === 'linux' },
24
+ { name: 'xsel', cmd: 'xsel', args: ['--clipboard', '--input'], test: () => process.platform === 'linux' },
25
+ { name: 'clip', cmd: 'clip', args: [], test: () => process.platform === 'win32' },
26
+ ];
27
+
28
+ function toolAvailable(cmd) {
29
+ // `sh` doesn't exist on Windows — use `where` there, `command -v` on POSIX.
30
+ const isWin = process.platform === 'win32';
31
+ const r = isWin
32
+ ? spawnSync('where', [cmd], { encoding: 'utf8', timeout: 3000, windowsHide: true })
33
+ : spawnSync('sh', ['-c', `command -v ${cmd}`], { encoding: 'utf8', timeout: 3000 });
34
+ return !r.error && r.status === 0;
35
+ }
36
+
37
+ /**
38
+ * Copy text to the system clipboard.
39
+ * Returns { ok, via, path? } — via is 'tool:<name>' or 'file'.
40
+ */
41
+ function copyToClipboard(text) {
42
+ const forced = process.env.AEGISCODE_NO_CLIPBOARD === '1';
43
+ if (!forced) {
44
+ for (const t of TOOLS) {
45
+ if (!t.test()) continue;
46
+ if (!toolAvailable(t.cmd)) continue;
47
+ const r = spawnSync(t.cmd, t.args, { input: String(text), encoding: 'utf8', timeout: 4000 });
48
+ if (!r.error && r.status === 0) return { ok: true, via: `tool:${t.name}` };
49
+ }
50
+ }
51
+ // Fallback: write to the data dir and report the path.
52
+ try {
53
+ fs.mkdirSync(aegisDir(), { recursive: true });
54
+ const p = path.join(aegisDir(), 'clipboard.txt');
55
+ fs.writeFileSync(p, String(text), 'utf8');
56
+ return { ok: true, via: 'file', path: p };
57
+ } catch {
58
+ return { ok: false, via: 'none' };
59
+ }
60
+ }
61
+
62
+ module.exports = { copyToClipboard };