notoken-core 1.4.0 → 1.5.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.
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -1
- package/dist/utils/llmManager.d.ts +18 -2
- package/dist/utils/llmManager.js +45 -14
- package/dist/utils/sessionSummary.d.ts +39 -0
- package/dist/utils/sessionSummary.js +185 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -45,7 +45,8 @@ export { formatParsedCommand } from "./utils/output.js";
|
|
|
45
45
|
export { Spinner, withSpinner, progressBar } from "./utils/spinner.js";
|
|
46
46
|
export { createBackup, rollback, listBackups, cleanExpiredBackups, formatBackupList } from "./utils/autoBackup.js";
|
|
47
47
|
export { checkForUpdate, checkForUpdateSync, runUpdate, formatUpdateBanner, type UpdateInfo } from "./utils/updater.js";
|
|
48
|
-
export { detectProviders, formatStatus, goOffline, goOnline, disableLLM, enableLLM, isOfflineMode, isLLMDisabled, recordOfflineCommand, getTokensSaved, formatTokensSaved, formatTokensSavedBrief, saveOnExit, type LLMProvider, type LLMState, } from "./utils/llmManager.js";
|
|
48
|
+
export { detectProviders, formatStatus, goOffline, goOnline, disableLLM, enableLLM, isOfflineMode, isLLMDisabled, recordOfflineCommand, getTokensSaved, formatTokensSaved, formatTokensSavedBrief, saveOnExit, getSessionId, type LLMProvider, type LLMState, } from "./utils/llmManager.js";
|
|
49
|
+
export { getRecentSessions, getSessionsForFolder, formatSessionSummary, formatSessionList, type SessionSummary, } from "./utils/sessionSummary.js";
|
|
49
50
|
export { logFailure, loadFailures, clearFailures } from "./utils/logger.js";
|
|
50
51
|
export { logUncertainty, loadUncertaintyLog, getUncertaintySummary } from "./nlp/uncertainty.js";
|
|
51
52
|
export { recordHistory, loadHistory, getRecentHistory, searchHistory } from "./context/history.js";
|
package/dist/index.js
CHANGED
|
@@ -59,7 +59,9 @@ export { createBackup, rollback, listBackups, cleanExpiredBackups, formatBackupL
|
|
|
59
59
|
// ── Updates ──
|
|
60
60
|
export { checkForUpdate, checkForUpdateSync, runUpdate, formatUpdateBanner } from "./utils/updater.js";
|
|
61
61
|
// ── LLM Manager ──
|
|
62
|
-
export { detectProviders, formatStatus, goOffline, goOnline, disableLLM, enableLLM, isOfflineMode, isLLMDisabled, recordOfflineCommand, getTokensSaved, formatTokensSaved, formatTokensSavedBrief, saveOnExit, } from "./utils/llmManager.js";
|
|
62
|
+
export { detectProviders, formatStatus, goOffline, goOnline, disableLLM, enableLLM, isOfflineMode, isLLMDisabled, recordOfflineCommand, getTokensSaved, formatTokensSaved, formatTokensSavedBrief, saveOnExit, getSessionId, } from "./utils/llmManager.js";
|
|
63
|
+
// ── Session Summaries ──
|
|
64
|
+
export { getRecentSessions, getSessionsForFolder, formatSessionSummary, formatSessionList, } from "./utils/sessionSummary.js";
|
|
63
65
|
// ── Logging ──
|
|
64
66
|
export { logFailure, loadFailures, clearFailures } from "./utils/logger.js";
|
|
65
67
|
export { logUncertainty, loadUncertaintyLog, getUncertaintySummary } from "./nlp/uncertainty.js";
|
|
@@ -25,6 +25,15 @@ export interface LLMState {
|
|
|
25
25
|
tokensSaved: number;
|
|
26
26
|
commandsHandledOffline: number;
|
|
27
27
|
lastSaved: string;
|
|
28
|
+
sessions: SessionStats[];
|
|
29
|
+
}
|
|
30
|
+
export interface SessionStats {
|
|
31
|
+
id: string;
|
|
32
|
+
folder: string;
|
|
33
|
+
startedAt: string;
|
|
34
|
+
endedAt?: string;
|
|
35
|
+
tokensSaved: number;
|
|
36
|
+
commandsHandled: number;
|
|
28
37
|
}
|
|
29
38
|
export declare function detectProviders(): LLMProvider[];
|
|
30
39
|
export declare function formatStatus(): string;
|
|
@@ -36,9 +45,16 @@ export declare function isOfflineMode(): boolean;
|
|
|
36
45
|
export declare function isLLMDisabled(name: string): boolean;
|
|
37
46
|
export declare function recordOfflineCommand(): void;
|
|
38
47
|
export declare function getTokensSaved(): {
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
session: {
|
|
49
|
+
tokens: number;
|
|
50
|
+
commands: number;
|
|
51
|
+
};
|
|
52
|
+
total: {
|
|
53
|
+
tokens: number;
|
|
54
|
+
commands: number;
|
|
55
|
+
};
|
|
41
56
|
};
|
|
42
57
|
export declare function formatTokensSaved(): string;
|
|
43
58
|
export declare function formatTokensSavedBrief(): string;
|
|
59
|
+
export declare function getSessionId(): string;
|
|
44
60
|
export declare function saveOnExit(): void;
|
package/dist/utils/llmManager.js
CHANGED
|
@@ -21,6 +21,12 @@ const c = {
|
|
|
21
21
|
green: "\x1b[32m", yellow: "\x1b[33m", red: "\x1b[31m", cyan: "\x1b[36m",
|
|
22
22
|
};
|
|
23
23
|
// ─── State Persistence ──────────────────────────────────────────────────────
|
|
24
|
+
// Per-session counters (reset each launch)
|
|
25
|
+
let sessionTokens = 0;
|
|
26
|
+
let sessionCommands = 0;
|
|
27
|
+
const sessionId = `session_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
28
|
+
const sessionStartedAt = new Date().toISOString();
|
|
29
|
+
const sessionFolder = process.cwd();
|
|
24
30
|
function loadState() {
|
|
25
31
|
try {
|
|
26
32
|
if (existsSync(STATE_FILE)) {
|
|
@@ -28,13 +34,16 @@ function loadState() {
|
|
|
28
34
|
}
|
|
29
35
|
}
|
|
30
36
|
catch { }
|
|
31
|
-
return { offlineMode: false, disabled: [], tokensSaved: 0, commandsHandledOffline: 0, lastSaved: new Date().toISOString() };
|
|
37
|
+
return { offlineMode: false, disabled: [], tokensSaved: 0, commandsHandledOffline: 0, lastSaved: new Date().toISOString(), sessions: [] };
|
|
32
38
|
}
|
|
33
|
-
function saveState(
|
|
39
|
+
function saveState(s) {
|
|
34
40
|
try {
|
|
35
41
|
mkdirSync(USER_HOME, { recursive: true });
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
s.lastSaved = new Date().toISOString();
|
|
43
|
+
// Keep last 50 sessions
|
|
44
|
+
if (s.sessions.length > 50)
|
|
45
|
+
s.sessions = s.sessions.slice(-50);
|
|
46
|
+
writeFileSync(STATE_FILE, JSON.stringify(s, null, 2));
|
|
38
47
|
}
|
|
39
48
|
catch { }
|
|
40
49
|
}
|
|
@@ -111,7 +120,9 @@ export function formatStatus() {
|
|
|
111
120
|
}
|
|
112
121
|
// Token savings
|
|
113
122
|
lines.push("");
|
|
114
|
-
lines.push(` ${c.
|
|
123
|
+
lines.push(` ${c.bold}This session:${c.reset} ~${formatTokens(sessionTokens)} tokens saved (${sessionCommands} commands)`);
|
|
124
|
+
lines.push(` ${c.bold}All time:${c.reset} ~${formatTokens(state.tokensSaved)} tokens saved (${state.commandsHandledOffline} commands)`);
|
|
125
|
+
lines.push(` ${c.dim}Session: ${sessionId} | Folder: ${sessionFolder}${c.reset}`);
|
|
115
126
|
return lines.join("\n");
|
|
116
127
|
}
|
|
117
128
|
// ─── Controls ───────────────────────────────────────────────────────────────
|
|
@@ -150,27 +161,47 @@ const AVG_TOKENS_PER_CALL = 700;
|
|
|
150
161
|
export function recordOfflineCommand() {
|
|
151
162
|
state.commandsHandledOffline++;
|
|
152
163
|
state.tokensSaved += AVG_TOKENS_PER_CALL;
|
|
153
|
-
|
|
164
|
+
sessionCommands++;
|
|
165
|
+
sessionTokens += AVG_TOKENS_PER_CALL;
|
|
154
166
|
if (state.commandsHandledOffline % 10 === 0)
|
|
155
167
|
saveState(state);
|
|
156
168
|
}
|
|
157
169
|
export function getTokensSaved() {
|
|
158
|
-
return {
|
|
170
|
+
return {
|
|
171
|
+
session: { tokens: sessionTokens, commands: sessionCommands },
|
|
172
|
+
total: { tokens: state.tokensSaved, commands: state.commandsHandledOffline },
|
|
173
|
+
};
|
|
159
174
|
}
|
|
160
175
|
export function formatTokensSaved() {
|
|
161
|
-
|
|
162
|
-
if (commands === 0)
|
|
176
|
+
if (sessionCommands === 0 && state.commandsHandledOffline === 0)
|
|
163
177
|
return "";
|
|
164
|
-
|
|
178
|
+
const lines = [];
|
|
179
|
+
if (sessionCommands > 0) {
|
|
180
|
+
lines.push(`${c.dim}This session: ~${formatTokens(sessionTokens)} tokens saved (${sessionCommands} commands)${c.reset}`);
|
|
181
|
+
}
|
|
182
|
+
lines.push(`${c.dim}All time: ~${formatTokens(state.tokensSaved)} tokens saved (${state.commandsHandledOffline} commands)${c.reset}`);
|
|
183
|
+
return lines.join("\n");
|
|
165
184
|
}
|
|
166
185
|
export function formatTokensSavedBrief() {
|
|
167
|
-
|
|
168
|
-
if (commands === 0)
|
|
186
|
+
if (sessionCommands === 0)
|
|
169
187
|
return "";
|
|
170
|
-
return `${c.dim}
|
|
188
|
+
return `${c.dim}Session: ~${formatTokens(sessionTokens)} saved | Total: ~${formatTokens(state.tokensSaved)}${c.reset}`;
|
|
171
189
|
}
|
|
172
|
-
|
|
190
|
+
export function getSessionId() {
|
|
191
|
+
return sessionId;
|
|
192
|
+
}
|
|
193
|
+
// Save state on exit — record session stats
|
|
173
194
|
export function saveOnExit() {
|
|
195
|
+
if (sessionCommands > 0) {
|
|
196
|
+
state.sessions.push({
|
|
197
|
+
id: sessionId,
|
|
198
|
+
folder: sessionFolder,
|
|
199
|
+
startedAt: sessionStartedAt,
|
|
200
|
+
endedAt: new Date().toISOString(),
|
|
201
|
+
tokensSaved: sessionTokens,
|
|
202
|
+
commandsHandled: sessionCommands,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
174
205
|
saveState(state);
|
|
175
206
|
}
|
|
176
207
|
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session summary generator.
|
|
3
|
+
*
|
|
4
|
+
* Reads conversation turns from ~/.notoken/conversations/ and generates
|
|
5
|
+
* summaries of what was done in each session.
|
|
6
|
+
*
|
|
7
|
+
* Used by:
|
|
8
|
+
* - Desktop app dashboard session card
|
|
9
|
+
* - :sessions command in CLI
|
|
10
|
+
* - Exit summary
|
|
11
|
+
*/
|
|
12
|
+
export interface SessionSummary {
|
|
13
|
+
id: string;
|
|
14
|
+
folder: string;
|
|
15
|
+
startedAt: string;
|
|
16
|
+
endedAt: string;
|
|
17
|
+
turns: number;
|
|
18
|
+
commands: string[];
|
|
19
|
+
intents: string[];
|
|
20
|
+
entities: string[];
|
|
21
|
+
errors: number;
|
|
22
|
+
highlights: string[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get summaries for recent sessions across all folders.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getRecentSessions(limit?: number): SessionSummary[];
|
|
28
|
+
/**
|
|
29
|
+
* Get sessions for a specific folder.
|
|
30
|
+
*/
|
|
31
|
+
export declare function getSessionsForFolder(folder: string, limit?: number): SessionSummary[];
|
|
32
|
+
/**
|
|
33
|
+
* Format a session summary for display.
|
|
34
|
+
*/
|
|
35
|
+
export declare function formatSessionSummary(session: SessionSummary): string;
|
|
36
|
+
/**
|
|
37
|
+
* Format multiple sessions as a list.
|
|
38
|
+
*/
|
|
39
|
+
export declare function formatSessionList(sessions: SessionSummary[]): string;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session summary generator.
|
|
3
|
+
*
|
|
4
|
+
* Reads conversation turns from ~/.notoken/conversations/ and generates
|
|
5
|
+
* summaries of what was done in each session.
|
|
6
|
+
*
|
|
7
|
+
* Used by:
|
|
8
|
+
* - Desktop app dashboard session card
|
|
9
|
+
* - :sessions command in CLI
|
|
10
|
+
* - Exit summary
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
13
|
+
import { resolve } from "node:path";
|
|
14
|
+
import { homedir } from "node:os";
|
|
15
|
+
const CONVERSATIONS_ROOT = resolve(homedir(), ".notoken", "conversations");
|
|
16
|
+
/**
|
|
17
|
+
* Get summaries for recent sessions across all folders.
|
|
18
|
+
*/
|
|
19
|
+
export function getRecentSessions(limit = 20) {
|
|
20
|
+
const sessions = [];
|
|
21
|
+
if (!existsSync(CONVERSATIONS_ROOT))
|
|
22
|
+
return sessions;
|
|
23
|
+
// Walk all folder subdirectories
|
|
24
|
+
try {
|
|
25
|
+
const walkDir = (dir) => {
|
|
26
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
const full = resolve(dir, entry.name);
|
|
29
|
+
if (entry.isDirectory()) {
|
|
30
|
+
walkDir(full);
|
|
31
|
+
}
|
|
32
|
+
else if (entry.name.endsWith(".json")) {
|
|
33
|
+
try {
|
|
34
|
+
const conv = JSON.parse(readFileSync(full, "utf-8"));
|
|
35
|
+
if (conv.turns && conv.turns.length > 0) {
|
|
36
|
+
sessions.push(summarizeConversation(conv, dir));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch { }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
walkDir(CONVERSATIONS_ROOT);
|
|
44
|
+
}
|
|
45
|
+
catch { }
|
|
46
|
+
// Sort by most recent first
|
|
47
|
+
sessions.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
|
|
48
|
+
return sessions.slice(0, limit);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get sessions for a specific folder.
|
|
52
|
+
*/
|
|
53
|
+
export function getSessionsForFolder(folder, limit = 10) {
|
|
54
|
+
const safePath = folder.replace(/[^a-zA-Z0-9_\-\/]/g, "_").replace(/^\/+/, "");
|
|
55
|
+
const dir = resolve(CONVERSATIONS_ROOT, safePath || "default");
|
|
56
|
+
if (!existsSync(dir))
|
|
57
|
+
return [];
|
|
58
|
+
const sessions = [];
|
|
59
|
+
const files = readdirSync(dir).filter(f => f.endsWith(".json")).sort().reverse();
|
|
60
|
+
for (const file of files.slice(0, limit)) {
|
|
61
|
+
try {
|
|
62
|
+
const conv = JSON.parse(readFileSync(resolve(dir, file), "utf-8"));
|
|
63
|
+
if (conv.turns && conv.turns.length > 0) {
|
|
64
|
+
sessions.push(summarizeConversation(conv, folder));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch { }
|
|
68
|
+
}
|
|
69
|
+
return sessions;
|
|
70
|
+
}
|
|
71
|
+
function summarizeConversation(conv, folder) {
|
|
72
|
+
const turns = conv.turns ?? [];
|
|
73
|
+
const userTurns = turns.filter(t => t.role === "user");
|
|
74
|
+
const systemTurns = turns.filter(t => t.role === "system");
|
|
75
|
+
// Extract unique intents
|
|
76
|
+
const intents = [...new Set(userTurns.map(t => t.intent).filter(Boolean))];
|
|
77
|
+
// Extract commands (raw text of user turns)
|
|
78
|
+
const commands = userTurns.map(t => t.rawText).filter(Boolean);
|
|
79
|
+
// Extract entities from knowledge tree
|
|
80
|
+
const knowledgeTree = conv.knowledgeTree ?? [];
|
|
81
|
+
const entities = knowledgeTree.map(e => `${e.entity} (${e.type})`);
|
|
82
|
+
// Count errors
|
|
83
|
+
const errors = systemTurns.filter(t => t.error).length;
|
|
84
|
+
// Generate highlights — most interesting things that happened
|
|
85
|
+
const highlights = [];
|
|
86
|
+
if (intents.includes("service.restart"))
|
|
87
|
+
highlights.push("Restarted services");
|
|
88
|
+
if (intents.includes("deploy.run"))
|
|
89
|
+
highlights.push("Deployed");
|
|
90
|
+
if (intents.includes("deploy.rollback"))
|
|
91
|
+
highlights.push("Rolled back deploy");
|
|
92
|
+
if (intents.some(i => i.startsWith("docker.")))
|
|
93
|
+
highlights.push("Docker operations");
|
|
94
|
+
if (intents.some(i => i.startsWith("git.")))
|
|
95
|
+
highlights.push("Git operations");
|
|
96
|
+
if (intents.some(i => i.startsWith("security.")))
|
|
97
|
+
highlights.push("Security checks");
|
|
98
|
+
if (intents.includes("server.check_disk"))
|
|
99
|
+
highlights.push("Disk check");
|
|
100
|
+
if (intents.includes("server.check_memory"))
|
|
101
|
+
highlights.push("Memory check");
|
|
102
|
+
if (intents.some(i => i.startsWith("logs.")))
|
|
103
|
+
highlights.push("Log inspection");
|
|
104
|
+
if (intents.some(i => i.startsWith("file.")))
|
|
105
|
+
highlights.push("File operations");
|
|
106
|
+
if (intents.some(i => i.startsWith("db.")))
|
|
107
|
+
highlights.push("Database operations");
|
|
108
|
+
if (intents.some(i => i.startsWith("backup.")))
|
|
109
|
+
highlights.push("Backup operations");
|
|
110
|
+
if (errors > 0)
|
|
111
|
+
highlights.push(`${errors} error(s)`);
|
|
112
|
+
// If no specific highlights, summarize by count
|
|
113
|
+
if (highlights.length === 0 && intents.length > 0) {
|
|
114
|
+
highlights.push(`${intents.length} different operations`);
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
id: conv.id ?? "unknown",
|
|
118
|
+
folder,
|
|
119
|
+
startedAt: conv.createdAt ?? "",
|
|
120
|
+
endedAt: conv.updatedAt ?? "",
|
|
121
|
+
turns: turns.length,
|
|
122
|
+
commands: commands.slice(0, 10),
|
|
123
|
+
intents,
|
|
124
|
+
entities: entities.slice(0, 10),
|
|
125
|
+
errors,
|
|
126
|
+
highlights,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Format a session summary for display.
|
|
131
|
+
*/
|
|
132
|
+
export function formatSessionSummary(session) {
|
|
133
|
+
const c = { reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m", cyan: "\x1b[36m", green: "\x1b[32m", yellow: "\x1b[33m", red: "\x1b[31m" };
|
|
134
|
+
const ago = timeAgo(session.startedAt);
|
|
135
|
+
const duration = timeBetween(session.startedAt, session.endedAt);
|
|
136
|
+
const errorTag = session.errors > 0 ? ` ${c.red}(${session.errors} errors)${c.reset}` : "";
|
|
137
|
+
const lines = [];
|
|
138
|
+
lines.push(`${c.bold}${ago}${c.reset} — ${duration}${errorTag}`);
|
|
139
|
+
lines.push(`${c.dim}${session.folder} | ${session.turns} turns | ${session.id}${c.reset}`);
|
|
140
|
+
if (session.highlights.length > 0) {
|
|
141
|
+
lines.push(`${c.cyan}${session.highlights.join(" · ")}${c.reset}`);
|
|
142
|
+
}
|
|
143
|
+
if (session.commands.length > 0) {
|
|
144
|
+
lines.push(`${c.dim}Commands: ${session.commands.slice(0, 5).join(", ")}${session.commands.length > 5 ? "..." : ""}${c.reset}`);
|
|
145
|
+
}
|
|
146
|
+
return lines.join("\n");
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Format multiple sessions as a list.
|
|
150
|
+
*/
|
|
151
|
+
export function formatSessionList(sessions) {
|
|
152
|
+
if (sessions.length === 0)
|
|
153
|
+
return "\x1b[2mNo sessions found.\x1b[0m";
|
|
154
|
+
return sessions.map((s, i) => {
|
|
155
|
+
const sep = i < sessions.length - 1 ? "\n" : "";
|
|
156
|
+
return ` ${formatSessionSummary(s)}${sep}`;
|
|
157
|
+
}).join("\n");
|
|
158
|
+
}
|
|
159
|
+
function timeAgo(dateStr) {
|
|
160
|
+
const ms = Date.now() - new Date(dateStr).getTime();
|
|
161
|
+
const mins = Math.floor(ms / 60000);
|
|
162
|
+
if (mins < 1)
|
|
163
|
+
return "Just now";
|
|
164
|
+
if (mins < 60)
|
|
165
|
+
return `${mins}m ago`;
|
|
166
|
+
const hours = Math.floor(mins / 60);
|
|
167
|
+
if (hours < 24)
|
|
168
|
+
return `${hours}h ago`;
|
|
169
|
+
const days = Math.floor(hours / 24);
|
|
170
|
+
if (days < 7)
|
|
171
|
+
return `${days}d ago`;
|
|
172
|
+
return new Date(dateStr).toLocaleDateString();
|
|
173
|
+
}
|
|
174
|
+
function timeBetween(start, end) {
|
|
175
|
+
const ms = new Date(end).getTime() - new Date(start).getTime();
|
|
176
|
+
const mins = Math.floor(ms / 60000);
|
|
177
|
+
if (mins < 1)
|
|
178
|
+
return "<1m";
|
|
179
|
+
if (mins < 60)
|
|
180
|
+
return `${mins}m`;
|
|
181
|
+
const hours = Math.floor(mins / 60);
|
|
182
|
+
if (hours < 24)
|
|
183
|
+
return `${hours}h ${mins % 60}m`;
|
|
184
|
+
return `${Math.floor(hours / 24)}d`;
|
|
185
|
+
}
|