notoken-core 1.3.0 → 1.4.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 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,44 @@
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
+ }
29
+ export declare function detectProviders(): LLMProvider[];
30
+ export declare function formatStatus(): string;
31
+ export declare function goOffline(): string;
32
+ export declare function goOnline(): string;
33
+ export declare function disableLLM(name: string): string;
34
+ export declare function enableLLM(name: string): string;
35
+ export declare function isOfflineMode(): boolean;
36
+ export declare function isLLMDisabled(name: string): boolean;
37
+ export declare function recordOfflineCommand(): void;
38
+ export declare function getTokensSaved(): {
39
+ tokens: number;
40
+ commands: number;
41
+ };
42
+ export declare function formatTokensSaved(): string;
43
+ export declare function formatTokensSavedBrief(): string;
44
+ export declare function saveOnExit(): void;
@@ -0,0 +1,191 @@
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
+ function loadState() {
25
+ try {
26
+ if (existsSync(STATE_FILE)) {
27
+ return JSON.parse(readFileSync(STATE_FILE, "utf-8"));
28
+ }
29
+ }
30
+ catch { }
31
+ return { offlineMode: false, disabled: [], tokensSaved: 0, commandsHandledOffline: 0, lastSaved: new Date().toISOString() };
32
+ }
33
+ function saveState(state) {
34
+ try {
35
+ mkdirSync(USER_HOME, { recursive: true });
36
+ state.lastSaved = new Date().toISOString();
37
+ writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
38
+ }
39
+ catch { }
40
+ }
41
+ let state = loadState();
42
+ // ─── Provider Detection ─────────────────────────────────────────────────────
43
+ export function detectProviders() {
44
+ const providers = [];
45
+ // Claude CLI
46
+ const claudeVersion = tryExec("claude --version");
47
+ providers.push({
48
+ name: "claude",
49
+ type: "cli",
50
+ available: !!claudeVersion,
51
+ enabled: !state.offlineMode && !state.disabled.includes("claude"),
52
+ version: claudeVersion ?? undefined,
53
+ });
54
+ // Ollama
55
+ const ollamaVersion = tryExec("ollama --version");
56
+ let ollamaRunning = false;
57
+ let ollamaModel;
58
+ if (ollamaVersion) {
59
+ try {
60
+ const tags = tryExec("curl -sf --max-time 1 http://localhost:11434/api/tags");
61
+ if (tags) {
62
+ ollamaRunning = true;
63
+ const parsed = JSON.parse(tags);
64
+ ollamaModel = parsed.models?.[0]?.name;
65
+ }
66
+ }
67
+ catch { }
68
+ }
69
+ providers.push({
70
+ name: "ollama",
71
+ type: "local",
72
+ available: ollamaRunning,
73
+ enabled: !state.offlineMode && !state.disabled.includes("ollama"),
74
+ version: ollamaVersion ?? undefined,
75
+ model: ollamaModel,
76
+ });
77
+ // API endpoint
78
+ const apiEndpoint = process.env.NOTOKEN_LLM_ENDPOINT;
79
+ providers.push({
80
+ name: "api",
81
+ type: "api",
82
+ available: !!apiEndpoint,
83
+ enabled: !state.offlineMode && !state.disabled.includes("api") && !!apiEndpoint,
84
+ version: apiEndpoint ? "configured" : undefined,
85
+ model: process.env.NOTOKEN_LLM_MODEL,
86
+ });
87
+ return providers;
88
+ }
89
+ // ─── Status ─────────────────────────────────────────────────────────────────
90
+ export function formatStatus() {
91
+ const providers = detectProviders();
92
+ const lines = [];
93
+ lines.push(`${c.bold}LLM Status${c.reset}${state.offlineMode ? ` ${c.yellow}[OFFLINE MODE]${c.reset}` : ""}\n`);
94
+ for (const p of providers) {
95
+ const icon = p.available && p.enabled ? `${c.green}⬤${c.reset}` :
96
+ p.available && !p.enabled ? `${c.yellow}⬤${c.reset}` :
97
+ `${c.dim}○${c.reset}`;
98
+ const status = p.available && p.enabled ? `${c.green}active${c.reset}` :
99
+ p.available && !p.enabled ? `${c.yellow}disabled${c.reset}` :
100
+ `${c.dim}not available${c.reset}`;
101
+ const detail = [p.version, p.model].filter(Boolean).join(" / ");
102
+ lines.push(` ${icon} ${c.bold}${p.name}${c.reset} (${p.type}) — ${status}${detail ? ` ${c.dim}${detail}${c.reset}` : ""}`);
103
+ }
104
+ const active = providers.filter(p => p.available && p.enabled);
105
+ lines.push("");
106
+ if (active.length > 0) {
107
+ lines.push(` ${c.dim}Active chain: ${active.map(p => p.name).join(" → ")}${c.reset}`);
108
+ }
109
+ else {
110
+ lines.push(` ${c.dim}Running in deterministic mode (no LLM)${c.reset}`);
111
+ }
112
+ // Token savings
113
+ lines.push("");
114
+ lines.push(` ${c.cyan}Tokens saved:${c.reset} ~${formatTokens(state.tokensSaved)} (${state.commandsHandledOffline} commands handled offline)`);
115
+ return lines.join("\n");
116
+ }
117
+ // ─── Controls ───────────────────────────────────────────────────────────────
118
+ export function goOffline() {
119
+ state.offlineMode = true;
120
+ saveState(state);
121
+ return `${c.yellow}Offline mode enabled.${c.reset} All LLM providers disabled. Deterministic engine only.`;
122
+ }
123
+ export function goOnline() {
124
+ state.offlineMode = false;
125
+ saveState(state);
126
+ const providers = detectProviders().filter(p => p.available && p.enabled);
127
+ return `${c.green}Online mode.${c.reset} Active: ${providers.map(p => p.name).join(", ") || "none detected"}`;
128
+ }
129
+ export function disableLLM(name) {
130
+ if (!state.disabled.includes(name)) {
131
+ state.disabled.push(name);
132
+ saveState(state);
133
+ }
134
+ return `${c.yellow}${name} disabled.${c.reset}`;
135
+ }
136
+ export function enableLLM(name) {
137
+ state.disabled = state.disabled.filter(n => n !== name);
138
+ saveState(state);
139
+ return `${c.green}${name} enabled.${c.reset}`;
140
+ }
141
+ export function isOfflineMode() {
142
+ return state.offlineMode;
143
+ }
144
+ export function isLLMDisabled(name) {
145
+ return state.offlineMode || state.disabled.includes(name);
146
+ }
147
+ // ─── Token Savings Tracker ──────────────────────────────────────────────────
148
+ // Rough estimate: average LLM call uses ~500 tokens input + ~200 output
149
+ const AVG_TOKENS_PER_CALL = 700;
150
+ export function recordOfflineCommand() {
151
+ state.commandsHandledOffline++;
152
+ state.tokensSaved += AVG_TOKENS_PER_CALL;
153
+ // Save periodically (every 10 commands)
154
+ if (state.commandsHandledOffline % 10 === 0)
155
+ saveState(state);
156
+ }
157
+ export function getTokensSaved() {
158
+ return { tokens: state.tokensSaved, commands: state.commandsHandledOffline };
159
+ }
160
+ export function formatTokensSaved() {
161
+ const { tokens, commands } = getTokensSaved();
162
+ if (commands === 0)
163
+ return "";
164
+ return `${c.dim}~${formatTokens(tokens)} tokens saved (${commands} commands handled offline)${c.reset}`;
165
+ }
166
+ export function formatTokensSavedBrief() {
167
+ const { tokens, commands } = getTokensSaved();
168
+ if (commands === 0)
169
+ return "";
170
+ return `${c.dim}Tokens saved: ~${formatTokens(tokens)}${c.reset}`;
171
+ }
172
+ // Save state on exit
173
+ export function saveOnExit() {
174
+ saveState(state);
175
+ }
176
+ // ─── Helpers ────────────────────────────────────────────────────────────────
177
+ function formatTokens(n) {
178
+ if (n < 1000)
179
+ return String(n);
180
+ if (n < 1_000_000)
181
+ return `${(n / 1000).toFixed(1)}K`;
182
+ return `${(n / 1_000_000).toFixed(2)}M`;
183
+ }
184
+ function tryExec(cmd) {
185
+ try {
186
+ return execSync(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim() || null;
187
+ }
188
+ catch {
189
+ return null;
190
+ }
191
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "notoken-core",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Shared engine for notoken — NLP parsing, execution, detection, analysis",
5
5
  "type": "module",
6
6
  "license": "MIT",