ispbills-icli 8.5.5 → 8.5.7
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 +1 -1
- package/src/session.js +133 -17
- package/src/tui/app.js +607 -82
- package/src/tui/components.js +208 -23
- package/src/tui/composer.js +49 -6
- package/src/tui/theme.js +1 -1
package/package.json
CHANGED
package/src/session.js
CHANGED
|
@@ -1,9 +1,50 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from 'fs';
|
|
2
2
|
import { homedir } from 'os';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
|
|
5
5
|
const SESSION_DIR = join(homedir(), '.config', 'ispbills-icli', 'sessions');
|
|
6
6
|
|
|
7
|
+
function safeJson(raw, fallback = null) {
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(raw);
|
|
10
|
+
} catch {
|
|
11
|
+
return fallback;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeMessages(messages = []) {
|
|
16
|
+
return messages
|
|
17
|
+
.filter((m) => m && typeof m.role === 'string')
|
|
18
|
+
.map((m) => ({ role: m.role, content: String(m.content ?? '') }));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function autoNameFromMessages(messages = []) {
|
|
22
|
+
const firstUser = messages.find((m) => m?.role === 'user' && String(m.content || '').trim());
|
|
23
|
+
const text = String(firstUser?.content || '').replace(/\s+/g, ' ').trim();
|
|
24
|
+
if (!text) return 'Untitled session';
|
|
25
|
+
return text.slice(0, 40).trim();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeSession(session = {}) {
|
|
29
|
+
const now = new Date().toISOString();
|
|
30
|
+
const messages = normalizeMessages(session.messages);
|
|
31
|
+
const id = String(session.id || Date.now().toString(36));
|
|
32
|
+
return {
|
|
33
|
+
id,
|
|
34
|
+
name: String(session.name || autoNameFromMessages(messages) || 'Untitled session'),
|
|
35
|
+
cwd: String(session.cwd || process.cwd()),
|
|
36
|
+
branch: String(session.branch || ''),
|
|
37
|
+
model: String(session.model || ''),
|
|
38
|
+
createdAt: session.createdAt || now,
|
|
39
|
+
updatedAt: session.updatedAt || now,
|
|
40
|
+
messages,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function sessionPathFor(id) {
|
|
45
|
+
return join(SESSION_DIR, `${id}.json`);
|
|
46
|
+
}
|
|
47
|
+
|
|
7
48
|
export function sessionDir() {
|
|
8
49
|
return SESSION_DIR;
|
|
9
50
|
}
|
|
@@ -12,30 +53,105 @@ export function initSessionDir() {
|
|
|
12
53
|
if (!existsSync(SESSION_DIR)) mkdirSync(SESSION_DIR, { recursive: true });
|
|
13
54
|
}
|
|
14
55
|
|
|
15
|
-
export function
|
|
16
|
-
|
|
17
|
-
|
|
56
|
+
export function saveSession(session) {
|
|
57
|
+
try {
|
|
58
|
+
initSessionDir();
|
|
59
|
+
const existing = loadSessionById(session?.id);
|
|
60
|
+
const normalized = normalizeSession({
|
|
61
|
+
...existing,
|
|
62
|
+
...session,
|
|
63
|
+
id: session?.id || existing?.id,
|
|
64
|
+
createdAt: session?.createdAt || existing?.createdAt,
|
|
65
|
+
updatedAt: new Date().toISOString(),
|
|
66
|
+
name: session?.name || existing?.name,
|
|
67
|
+
});
|
|
68
|
+
if (!normalized.name || normalized.name === 'Untitled session') {
|
|
69
|
+
normalized.name = autoNameFromMessages(normalized.messages);
|
|
70
|
+
}
|
|
71
|
+
writeFileSync(sessionPathFor(normalized.id), JSON.stringify(normalized, null, 2) + '\n');
|
|
72
|
+
return normalized;
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
18
76
|
}
|
|
19
77
|
|
|
20
|
-
|
|
21
|
-
|
|
78
|
+
export function loadSessionById(id) {
|
|
79
|
+
if (!id) return null;
|
|
80
|
+
const path = sessionPathFor(id);
|
|
81
|
+
if (!existsSync(path)) return null;
|
|
82
|
+
const loaded = safeJson(readFileSync(path, 'utf8'), null);
|
|
83
|
+
return loaded ? normalizeSession(loaded) : null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function deleteSession(id) {
|
|
22
87
|
try {
|
|
23
|
-
|
|
88
|
+
const path = sessionPathFor(id);
|
|
89
|
+
if (!existsSync(path)) return false;
|
|
90
|
+
unlinkSync(path);
|
|
91
|
+
return true;
|
|
24
92
|
} catch {
|
|
25
|
-
|
|
93
|
+
return false;
|
|
26
94
|
}
|
|
27
95
|
}
|
|
28
96
|
|
|
29
|
-
export function
|
|
97
|
+
export function listSessionsDetailed() {
|
|
30
98
|
if (!existsSync(SESSION_DIR)) return [];
|
|
31
|
-
return readdirSync(SESSION_DIR)
|
|
99
|
+
return readdirSync(SESSION_DIR)
|
|
100
|
+
.filter((f) => f.endsWith('.json'))
|
|
101
|
+
.map((file) => {
|
|
102
|
+
const full = join(SESSION_DIR, file);
|
|
103
|
+
const session = safeJson(readFileSync(full, 'utf8'), null);
|
|
104
|
+
if (!session) return null;
|
|
105
|
+
const normalized = normalizeSession(session);
|
|
106
|
+
const stats = statSync(full);
|
|
107
|
+
return {
|
|
108
|
+
id: normalized.id,
|
|
109
|
+
name: normalized.name || autoNameFromMessages(normalized.messages),
|
|
110
|
+
cwd: normalized.cwd,
|
|
111
|
+
branch: normalized.branch,
|
|
112
|
+
model: normalized.model,
|
|
113
|
+
createdAt: normalized.createdAt,
|
|
114
|
+
updatedAt: normalized.updatedAt,
|
|
115
|
+
messageCount: normalized.messages.length,
|
|
116
|
+
messages: normalized.messages,
|
|
117
|
+
mtimeMs: stats.mtimeMs,
|
|
118
|
+
};
|
|
119
|
+
})
|
|
120
|
+
.filter(Boolean)
|
|
121
|
+
.sort((a, b) => new Date(b.updatedAt || 0).getTime() - new Date(a.updatedAt || 0).getTime());
|
|
32
122
|
}
|
|
33
123
|
|
|
34
|
-
export function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
124
|
+
export function loadMostRecent() {
|
|
125
|
+
const [session] = listSessionsDetailed();
|
|
126
|
+
if (!session?.id) return null;
|
|
127
|
+
return loadSessionById(session.id);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function listSessions() {
|
|
131
|
+
return listSessionsDetailed().map((s) => s.id);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function loadSession(sessionPathOrId) {
|
|
135
|
+
if (!sessionPathOrId) return [];
|
|
136
|
+
if (sessionPathOrId.endsWith?.('.json')) {
|
|
137
|
+
const session = safeJson(readFileSync(sessionPathOrId, 'utf8'), null);
|
|
138
|
+
return normalizeSession(session || {}).messages;
|
|
139
|
+
}
|
|
140
|
+
return loadSessionById(sessionPathOrId)?.messages || [];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function newSessionPath() {
|
|
144
|
+
initSessionDir();
|
|
145
|
+
return sessionPathFor(Date.now().toString(36));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function saveMessage(sessionPathOrId, message) {
|
|
149
|
+
if (!message) return null;
|
|
150
|
+
const id = String(sessionPathOrId || Date.now().toString(36)).replace(/\.json$/, '');
|
|
151
|
+
const existing = loadSessionById(id);
|
|
152
|
+
return saveSession({
|
|
153
|
+
...(existing || {}),
|
|
154
|
+
id,
|
|
155
|
+
messages: [...(existing?.messages || []), message],
|
|
156
|
+
});
|
|
41
157
|
}
|