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,38 @@
1
+ /**
2
+ * Achievement tracker — gamify CLI usage.
3
+ *
4
+ * Tracks milestones and shows achievements when hit:
5
+ * "🏆 First Command!" — ran your first command
6
+ * "🔥 10 Streak!" — 10 commands in one session
7
+ * "🌍 World Traveler" — connected to 5 different servers
8
+ */
9
+ interface UsageStats {
10
+ totalCommands: number;
11
+ sessionCommands: number;
12
+ uniqueIntents: string[];
13
+ uniqueServers: string[];
14
+ firstCommandDate?: string;
15
+ streakDays: number;
16
+ lastActiveDate?: string;
17
+ achievements: string[];
18
+ }
19
+ interface Achievement {
20
+ id: string;
21
+ name: string;
22
+ emoji: string;
23
+ description: string;
24
+ }
25
+ /**
26
+ * Record a command execution and check for new achievements.
27
+ * Returns newly unlocked achievements (if any).
28
+ */
29
+ export declare function recordCommand(intent: string, environment?: string): Achievement[];
30
+ /** Get all achievements (unlocked and locked). */
31
+ export declare function getAchievements(): Array<Achievement & {
32
+ unlocked: boolean;
33
+ }>;
34
+ /** Get usage stats for display. */
35
+ export declare function getUsageStats(): UsageStats;
36
+ /** Flush stats to disk. */
37
+ export declare function flushStats(): void;
38
+ export {};
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Achievement tracker — gamify CLI usage.
3
+ *
4
+ * Tracks milestones and shows achievements when hit:
5
+ * "🏆 First Command!" — ran your first command
6
+ * "🔥 10 Streak!" — 10 commands in one session
7
+ * "🌍 World Traveler" — connected to 5 different servers
8
+ */
9
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
10
+ import { resolve } from "node:path";
11
+ import { homedir } from "node:os";
12
+ const STATS_PATH = resolve(homedir(), ".notoken", "usage-stats.json");
13
+ let _stats = null;
14
+ function loadStats() {
15
+ if (_stats)
16
+ return _stats;
17
+ if (existsSync(STATS_PATH)) {
18
+ try {
19
+ _stats = JSON.parse(readFileSync(STATS_PATH, "utf-8"));
20
+ return _stats;
21
+ }
22
+ catch { }
23
+ }
24
+ _stats = { totalCommands: 0, sessionCommands: 0, uniqueIntents: [], uniqueServers: [], streakDays: 0, achievements: [] };
25
+ return _stats;
26
+ }
27
+ function saveStats() {
28
+ const dir = resolve(STATS_PATH, "..");
29
+ if (!existsSync(dir))
30
+ mkdirSync(dir, { recursive: true });
31
+ writeFileSync(STATS_PATH, JSON.stringify(loadStats(), null, 2));
32
+ }
33
+ const ACHIEVEMENTS = [
34
+ { id: "first_command", name: "First Steps", emoji: "🎯", description: "Ran your first command" },
35
+ { id: "10_commands", name: "Getting Started", emoji: "🚀", description: "Ran 10 commands" },
36
+ { id: "50_commands", name: "Power User", emoji: "⚡", description: "Ran 50 commands" },
37
+ { id: "100_commands", name: "Command Master", emoji: "🏆", description: "Ran 100 commands" },
38
+ { id: "500_commands", name: "Terminal Legend", emoji: "👑", description: "Ran 500 commands" },
39
+ { id: "1000_commands", name: "CLI God", emoji: "🌟", description: "Ran 1000 commands" },
40
+ { id: "10_intents", name: "Explorer", emoji: "🗺️", description: "Used 10 different commands" },
41
+ { id: "25_intents", name: "Versatile", emoji: "🎭", description: "Used 25 different commands" },
42
+ { id: "50_intents", name: "Jack of All Trades", emoji: "🃏", description: "Used 50 different commands" },
43
+ { id: "first_server", name: "Remote Access", emoji: "🌐", description: "Connected to a remote server" },
44
+ { id: "5_servers", name: "World Traveler", emoji: "✈️", description: "Connected to 5 different servers" },
45
+ { id: "3_day_streak", name: "Consistent", emoji: "🔥", description: "Used notoken 3 days in a row" },
46
+ { id: "7_day_streak", name: "Dedicated", emoji: "💪", description: "Used notoken 7 days in a row" },
47
+ { id: "30_day_streak", name: "Unstoppable", emoji: "🏅", description: "Used notoken 30 days in a row" },
48
+ { id: "night_owl", name: "Night Owl", emoji: "🦉", description: "Used notoken after midnight" },
49
+ { id: "early_bird", name: "Early Bird", emoji: "🐦", description: "Used notoken before 6 AM" },
50
+ { id: "first_joke", name: "Comedy Fan", emoji: "😂", description: "Asked for a joke" },
51
+ { id: "first_image", name: "Artist", emoji: "🎨", description: "Generated an image" },
52
+ ];
53
+ /**
54
+ * Record a command execution and check for new achievements.
55
+ * Returns newly unlocked achievements (if any).
56
+ */
57
+ export function recordCommand(intent, environment) {
58
+ const stats = loadStats();
59
+ const newAchievements = [];
60
+ stats.totalCommands++;
61
+ stats.sessionCommands++;
62
+ if (!stats.firstCommandDate)
63
+ stats.firstCommandDate = new Date().toISOString();
64
+ if (!stats.uniqueIntents.includes(intent))
65
+ stats.uniqueIntents.push(intent);
66
+ if (environment && environment !== "local" && environment !== "localhost" && environment !== "dev") {
67
+ if (!stats.uniqueServers.includes(environment))
68
+ stats.uniqueServers.push(environment);
69
+ }
70
+ // Update streak
71
+ const today = new Date().toISOString().split("T")[0];
72
+ if (stats.lastActiveDate) {
73
+ const last = new Date(stats.lastActiveDate);
74
+ const diff = Math.floor((Date.now() - last.getTime()) / 86400000);
75
+ if (diff === 1)
76
+ stats.streakDays++;
77
+ else if (diff > 1)
78
+ stats.streakDays = 1;
79
+ }
80
+ else {
81
+ stats.streakDays = 1;
82
+ }
83
+ stats.lastActiveDate = today;
84
+ // Check achievements
85
+ const check = (id, condition) => {
86
+ if (condition && !stats.achievements.includes(id)) {
87
+ stats.achievements.push(id);
88
+ const a = ACHIEVEMENTS.find(a => a.id === id);
89
+ if (a)
90
+ newAchievements.push(a);
91
+ }
92
+ };
93
+ check("first_command", stats.totalCommands >= 1);
94
+ check("10_commands", stats.totalCommands >= 10);
95
+ check("50_commands", stats.totalCommands >= 50);
96
+ check("100_commands", stats.totalCommands >= 100);
97
+ check("500_commands", stats.totalCommands >= 500);
98
+ check("1000_commands", stats.totalCommands >= 1000);
99
+ check("10_intents", stats.uniqueIntents.length >= 10);
100
+ check("25_intents", stats.uniqueIntents.length >= 25);
101
+ check("50_intents", stats.uniqueIntents.length >= 50);
102
+ check("first_server", stats.uniqueServers.length >= 1);
103
+ check("5_servers", stats.uniqueServers.length >= 5);
104
+ check("3_day_streak", stats.streakDays >= 3);
105
+ check("7_day_streak", stats.streakDays >= 7);
106
+ check("30_day_streak", stats.streakDays >= 30);
107
+ check("night_owl", new Date().getHours() >= 0 && new Date().getHours() < 4);
108
+ check("early_bird", new Date().getHours() >= 4 && new Date().getHours() < 6);
109
+ check("first_joke", intent === "chat.joke");
110
+ check("first_image", intent === "ai.generate_image");
111
+ // Save periodically
112
+ if (stats.totalCommands % 5 === 0)
113
+ saveStats();
114
+ return newAchievements;
115
+ }
116
+ /** Get all achievements (unlocked and locked). */
117
+ export function getAchievements() {
118
+ const stats = loadStats();
119
+ return ACHIEVEMENTS.map(a => ({ ...a, unlocked: stats.achievements.includes(a.id) }));
120
+ }
121
+ /** Get usage stats for display. */
122
+ export function getUsageStats() {
123
+ return { ...loadStats() };
124
+ }
125
+ /** Flush stats to disk. */
126
+ export function flushStats() { saveStats(); }
@@ -0,0 +1,5 @@
1
+ export declare function loadAliases(): Record<string, string>;
2
+ export declare function resolveAlias(text: string): string;
3
+ export declare function saveAlias(name: string, command: string): void;
4
+ export declare function removeAlias(name: string): boolean;
5
+ export declare function listAliases(): Record<string, string>;
@@ -0,0 +1,39 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { USER_HOME } from "./paths.js";
4
+ const ALIASES_FILE = resolve(USER_HOME, "aliases.json");
5
+ function ensureDir() {
6
+ if (!existsSync(USER_HOME))
7
+ mkdirSync(USER_HOME, { recursive: true });
8
+ }
9
+ export function loadAliases() {
10
+ try {
11
+ const raw = readFileSync(ALIASES_FILE, "utf-8");
12
+ return JSON.parse(raw);
13
+ }
14
+ catch {
15
+ return {};
16
+ }
17
+ }
18
+ export function resolveAlias(text) {
19
+ const aliases = loadAliases();
20
+ return aliases[text] ?? text;
21
+ }
22
+ export function saveAlias(name, command) {
23
+ ensureDir();
24
+ const aliases = loadAliases();
25
+ aliases[name] = command;
26
+ writeFileSync(ALIASES_FILE, JSON.stringify(aliases, null, 2) + "\n", "utf-8");
27
+ }
28
+ export function removeAlias(name) {
29
+ const aliases = loadAliases();
30
+ if (!(name in aliases))
31
+ return false;
32
+ delete aliases[name];
33
+ ensureDir();
34
+ writeFileSync(ALIASES_FILE, JSON.stringify(aliases, null, 2) + "\n", "utf-8");
35
+ return true;
36
+ }
37
+ export function listAliases() {
38
+ return loadAliases();
39
+ }
@@ -8,6 +8,7 @@
8
8
  * - Directory: detects project types, file breakdowns
9
9
  */
10
10
  import { analyzeDirectory as analyzeDirectoryImpl } from "./dirAnalysis.js";
11
+ import { detectLocalPlatform } from "./platform.js";
11
12
  function analyzeDirectoryOutput(output) {
12
13
  return analyzeDirectoryImpl(output);
13
14
  }
@@ -74,6 +75,29 @@ export function analyzeLoad(output) {
74
75
  else {
75
76
  lines.push(` ${c.dim}→ Load is stable.${c.reset}`);
76
77
  }
78
+ // Extract top CPU-heavy processes from the output
79
+ const psLines = output.split("\n").filter(l => /^\S+\s+\d+\s+\d+/.test(l));
80
+ const heavyProcs = psLines
81
+ .map(l => {
82
+ const parts = l.trim().split(/\s+/);
83
+ const cpu = parseFloat(parts[2]);
84
+ const mem = parseFloat(parts[3]);
85
+ const cmd = parts.slice(10).join(" ").replace(/^\/\S+\//, "").split(" ")[0]; // basename
86
+ return { user: parts[0], pid: parts[1], cpu, mem, cmd };
87
+ })
88
+ .filter(p => p.cpu > 5) // Only show processes using >5% CPU
89
+ .filter((p, i, arr) => arr.findIndex(x => x.pid === p.pid) === i) // Dedup by PID
90
+ .slice(0, 5);
91
+ if (heavyProcs.length > 0) {
92
+ lines.push(`\n ${c.bold}Heavy processes:${c.reset}`);
93
+ for (const p of heavyProcs) {
94
+ const cpuBar = p.cpu > 50 ? c.red : p.cpu > 20 ? c.yellow : c.dim;
95
+ lines.push(` ${cpuBar}${p.cpu.toFixed(0)}% CPU${c.reset} ${p.mem.toFixed(0)}% RAM ${c.bold}${p.cmd}${c.reset} ${c.dim}(${p.user}, PID ${p.pid})${c.reset}`);
96
+ }
97
+ }
98
+ else if (ratio1 < 0.3) {
99
+ lines.push(`\n ${c.green}No heavy processes — system is idle.${c.reset}`);
100
+ }
77
101
  return lines.join("\n");
78
102
  }
79
103
  export function analyzeDisk(output, specificPath) {
@@ -103,17 +127,24 @@ export function analyzeDisk(output, specificPath) {
103
127
  !p.filesystem.startsWith("none") &&
104
128
  !p.filesystem.startsWith("rootfs") &&
105
129
  p.mountPoint !== "/snap" &&
106
- !p.mountPoint.startsWith("/snap/"));
130
+ !p.mountPoint.startsWith("/snap/") &&
131
+ !p.mountPoint.includes("docker-desktop/cli-tools") &&
132
+ !p.filesystem.startsWith("/dev/loop"));
107
133
  for (const p of realPartitions) {
108
- if (p.usePercent >= 95) {
109
- lines.push(` ${c.red}⚠ CRITICAL: ${p.mountPoint} is ${p.usePercent}% full (${p.available} free)${c.reset}`);
134
+ // Use absolute free space (GB) for thresholds — percentage is misleading on large drives
135
+ // e.g. 97% on 2TB = 60GB free (fine), 95% on 100GB = 5GB free (critical)
136
+ const freeGB = parseFloat(p.available.replace(/[^\d.]/g, ""));
137
+ const freeUnit = p.available.replace(/[\d.]/g, "").trim().toUpperCase();
138
+ const freeGBNorm = freeUnit.startsWith("T") ? freeGB * 1024 : freeUnit.startsWith("M") ? freeGB / 1024 : freeGB;
139
+ if (freeGBNorm < 5) {
140
+ lines.push(` ${c.red}⚠ CRITICAL: ${p.mountPoint} has only ${p.available} free (${p.usePercent}% used)${c.reset}`);
110
141
  criticalCount++;
111
142
  }
112
- else if (p.usePercent >= 85) {
113
- lines.push(` ${c.yellow}⚠ WARNING: ${p.mountPoint} is ${p.usePercent}% full (${p.available} free)${c.reset}`);
143
+ else if (freeGBNorm < 20 && p.usePercent >= 90) {
144
+ lines.push(` ${c.yellow}⚠ WARNING: ${p.mountPoint} has ${p.available} free (${p.usePercent}% used)${c.reset}`);
114
145
  warningCount++;
115
146
  }
116
- else if (p.usePercent >= 70) {
147
+ else if (p.usePercent >= 85) {
117
148
  lines.push(` ${c.dim} ${p.mountPoint}: ${p.usePercent}% used (${p.available} free)${c.reset}`);
118
149
  }
119
150
  }
@@ -127,7 +158,29 @@ export function analyzeDisk(output, specificPath) {
127
158
  if (warningCount > 0) {
128
159
  lines.push(` ${c.yellow} ${warningCount} partition(s) approaching full.${c.reset}`);
129
160
  }
130
- lines.push(` ${c.dim} Tip: Run "disk analysis" playbook for detailed breakdown.${c.reset}`);
161
+ if (criticalCount > 0) {
162
+ lines.push(` ${c.yellow}${c.bold} → Try "free up space" or "disk cleanup" to scan for reclaimable space.${c.reset}`);
163
+ // WSL-specific: warn about I/O errors and need to restart WSL
164
+ const platform = detectLocalPlatform();
165
+ if (platform.isWSL) {
166
+ const windowsDriveFull = realPartitions.some((p) => {
167
+ if (!p.mountPoint.startsWith("/mnt/"))
168
+ return false;
169
+ const fGB = parseFloat(p.available.replace(/[^\d.]/g, ""));
170
+ const fU = p.available.replace(/[\d.]/g, "").trim().toUpperCase();
171
+ const freeNorm = fU.startsWith("T") ? fGB * 1024 : fU.startsWith("M") ? fGB / 1024 : fGB;
172
+ return freeNorm < 5;
173
+ });
174
+ if (windowsDriveFull) {
175
+ lines.push(`\n ${c.red}${c.bold} ⚠ WSL WARNING:${c.reset} Windows drive is critically full.`);
176
+ lines.push(` ${c.yellow} WSL shares disk with Windows — I/O errors and instability are likely.${c.reset}`);
177
+ lines.push(` ${c.yellow} Run "free up space" to clean and optionally restart WSL.${c.reset}`);
178
+ }
179
+ }
180
+ }
181
+ else {
182
+ lines.push(` ${c.dim} Tip: Run "disk analysis" playbook for detailed breakdown.${c.reset}`);
183
+ }
131
184
  }
132
185
  // Highlight largest partitions
133
186
  const sorted = [...realPartitions].sort((a, b) => b.usePercent - a.usePercent);
@@ -173,10 +226,10 @@ function findPartitionForPath(partitions, path) {
173
226
  "var": ["/var"],
174
227
  "log": ["/var/log", "/var"],
175
228
  "www": ["/var/www"],
176
- "c drive": ["/mnt/c"],
177
- "d drive": ["/mnt/d"],
178
- "e drive": ["/mnt/e"],
179
- "f drive": ["/mnt/f"],
229
+ "c drive": ["/mnt/c", "C:\\"],
230
+ "d drive": ["/mnt/d", "D:\\"],
231
+ "e drive": ["/mnt/e", "E:\\"],
232
+ "f drive": ["/mnt/f", "F:\\"],
180
233
  };
181
234
  // Check aliases first
182
235
  for (const [alias, paths] of Object.entries(aliases)) {
@@ -199,10 +252,13 @@ function findPartitionForPath(partitions, path) {
199
252
  return null;
200
253
  }
201
254
  function formatPartitionHealth(p) {
202
- if (p.usePercent >= 95)
203
- return `${c.red}⚠ CRITICAL: ${p.usePercent}% full! Only ${p.available} free on ${p.size} total.${c.reset}`;
204
- if (p.usePercent >= 85)
205
- return `${c.yellow}⚠ WARNING: ${p.usePercent}% full. ${p.available} free on ${p.size} total.${c.reset}`;
255
+ const fGB = parseFloat(p.available.replace(/[^\d.]/g, ""));
256
+ const fU = p.available.replace(/[\d.]/g, "").trim().toUpperCase();
257
+ const freeNorm = fU.startsWith("T") ? fGB * 1024 : fU.startsWith("M") ? fGB / 1024 : fGB;
258
+ if (freeNorm < 5)
259
+ return `${c.red}⚠ CRITICAL: Only ${p.available} free on ${p.size} total (${p.usePercent}% used).${c.reset}`;
260
+ if (freeNorm < 20 && p.usePercent >= 90)
261
+ return `${c.yellow}⚠ WARNING: ${p.available} free on ${p.size} total (${p.usePercent}% used).${c.reset}`;
206
262
  if (p.usePercent >= 70)
207
263
  return `${c.dim}Moderate: ${p.usePercent}% full. ${p.available} free on ${p.size} total.${c.reset}`;
208
264
  return `${c.green}✓ Healthy: ${p.usePercent}% used. ${p.available} free on ${p.size} total.${c.reset}`;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Bookmarks — save / list / retrieve / remove named commands.
3
+ *
4
+ * Stored in ~/.notoken/bookmarks.json as a simple { name: command } map.
5
+ */
6
+ /** Save a command under a bookmark name. */
7
+ export declare function saveBookmark(name: string, command: string): void;
8
+ /** List all bookmarks as a formatted string. */
9
+ export declare function listBookmarks(): string;
10
+ /** Get the command for a bookmark (or undefined). */
11
+ export declare function getBookmark(name: string): string | undefined;
12
+ /** Remove a bookmark by name. Returns true if it existed. */
13
+ export declare function removeBookmark(name: string): boolean;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Bookmarks — save / list / retrieve / remove named commands.
3
+ *
4
+ * Stored in ~/.notoken/bookmarks.json as a simple { name: command } map.
5
+ */
6
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
7
+ import { resolve } from "node:path";
8
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? ".";
9
+ const dir = resolve(home, ".notoken");
10
+ const file = resolve(dir, "bookmarks.json");
11
+ function load() {
12
+ if (!existsSync(file))
13
+ return {};
14
+ try {
15
+ return JSON.parse(readFileSync(file, "utf-8"));
16
+ }
17
+ catch {
18
+ return {};
19
+ }
20
+ }
21
+ function save(data) {
22
+ mkdirSync(dir, { recursive: true });
23
+ writeFileSync(file, JSON.stringify(data, null, 2), "utf-8");
24
+ }
25
+ /** Save a command under a bookmark name. */
26
+ export function saveBookmark(name, command) {
27
+ const bk = load();
28
+ bk[name] = command;
29
+ save(bk);
30
+ }
31
+ /** List all bookmarks as a formatted string. */
32
+ export function listBookmarks() {
33
+ const bk = load();
34
+ const keys = Object.keys(bk);
35
+ if (keys.length === 0)
36
+ return "No bookmarks saved.";
37
+ return keys.map((k) => ` ${k} → ${bk[k]}`).join("\n");
38
+ }
39
+ /** Get the command for a bookmark (or undefined). */
40
+ export function getBookmark(name) {
41
+ return load()[name];
42
+ }
43
+ /** Remove a bookmark by name. Returns true if it existed. */
44
+ export function removeBookmark(name) {
45
+ const bk = load();
46
+ if (!(name in bk))
47
+ return false;
48
+ delete bk[name];
49
+ save(bk);
50
+ return true;
51
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Browser Manager.
3
+ *
4
+ * Detects, installs, and launches browser automation engines.
5
+ *
6
+ * Priority:
7
+ * 1. Patchright (patched Playwright — anti-detection)
8
+ * 2. Playwright
9
+ * 3. Docker (browserless/chromium container)
10
+ * 4. System browser (xdg-open / open / start)
11
+ *
12
+ * Usage:
13
+ * notoken browse <url>
14
+ * notoken browse install
15
+ * notoken browse status
16
+ * "open google.com"
17
+ * "take screenshot of example.com"
18
+ * "browse to localhost:3000"
19
+ */
20
+ export type BrowserEngine = "patchright" | "playwright" | "docker" | "system";
21
+ export interface BrowserStatus {
22
+ engine: BrowserEngine;
23
+ available: boolean;
24
+ version?: string;
25
+ browsersInstalled?: boolean;
26
+ dockerImage?: string;
27
+ }
28
+ export interface BrowseOptions {
29
+ url: string;
30
+ headless?: boolean;
31
+ screenshot?: boolean;
32
+ screenshotPath?: string;
33
+ waitFor?: number;
34
+ userAgent?: string;
35
+ viewport?: {
36
+ width: number;
37
+ height: number;
38
+ };
39
+ }
40
+ export interface BrowseResult {
41
+ engine: BrowserEngine;
42
+ url: string;
43
+ title?: string;
44
+ screenshotPath?: string;
45
+ error?: string;
46
+ }
47
+ export declare function detectBrowserEngines(): BrowserStatus[];
48
+ /**
49
+ * Get the best available engine.
50
+ */
51
+ export declare function getBestEngine(): BrowserStatus | null;
52
+ export interface InstallResult {
53
+ success: boolean;
54
+ engine: BrowserEngine;
55
+ message: string;
56
+ }
57
+ export declare function installBrowserEngine(engine?: BrowserEngine): Promise<InstallResult>;
58
+ /**
59
+ * Open a URL using the best available engine.
60
+ */
61
+ export declare function browse(opts: BrowseOptions): Promise<BrowseResult>;
62
+ export declare function normalizeUrl(url: string): string;
63
+ export declare function formatBrowserStatus(engines: BrowserStatus[]): string;
64
+ export declare function stopDockerBrowser(): string;