notoken-core 1.6.0 → 1.8.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.
Files changed (123) hide show
  1. package/config/ascii-art.json +12 -0
  2. package/config/chat-responses.json +1019 -0
  3. package/config/cheat-sheets.json +94 -0
  4. package/config/concept-clusters.json +31 -0
  5. package/config/daily-tips.json +105 -0
  6. package/config/entities.json +93 -0
  7. package/config/history-today.json +9762 -0
  8. package/config/image-prompts.json +20 -0
  9. package/config/intent-vectors.json +1 -0
  10. package/config/intents.json +5354 -85
  11. package/config/ollama-models.json +193 -0
  12. package/config/rules.json +32 -1
  13. package/config/startup-quotes.json +45 -0
  14. package/dist/automation/discordPatchright.d.ts +35 -0
  15. package/dist/automation/discordPatchright.js +424 -0
  16. package/dist/automation/discordSetup.d.ts +31 -0
  17. package/dist/automation/discordSetup.js +338 -0
  18. package/dist/automation/smAutomation.d.ts +82 -0
  19. package/dist/automation/smAutomation.js +448 -0
  20. package/dist/conversation/coreference.js +44 -4
  21. package/dist/conversation/pendingActions.d.ts +55 -0
  22. package/dist/conversation/pendingActions.js +127 -0
  23. package/dist/conversation/store.d.ts +72 -0
  24. package/dist/conversation/store.js +140 -1
  25. package/dist/conversation/topicTracker.d.ts +36 -0
  26. package/dist/conversation/topicTracker.js +141 -0
  27. package/dist/execution/ssh.d.ts +42 -1
  28. package/dist/execution/ssh.js +538 -3
  29. package/dist/handlers/executor.d.ts +2 -0
  30. package/dist/handlers/executor.js +4234 -31
  31. package/dist/index.d.ts +35 -4
  32. package/dist/index.js +51 -3
  33. package/dist/nlp/batchParser.d.ts +30 -0
  34. package/dist/nlp/batchParser.js +77 -0
  35. package/dist/nlp/conceptExpansion.d.ts +54 -0
  36. package/dist/nlp/conceptExpansion.js +136 -0
  37. package/dist/nlp/conceptRouter.d.ts +49 -0
  38. package/dist/nlp/conceptRouter.js +302 -0
  39. package/dist/nlp/confidenceCalibrator.d.ts +62 -0
  40. package/dist/nlp/confidenceCalibrator.js +116 -0
  41. package/dist/nlp/correctionLearner.d.ts +45 -0
  42. package/dist/nlp/correctionLearner.js +207 -0
  43. package/dist/nlp/entitySpellCorrect.d.ts +35 -0
  44. package/dist/nlp/entitySpellCorrect.js +141 -0
  45. package/dist/nlp/knowledgeGraph.d.ts +70 -0
  46. package/dist/nlp/knowledgeGraph.js +380 -0
  47. package/dist/nlp/llmFallback.js +28 -1
  48. package/dist/nlp/multiClassifier.js +91 -6
  49. package/dist/nlp/multiIntent.d.ts +43 -0
  50. package/dist/nlp/multiIntent.js +154 -0
  51. package/dist/nlp/parseIntent.d.ts +6 -1
  52. package/dist/nlp/parseIntent.js +180 -5
  53. package/dist/nlp/ruleParser.js +317 -0
  54. package/dist/nlp/semanticSimilarity.d.ts +30 -0
  55. package/dist/nlp/semanticSimilarity.js +174 -0
  56. package/dist/nlp/vocabularyBuilder.d.ts +43 -0
  57. package/dist/nlp/vocabularyBuilder.js +224 -0
  58. package/dist/nlp/wikidata.d.ts +49 -0
  59. package/dist/nlp/wikidata.js +228 -0
  60. package/dist/policy/confirm.d.ts +10 -0
  61. package/dist/policy/confirm.js +39 -0
  62. package/dist/policy/safety.js +6 -4
  63. package/dist/types/intent.d.ts +8 -0
  64. package/dist/types/intent.js +1 -0
  65. package/dist/utils/achievements.d.ts +38 -0
  66. package/dist/utils/achievements.js +126 -0
  67. package/dist/utils/aliases.d.ts +5 -0
  68. package/dist/utils/aliases.js +39 -0
  69. package/dist/utils/analysis.js +71 -15
  70. package/dist/utils/bookmarks.d.ts +13 -0
  71. package/dist/utils/bookmarks.js +51 -0
  72. package/dist/utils/browser.d.ts +64 -0
  73. package/dist/utils/browser.js +364 -0
  74. package/dist/utils/commandHistory.d.ts +20 -0
  75. package/dist/utils/commandHistory.js +108 -0
  76. package/dist/utils/completer.d.ts +17 -0
  77. package/dist/utils/completer.js +79 -0
  78. package/dist/utils/config.js +32 -2
  79. package/dist/utils/dbQuery.d.ts +25 -0
  80. package/dist/utils/dbQuery.js +248 -0
  81. package/dist/utils/devTools.d.ts +35 -0
  82. package/dist/utils/devTools.js +95 -0
  83. package/dist/utils/discordDiag.d.ts +35 -0
  84. package/dist/utils/discordDiag.js +826 -0
  85. package/dist/utils/diskCleanup.d.ts +36 -0
  86. package/dist/utils/diskCleanup.js +775 -0
  87. package/dist/utils/entityResolver.d.ts +107 -0
  88. package/dist/utils/entityResolver.js +468 -0
  89. package/dist/utils/imageGen.d.ts +92 -0
  90. package/dist/utils/imageGen.js +2031 -0
  91. package/dist/utils/installTracker.d.ts +57 -0
  92. package/dist/utils/installTracker.js +160 -0
  93. package/dist/utils/multiExec.d.ts +21 -0
  94. package/dist/utils/multiExec.js +141 -0
  95. package/dist/utils/openclawDiag.d.ts +29 -0
  96. package/dist/utils/openclawDiag.js +1035 -0
  97. package/dist/utils/output.js +4 -0
  98. package/dist/utils/platform.js +2 -1
  99. package/dist/utils/progressReporter.d.ts +50 -0
  100. package/dist/utils/progressReporter.js +58 -0
  101. package/dist/utils/projectDetect.d.ts +44 -0
  102. package/dist/utils/projectDetect.js +319 -0
  103. package/dist/utils/projectScanner.d.ts +44 -0
  104. package/dist/utils/projectScanner.js +312 -0
  105. package/dist/utils/shellCompat.d.ts +78 -0
  106. package/dist/utils/shellCompat.js +186 -0
  107. package/dist/utils/smartArchive.d.ts +16 -0
  108. package/dist/utils/smartArchive.js +172 -0
  109. package/dist/utils/smartRetry.d.ts +26 -0
  110. package/dist/utils/smartRetry.js +114 -0
  111. package/dist/utils/snippets.d.ts +13 -0
  112. package/dist/utils/snippets.js +53 -0
  113. package/dist/utils/stabilityMatrixManager.d.ts +80 -0
  114. package/dist/utils/stabilityMatrixManager.js +268 -0
  115. package/dist/utils/teachMode.d.ts +41 -0
  116. package/dist/utils/teachMode.js +100 -0
  117. package/dist/utils/timer.d.ts +22 -0
  118. package/dist/utils/timer.js +52 -0
  119. package/dist/utils/updater.d.ts +1 -0
  120. package/dist/utils/updater.js +1 -1
  121. package/dist/utils/version.d.ts +20 -0
  122. package/dist/utils/version.js +212 -0
  123. package/package.json +6 -3
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Stability Matrix Manager.
3
+ *
4
+ * Controls Stability Matrix without browser automation — reads/writes
5
+ * settings.json directly, launches packages via their launch commands,
6
+ * and manages models via the shared folder structure.
7
+ *
8
+ * SM stores config in:
9
+ * <SM_DIR>/Data/settings.json — main config
10
+ * <SM_DIR>/Packages/<name>/ — installed packages
11
+ * <SM_DIR>/Models/ — shared models folder
12
+ */
13
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from "node:fs";
14
+ import { resolve } from "node:path";
15
+ import { execSync } from "node:child_process";
16
+ import { homedir } from "node:os";
17
+ const c = {
18
+ reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
19
+ green: "\x1b[32m", yellow: "\x1b[33m", red: "\x1b[31m", cyan: "\x1b[36m",
20
+ };
21
+ function tryExec(cmd, timeout = 5000) {
22
+ try {
23
+ return execSync(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout }).trim() || null;
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ }
29
+ export function findStabilityMatrix() {
30
+ const candidates = [
31
+ // WSL paths to Windows drives
32
+ "/mnt/d/notoken/ai/StabilityMatrix",
33
+ "/mnt/c/notoken/ai/StabilityMatrix",
34
+ "/mnt/d/StabilityMatrix",
35
+ resolve(homedir(), "StabilityMatrix"),
36
+ resolve(homedir(), "AppData", "Local", "StabilityMatrix"),
37
+ // Native Windows paths
38
+ "D:\\notoken\\ai\\StabilityMatrix",
39
+ "C:\\notoken\\ai\\StabilityMatrix",
40
+ ];
41
+ for (const p of candidates) {
42
+ const settingsPath = resolve(p, "Data", "settings.json");
43
+ if (existsSync(settingsPath)) {
44
+ const isWSL = p.startsWith("/mnt/");
45
+ return {
46
+ path: p,
47
+ platform: isWSL ? "wsl" : "windows",
48
+ settingsPath,
49
+ packagesDir: resolve(p, "Data", "Packages"),
50
+ modelsDir: resolve(p, "Data", "Models"),
51
+ };
52
+ }
53
+ // Also check if SM exe exists without settings yet
54
+ if (existsSync(resolve(p, "StabilityMatrix.exe"))) {
55
+ const isWSL = p.startsWith("/mnt/");
56
+ return {
57
+ path: p,
58
+ platform: isWSL ? "wsl" : "windows",
59
+ settingsPath: resolve(p, "Data", "settings.json"),
60
+ packagesDir: resolve(p, "Data", "Packages"),
61
+ modelsDir: resolve(p, "Data", "Models"),
62
+ };
63
+ }
64
+ }
65
+ return null;
66
+ }
67
+ export function readSMSettings(sm) {
68
+ try {
69
+ return JSON.parse(readFileSync(sm.settingsPath, "utf-8"));
70
+ }
71
+ catch {
72
+ return null;
73
+ }
74
+ }
75
+ export function writeSMSettings(sm, settings) {
76
+ mkdirSync(resolve(sm.settingsPath, ".."), { recursive: true });
77
+ writeFileSync(sm.settingsPath, JSON.stringify(settings, null, 2));
78
+ }
79
+ // ─── Package Management ────────────────────────────────────────────────────
80
+ export function getInstalledPackages(sm) {
81
+ const settings = readSMSettings(sm);
82
+ return settings?.InstalledPackages ?? [];
83
+ }
84
+ export function getActivePackage(sm) {
85
+ const settings = readSMSettings(sm);
86
+ if (!settings)
87
+ return null;
88
+ return settings.InstalledPackages.find(p => p.Id === settings.ActiveInstalledPackage) ?? null;
89
+ }
90
+ export function isPackageRunning(sm) {
91
+ // Check common ports
92
+ for (const port of [7860, 7861, 8188, 9090]) {
93
+ const check = tryExec(`curl -sf --max-time 2 http://localhost:${port}/sdapi/v1/sd-models 2>/dev/null`);
94
+ if (check)
95
+ return { running: true, port, url: `http://localhost:${port}` };
96
+ // ComfyUI
97
+ const comfyCheck = tryExec(`curl -sf --max-time 2 http://localhost:${port}/system_stats 2>/dev/null`);
98
+ if (comfyCheck)
99
+ return { running: true, port, url: `http://localhost:${port}` };
100
+ }
101
+ return { running: false, port: 0, url: "" };
102
+ }
103
+ export function launchPackage(sm, pkg) {
104
+ const active = pkg ?? getActivePackage(sm);
105
+ if (!active)
106
+ return { success: false, message: "No active package found in Stability Matrix" };
107
+ const pkgDir = resolve(sm.packagesDir, active.LibraryPath.replace(/\\/g, "/"));
108
+ if (!existsSync(pkgDir))
109
+ return { success: false, message: `Package directory not found: ${pkgDir}` };
110
+ // Build launch args
111
+ const args = active.LaunchArgs
112
+ .filter(a => a.OptionValue === true)
113
+ .map(a => a.Name)
114
+ .join(" ");
115
+ const launchCmd = active.LaunchCommand;
116
+ const pythonDir = resolve(sm.path, "Data", "Assets", `python-${active.PythonVersion}`);
117
+ const venvDir = resolve(pkgDir, "venv");
118
+ // Launch via Windows PowerShell (SM packages run on Windows)
119
+ if (sm.platform === "wsl") {
120
+ const winPkgDir = tryExec(`wslpath -w "${pkgDir}"`);
121
+ if (!winPkgDir)
122
+ return { success: false, message: "Could not convert path" };
123
+ try {
124
+ execSync(`/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -Command "Start-Process -WorkingDirectory '${winPkgDir}' -FilePath 'python' -ArgumentList '${launchCmd} --api ${args}' -WindowStyle Normal" 2>/dev/null`, { stdio: "ignore", timeout: 10000 });
125
+ return { success: true, message: `${c.green}✓${c.reset} Launched ${active.DisplayName} (${launchCmd} --api ${args})` };
126
+ }
127
+ catch (err) {
128
+ return { success: false, message: `Launch failed: ${err instanceof Error ? err.message : err}` };
129
+ }
130
+ }
131
+ // Native Windows
132
+ try {
133
+ execSync(`start "" /D "${pkgDir}" python ${launchCmd} --api ${args}`, { stdio: "ignore", shell: "cmd.exe", timeout: 10000 });
134
+ return { success: true, message: `${c.green}✓${c.reset} Launched ${active.DisplayName}` };
135
+ }
136
+ catch (err) {
137
+ return { success: false, message: `Launch failed: ${err instanceof Error ? err.message : err}` };
138
+ }
139
+ }
140
+ export function stopPackage() {
141
+ // Kill python processes running SD
142
+ try {
143
+ const killed = tryExec("pkill -f 'launch.py\\|webui.py\\|main.py' 2>/dev/null");
144
+ // Also try Windows side
145
+ tryExec(`/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -Command "Get-Process python -ErrorAction SilentlyContinue | Where-Object {\\$_.CommandLine -like '*launch.py*'} | Stop-Process -Force" 2>/dev/null`);
146
+ return { success: true, message: `${c.green}✓${c.reset} Stopped SD processes` };
147
+ }
148
+ catch {
149
+ return { success: false, message: "Could not stop processes" };
150
+ }
151
+ }
152
+ export function listModels(sm) {
153
+ const models = [];
154
+ const modelsDir = sm.modelsDir;
155
+ if (!existsSync(modelsDir))
156
+ return models;
157
+ const typeMap = {
158
+ "StableDiffusion": "checkpoint",
159
+ "Lora": "lora",
160
+ "VAE": "vae",
161
+ "TextualInversion": "embedding",
162
+ "ControlNet": "controlnet",
163
+ };
164
+ for (const [dirName, type] of Object.entries(typeMap)) {
165
+ const typeDir = resolve(modelsDir, dirName);
166
+ if (!existsSync(typeDir))
167
+ continue;
168
+ try {
169
+ for (const file of readdirSync(typeDir, { recursive: true })) {
170
+ const fName = String(file);
171
+ if (fName.endsWith(".safetensors") || fName.endsWith(".ckpt") || fName.endsWith(".pt")) {
172
+ const fullPath = resolve(typeDir, fName);
173
+ const size = tryExec(`du -sh "${fullPath}" 2>/dev/null`)?.split("\t")[0] ?? "?";
174
+ models.push({ name: fName, path: fullPath, size, type });
175
+ }
176
+ }
177
+ }
178
+ catch { }
179
+ }
180
+ return models;
181
+ }
182
+ export async function downloadModel(sm, url, name) {
183
+ const modelsDir = resolve(sm.modelsDir, "StableDiffusion");
184
+ mkdirSync(modelsDir, { recursive: true });
185
+ const fileName = name ?? url.split("/").pop() ?? "model.safetensors";
186
+ const destPath = resolve(modelsDir, fileName);
187
+ if (existsSync(destPath)) {
188
+ return { success: true, message: `${c.green}✓${c.reset} Model already exists: ${fileName}` };
189
+ }
190
+ console.log(`${c.dim}Downloading model to ${destPath}...${c.reset}`);
191
+ try {
192
+ execSync(`curl -L --progress-bar -o "${destPath}" "${url}"`, { stdio: "inherit", timeout: 600000 });
193
+ return { success: true, message: `${c.green}✓${c.reset} Model downloaded: ${fileName}` };
194
+ }
195
+ catch (err) {
196
+ return { success: false, message: `Download failed: ${err instanceof Error ? err.message : err}` };
197
+ }
198
+ }
199
+ // ─── Status Formatting ─────────────────────────────────────────────────────
200
+ export function formatSMStatus(sm) {
201
+ const lines = [];
202
+ const settings = readSMSettings(sm);
203
+ const packages = getInstalledPackages(sm);
204
+ const active = getActivePackage(sm);
205
+ const running = isPackageRunning(sm);
206
+ const models = listModels(sm);
207
+ lines.push(`${c.bold}${c.cyan}Stability Matrix${c.reset}`);
208
+ lines.push(` ${c.dim}Location: ${sm.path} (${sm.platform})${c.reset}`);
209
+ if (settings?.PreferredGpu) {
210
+ const gpu = settings.PreferredGpu;
211
+ const vram = (gpu.MemoryBytes / 1073741824).toFixed(1);
212
+ lines.push(` ${c.dim}GPU: ${gpu.Name} (${vram}GB)${c.reset}`);
213
+ }
214
+ lines.push("");
215
+ // Packages
216
+ lines.push(`${c.bold}Installed Packages:${c.reset}`);
217
+ if (packages.length === 0) {
218
+ lines.push(` ${c.dim}None — open SM and click "+" to install one${c.reset}`);
219
+ }
220
+ else {
221
+ for (const pkg of packages) {
222
+ const isActive = pkg.Id === settings?.ActiveInstalledPackage;
223
+ const icon = isActive ? (running.running ? `${c.green}⬤${c.reset}` : `${c.yellow}⬤${c.reset}`) : `${c.dim}○${c.reset}`;
224
+ const status = isActive && running.running ? `${c.green}running${c.reset} at ${running.url}` :
225
+ isActive ? `${c.yellow}active (stopped)${c.reset}` : `${c.dim}inactive${c.reset}`;
226
+ lines.push(` ${icon} ${c.bold}${pkg.DisplayName}${c.reset} — ${status}`);
227
+ lines.push(` ${c.dim}Python ${pkg.PythonVersion} | ${pkg.LaunchCommand} | Branch: ${pkg.Version.InstalledBranch}${c.reset}`);
228
+ }
229
+ }
230
+ lines.push("");
231
+ // Models
232
+ if (models.length > 0) {
233
+ lines.push(`${c.bold}Models:${c.reset}`);
234
+ for (const m of models) {
235
+ lines.push(` ${c.dim}${m.type}:${c.reset} ${m.name} (${m.size})`);
236
+ }
237
+ }
238
+ else {
239
+ lines.push(`${c.bold}Models:${c.reset} ${c.dim}None yet — SM will download on first launch${c.reset}`);
240
+ }
241
+ return lines.join("\n");
242
+ }
243
+ // ─── Configure Launch Args ─────────────────────────────────────────────────
244
+ export function setLaunchArgs(sm, args) {
245
+ const settings = readSMSettings(sm);
246
+ if (!settings)
247
+ return;
248
+ const active = settings.InstalledPackages.find(p => p.Id === settings.ActiveInstalledPackage);
249
+ if (!active)
250
+ return;
251
+ for (const [name, value] of Object.entries(args)) {
252
+ const existing = active.LaunchArgs.find(a => a.Name === name);
253
+ if (existing) {
254
+ existing.OptionValue = value;
255
+ }
256
+ else {
257
+ active.LaunchArgs.push({ Name: name, Type: "Bool", OptionValue: value });
258
+ }
259
+ }
260
+ // Ensure --api is always enabled for NoToken integration
261
+ if (!active.LaunchArgs.find(a => a.Name === "--api")) {
262
+ active.LaunchArgs.push({ Name: "--api", Type: "Bool", OptionValue: true });
263
+ }
264
+ writeSMSettings(sm, settings);
265
+ }
266
+ export function enableAPI(sm) {
267
+ setLaunchArgs(sm, { "--api": true, "--listen": true });
268
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Teach Mode — let users define custom command mappings.
3
+ *
4
+ * "remember that deploy means git pull && npm install && pm2 restart"
5
+ * Next time: "deploy" → runs that command chain
6
+ *
7
+ * Stored in ~/.notoken/learned-commands.json
8
+ */
9
+ interface LearnedCommand {
10
+ trigger: string;
11
+ command: string;
12
+ description?: string;
13
+ learnedAt: string;
14
+ usedCount: number;
15
+ }
16
+ /**
17
+ * Teach a new command.
18
+ * "remember that deploy means git pull && npm install && pm2 restart api"
19
+ */
20
+ export declare function teachCommand(trigger: string, command: string, description?: string): void;
21
+ /**
22
+ * Look up a learned command by trigger.
23
+ */
24
+ export declare function getLearnedCommand(trigger: string): LearnedCommand | null;
25
+ /**
26
+ * List all learned commands.
27
+ */
28
+ export declare function listLearnedCommands(): LearnedCommand[];
29
+ /**
30
+ * Forget a learned command.
31
+ */
32
+ export declare function forgetCommand(trigger: string): boolean;
33
+ /**
34
+ * Parse a "remember that X means Y" statement.
35
+ * Returns { trigger, command } or null.
36
+ */
37
+ export declare function parseTeachStatement(text: string): {
38
+ trigger: string;
39
+ command: string;
40
+ } | null;
41
+ export {};
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Teach Mode — let users define custom command mappings.
3
+ *
4
+ * "remember that deploy means git pull && npm install && pm2 restart"
5
+ * Next time: "deploy" → runs that command chain
6
+ *
7
+ * Stored in ~/.notoken/learned-commands.json
8
+ */
9
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
10
+ import { resolve } from "node:path";
11
+ import { homedir } from "node:os";
12
+ const LEARNED_PATH = resolve(homedir(), ".notoken", "learned-commands.json");
13
+ let _commands = null;
14
+ function load() {
15
+ if (_commands)
16
+ return _commands;
17
+ if (existsSync(LEARNED_PATH)) {
18
+ try {
19
+ _commands = JSON.parse(readFileSync(LEARNED_PATH, "utf-8"));
20
+ return _commands;
21
+ }
22
+ catch { }
23
+ }
24
+ _commands = [];
25
+ return _commands;
26
+ }
27
+ function save() {
28
+ const dir = resolve(LEARNED_PATH, "..");
29
+ if (!existsSync(dir))
30
+ mkdirSync(dir, { recursive: true });
31
+ writeFileSync(LEARNED_PATH, JSON.stringify(load(), null, 2));
32
+ }
33
+ /**
34
+ * Teach a new command.
35
+ * "remember that deploy means git pull && npm install && pm2 restart api"
36
+ */
37
+ export function teachCommand(trigger, command, description) {
38
+ const cmds = load();
39
+ const existing = cmds.findIndex(c => c.trigger.toLowerCase() === trigger.toLowerCase());
40
+ if (existing >= 0) {
41
+ cmds[existing].command = command;
42
+ cmds[existing].description = description;
43
+ cmds[existing].learnedAt = new Date().toISOString();
44
+ }
45
+ else {
46
+ cmds.push({ trigger: trigger.toLowerCase(), command, description, learnedAt: new Date().toISOString(), usedCount: 0 });
47
+ }
48
+ save();
49
+ }
50
+ /**
51
+ * Look up a learned command by trigger.
52
+ */
53
+ export function getLearnedCommand(trigger) {
54
+ const cmds = load();
55
+ const found = cmds.find(c => c.trigger.toLowerCase() === trigger.toLowerCase());
56
+ if (found) {
57
+ found.usedCount++;
58
+ if (found.usedCount % 5 === 0)
59
+ save(); // periodic save
60
+ }
61
+ return found ?? null;
62
+ }
63
+ /**
64
+ * List all learned commands.
65
+ */
66
+ export function listLearnedCommands() {
67
+ return [...load()].sort((a, b) => b.usedCount - a.usedCount);
68
+ }
69
+ /**
70
+ * Forget a learned command.
71
+ */
72
+ export function forgetCommand(trigger) {
73
+ const cmds = load();
74
+ const idx = cmds.findIndex(c => c.trigger.toLowerCase() === trigger.toLowerCase());
75
+ if (idx >= 0) {
76
+ cmds.splice(idx, 1);
77
+ save();
78
+ return true;
79
+ }
80
+ return false;
81
+ }
82
+ /**
83
+ * Parse a "remember that X means Y" statement.
84
+ * Returns { trigger, command } or null.
85
+ */
86
+ export function parseTeachStatement(text) {
87
+ // "remember that deploy means git pull && npm install"
88
+ const m1 = text.match(/^remember\s+(?:that\s+)?(.+?)\s+(?:means|is|equals|does|runs|executes)\s+(.+)$/i);
89
+ if (m1)
90
+ return { trigger: m1[1].trim(), command: m1[2].trim() };
91
+ // "when I say deploy, run git pull && npm install"
92
+ const m2 = text.match(/^when\s+(?:i\s+)?say\s+(.+?),?\s+(?:run|execute|do)\s+(.+)$/i);
93
+ if (m2)
94
+ return { trigger: m2[1].trim(), command: m2[2].trim() };
95
+ // "teach: deploy = git pull && npm install"
96
+ const m3 = text.match(/^teach:?\s+(.+?)\s*=\s*(.+)$/i);
97
+ if (m3)
98
+ return { trigger: m3[1].trim(), command: m3[2].trim() };
99
+ return null;
100
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Timer / Reminders — lightweight in-process countdown timers.
3
+ *
4
+ * When a timer expires it pushes a message to `pendingNotifications`
5
+ * so the next REPL tick can display it.
6
+ */
7
+ export interface Timer {
8
+ id: number;
9
+ label: string;
10
+ endsAt: number;
11
+ timeout: NodeJS.Timeout;
12
+ }
13
+ /** Messages ready for the REPL to display on next tick. */
14
+ export declare const pendingNotifications: string[];
15
+ /** Start a countdown timer. Returns the timer ID. */
16
+ export declare function startTimer(minutes: number, label?: string): number;
17
+ /** List active timers with remaining time. */
18
+ export declare function listTimers(): string;
19
+ /** Cancel a timer by ID. Returns true if found. */
20
+ export declare function cancelTimer(id: number): boolean;
21
+ /** Drain all pending notifications (caller should display them). */
22
+ export declare function drainNotifications(): string[];
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Timer / Reminders — lightweight in-process countdown timers.
3
+ *
4
+ * When a timer expires it pushes a message to `pendingNotifications`
5
+ * so the next REPL tick can display it.
6
+ */
7
+ let nextId = 1;
8
+ const timers = new Map();
9
+ /** Messages ready for the REPL to display on next tick. */
10
+ export const pendingNotifications = [];
11
+ /** Start a countdown timer. Returns the timer ID. */
12
+ export function startTimer(minutes, label) {
13
+ const id = nextId++;
14
+ const tag = label ?? `Timer #${id}`;
15
+ const ms = minutes * 60_000;
16
+ const endsAt = Date.now() + ms;
17
+ const timeout = setTimeout(() => {
18
+ timers.delete(id);
19
+ pendingNotifications.push(`\x1b[33m⏰ ${tag} — ${minutes} min timer finished!\x1b[0m`);
20
+ }, ms);
21
+ // Allow the Node process to exit even if timers are running
22
+ if (timeout.unref)
23
+ timeout.unref();
24
+ timers.set(id, { id, label: tag, endsAt, timeout });
25
+ return id;
26
+ }
27
+ /** List active timers with remaining time. */
28
+ export function listTimers() {
29
+ if (timers.size === 0)
30
+ return "No active timers.";
31
+ const lines = [];
32
+ for (const t of timers.values()) {
33
+ const remaining = Math.max(0, Math.ceil((t.endsAt - Date.now()) / 1000));
34
+ const m = Math.floor(remaining / 60);
35
+ const s = remaining % 60;
36
+ lines.push(` #${t.id} ${t.label} — ${m}m ${s}s remaining`);
37
+ }
38
+ return `Active timers:\n${lines.join("\n")}`;
39
+ }
40
+ /** Cancel a timer by ID. Returns true if found. */
41
+ export function cancelTimer(id) {
42
+ const t = timers.get(id);
43
+ if (!t)
44
+ return false;
45
+ clearTimeout(t.timeout);
46
+ timers.delete(id);
47
+ return true;
48
+ }
49
+ /** Drain all pending notifications (caller should display them). */
50
+ export function drainNotifications() {
51
+ return pendingNotifications.splice(0, pendingNotifications.length);
52
+ }
@@ -30,3 +30,4 @@ export declare function runUpdate(): string;
30
30
  * Format update banner for terminal.
31
31
  */
32
32
  export declare function formatUpdateBanner(info: UpdateInfo): string;
33
+ export declare function isNewer(latest: string, current: string): boolean;
@@ -113,7 +113,7 @@ function getInstalledVersion() {
113
113
  return "0.0.0";
114
114
  }
115
115
  }
116
- function isNewer(latest, current) {
116
+ export function isNewer(latest, current) {
117
117
  const l = latest.split(".").map(Number);
118
118
  const c = current.split(".").map(Number);
119
119
  for (let i = 0; i < 3; i++) {
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Version check and self-upgrade utilities.
3
+ *
4
+ * Checks npm registry for latest published version and compares
5
+ * with the locally installed version.
6
+ */
7
+ /** Get the current local version from package.json. */
8
+ export declare function getLocalVersion(): string;
9
+ /** Fetch the latest version from npm registry (non-blocking, best-effort). */
10
+ export declare function getLatestVersion(): Promise<string | null>;
11
+ /** Compare two semver strings. Returns 1 if a > b, -1 if a < b, 0 if equal. */
12
+ export declare function compareSemver(a: string, b: string): number;
13
+ /** Check if an update is available. Returns formatted message or null. */
14
+ export declare function checkForUpdate(): Promise<string | null>;
15
+ /** Run the upgrade. Records current version before upgrading for rollback. */
16
+ export declare function runUpgrade(): Promise<void>;
17
+ /** Roll back to the previous version. */
18
+ export declare function runRollback(targetVersion?: string): Promise<void>;
19
+ /** Show version history. */
20
+ export declare function showVersionHistory(): void;