notoken-core 1.6.0 → 2.0.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/chat-responses.json +767 -0
- package/config/concept-clusters.json +31 -0
- package/config/entities.json +93 -0
- package/config/image-prompts.json +20 -0
- package/config/intent-vectors.json +1 -0
- package/config/intents.json +4946 -83
- package/config/ollama-models.json +193 -0
- package/config/rules.json +32 -1
- 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/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 +532 -3
- package/dist/handlers/executor.js +3981 -16
- package/dist/index.d.ts +25 -3
- package/dist/index.js +36 -2
- 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 +315 -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/utils/aliases.d.ts +5 -0
- package/dist/utils/aliases.js +39 -0
- package/dist/utils/analysis.js +71 -15
- 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/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/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,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart archive creation.
|
|
3
|
+
*
|
|
4
|
+
* Auto-excludes heavy/non-essential directories (node_modules, .git, etc.)
|
|
5
|
+
* unless the user explicitly requests them. Checks disk space before archiving.
|
|
6
|
+
*/
|
|
7
|
+
import { exec } from "node:child_process";
|
|
8
|
+
import { promisify } from "node:util";
|
|
9
|
+
import { existsSync, statSync } from "node:fs";
|
|
10
|
+
import { resolve, basename } from "node:path";
|
|
11
|
+
import { detectLocalPlatform } from "./platform.js";
|
|
12
|
+
import { askForConfirmation } from "../policy/confirm.js";
|
|
13
|
+
const execAsync = promisify(exec);
|
|
14
|
+
const c = {
|
|
15
|
+
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
|
|
16
|
+
green: "\x1b[32m", yellow: "\x1b[33m", red: "\x1b[31m", cyan: "\x1b[36m",
|
|
17
|
+
};
|
|
18
|
+
/** Directories that are safe to exclude by default (regenerable/cached). */
|
|
19
|
+
const DEFAULT_EXCLUDES = [
|
|
20
|
+
"node_modules",
|
|
21
|
+
".git",
|
|
22
|
+
".next",
|
|
23
|
+
".nuxt",
|
|
24
|
+
"dist",
|
|
25
|
+
"build",
|
|
26
|
+
"__pycache__",
|
|
27
|
+
".venv",
|
|
28
|
+
"venv",
|
|
29
|
+
".env.local",
|
|
30
|
+
".cache",
|
|
31
|
+
".turbo",
|
|
32
|
+
"vendor", // PHP composer
|
|
33
|
+
"target", // Rust cargo
|
|
34
|
+
".gradle",
|
|
35
|
+
".idea",
|
|
36
|
+
".vscode",
|
|
37
|
+
"*.pyc",
|
|
38
|
+
"*.o",
|
|
39
|
+
"*.class",
|
|
40
|
+
".DS_Store",
|
|
41
|
+
"Thumbs.db",
|
|
42
|
+
];
|
|
43
|
+
/** Estimate the size of a directory (excluding default excludes). */
|
|
44
|
+
async function estimateSize(source, excludes) {
|
|
45
|
+
const plat = detectLocalPlatform();
|
|
46
|
+
try {
|
|
47
|
+
// Total size
|
|
48
|
+
const { stdout: totalOut } = await execAsync(`du -sb "${source}" 2>/dev/null | cut -f1`, { timeout: 30_000 });
|
|
49
|
+
const totalBytes = parseInt(totalOut.trim()) || 0;
|
|
50
|
+
// Size of excluded dirs
|
|
51
|
+
let excludedBytes = 0;
|
|
52
|
+
for (const ex of excludes) {
|
|
53
|
+
if (ex.startsWith("*"))
|
|
54
|
+
continue; // skip glob patterns
|
|
55
|
+
const exPath = resolve(source, ex);
|
|
56
|
+
if (existsSync(exPath)) {
|
|
57
|
+
try {
|
|
58
|
+
const { stdout } = await execAsync(`du -sb "${exPath}" 2>/dev/null | cut -f1`, { timeout: 10_000 });
|
|
59
|
+
excludedBytes += parseInt(stdout.trim()) || 0;
|
|
60
|
+
}
|
|
61
|
+
catch { }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
totalGB: totalBytes / 1073741824,
|
|
66
|
+
excludedGB: excludedBytes / 1073741824,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return { totalGB: 0, excludedGB: 0 };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Check available disk space at a path. */
|
|
74
|
+
async function getAvailableSpaceGB(path) {
|
|
75
|
+
try {
|
|
76
|
+
const { stdout } = await execAsync(`df -B1 "${path}" 2>/dev/null | tail -1 | awk '{print $4}'`, { timeout: 5000 });
|
|
77
|
+
return parseInt(stdout.trim()) / 1073741824;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return -1; // unknown
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Smart archive: checks space, shows what will be excluded, asks before creating.
|
|
85
|
+
*/
|
|
86
|
+
export async function smartArchive(options) {
|
|
87
|
+
const source = resolve(options.source);
|
|
88
|
+
const lines = [];
|
|
89
|
+
if (!existsSync(source)) {
|
|
90
|
+
return `${c.red}✗ Source not found: ${source}${c.reset}`;
|
|
91
|
+
}
|
|
92
|
+
// Determine destination
|
|
93
|
+
const destName = options.destination || `${basename(source)}.tar.gz`;
|
|
94
|
+
const dest = resolve(destName.endsWith(".tar.gz") || destName.endsWith(".tgz") ? destName : destName + ".tar.gz");
|
|
95
|
+
// Determine exclusions
|
|
96
|
+
const excludes = options.includeAll ? [] : DEFAULT_EXCLUDES;
|
|
97
|
+
const foundExcludes = [];
|
|
98
|
+
// Check which excludable dirs actually exist in source
|
|
99
|
+
if (!options.includeAll) {
|
|
100
|
+
for (const ex of DEFAULT_EXCLUDES) {
|
|
101
|
+
if (ex.startsWith("*"))
|
|
102
|
+
continue;
|
|
103
|
+
const exPath = resolve(source, ex);
|
|
104
|
+
if (existsSync(exPath)) {
|
|
105
|
+
try {
|
|
106
|
+
const stat = statSync(exPath);
|
|
107
|
+
if (stat.isDirectory()) {
|
|
108
|
+
const { stdout } = await execAsync(`du -sb "${exPath}" 2>/dev/null | cut -f1`, { timeout: 10_000 });
|
|
109
|
+
const sizeGB = parseInt(stdout.trim()) / 1073741824;
|
|
110
|
+
foundExcludes.push({ name: ex, sizeGB });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch { }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Estimate sizes
|
|
118
|
+
const sizes = await estimateSize(source, options.includeAll ? [] : DEFAULT_EXCLUDES.filter(e => !e.startsWith("*")));
|
|
119
|
+
const archiveEstimateGB = (sizes.totalGB - sizes.excludedGB) * 0.3; // rough compression ratio
|
|
120
|
+
const availableGB = await getAvailableSpaceGB(resolve(dest, ".."));
|
|
121
|
+
lines.push(`\n${c.bold}${c.cyan}── Smart Archive ──${c.reset}\n`);
|
|
122
|
+
lines.push(` Source: ${c.bold}${source}${c.reset}`);
|
|
123
|
+
lines.push(` Destination: ${c.bold}${dest}${c.reset}`);
|
|
124
|
+
lines.push(` Source size: ${c.bold}${sizes.totalGB.toFixed(2)} GB${c.reset}`);
|
|
125
|
+
if (foundExcludes.length > 0) {
|
|
126
|
+
const totalExcluded = foundExcludes.reduce((s, e) => s + e.sizeGB, 0);
|
|
127
|
+
lines.push(`\n ${c.bold}Auto-excluding (${totalExcluded.toFixed(2)} GB saved):${c.reset}`);
|
|
128
|
+
for (const ex of foundExcludes.sort((a, b) => b.sizeGB - a.sizeGB)) {
|
|
129
|
+
const sizeStr = ex.sizeGB >= 1 ? `${ex.sizeGB.toFixed(2)} GB` : `${(ex.sizeGB * 1024).toFixed(0)} MB`;
|
|
130
|
+
lines.push(` ${c.yellow}${sizeStr.padStart(10)}${c.reset} ${ex.name}/`);
|
|
131
|
+
}
|
|
132
|
+
lines.push(`\n ${c.dim}To include everything: add "include all" or "with node_modules"${c.reset}`);
|
|
133
|
+
}
|
|
134
|
+
lines.push(`\n Estimated archive: ${c.bold}~${archiveEstimateGB.toFixed(2)} GB${c.reset} ${c.dim}(compressed)${c.reset}`);
|
|
135
|
+
// Space check
|
|
136
|
+
if (availableGB >= 0) {
|
|
137
|
+
lines.push(` Available space: ${c.bold}${availableGB.toFixed(2)} GB${c.reset}`);
|
|
138
|
+
if (archiveEstimateGB > availableGB * 0.9) {
|
|
139
|
+
lines.push(`\n ${c.red}${c.bold}⚠ NOT ENOUGH SPACE!${c.reset} Archive (~${archiveEstimateGB.toFixed(2)} GB) may exceed available space (${availableGB.toFixed(2)} GB).`);
|
|
140
|
+
lines.push(` ${c.yellow}Free up space first: notoken "free up space"${c.reset}`);
|
|
141
|
+
return lines.join("\n");
|
|
142
|
+
}
|
|
143
|
+
else if (archiveEstimateGB > availableGB * 0.5) {
|
|
144
|
+
lines.push(` ${c.yellow}⚠ This will use ${Math.round((archiveEstimateGB / availableGB) * 100)}% of remaining space.${c.reset}`);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
lines.push(` ${c.green}✓ Plenty of space.${c.reset}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
console.log(lines.join("\n"));
|
|
151
|
+
// Confirm
|
|
152
|
+
const ok = await askForConfirmation(`\nCreate archive?`);
|
|
153
|
+
if (!ok) {
|
|
154
|
+
return `${c.dim}Cancelled.${c.reset}`;
|
|
155
|
+
}
|
|
156
|
+
// Build tar command
|
|
157
|
+
const excludeFlags = excludes.map((e) => `--exclude='${e}'`).join(" ");
|
|
158
|
+
const tarCmd = `tar -czf "${dest}" ${excludeFlags} -C "${resolve(source, "..")}" "${basename(source)}"`;
|
|
159
|
+
console.log(`\n${c.dim}→ ${tarCmd}${c.reset}`);
|
|
160
|
+
try {
|
|
161
|
+
await execAsync(tarCmd, { timeout: 600_000 }); // 10 min timeout for large archives
|
|
162
|
+
// Show result
|
|
163
|
+
const stat = statSync(dest);
|
|
164
|
+
const resultGB = stat.size / 1073741824;
|
|
165
|
+
const sizeStr = resultGB >= 1 ? `${resultGB.toFixed(2)} GB` : `${(resultGB * 1024).toFixed(0)} MB`;
|
|
166
|
+
return `\n${c.green}${c.bold}✓ Archive created: ${dest}${c.reset}\n Size: ${c.bold}${sizeStr}${c.reset}`;
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
170
|
+
return `\n${c.red}✗ Archive failed: ${msg.split("\n")[0]}${c.reset}`;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Retry — analyzes command failures and suggests fixes.
|
|
3
|
+
*
|
|
4
|
+
* When a command fails, this module inspects the error message,
|
|
5
|
+
* identifies common patterns, and suggests an actionable fix
|
|
6
|
+
* that can be registered as a pending action.
|
|
7
|
+
*/
|
|
8
|
+
export interface FailureAnalysis {
|
|
9
|
+
/** Whether this failure has an automated fix */
|
|
10
|
+
canFix: boolean;
|
|
11
|
+
/** Short human-friendly suggestion shown to the user */
|
|
12
|
+
suggestion: string;
|
|
13
|
+
/** The command/intent text to execute as the fix */
|
|
14
|
+
fixCommand: string;
|
|
15
|
+
/** Why this fix should help */
|
|
16
|
+
explanation: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Analyze a command failure and suggest a fix.
|
|
20
|
+
*
|
|
21
|
+
* @param intent - The intent name or raw text that failed
|
|
22
|
+
* @param error - The error (string or Error)
|
|
23
|
+
* @param fields - Parsed intent fields (service name, tool, path, etc.)
|
|
24
|
+
* @returns A FailureAnalysis if a known pattern matches, or null
|
|
25
|
+
*/
|
|
26
|
+
export declare function analyzeFailure(intent: string, error: string | Error, fields?: Record<string, unknown>): FailureAnalysis | null;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Retry — analyzes command failures and suggests fixes.
|
|
3
|
+
*
|
|
4
|
+
* When a command fails, this module inspects the error message,
|
|
5
|
+
* identifies common patterns, and suggests an actionable fix
|
|
6
|
+
* that can be registered as a pending action.
|
|
7
|
+
*/
|
|
8
|
+
const patterns = [
|
|
9
|
+
{
|
|
10
|
+
pattern: /command not found[:\s]*(\S+)|(\S+):\s*not found|'(\S+)' is not recognized/i,
|
|
11
|
+
build: (m) => {
|
|
12
|
+
const tool = (m[1] || m[2] || m[3]).replace(/['"]/g, "");
|
|
13
|
+
return {
|
|
14
|
+
canFix: true,
|
|
15
|
+
suggestion: `${tool} is not installed. Install it?`,
|
|
16
|
+
fixCommand: `install ${tool}`,
|
|
17
|
+
explanation: `The command "${tool}" was not found on this system. Installing it should fix the issue.`,
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
pattern: /connection refused|ECONNREFUSED/i,
|
|
23
|
+
build: (_m, _intent, fields) => {
|
|
24
|
+
const service = fields.service || fields.tool || "the service";
|
|
25
|
+
return {
|
|
26
|
+
canFix: true,
|
|
27
|
+
suggestion: `Can't connect — ${service} may not be running. Start it?`,
|
|
28
|
+
fixCommand: `start ${service}`,
|
|
29
|
+
explanation: `Connection was refused, which usually means the target service is not running.`,
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
pattern: /permission denied|EACCES|access denied/i,
|
|
35
|
+
build: (_m, intent) => ({
|
|
36
|
+
canFix: true,
|
|
37
|
+
suggestion: "Permission denied. Retry with elevated privileges?",
|
|
38
|
+
fixCommand: `sudo ${intent}`,
|
|
39
|
+
explanation: "The command failed due to insufficient permissions. Running with sudo may resolve it.",
|
|
40
|
+
}),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
pattern: /no such file or directory[:\s]*(.+)|ENOENT[:\s]*(.+)/i,
|
|
44
|
+
build: (m) => {
|
|
45
|
+
const raw = (m[1] || m[2] || "").trim().replace(/['"]/g, "");
|
|
46
|
+
const filename = raw.split("/").pop() || raw;
|
|
47
|
+
return {
|
|
48
|
+
canFix: true,
|
|
49
|
+
suggestion: `File not found: ${filename}. Search for it?`,
|
|
50
|
+
fixCommand: `find ${filename}`,
|
|
51
|
+
explanation: `The path "${raw}" does not exist. A search may locate the correct path.`,
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
pattern: /address already in use|port.*already in use|EADDRINUSE/i,
|
|
57
|
+
build: () => ({
|
|
58
|
+
canFix: true,
|
|
59
|
+
suggestion: "Port already in use. Check what's using it?",
|
|
60
|
+
fixCommand: "check ports",
|
|
61
|
+
explanation: "Another process is occupying the required port.",
|
|
62
|
+
}),
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
pattern: /no space left|disk full|ENOSPC/i,
|
|
66
|
+
build: () => ({
|
|
67
|
+
canFix: true,
|
|
68
|
+
suggestion: "Disk is full. Free up space?",
|
|
69
|
+
fixCommand: "free up space",
|
|
70
|
+
explanation: "The disk has no remaining space. Clearing caches or temp files may help.",
|
|
71
|
+
}),
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
pattern: /timed?\s*out|ETIMEDOUT|ESOCKETTIMEDOUT/i,
|
|
75
|
+
build: (_m, intent) => ({
|
|
76
|
+
canFix: true,
|
|
77
|
+
suggestion: "Command timed out. Try again?",
|
|
78
|
+
fixCommand: intent,
|
|
79
|
+
explanation: "The operation exceeded its time limit. A retry may succeed if the issue was transient.",
|
|
80
|
+
}),
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
pattern: /ECONNREFUSED/i,
|
|
84
|
+
build: (_m, _intent, fields) => {
|
|
85
|
+
const service = fields.service || fields.tool || "the service";
|
|
86
|
+
return {
|
|
87
|
+
canFix: true,
|
|
88
|
+
suggestion: `Can't connect to ${service}. Check if it's running?`,
|
|
89
|
+
fixCommand: `check status ${service}`,
|
|
90
|
+
explanation: "The connection was actively refused — the target service may be down.",
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
/**
|
|
96
|
+
* Analyze a command failure and suggest a fix.
|
|
97
|
+
*
|
|
98
|
+
* @param intent - The intent name or raw text that failed
|
|
99
|
+
* @param error - The error (string or Error)
|
|
100
|
+
* @param fields - Parsed intent fields (service name, tool, path, etc.)
|
|
101
|
+
* @returns A FailureAnalysis if a known pattern matches, or null
|
|
102
|
+
*/
|
|
103
|
+
export function analyzeFailure(intent, error, fields = {}) {
|
|
104
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
105
|
+
if (!message)
|
|
106
|
+
return null;
|
|
107
|
+
for (const { pattern, build } of patterns) {
|
|
108
|
+
const match = message.match(pattern);
|
|
109
|
+
if (match) {
|
|
110
|
+
return build(match, intent, fields);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
package/dist/utils/updater.d.ts
CHANGED
package/dist/utils/updater.js
CHANGED
|
@@ -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;
|
|
@@ -0,0 +1,212 @@
|
|
|
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
|
+
import { execSync } from "node:child_process";
|
|
8
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
9
|
+
import { resolve, dirname } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
const c = {
|
|
13
|
+
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
|
|
14
|
+
green: "\x1b[32m", yellow: "\x1b[33m", red: "\x1b[31m", cyan: "\x1b[36m",
|
|
15
|
+
};
|
|
16
|
+
/** Get the current local version from package.json. */
|
|
17
|
+
export function getLocalVersion() {
|
|
18
|
+
try {
|
|
19
|
+
// Try relative to this file first (works in dist/)
|
|
20
|
+
const pkgPath = resolve(dirname(fileURLToPath(import.meta.url)), "../../package.json");
|
|
21
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
22
|
+
return pkg.version ?? "unknown";
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return "unknown";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Fetch the latest version from npm registry (non-blocking, best-effort). */
|
|
29
|
+
export async function getLatestVersion() {
|
|
30
|
+
try {
|
|
31
|
+
const result = execSync("npm view notoken version 2>/dev/null", {
|
|
32
|
+
encoding: "utf-8",
|
|
33
|
+
timeout: 10_000,
|
|
34
|
+
}).trim();
|
|
35
|
+
return result || null;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Compare two semver strings. Returns 1 if a > b, -1 if a < b, 0 if equal. */
|
|
42
|
+
export function compareSemver(a, b) {
|
|
43
|
+
const pa = a.split(".").map(Number);
|
|
44
|
+
const pb = b.split(".").map(Number);
|
|
45
|
+
for (let i = 0; i < 3; i++) {
|
|
46
|
+
if ((pa[i] ?? 0) > (pb[i] ?? 0))
|
|
47
|
+
return 1;
|
|
48
|
+
if ((pa[i] ?? 0) < (pb[i] ?? 0))
|
|
49
|
+
return -1;
|
|
50
|
+
}
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
/** Check if an update is available. Returns formatted message or null. */
|
|
54
|
+
export async function checkForUpdate() {
|
|
55
|
+
const local = getLocalVersion();
|
|
56
|
+
if (local === "unknown")
|
|
57
|
+
return null;
|
|
58
|
+
const latest = await getLatestVersion();
|
|
59
|
+
if (!latest)
|
|
60
|
+
return null;
|
|
61
|
+
if (compareSemver(latest, local) > 0) {
|
|
62
|
+
return `${c.yellow}Update available: ${c.bold}v${local}${c.reset}${c.yellow} → ${c.bold}${c.green}v${latest}${c.reset}${c.yellow} Run: ${c.cyan}notoken upgrade${c.reset}`;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
// ─── Version history ─────────────────────────────────────────────────────────
|
|
67
|
+
const VERSION_HISTORY_PATH = resolve(homedir(), ".notoken", "version-history.json");
|
|
68
|
+
function loadVersionHistory() {
|
|
69
|
+
try {
|
|
70
|
+
if (existsSync(VERSION_HISTORY_PATH)) {
|
|
71
|
+
return JSON.parse(readFileSync(VERSION_HISTORY_PATH, "utf-8"));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch { }
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
function saveVersionHistory(history) {
|
|
78
|
+
const dir = dirname(VERSION_HISTORY_PATH);
|
|
79
|
+
if (!existsSync(dir))
|
|
80
|
+
mkdirSync(dir, { recursive: true });
|
|
81
|
+
writeFileSync(VERSION_HISTORY_PATH, JSON.stringify(history, null, 2) + "\n");
|
|
82
|
+
}
|
|
83
|
+
function recordVersion(version, action) {
|
|
84
|
+
const history = loadVersionHistory();
|
|
85
|
+
history.push({ version, date: new Date().toISOString(), action });
|
|
86
|
+
// Keep last 20 entries
|
|
87
|
+
if (history.length > 20)
|
|
88
|
+
history.splice(0, history.length - 20);
|
|
89
|
+
saveVersionHistory(history);
|
|
90
|
+
}
|
|
91
|
+
/** Get the previous version from history. */
|
|
92
|
+
function getPreviousVersion() {
|
|
93
|
+
const history = loadVersionHistory();
|
|
94
|
+
// Find the second-to-last unique version
|
|
95
|
+
const versions = [...new Set(history.map((h) => h.version))];
|
|
96
|
+
return versions.length >= 2 ? versions[versions.length - 2] : null;
|
|
97
|
+
}
|
|
98
|
+
// ─── Upgrade ─────────────────────────────────────────────────────────────────
|
|
99
|
+
/** Run the upgrade. Records current version before upgrading for rollback. */
|
|
100
|
+
export async function runUpgrade() {
|
|
101
|
+
const local = getLocalVersion();
|
|
102
|
+
const latest = await getLatestVersion();
|
|
103
|
+
console.log(`${c.bold}notoken upgrade${c.reset}\n`);
|
|
104
|
+
console.log(` Current version: ${c.bold}v${local}${c.reset}`);
|
|
105
|
+
if (!latest) {
|
|
106
|
+
console.log(` ${c.yellow}Could not check npm registry.${c.reset}`);
|
|
107
|
+
console.log(` ${c.dim}Try manually: npm install -g notoken@latest${c.reset}`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
console.log(` Latest version: ${c.bold}v${latest}${c.reset}`);
|
|
111
|
+
if (compareSemver(latest, local) <= 0) {
|
|
112
|
+
console.log(`\n ${c.green}✓ Already up to date.${c.reset}`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// Record current version before upgrading
|
|
116
|
+
recordVersion(local, "upgrade");
|
|
117
|
+
console.log(`\n ${c.cyan}Upgrading v${local} → v${latest}...${c.reset}`);
|
|
118
|
+
console.log(` ${c.dim}Previous version saved — run "notoken rollback" to revert.${c.reset}\n`);
|
|
119
|
+
try {
|
|
120
|
+
execSync("npm install -g notoken@latest", {
|
|
121
|
+
stdio: "inherit",
|
|
122
|
+
timeout: 120_000,
|
|
123
|
+
});
|
|
124
|
+
recordVersion(latest, "upgrade");
|
|
125
|
+
console.log(`\n ${c.green}✓ Upgraded to v${latest}${c.reset}`);
|
|
126
|
+
console.log(` ${c.dim}If something breaks: notoken rollback${c.reset}`);
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
130
|
+
console.error(`\n ${c.red}✗ Upgrade failed: ${msg.split("\n")[0]}${c.reset}`);
|
|
131
|
+
console.error(` ${c.dim}Try manually: npm install -g notoken@latest${c.reset}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// ─── Rollback ────────────────────────────────────────────────────────────────
|
|
135
|
+
/** Roll back to the previous version. */
|
|
136
|
+
export async function runRollback(targetVersion) {
|
|
137
|
+
const local = getLocalVersion();
|
|
138
|
+
console.log(`${c.bold}notoken rollback${c.reset}\n`);
|
|
139
|
+
console.log(` Current version: ${c.bold}v${local}${c.reset}`);
|
|
140
|
+
// Determine target version
|
|
141
|
+
let target = targetVersion;
|
|
142
|
+
if (!target) {
|
|
143
|
+
target = getPreviousVersion() ?? undefined;
|
|
144
|
+
}
|
|
145
|
+
if (!target) {
|
|
146
|
+
// No history — list available versions for the user to pick
|
|
147
|
+
console.log(` ${c.yellow}No previous version in history.${c.reset}\n`);
|
|
148
|
+
console.log(` ${c.bold}Available versions:${c.reset}`);
|
|
149
|
+
await listVersions();
|
|
150
|
+
console.log(`\n ${c.dim}To rollback to a specific version:${c.reset}`);
|
|
151
|
+
console.log(` ${c.cyan}notoken rollback 1.0.1${c.reset}`);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (target === local) {
|
|
155
|
+
console.log(` ${c.green}✓ Already on v${target}.${c.reset}`);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
console.log(` Rolling back to: ${c.bold}v${target}${c.reset}\n`);
|
|
159
|
+
try {
|
|
160
|
+
execSync(`npm install -g notoken@${target}`, {
|
|
161
|
+
stdio: "inherit",
|
|
162
|
+
timeout: 120_000,
|
|
163
|
+
});
|
|
164
|
+
recordVersion(target, "rollback");
|
|
165
|
+
console.log(`\n ${c.green}✓ Rolled back to v${target}${c.reset}`);
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
169
|
+
console.error(`\n ${c.red}✗ Rollback failed: ${msg.split("\n")[0]}${c.reset}`);
|
|
170
|
+
console.error(` ${c.dim}Try manually: npm install -g notoken@${target}${c.reset}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** List recent published versions from npm. */
|
|
174
|
+
async function listVersions() {
|
|
175
|
+
try {
|
|
176
|
+
const result = execSync("npm view notoken versions --json 2>/dev/null", {
|
|
177
|
+
encoding: "utf-8",
|
|
178
|
+
timeout: 10_000,
|
|
179
|
+
}).trim();
|
|
180
|
+
const versions = JSON.parse(result);
|
|
181
|
+
const recent = versions.slice(-10).reverse();
|
|
182
|
+
const local = getLocalVersion();
|
|
183
|
+
for (const v of recent) {
|
|
184
|
+
const marker = v === local ? ` ${c.green}← current${c.reset}` : "";
|
|
185
|
+
console.log(` ${c.cyan}v${v}${c.reset}${marker}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
console.log(` ${c.yellow}Could not fetch versions from npm.${c.reset}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/** Show version history. */
|
|
193
|
+
export function showVersionHistory() {
|
|
194
|
+
const history = loadVersionHistory();
|
|
195
|
+
console.log(`${c.bold}notoken version history${c.reset}\n`);
|
|
196
|
+
console.log(` Current: ${c.bold}v${getLocalVersion()}${c.reset}\n`);
|
|
197
|
+
if (history.length === 0) {
|
|
198
|
+
console.log(` ${c.dim}No upgrade/rollback history yet.${c.reset}`);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
for (const entry of history.slice(-10).reverse()) {
|
|
202
|
+
const icon = entry.action === "upgrade" ? `${c.green}↑${c.reset}` :
|
|
203
|
+
entry.action === "rollback" ? `${c.yellow}↓${c.reset}` :
|
|
204
|
+
`${c.cyan}•${c.reset}`;
|
|
205
|
+
const date = new Date(entry.date).toLocaleDateString();
|
|
206
|
+
console.log(` ${icon} v${entry.version} ${c.dim}${date} ${entry.action}${c.reset}`);
|
|
207
|
+
}
|
|
208
|
+
const prev = getPreviousVersion();
|
|
209
|
+
if (prev) {
|
|
210
|
+
console.log(`\n ${c.dim}To rollback: ${c.cyan}notoken rollback${c.reset} ${c.dim}(→ v${prev})${c.reset}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "notoken-core",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Shared engine for notoken
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Shared engine for notoken \u2014 NLP parsing, execution, detection, analysis",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Dino Bartolome",
|
|
@@ -25,18 +25,21 @@
|
|
|
25
25
|
"test": "vitest run"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
+
"@types/ssh2": "^1.15.5",
|
|
28
29
|
"compromise": "^14.15.0",
|
|
29
30
|
"dotenv": "^17.3.1",
|
|
30
31
|
"simple-git": "^3.33.0",
|
|
32
|
+
"ssh2": "^1.17.0",
|
|
31
33
|
"yaml": "^2.8.3",
|
|
32
34
|
"zod": "^3.22.0"
|
|
33
35
|
},
|
|
34
36
|
"devDependencies": {
|
|
35
37
|
"@types/node": "^20.0.0",
|
|
38
|
+
"playwright": "^1.59.1",
|
|
36
39
|
"typescript": "^5.3.0",
|
|
37
40
|
"vitest": "^1.6.1"
|
|
38
41
|
},
|
|
39
42
|
"engines": {
|
|
40
43
|
"node": ">=18"
|
|
41
44
|
}
|
|
42
|
-
}
|
|
45
|
+
}
|