notoken-core 1.3.0 → 1.4.1

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 CHANGED
@@ -45,6 +45,7 @@ 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
49
  export { logFailure, loadFailures, clearFailures } from "./utils/logger.js";
49
50
  export { logUncertainty, loadUncertaintyLog, getUncertaintySummary } from "./nlp/uncertainty.js";
50
51
  export { recordHistory, loadHistory, getRecentHistory, searchHistory } from "./context/history.js";
package/dist/index.js CHANGED
@@ -58,6 +58,8 @@ export { Spinner, withSpinner, progressBar } from "./utils/spinner.js";
58
58
  export { createBackup, rollback, listBackups, cleanExpiredBackups, formatBackupList } from "./utils/autoBackup.js";
59
59
  // ── Updates ──
60
60
  export { checkForUpdate, checkForUpdateSync, runUpdate, formatUpdateBanner } from "./utils/updater.js";
61
+ // ── LLM Manager ──
62
+ export { detectProviders, formatStatus, goOffline, goOnline, disableLLM, enableLLM, isOfflineMode, isLLMDisabled, recordOfflineCommand, getTokensSaved, formatTokensSaved, formatTokensSavedBrief, saveOnExit, } from "./utils/llmManager.js";
61
63
  // ── Logging ──
62
64
  export { logFailure, loadFailures, clearFailures } from "./utils/logger.js";
63
65
  export { logUncertainty, loadUncertaintyLog, getUncertaintySummary } from "./nlp/uncertainty.js";
@@ -0,0 +1,60 @@
1
+ /**
2
+ * LLM Manager.
3
+ *
4
+ * Manages which LLMs are connected, enabled/disabled, and offline mode.
5
+ * Also tracks estimated tokens saved by using deterministic mode.
6
+ *
7
+ * Commands:
8
+ * :status — show which LLMs are connected
9
+ * :offline — go fully offline (disable all LLMs)
10
+ * :online — re-enable LLMs
11
+ * :disable <llm> — disable a specific LLM
12
+ * :enable <llm> — enable a specific LLM
13
+ */
14
+ export interface LLMProvider {
15
+ name: string;
16
+ type: "cli" | "api" | "local";
17
+ available: boolean;
18
+ enabled: boolean;
19
+ version?: string;
20
+ model?: string;
21
+ }
22
+ export interface LLMState {
23
+ offlineMode: boolean;
24
+ disabled: string[];
25
+ tokensSaved: number;
26
+ commandsHandledOffline: number;
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;
37
+ }
38
+ export declare function detectProviders(): LLMProvider[];
39
+ export declare function formatStatus(): string;
40
+ export declare function goOffline(): string;
41
+ export declare function goOnline(): string;
42
+ export declare function disableLLM(name: string): string;
43
+ export declare function enableLLM(name: string): string;
44
+ export declare function isOfflineMode(): boolean;
45
+ export declare function isLLMDisabled(name: string): boolean;
46
+ export declare function recordOfflineCommand(): void;
47
+ export declare function getTokensSaved(): {
48
+ session: {
49
+ tokens: number;
50
+ commands: number;
51
+ };
52
+ total: {
53
+ tokens: number;
54
+ commands: number;
55
+ };
56
+ };
57
+ export declare function formatTokensSaved(): string;
58
+ export declare function formatTokensSavedBrief(): string;
59
+ export declare function getSessionId(): string;
60
+ export declare function saveOnExit(): void;
@@ -0,0 +1,222 @@
1
+ /**
2
+ * LLM Manager.
3
+ *
4
+ * Manages which LLMs are connected, enabled/disabled, and offline mode.
5
+ * Also tracks estimated tokens saved by using deterministic mode.
6
+ *
7
+ * Commands:
8
+ * :status — show which LLMs are connected
9
+ * :offline — go fully offline (disable all LLMs)
10
+ * :online — re-enable LLMs
11
+ * :disable <llm> — disable a specific LLM
12
+ * :enable <llm> — enable a specific LLM
13
+ */
14
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
15
+ import { resolve } from "node:path";
16
+ import { execSync } from "node:child_process";
17
+ import { USER_HOME } from "./paths.js";
18
+ const STATE_FILE = resolve(USER_HOME, "llm-state.json");
19
+ const c = {
20
+ reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
21
+ green: "\x1b[32m", yellow: "\x1b[33m", red: "\x1b[31m", cyan: "\x1b[36m",
22
+ };
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();
30
+ function loadState() {
31
+ try {
32
+ if (existsSync(STATE_FILE)) {
33
+ return JSON.parse(readFileSync(STATE_FILE, "utf-8"));
34
+ }
35
+ }
36
+ catch { }
37
+ return { offlineMode: false, disabled: [], tokensSaved: 0, commandsHandledOffline: 0, lastSaved: new Date().toISOString(), sessions: [] };
38
+ }
39
+ function saveState(s) {
40
+ try {
41
+ mkdirSync(USER_HOME, { recursive: true });
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));
47
+ }
48
+ catch { }
49
+ }
50
+ let state = loadState();
51
+ // ─── Provider Detection ─────────────────────────────────────────────────────
52
+ export function detectProviders() {
53
+ const providers = [];
54
+ // Claude CLI
55
+ const claudeVersion = tryExec("claude --version");
56
+ providers.push({
57
+ name: "claude",
58
+ type: "cli",
59
+ available: !!claudeVersion,
60
+ enabled: !state.offlineMode && !state.disabled.includes("claude"),
61
+ version: claudeVersion ?? undefined,
62
+ });
63
+ // Ollama
64
+ const ollamaVersion = tryExec("ollama --version");
65
+ let ollamaRunning = false;
66
+ let ollamaModel;
67
+ if (ollamaVersion) {
68
+ try {
69
+ const tags = tryExec("curl -sf --max-time 1 http://localhost:11434/api/tags");
70
+ if (tags) {
71
+ ollamaRunning = true;
72
+ const parsed = JSON.parse(tags);
73
+ ollamaModel = parsed.models?.[0]?.name;
74
+ }
75
+ }
76
+ catch { }
77
+ }
78
+ providers.push({
79
+ name: "ollama",
80
+ type: "local",
81
+ available: ollamaRunning,
82
+ enabled: !state.offlineMode && !state.disabled.includes("ollama"),
83
+ version: ollamaVersion ?? undefined,
84
+ model: ollamaModel,
85
+ });
86
+ // API endpoint
87
+ const apiEndpoint = process.env.NOTOKEN_LLM_ENDPOINT;
88
+ providers.push({
89
+ name: "api",
90
+ type: "api",
91
+ available: !!apiEndpoint,
92
+ enabled: !state.offlineMode && !state.disabled.includes("api") && !!apiEndpoint,
93
+ version: apiEndpoint ? "configured" : undefined,
94
+ model: process.env.NOTOKEN_LLM_MODEL,
95
+ });
96
+ return providers;
97
+ }
98
+ // ─── Status ─────────────────────────────────────────────────────────────────
99
+ export function formatStatus() {
100
+ const providers = detectProviders();
101
+ const lines = [];
102
+ lines.push(`${c.bold}LLM Status${c.reset}${state.offlineMode ? ` ${c.yellow}[OFFLINE MODE]${c.reset}` : ""}\n`);
103
+ for (const p of providers) {
104
+ const icon = p.available && p.enabled ? `${c.green}⬤${c.reset}` :
105
+ p.available && !p.enabled ? `${c.yellow}⬤${c.reset}` :
106
+ `${c.dim}○${c.reset}`;
107
+ const status = p.available && p.enabled ? `${c.green}active${c.reset}` :
108
+ p.available && !p.enabled ? `${c.yellow}disabled${c.reset}` :
109
+ `${c.dim}not available${c.reset}`;
110
+ const detail = [p.version, p.model].filter(Boolean).join(" / ");
111
+ lines.push(` ${icon} ${c.bold}${p.name}${c.reset} (${p.type}) — ${status}${detail ? ` ${c.dim}${detail}${c.reset}` : ""}`);
112
+ }
113
+ const active = providers.filter(p => p.available && p.enabled);
114
+ lines.push("");
115
+ if (active.length > 0) {
116
+ lines.push(` ${c.dim}Active chain: ${active.map(p => p.name).join(" → ")}${c.reset}`);
117
+ }
118
+ else {
119
+ lines.push(` ${c.dim}Running in deterministic mode (no LLM)${c.reset}`);
120
+ }
121
+ // Token savings
122
+ lines.push("");
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}`);
126
+ return lines.join("\n");
127
+ }
128
+ // ─── Controls ───────────────────────────────────────────────────────────────
129
+ export function goOffline() {
130
+ state.offlineMode = true;
131
+ saveState(state);
132
+ return `${c.yellow}Offline mode enabled.${c.reset} All LLM providers disabled. Deterministic engine only.`;
133
+ }
134
+ export function goOnline() {
135
+ state.offlineMode = false;
136
+ saveState(state);
137
+ const providers = detectProviders().filter(p => p.available && p.enabled);
138
+ return `${c.green}Online mode.${c.reset} Active: ${providers.map(p => p.name).join(", ") || "none detected"}`;
139
+ }
140
+ export function disableLLM(name) {
141
+ if (!state.disabled.includes(name)) {
142
+ state.disabled.push(name);
143
+ saveState(state);
144
+ }
145
+ return `${c.yellow}${name} disabled.${c.reset}`;
146
+ }
147
+ export function enableLLM(name) {
148
+ state.disabled = state.disabled.filter(n => n !== name);
149
+ saveState(state);
150
+ return `${c.green}${name} enabled.${c.reset}`;
151
+ }
152
+ export function isOfflineMode() {
153
+ return state.offlineMode;
154
+ }
155
+ export function isLLMDisabled(name) {
156
+ return state.offlineMode || state.disabled.includes(name);
157
+ }
158
+ // ─── Token Savings Tracker ──────────────────────────────────────────────────
159
+ // Rough estimate: average LLM call uses ~500 tokens input + ~200 output
160
+ const AVG_TOKENS_PER_CALL = 700;
161
+ export function recordOfflineCommand() {
162
+ state.commandsHandledOffline++;
163
+ state.tokensSaved += AVG_TOKENS_PER_CALL;
164
+ sessionCommands++;
165
+ sessionTokens += AVG_TOKENS_PER_CALL;
166
+ if (state.commandsHandledOffline % 10 === 0)
167
+ saveState(state);
168
+ }
169
+ export function getTokensSaved() {
170
+ return {
171
+ session: { tokens: sessionTokens, commands: sessionCommands },
172
+ total: { tokens: state.tokensSaved, commands: state.commandsHandledOffline },
173
+ };
174
+ }
175
+ export function formatTokensSaved() {
176
+ if (sessionCommands === 0 && state.commandsHandledOffline === 0)
177
+ return "";
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");
184
+ }
185
+ export function formatTokensSavedBrief() {
186
+ if (sessionCommands === 0)
187
+ return "";
188
+ return `${c.dim}Session: ~${formatTokens(sessionTokens)} saved | Total: ~${formatTokens(state.tokensSaved)}${c.reset}`;
189
+ }
190
+ export function getSessionId() {
191
+ return sessionId;
192
+ }
193
+ // Save state on exit — record session stats
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
+ }
205
+ saveState(state);
206
+ }
207
+ // ─── Helpers ────────────────────────────────────────────────────────────────
208
+ function formatTokens(n) {
209
+ if (n < 1000)
210
+ return String(n);
211
+ if (n < 1_000_000)
212
+ return `${(n / 1000).toFixed(1)}K`;
213
+ return `${(n / 1_000_000).toFixed(2)}M`;
214
+ }
215
+ function tryExec(cmd) {
216
+ try {
217
+ return execSync(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim() || null;
218
+ }
219
+ catch {
220
+ return null;
221
+ }
222
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "notoken-core",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "description": "Shared engine for notoken — NLP parsing, execution, detection, analysis",
5
5
  "type": "module",
6
6
  "license": "MIT",