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.
- package/config/ascii-art.json +12 -0
- package/config/chat-responses.json +1019 -0
- package/config/cheat-sheets.json +94 -0
- package/config/concept-clusters.json +31 -0
- package/config/daily-tips.json +105 -0
- package/config/entities.json +93 -0
- package/config/history-today.json +9762 -0
- package/config/image-prompts.json +20 -0
- package/config/intent-vectors.json +1 -0
- package/config/intents.json +5354 -85
- package/config/ollama-models.json +193 -0
- package/config/rules.json +32 -1
- package/config/startup-quotes.json +45 -0
- package/dist/automation/discordPatchright.d.ts +35 -0
- package/dist/automation/discordPatchright.js +424 -0
- package/dist/automation/discordSetup.d.ts +31 -0
- package/dist/automation/discordSetup.js +338 -0
- package/dist/automation/smAutomation.d.ts +82 -0
- package/dist/automation/smAutomation.js +448 -0
- package/dist/conversation/coreference.js +44 -4
- package/dist/conversation/pendingActions.d.ts +55 -0
- package/dist/conversation/pendingActions.js +127 -0
- package/dist/conversation/store.d.ts +72 -0
- package/dist/conversation/store.js +140 -1
- package/dist/conversation/topicTracker.d.ts +36 -0
- package/dist/conversation/topicTracker.js +141 -0
- package/dist/execution/ssh.d.ts +42 -1
- package/dist/execution/ssh.js +538 -3
- package/dist/handlers/executor.d.ts +2 -0
- package/dist/handlers/executor.js +4234 -31
- package/dist/index.d.ts +35 -4
- package/dist/index.js +51 -3
- package/dist/nlp/batchParser.d.ts +30 -0
- package/dist/nlp/batchParser.js +77 -0
- package/dist/nlp/conceptExpansion.d.ts +54 -0
- package/dist/nlp/conceptExpansion.js +136 -0
- package/dist/nlp/conceptRouter.d.ts +49 -0
- package/dist/nlp/conceptRouter.js +302 -0
- package/dist/nlp/confidenceCalibrator.d.ts +62 -0
- package/dist/nlp/confidenceCalibrator.js +116 -0
- package/dist/nlp/correctionLearner.d.ts +45 -0
- package/dist/nlp/correctionLearner.js +207 -0
- package/dist/nlp/entitySpellCorrect.d.ts +35 -0
- package/dist/nlp/entitySpellCorrect.js +141 -0
- package/dist/nlp/knowledgeGraph.d.ts +70 -0
- package/dist/nlp/knowledgeGraph.js +380 -0
- package/dist/nlp/llmFallback.js +28 -1
- package/dist/nlp/multiClassifier.js +91 -6
- package/dist/nlp/multiIntent.d.ts +43 -0
- package/dist/nlp/multiIntent.js +154 -0
- package/dist/nlp/parseIntent.d.ts +6 -1
- package/dist/nlp/parseIntent.js +180 -5
- package/dist/nlp/ruleParser.js +317 -0
- package/dist/nlp/semanticSimilarity.d.ts +30 -0
- package/dist/nlp/semanticSimilarity.js +174 -0
- package/dist/nlp/vocabularyBuilder.d.ts +43 -0
- package/dist/nlp/vocabularyBuilder.js +224 -0
- package/dist/nlp/wikidata.d.ts +49 -0
- package/dist/nlp/wikidata.js +228 -0
- package/dist/policy/confirm.d.ts +10 -0
- package/dist/policy/confirm.js +39 -0
- package/dist/policy/safety.js +6 -4
- package/dist/types/intent.d.ts +8 -0
- package/dist/types/intent.js +1 -0
- package/dist/utils/achievements.d.ts +38 -0
- package/dist/utils/achievements.js +126 -0
- package/dist/utils/aliases.d.ts +5 -0
- package/dist/utils/aliases.js +39 -0
- package/dist/utils/analysis.js +71 -15
- package/dist/utils/bookmarks.d.ts +13 -0
- package/dist/utils/bookmarks.js +51 -0
- package/dist/utils/browser.d.ts +64 -0
- package/dist/utils/browser.js +364 -0
- package/dist/utils/commandHistory.d.ts +20 -0
- package/dist/utils/commandHistory.js +108 -0
- package/dist/utils/completer.d.ts +17 -0
- package/dist/utils/completer.js +79 -0
- package/dist/utils/config.js +32 -2
- package/dist/utils/dbQuery.d.ts +25 -0
- package/dist/utils/dbQuery.js +248 -0
- package/dist/utils/devTools.d.ts +35 -0
- package/dist/utils/devTools.js +95 -0
- package/dist/utils/discordDiag.d.ts +35 -0
- package/dist/utils/discordDiag.js +826 -0
- package/dist/utils/diskCleanup.d.ts +36 -0
- package/dist/utils/diskCleanup.js +775 -0
- package/dist/utils/entityResolver.d.ts +107 -0
- package/dist/utils/entityResolver.js +468 -0
- package/dist/utils/imageGen.d.ts +92 -0
- package/dist/utils/imageGen.js +2031 -0
- package/dist/utils/installTracker.d.ts +57 -0
- package/dist/utils/installTracker.js +160 -0
- package/dist/utils/multiExec.d.ts +21 -0
- package/dist/utils/multiExec.js +141 -0
- package/dist/utils/openclawDiag.d.ts +29 -0
- package/dist/utils/openclawDiag.js +1035 -0
- package/dist/utils/output.js +4 -0
- package/dist/utils/platform.js +2 -1
- package/dist/utils/progressReporter.d.ts +50 -0
- package/dist/utils/progressReporter.js +58 -0
- package/dist/utils/projectDetect.d.ts +44 -0
- package/dist/utils/projectDetect.js +319 -0
- package/dist/utils/projectScanner.d.ts +44 -0
- package/dist/utils/projectScanner.js +312 -0
- package/dist/utils/shellCompat.d.ts +78 -0
- package/dist/utils/shellCompat.js +186 -0
- package/dist/utils/smartArchive.d.ts +16 -0
- package/dist/utils/smartArchive.js +172 -0
- package/dist/utils/smartRetry.d.ts +26 -0
- package/dist/utils/smartRetry.js +114 -0
- package/dist/utils/snippets.d.ts +13 -0
- package/dist/utils/snippets.js +53 -0
- package/dist/utils/stabilityMatrixManager.d.ts +80 -0
- package/dist/utils/stabilityMatrixManager.js +268 -0
- package/dist/utils/teachMode.d.ts +41 -0
- package/dist/utils/teachMode.js +100 -0
- package/dist/utils/timer.d.ts +22 -0
- package/dist/utils/timer.js +52 -0
- package/dist/utils/updater.d.ts +1 -0
- package/dist/utils/updater.js +1 -1
- package/dist/utils/version.d.ts +20 -0
- package/dist/utils/version.js +212 -0
- 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
|
+
}
|
package/dist/utils/analysis.js
CHANGED
|
@@ -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
|
-
|
|
109
|
-
|
|
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 >=
|
|
113
|
-
lines.push(` ${c.yellow}⚠ WARNING: ${p.mountPoint}
|
|
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 >=
|
|
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
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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;
|