@lardexx/arenacode 2.11.8 → 2.11.9
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 +21 -7
- package/package.json +1 -1
- package/src/tui/app.js +48 -12
- package/src/tui/history.js +187 -73
package/README.md
CHANGED
|
@@ -327,17 +327,31 @@ Every timeline in the interface is a **session**: its own transcript, its own ta
|
|
|
327
327
|
typed in it; `enter` switches to the selected session, `n` (or the `+ new session` row) starts a fresh one.
|
|
328
328
|
`ctrl+x n` is the shortcut for a new session.
|
|
329
329
|
|
|
330
|
-
**The history is on disk
|
|
330
|
+
**The history is on disk, one file per session.** The sessions live next to the config file — the same base
|
|
331
|
+
folder on the three systems, so a portable install (`ARENACODE_CONFIG`) carries its history with it:
|
|
332
|
+
|
|
333
|
+
| System | Directory |
|
|
334
|
+
|---|---|
|
|
335
|
+
| Windows | `%APPDATA%\ArenaCode\sessions\<folder>-<hash>\` |
|
|
336
|
+
| macOS | `~/.arenacode/sessions/<folder>-<hash>/` |
|
|
337
|
+
| Linux | `~/.arenacode/sessions/<folder>-<hash>/` |
|
|
338
|
+
|
|
339
|
+
Inside that directory there is **one JSON file per session** plus a small `index.json`:
|
|
331
340
|
|
|
332
341
|
```
|
|
333
|
-
|
|
342
|
+
session-1.json the transcript of session 1: items, task id, mode, label, diffs
|
|
343
|
+
session-2.json the transcript of session 2 — writing session 1 never touches this
|
|
344
|
+
index.json the order of the sessions and which one was open
|
|
334
345
|
```
|
|
335
346
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
a
|
|
347
|
+
One file per session means a turn rewrites only its own transcript, and nothing is capped: no limit on the
|
|
348
|
+
number of sessions, on the entries of a session, on the length of a message or on the rows of a diff. Writes
|
|
349
|
+
are atomic (a temporary file in the same directory, then a rename; on Windows, where an indexer can hold the
|
|
350
|
+
file, it retries and then writes in place) and happen a moment after each change, on `/quit`, and on the way
|
|
351
|
+
out through EOF, `Ctrl+C` or a signal. The file name is sanitised so a project called `a folder: <weird>?` still
|
|
352
|
+
gives a legal name on Windows, and the folder carries a hash of the absolute path: two folders with the same
|
|
353
|
+
name never mix. A corrupted `index.json` is rebuilt from the files, a corrupted `session-*.json` is skipped and
|
|
354
|
+
the others still load; a turn that was running when the client closed comes back marked `closed`.
|
|
341
355
|
|
|
342
356
|
The bridge keeps the tasks themselves — `/queue` shows them with their state and id.
|
|
343
357
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lardexx/arenacode",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.9",
|
|
4
4
|
"description": "Terminal client for ArenaCode: talks to an arena-bridge, streams the Agent Mode timeline, and runs local commands, file operations and computer automation on your PC (windows-use.dll on Windows, Xlib/XTest on Linux, osascript on macOS). Node port of the ArenaCode desktop client.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
package/src/tui/app.js
CHANGED
|
@@ -114,6 +114,32 @@ class App {
|
|
|
114
114
|
this.flush();
|
|
115
115
|
});
|
|
116
116
|
this.term.on('resize', () => this.invalidate());
|
|
117
|
+
// Leaving through EOF, Ctrl+C or a signal skips quit(): the history is written here too,
|
|
118
|
+
// synchronously, because the debounced save is not guaranteed to run before exit.
|
|
119
|
+
const flush = () => {
|
|
120
|
+
try {
|
|
121
|
+
if (this._saveTimer) {
|
|
122
|
+
clearTimeout(this._saveTimer);
|
|
123
|
+
this._saveTimer = null;
|
|
124
|
+
}
|
|
125
|
+
this.persistSessions();
|
|
126
|
+
} catch {
|
|
127
|
+
/* nothing to do on the way out */
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
process.once('exit', flush);
|
|
131
|
+
for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
|
|
132
|
+
try {
|
|
133
|
+
process.once(signal, () => {
|
|
134
|
+
flush();
|
|
135
|
+
this.running = false;
|
|
136
|
+
this.term.exit();
|
|
137
|
+
process.exit(0);
|
|
138
|
+
});
|
|
139
|
+
} catch {
|
|
140
|
+
/* a platform without that signal */
|
|
141
|
+
}
|
|
142
|
+
}
|
|
117
143
|
this.running = true;
|
|
118
144
|
if (this.approval === 'proceed') {
|
|
119
145
|
this.push({
|
|
@@ -447,7 +473,7 @@ class App {
|
|
|
447
473
|
|
|
448
474
|
// ---------------------------------------------------------------- sessions
|
|
449
475
|
|
|
450
|
-
/** Brings back the sessions of this folder from
|
|
476
|
+
/** Brings back the sessions of this folder from the history directory (if any). */
|
|
451
477
|
restoreSessions() {
|
|
452
478
|
let saved = null;
|
|
453
479
|
try {
|
|
@@ -456,29 +482,37 @@ class App {
|
|
|
456
482
|
saved = null;
|
|
457
483
|
}
|
|
458
484
|
if (!saved) return false;
|
|
459
|
-
this.sessions = saved.sessions
|
|
460
|
-
...slot,
|
|
461
|
-
seqItems: new Map(),
|
|
462
|
-
since: 0,
|
|
463
|
-
}));
|
|
485
|
+
this.sessions = saved.sessions;
|
|
464
486
|
this.sessionSeq = this.sessions.reduce((max, slot) => Math.max(max, slot.id || 0), this.sessions.length);
|
|
465
487
|
this.sessionIndex = saved.active;
|
|
466
488
|
this.applySession();
|
|
467
|
-
this.
|
|
489
|
+
this.historyDir = saved.dir;
|
|
468
490
|
// a transient note, not a transcript line: the transcript must be exactly what was saved
|
|
469
491
|
this.toast(`history: ${this.sessions.length} session(s) restored`);
|
|
470
492
|
return true;
|
|
471
493
|
}
|
|
472
494
|
|
|
473
|
-
/** Writes
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
const res = history.save(this.folder, { sessions: this.sessions, active: this.sessionIndex });
|
|
495
|
+
/** Writes one session to its own file. */
|
|
496
|
+
persistSlot(slot) {
|
|
497
|
+
if (!slot) return null;
|
|
498
|
+
const res = history.saveSession(this.folder, slot);
|
|
478
499
|
if (!res.ok) this.historyError = res.error;
|
|
479
500
|
return res;
|
|
480
501
|
}
|
|
481
502
|
|
|
503
|
+
/**
|
|
504
|
+
* Writes the active session and the index. One file per session means a turn rewrites
|
|
505
|
+
* only its own transcript; the other sessions are untouched.
|
|
506
|
+
*/
|
|
507
|
+
persistSessions() {
|
|
508
|
+
this.saveSession();
|
|
509
|
+
const res = this.persistSlot(this.sessions[this.sessionIndex]);
|
|
510
|
+
const index = history.saveIndex(this.folder, { sessions: this.sessions, active: this.sessionIndex });
|
|
511
|
+
this.historyDir = history.folderDir(this.folder);
|
|
512
|
+
if (!index.ok) this.historyError = index.error;
|
|
513
|
+
return res || index;
|
|
514
|
+
}
|
|
515
|
+
|
|
482
516
|
/** Saves at most a few times a second: a turn pushes items in bursts. */
|
|
483
517
|
scheduleSave() {
|
|
484
518
|
if (this._saveTimer) return;
|
|
@@ -526,6 +560,7 @@ class App {
|
|
|
526
560
|
|
|
527
561
|
newSession({ silent = false } = {}) {
|
|
528
562
|
this.saveSession();
|
|
563
|
+
this.persistSlot(this.sessions[this.sessionIndex]);
|
|
529
564
|
this.sessionSeq += 1;
|
|
530
565
|
this.sessions.push({
|
|
531
566
|
id: this.sessionSeq,
|
|
@@ -550,6 +585,7 @@ class App {
|
|
|
550
585
|
return;
|
|
551
586
|
}
|
|
552
587
|
this.saveSession();
|
|
588
|
+
this.persistSlot(this.sessions[this.sessionIndex]);
|
|
553
589
|
this.sessionIndex = index;
|
|
554
590
|
this.applySession();
|
|
555
591
|
const slot = this.sessions[index];
|
package/src/tui/history.js
CHANGED
|
@@ -1,144 +1,258 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// Chat history on disk: one file per session, plus a small index.
|
|
4
4
|
//
|
|
5
|
-
//
|
|
5
|
+
// Windows %APPDATA%\ArenaCode\sessions\<folder>-<hash>\session-<id>.json
|
|
6
|
+
// macOS ~/.arenacode/sessions/<folder>-<hash>/session-<id>.json
|
|
7
|
+
// Linux ~/.arenacode/sessions/<folder>-<hash>/session-<id>.json
|
|
6
8
|
//
|
|
7
|
-
//
|
|
9
|
+
// The base folder is the one the config file lives in (`dirname(configPath())`), so the
|
|
10
|
+
// history follows the client's own configuration on the three systems, including when
|
|
11
|
+
// ARENACODE_CONFIG points somewhere else (tests, portable installs).
|
|
8
12
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
13
|
+
// One directory per project folder — the name carries a hash of the absolute path, so two
|
|
14
|
+
// folders with the same last segment never mix — and inside it one JSON file per session,
|
|
15
|
+
// so writing a session never rewrites the others.
|
|
11
16
|
|
|
12
17
|
const fs = require('node:fs');
|
|
13
|
-
const os = require('node:os');
|
|
14
18
|
const path = require('node:path');
|
|
15
19
|
const crypto = require('node:crypto');
|
|
16
20
|
|
|
21
|
+
const { configPath } = require('../config');
|
|
22
|
+
|
|
17
23
|
const VERSION = 1;
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
const INDEX = 'index.json';
|
|
25
|
+
|
|
26
|
+
/** Where the client keeps its configuration: the history lives next to it. */
|
|
27
|
+
function baseDir() {
|
|
28
|
+
return path.dirname(configPath());
|
|
29
|
+
}
|
|
22
30
|
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
/** A folder name that is legal on Windows, macOS and Linux whatever the project is called. */
|
|
32
|
+
function slug(name) {
|
|
33
|
+
const clean = String(name || '')
|
|
34
|
+
.replace(/[^A-Za-z0-9._-]+/g, '-')
|
|
35
|
+
.replace(/^[-.]+|[-.]+$/g, '')
|
|
36
|
+
.slice(0, 40);
|
|
37
|
+
return clean || 'root';
|
|
25
38
|
}
|
|
26
39
|
|
|
27
|
-
|
|
40
|
+
/** The directory of one project folder. */
|
|
41
|
+
function folderDir(folder) {
|
|
28
42
|
const abs = path.resolve(folder || '.');
|
|
29
43
|
const hash = crypto.createHash('sha1').update(abs).digest('hex').slice(0, 10);
|
|
30
|
-
|
|
31
|
-
return path.join(dir(), `${base}-${hash}.json`);
|
|
44
|
+
return path.join(baseDir(), 'sessions', `${slug(path.basename(abs))}-${hash}`);
|
|
32
45
|
}
|
|
33
46
|
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
return text.length > max ? `${text.slice(0, max)}…` : text;
|
|
47
|
+
function sessionFile(folder, id) {
|
|
48
|
+
return path.join(folderDir(folder), `session-${slug(String(id))}.json`);
|
|
37
49
|
}
|
|
38
50
|
|
|
39
|
-
|
|
51
|
+
function indexFile(folder) {
|
|
52
|
+
return path.join(folderDir(folder), INDEX);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Atomic write. `rename` replaces the target on POSIX and on Windows (Node asks for
|
|
57
|
+
* MOVEFILE_REPLACE_EXISTING), and the temporary file lives in the same directory, so it
|
|
58
|
+
* never crosses a filesystem. If Windows refuses the rename (an indexer or an antivirus
|
|
59
|
+
* holding the file), it retries, then writes in place so nothing is lost.
|
|
60
|
+
*/
|
|
61
|
+
function writeAtomic(file, body) {
|
|
62
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
63
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
64
|
+
fs.writeFileSync(tmp, body, 'utf8');
|
|
65
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
66
|
+
try {
|
|
67
|
+
fs.renameSync(tmp, file);
|
|
68
|
+
return { ok: true, file, bytes: Buffer.byteLength(body) };
|
|
69
|
+
} catch (err) {
|
|
70
|
+
if (attempt === 2) {
|
|
71
|
+
try {
|
|
72
|
+
fs.writeFileSync(file, body, 'utf8');
|
|
73
|
+
fs.rmSync(tmp, { force: true });
|
|
74
|
+
return { ok: true, file, bytes: Buffer.byteLength(body), fallback: err.code || err.message };
|
|
75
|
+
} catch (second) {
|
|
76
|
+
fs.rmSync(tmp, { force: true });
|
|
77
|
+
return { ok: false, error: second.message, file };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// a short, synchronous pause is enough for a lock to be released
|
|
81
|
+
const until = Date.now() + 40;
|
|
82
|
+
while (Date.now() < until) {
|
|
83
|
+
/* spin */
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { ok: false, error: 'rename failed', file };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** JSON with the fields that are worth keeping; anything else is dropped, never truncated. */
|
|
40
91
|
function cleanItem(item) {
|
|
41
92
|
if (!item || typeof item !== 'object') return null;
|
|
42
93
|
const out = {};
|
|
43
|
-
for (const key of ['kind', 'text', 'label', 'detail', 'state', 'icon', 'meta', 'command', 'secs', 'seq', 'color']) {
|
|
44
|
-
if (item[key] === undefined
|
|
45
|
-
out[key] =
|
|
94
|
+
for (const key of ['kind', 'text', 'label', 'detail', 'state', 'icon', 'meta', 'command', 'secs', 'seq', 'color', 't0', 'shell']) {
|
|
95
|
+
if (item[key] === undefined) continue;
|
|
96
|
+
out[key] = item[key];
|
|
46
97
|
}
|
|
47
|
-
if (
|
|
48
|
-
if (item.shell) out.shell = true;
|
|
98
|
+
if (Array.isArray(item.files)) out.files = item.files.slice();
|
|
49
99
|
if (item.diff && Array.isArray(item.diff.rows)) {
|
|
50
100
|
out.diff = {
|
|
51
|
-
file:
|
|
101
|
+
file: item.diff.file || '',
|
|
52
102
|
additions: Number(item.diff.additions) || 0,
|
|
53
103
|
deletions: Number(item.diff.deletions) || 0,
|
|
54
|
-
rows: item.diff.rows.
|
|
104
|
+
rows: item.diff.rows.map((row) => ({
|
|
55
105
|
type: row.type,
|
|
56
|
-
oldNo: row.oldNo ===
|
|
57
|
-
newNo: row.newNo ===
|
|
58
|
-
text:
|
|
106
|
+
oldNo: row.oldNo === undefined ? null : row.oldNo,
|
|
107
|
+
newNo: row.newNo === undefined ? null : row.newNo,
|
|
108
|
+
text: row.text,
|
|
59
109
|
})),
|
|
60
110
|
};
|
|
61
111
|
}
|
|
62
112
|
return out;
|
|
63
113
|
}
|
|
64
114
|
|
|
65
|
-
function cleanSession(slot,
|
|
115
|
+
function cleanSession(slot, fallbackId) {
|
|
66
116
|
if (!slot || typeof slot !== 'object') return null;
|
|
67
|
-
const items = (Array.isArray(slot.items) ? slot.items : [])
|
|
68
|
-
.slice(-MAX_ITEMS)
|
|
69
|
-
.map(cleanItem)
|
|
70
|
-
.filter(Boolean)
|
|
71
|
-
// a turn that was running when the client closed is not running any more
|
|
72
|
-
.map((item) => (item.state === 'running' ? { ...item, state: 'done', meta: item.meta || 'closed' } : item));
|
|
73
117
|
return {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
118
|
+
version: VERSION,
|
|
119
|
+
id: Number(slot.id) || fallbackId,
|
|
120
|
+
label: slot.label || '',
|
|
121
|
+
taskId: slot.taskId || null,
|
|
77
122
|
mode: slot.mode || 'agent',
|
|
78
123
|
startedAt: Number(slot.startedAt) || Date.now(),
|
|
79
|
-
|
|
124
|
+
updatedAt: new Date().toISOString(),
|
|
125
|
+
items: (Array.isArray(slot.items) ? slot.items : []).map(cleanItem).filter(Boolean),
|
|
80
126
|
};
|
|
81
127
|
}
|
|
82
128
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
return {
|
|
129
|
+
/** Writes one session to its own file. */
|
|
130
|
+
function saveSession(folder, slot) {
|
|
131
|
+
const clean = cleanSession(slot, Date.now());
|
|
132
|
+
if (!clean) return { ok: false, error: 'nothing to save', file: null };
|
|
133
|
+
const file = sessionFile(folder, clean.id);
|
|
134
|
+
const res = writeAtomic(file, JSON.stringify(clean));
|
|
135
|
+
return { ...res, id: clean.id };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Writes the index: the order of the sessions and which one was open. Small on purpose —
|
|
140
|
+
* the session list is drawn from here without reading a single transcript.
|
|
141
|
+
*/
|
|
142
|
+
function saveIndex(folder, { sessions = [], active = 0 } = {}) {
|
|
143
|
+
const clean = sessions
|
|
144
|
+
.map((slot, i) => ({
|
|
145
|
+
id: Number(slot && slot.id) || i + 1,
|
|
146
|
+
label: (slot && slot.label) || '',
|
|
147
|
+
taskId: (slot && slot.taskId) || null,
|
|
148
|
+
mode: (slot && slot.mode) || 'agent',
|
|
149
|
+
startedAt: Number(slot && slot.startedAt) || 0,
|
|
150
|
+
updatedAt: (slot && slot.updatedAt) || new Date().toISOString(),
|
|
151
|
+
items: Array.isArray(slot && slot.items) ? slot.items.length : 0,
|
|
152
|
+
}))
|
|
153
|
+
.sort((a, b) => a.id - b.id);
|
|
154
|
+
const body = {
|
|
90
155
|
version: VERSION,
|
|
91
156
|
folder: path.resolve(folder || '.'),
|
|
92
157
|
savedAt: new Date().toISOString(),
|
|
93
|
-
active:
|
|
158
|
+
active: Math.max(0, Math.min(Math.max(0, clean.length - 1), Number(active) || 0)),
|
|
94
159
|
sessions: clean,
|
|
95
160
|
};
|
|
161
|
+
return writeAtomic(indexFile(folder), JSON.stringify(body));
|
|
96
162
|
}
|
|
97
163
|
|
|
98
|
-
|
|
99
|
-
function load(folder) {
|
|
100
|
-
let raw;
|
|
164
|
+
function readJson(file) {
|
|
101
165
|
try {
|
|
102
|
-
|
|
166
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
103
167
|
} catch {
|
|
104
168
|
return null;
|
|
105
169
|
}
|
|
106
|
-
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Reads the sessions of a folder: the order comes from the index, a session whose file is
|
|
174
|
+
* missing or broken is skipped (never "all or nothing"), and with no usable index the
|
|
175
|
+
* files themselves are used, sorted by id.
|
|
176
|
+
*/
|
|
177
|
+
function load(folder) {
|
|
178
|
+
const dir = folderDir(folder);
|
|
179
|
+
const index = readJson(indexFile(folder));
|
|
180
|
+
const wanted = new Set();
|
|
181
|
+
if (index && Array.isArray(index.sessions)) for (const entry of index.sessions) wanted.add(`${entry.id}.json`);
|
|
182
|
+
|
|
183
|
+
let files = [];
|
|
107
184
|
try {
|
|
108
|
-
|
|
185
|
+
files = fs
|
|
186
|
+
.readdirSync(dir)
|
|
187
|
+
.filter((name) => /^session-.+\.json$/.test(name))
|
|
188
|
+
.sort((a, b) => String(a).localeCompare(String(b), undefined, { numeric: true }));
|
|
109
189
|
} catch {
|
|
110
|
-
|
|
190
|
+
files = [];
|
|
191
|
+
}
|
|
192
|
+
// the index decides the order; files the index does not know about are appended
|
|
193
|
+
const ordered = [
|
|
194
|
+
...files.filter((name) => wanted.has(name.replace(/^session-/, ''))),
|
|
195
|
+
...files.filter((name) => !wanted.has(name.replace(/^session-/, ''))),
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
const sessions = [];
|
|
199
|
+
for (const name of ordered) {
|
|
200
|
+
const data = readJson(path.join(dir, name));
|
|
201
|
+
if (!data || !Array.isArray(data.items)) continue;
|
|
202
|
+
sessions.push({
|
|
203
|
+
id: Number(data.id) || sessions.length + 1,
|
|
204
|
+
label: data.label || '',
|
|
205
|
+
taskId: data.taskId || null,
|
|
206
|
+
mode: data.mode || 'agent',
|
|
207
|
+
startedAt: Number(data.startedAt) || 0,
|
|
208
|
+
updatedAt: data.updatedAt || null,
|
|
209
|
+
items: data.items,
|
|
210
|
+
seqItems: new Map(),
|
|
211
|
+
since: 0,
|
|
212
|
+
});
|
|
111
213
|
}
|
|
112
|
-
if (!data || data.version !== VERSION || !Array.isArray(data.sessions) || !data.sessions.length) return null;
|
|
113
|
-
const sessions = data.sessions.map(cleanSession).filter(Boolean);
|
|
114
214
|
if (!sessions.length) return null;
|
|
115
|
-
|
|
116
|
-
|
|
215
|
+
sessions.sort((a, b) => a.id - b.id);
|
|
216
|
+
|
|
217
|
+
// which session was open: the index knows its id, and the id survives a missing file
|
|
218
|
+
let active = sessions.length - 1;
|
|
219
|
+
if (index && Array.isArray(index.sessions)) {
|
|
220
|
+
const entry = index.sessions[Number(index.active)];
|
|
221
|
+
const at = entry ? sessions.findIndex((slot) => slot.id === Number(entry.id)) : -1;
|
|
222
|
+
active = at >= 0 ? at : Math.max(0, Math.min(sessions.length - 1, Number(index.active) || 0));
|
|
223
|
+
}
|
|
224
|
+
return { sessions, active, dir };
|
|
117
225
|
}
|
|
118
226
|
|
|
119
|
-
|
|
120
|
-
function save(folder, state) {
|
|
121
|
-
const file = fileFor(folder);
|
|
122
|
-
const body = JSON.stringify(payload(folder, state || {}), null, 0);
|
|
227
|
+
function remove(folder, id) {
|
|
123
228
|
try {
|
|
124
|
-
fs.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return { ok: true, file, bytes: Buffer.byteLength(body) };
|
|
129
|
-
} catch (err) {
|
|
130
|
-
return { ok: false, error: err.message, file };
|
|
229
|
+
fs.rmSync(sessionFile(folder, id), { force: true });
|
|
230
|
+
return true;
|
|
231
|
+
} catch {
|
|
232
|
+
return false;
|
|
131
233
|
}
|
|
132
234
|
}
|
|
133
235
|
|
|
134
|
-
/**
|
|
236
|
+
/** Deletes every session of a folder. */
|
|
135
237
|
function clear(folder) {
|
|
136
238
|
try {
|
|
137
|
-
fs.
|
|
239
|
+
fs.rmSync(folderDir(folder), { recursive: true, force: true });
|
|
138
240
|
return true;
|
|
139
241
|
} catch {
|
|
140
242
|
return false;
|
|
141
243
|
}
|
|
142
244
|
}
|
|
143
245
|
|
|
144
|
-
module.exports = {
|
|
246
|
+
module.exports = {
|
|
247
|
+
VERSION,
|
|
248
|
+
baseDir,
|
|
249
|
+
folderDir,
|
|
250
|
+
sessionFile,
|
|
251
|
+
indexFile,
|
|
252
|
+
saveSession,
|
|
253
|
+
saveIndex,
|
|
254
|
+
load,
|
|
255
|
+
remove,
|
|
256
|
+
clear,
|
|
257
|
+
cleanItem,
|
|
258
|
+
};
|