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,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity resolver with fuzzy matching.
|
|
3
|
+
*
|
|
4
|
+
* Resolves user-defined aliases like:
|
|
5
|
+
* "metroplex" → 66.94.115.165
|
|
6
|
+
* "the 66 server" → 66.94.115.165 (IP fragment match)
|
|
7
|
+
* "66" → guesses: "Did you mean metroplex (66.94.115.165)?"
|
|
8
|
+
*
|
|
9
|
+
* When guessing, always verbalizes so the user can correct.
|
|
10
|
+
*/
|
|
11
|
+
export interface ServerEntity {
|
|
12
|
+
host: string;
|
|
13
|
+
user?: string;
|
|
14
|
+
port?: number;
|
|
15
|
+
description?: string;
|
|
16
|
+
aliases: string[];
|
|
17
|
+
}
|
|
18
|
+
export interface DatabaseEntity {
|
|
19
|
+
type: "postgres" | "mysql" | "redis" | "mongo";
|
|
20
|
+
host: string;
|
|
21
|
+
port?: number;
|
|
22
|
+
name: string;
|
|
23
|
+
user?: string;
|
|
24
|
+
aliases: string[];
|
|
25
|
+
}
|
|
26
|
+
export interface ServiceGroupEntity {
|
|
27
|
+
description?: string;
|
|
28
|
+
components: string[];
|
|
29
|
+
aliases: string[];
|
|
30
|
+
}
|
|
31
|
+
export interface InstallationEntity {
|
|
32
|
+
service: string;
|
|
33
|
+
environment: string;
|
|
34
|
+
path?: string;
|
|
35
|
+
version?: string;
|
|
36
|
+
port?: number;
|
|
37
|
+
model?: string;
|
|
38
|
+
status?: "running" | "stopped" | "unknown";
|
|
39
|
+
lastSeen?: string;
|
|
40
|
+
aliases: string[];
|
|
41
|
+
}
|
|
42
|
+
export interface EntitiesConfig {
|
|
43
|
+
servers: Record<string, ServerEntity>;
|
|
44
|
+
databases: Record<string, DatabaseEntity>;
|
|
45
|
+
services: Record<string, ServiceGroupEntity>;
|
|
46
|
+
installations: Record<string, InstallationEntity>;
|
|
47
|
+
}
|
|
48
|
+
export interface ResolvedEntity {
|
|
49
|
+
name: string;
|
|
50
|
+
type: "server" | "database" | "service";
|
|
51
|
+
data: ServerEntity | DatabaseEntity | ServiceGroupEntity;
|
|
52
|
+
confidence: "exact" | "alias" | "fuzzy" | "guess";
|
|
53
|
+
matchedOn: string;
|
|
54
|
+
}
|
|
55
|
+
export declare function loadEntities(forceReload?: boolean): EntitiesConfig;
|
|
56
|
+
/**
|
|
57
|
+
* Resolve a user reference to an entity.
|
|
58
|
+
* Returns the match and confidence level.
|
|
59
|
+
* When guessing, includes a verbalized message.
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolveEntity(input: string): ResolvedEntity | null;
|
|
62
|
+
/**
|
|
63
|
+
* Format a resolution result with verbalization.
|
|
64
|
+
* For exact/alias matches: silent (just uses it).
|
|
65
|
+
* For fuzzy/guess: tells the user what it's assuming.
|
|
66
|
+
*/
|
|
67
|
+
export declare function verbalizeResolution(resolved: ResolvedEntity): string;
|
|
68
|
+
/**
|
|
69
|
+
* Get related service components for a service group.
|
|
70
|
+
* E.g. "openclaw" → ["openclaw-gateway", "matrix-synapse", "mautrix-telegram", ...]
|
|
71
|
+
*/
|
|
72
|
+
export declare function getRelatedComponents(serviceName: string): string[];
|
|
73
|
+
/**
|
|
74
|
+
* Add or update a server entity.
|
|
75
|
+
*/
|
|
76
|
+
export declare function defineServer(name: string, host: string, user?: string, description?: string, aliases?: string[]): string;
|
|
77
|
+
/**
|
|
78
|
+
* Add or update a database entity.
|
|
79
|
+
*/
|
|
80
|
+
export declare function defineDatabase(name: string, type: DatabaseEntity["type"], host: string, dbName: string, user?: string, aliases?: string[]): string;
|
|
81
|
+
/**
|
|
82
|
+
* List all defined entities.
|
|
83
|
+
*/
|
|
84
|
+
export declare function listEntities(): string;
|
|
85
|
+
/**
|
|
86
|
+
* Register a discovered service installation.
|
|
87
|
+
*/
|
|
88
|
+
export declare function registerInstallation(id: string, install: InstallationEntity): string;
|
|
89
|
+
/**
|
|
90
|
+
* Update an installation's status/version/model.
|
|
91
|
+
*/
|
|
92
|
+
export declare function updateInstallation(id: string, updates: Partial<Pick<InstallationEntity, "status" | "version" | "model" | "port" | "lastSeen">>): void;
|
|
93
|
+
/**
|
|
94
|
+
* Get all installations for a service.
|
|
95
|
+
*/
|
|
96
|
+
export declare function getInstallationsForService(service: string): Array<[string, InstallationEntity]>;
|
|
97
|
+
/**
|
|
98
|
+
* Auto-discover OpenClaw installations across WSL, Windows host, and remote servers.
|
|
99
|
+
* Registers them as entities with aliases like "the windows one", "openclaw #1".
|
|
100
|
+
*/
|
|
101
|
+
export declare function discoverInstallations(service: string): Promise<string>;
|
|
102
|
+
/**
|
|
103
|
+
* Try to learn a new entity from natural language.
|
|
104
|
+
* "metroplex is 66.94.115.165" → defineServer("metroplex", "66.94.115.165")
|
|
105
|
+
* "astrotrain is the 197 server at 197.168.1.10" → defineServer(...)
|
|
106
|
+
*/
|
|
107
|
+
export declare function learnEntity(rawText: string): string | null;
|
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity resolver with fuzzy matching.
|
|
3
|
+
*
|
|
4
|
+
* Resolves user-defined aliases like:
|
|
5
|
+
* "metroplex" → 66.94.115.165
|
|
6
|
+
* "the 66 server" → 66.94.115.165 (IP fragment match)
|
|
7
|
+
* "66" → guesses: "Did you mean metroplex (66.94.115.165)?"
|
|
8
|
+
*
|
|
9
|
+
* When guessing, always verbalizes so the user can correct.
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { resolve } from "node:path";
|
|
13
|
+
const c = {
|
|
14
|
+
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
|
|
15
|
+
green: "\x1b[32m", yellow: "\x1b[33m", cyan: "\x1b[36m",
|
|
16
|
+
};
|
|
17
|
+
// ─── Config loading ──────────────────────────────────────────────────────────
|
|
18
|
+
const CONFIG_PATH = resolve(import.meta.url.replace("file://", "").replace(/\/[^/]+$/, ""), "../../config/entities.json");
|
|
19
|
+
let _cached = null;
|
|
20
|
+
export function loadEntities(forceReload = false) {
|
|
21
|
+
if (_cached && !forceReload)
|
|
22
|
+
return _cached;
|
|
23
|
+
// Try multiple paths — monorepo + flat + cwd
|
|
24
|
+
const paths = [
|
|
25
|
+
CONFIG_PATH,
|
|
26
|
+
resolve(process.cwd(), "config/entities.json"),
|
|
27
|
+
resolve(process.cwd(), "packages/core/config/entities.json"),
|
|
28
|
+
resolve(import.meta.url.replace("file://", "").replace(/dist\/.*$/, ""), "config/entities.json"),
|
|
29
|
+
resolve(import.meta.url.replace("file://", "").replace(/src\/.*$/, ""), "config/entities.json"),
|
|
30
|
+
];
|
|
31
|
+
for (const p of paths) {
|
|
32
|
+
if (existsSync(p)) {
|
|
33
|
+
try {
|
|
34
|
+
_cached = JSON.parse(readFileSync(p, "utf-8"));
|
|
35
|
+
return _cached;
|
|
36
|
+
}
|
|
37
|
+
catch { }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
_cached = { servers: {}, databases: {}, services: {}, installations: {} };
|
|
41
|
+
return _cached;
|
|
42
|
+
}
|
|
43
|
+
function saveEntities(config) {
|
|
44
|
+
const paths = [
|
|
45
|
+
CONFIG_PATH,
|
|
46
|
+
resolve(process.cwd(), "config/entities.json"),
|
|
47
|
+
];
|
|
48
|
+
for (const p of paths) {
|
|
49
|
+
if (existsSync(p) || existsSync(resolve(p, ".."))) {
|
|
50
|
+
try {
|
|
51
|
+
writeFileSync(p, JSON.stringify(config, null, 2) + "\n");
|
|
52
|
+
_cached = config;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
catch { }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// ─── Resolution ──────────────────────────────────────────────────────────────
|
|
60
|
+
/**
|
|
61
|
+
* Resolve a user reference to an entity.
|
|
62
|
+
* Returns the match and confidence level.
|
|
63
|
+
* When guessing, includes a verbalized message.
|
|
64
|
+
*/
|
|
65
|
+
export function resolveEntity(input) {
|
|
66
|
+
const lower = input.toLowerCase().trim();
|
|
67
|
+
const entities = loadEntities();
|
|
68
|
+
// 1. Exact name match
|
|
69
|
+
for (const [name, server] of Object.entries(entities.servers)) {
|
|
70
|
+
if (lower === name.toLowerCase()) {
|
|
71
|
+
return { name, type: "server", data: server, confidence: "exact", matchedOn: name };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (const [name, db] of Object.entries(entities.databases)) {
|
|
75
|
+
if (lower === name.toLowerCase()) {
|
|
76
|
+
return { name, type: "database", data: db, confidence: "exact", matchedOn: name };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for (const [name, svc] of Object.entries(entities.services)) {
|
|
80
|
+
if (lower === name.toLowerCase()) {
|
|
81
|
+
return { name, type: "service", data: svc, confidence: "exact", matchedOn: name };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// 2. Alias match
|
|
85
|
+
for (const [name, svc] of Object.entries(entities.services)) {
|
|
86
|
+
for (const alias of svc.aliases) {
|
|
87
|
+
if (lower === alias.toLowerCase() || lower.includes(alias.toLowerCase())) {
|
|
88
|
+
return { name, type: "service", data: svc, confidence: "alias", matchedOn: alias };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const [name, server] of Object.entries(entities.servers)) {
|
|
93
|
+
for (const alias of server.aliases) {
|
|
94
|
+
if (lower === alias.toLowerCase() || lower.includes(alias.toLowerCase())) {
|
|
95
|
+
return { name, type: "server", data: server, confidence: "alias", matchedOn: alias };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
for (const [name, db] of Object.entries(entities.databases)) {
|
|
100
|
+
for (const alias of db.aliases) {
|
|
101
|
+
if (lower === alias.toLowerCase() || lower.includes(alias.toLowerCase())) {
|
|
102
|
+
return { name, type: "database", data: db, confidence: "alias", matchedOn: alias };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// 3. IP fragment match — "66" matches host containing "66", "197" matches "197"
|
|
107
|
+
const ipMatch = lower.match(/\b(\d{1,3})\b/);
|
|
108
|
+
if (ipMatch) {
|
|
109
|
+
const fragment = ipMatch[1];
|
|
110
|
+
const matches = [];
|
|
111
|
+
for (const [name, server] of Object.entries(entities.servers)) {
|
|
112
|
+
const octets = server.host.split(".");
|
|
113
|
+
if (octets.some((o) => o === fragment) || server.host.includes(fragment)) {
|
|
114
|
+
matches.push({ name, server });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (matches.length === 1) {
|
|
118
|
+
return {
|
|
119
|
+
name: matches[0].name,
|
|
120
|
+
type: "server",
|
|
121
|
+
data: matches[0].server,
|
|
122
|
+
confidence: "fuzzy",
|
|
123
|
+
matchedOn: `IP contains "${fragment}"`,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (matches.length > 1) {
|
|
127
|
+
// Multiple matches — return the first as a guess
|
|
128
|
+
return {
|
|
129
|
+
name: matches[0].name,
|
|
130
|
+
type: "server",
|
|
131
|
+
data: matches[0].server,
|
|
132
|
+
confidence: "guess",
|
|
133
|
+
matchedOn: `IP contains "${fragment}" (${matches.length} matches)`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// 4. Partial name match — "metro" matches "metroplex"
|
|
138
|
+
for (const [name, server] of Object.entries(entities.servers)) {
|
|
139
|
+
if (name.toLowerCase().startsWith(lower) || lower.startsWith(name.toLowerCase())) {
|
|
140
|
+
return { name, type: "server", data: server, confidence: "fuzzy", matchedOn: `partial name "${input}"` };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
for (const [name, db] of Object.entries(entities.databases)) {
|
|
144
|
+
if (name.toLowerCase().startsWith(lower) || lower.startsWith(name.toLowerCase())) {
|
|
145
|
+
return { name, type: "database", data: db, confidence: "fuzzy", matchedOn: `partial name "${input}"` };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// 5. Installation match — "the windows openclaw", "openclaw #2", "the wsl one"
|
|
149
|
+
for (const [name, inst] of Object.entries(entities.installations ?? {})) {
|
|
150
|
+
if (lower === name.toLowerCase()) {
|
|
151
|
+
return { name, type: "service", data: inst, confidence: "exact", matchedOn: name };
|
|
152
|
+
}
|
|
153
|
+
for (const alias of inst.aliases) {
|
|
154
|
+
if (lower === alias.toLowerCase() || lower.includes(alias.toLowerCase())) {
|
|
155
|
+
return { name, type: "service", data: inst, confidence: "alias", matchedOn: alias };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Format a resolution result with verbalization.
|
|
163
|
+
* For exact/alias matches: silent (just uses it).
|
|
164
|
+
* For fuzzy/guess: tells the user what it's assuming.
|
|
165
|
+
*/
|
|
166
|
+
export function verbalizeResolution(resolved) {
|
|
167
|
+
if (resolved.confidence === "exact")
|
|
168
|
+
return "";
|
|
169
|
+
if (resolved.confidence === "alias") {
|
|
170
|
+
return `${c.dim}(${resolved.matchedOn} → ${resolved.name})${c.reset}`;
|
|
171
|
+
}
|
|
172
|
+
if (resolved.confidence === "fuzzy") {
|
|
173
|
+
const host = resolved.data.host ?? "";
|
|
174
|
+
return `${c.yellow}Assuming "${resolved.name}" (${host}) — matched on ${resolved.matchedOn}${c.reset}`;
|
|
175
|
+
}
|
|
176
|
+
if (resolved.confidence === "guess") {
|
|
177
|
+
const host = resolved.data.host ?? "";
|
|
178
|
+
return `${c.yellow}${c.bold}Best guess: "${resolved.name}" (${host})${c.reset} — ${c.yellow}${resolved.matchedOn}. Say "no" or "stop" to cancel.${c.reset}`;
|
|
179
|
+
}
|
|
180
|
+
return "";
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Get related service components for a service group.
|
|
184
|
+
* E.g. "openclaw" → ["openclaw-gateway", "matrix-synapse", "mautrix-telegram", ...]
|
|
185
|
+
*/
|
|
186
|
+
export function getRelatedComponents(serviceName) {
|
|
187
|
+
const entities = loadEntities();
|
|
188
|
+
// Check if it's a service group
|
|
189
|
+
for (const [name, svc] of Object.entries(entities.services)) {
|
|
190
|
+
if (name === serviceName || svc.aliases.includes(serviceName.toLowerCase())) {
|
|
191
|
+
return svc.components;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
196
|
+
// ─── Entity management ───────────────────────────────────────────────────────
|
|
197
|
+
/**
|
|
198
|
+
* Add or update a server entity.
|
|
199
|
+
*/
|
|
200
|
+
export function defineServer(name, host, user, description, aliases) {
|
|
201
|
+
const entities = loadEntities();
|
|
202
|
+
// Auto-generate aliases from IP
|
|
203
|
+
const autoAliases = [];
|
|
204
|
+
const octets = host.split(".");
|
|
205
|
+
if (octets.length === 4) {
|
|
206
|
+
autoAliases.push(`the ${octets[0]} server`, `${octets[0]} server`, `${octets[0]} box`);
|
|
207
|
+
autoAliases.push(`the ${octets[3]} server`, `${octets[3]} server`, `${octets[3]} box`);
|
|
208
|
+
}
|
|
209
|
+
entities.servers[name] = {
|
|
210
|
+
host,
|
|
211
|
+
user,
|
|
212
|
+
description,
|
|
213
|
+
aliases: [...new Set([...(aliases ?? []), ...autoAliases])],
|
|
214
|
+
};
|
|
215
|
+
saveEntities(entities);
|
|
216
|
+
return `${c.green}✓${c.reset} Defined server ${c.bold}${name}${c.reset} → ${host}${description ? ` (${description})` : ""}`;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Add or update a database entity.
|
|
220
|
+
*/
|
|
221
|
+
export function defineDatabase(name, type, host, dbName, user, aliases) {
|
|
222
|
+
const entities = loadEntities();
|
|
223
|
+
entities.databases[name] = { type, host, name: dbName, user, aliases: aliases ?? [] };
|
|
224
|
+
saveEntities(entities);
|
|
225
|
+
return `${c.green}✓${c.reset} Defined database ${c.bold}${name}${c.reset} → ${type}://${host}/${dbName}`;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* List all defined entities.
|
|
229
|
+
*/
|
|
230
|
+
export function listEntities() {
|
|
231
|
+
const entities = loadEntities();
|
|
232
|
+
const lines = [];
|
|
233
|
+
lines.push(`\n${c.bold}${c.cyan}── Defined Entities ──${c.reset}\n`);
|
|
234
|
+
const servers = Object.entries(entities.servers);
|
|
235
|
+
if (servers.length > 0) {
|
|
236
|
+
lines.push(` ${c.bold}Servers:${c.reset}`);
|
|
237
|
+
for (const [name, s] of servers) {
|
|
238
|
+
lines.push(` ${c.cyan}${name}${c.reset} → ${c.bold}${s.user ? s.user + "@" : ""}${s.host}${c.reset}${s.description ? ` ${c.dim}${s.description}${c.reset}` : ""}`);
|
|
239
|
+
if (s.aliases.length > 0) {
|
|
240
|
+
lines.push(` ${c.dim}aliases: ${s.aliases.join(", ")}${c.reset}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const dbs = Object.entries(entities.databases);
|
|
245
|
+
if (dbs.length > 0) {
|
|
246
|
+
lines.push(`\n ${c.bold}Databases:${c.reset}`);
|
|
247
|
+
for (const [name, d] of dbs) {
|
|
248
|
+
lines.push(` ${c.cyan}${name}${c.reset} → ${d.type}://${d.host}/${d.name}${d.user ? ` (${d.user})` : ""}`);
|
|
249
|
+
if (d.aliases.length > 0) {
|
|
250
|
+
lines.push(` ${c.dim}aliases: ${d.aliases.join(", ")}${c.reset}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const svcs = Object.entries(entities.services);
|
|
255
|
+
if (svcs.length > 0) {
|
|
256
|
+
lines.push(`\n ${c.bold}Service Groups:${c.reset}`);
|
|
257
|
+
for (const [name, s] of svcs) {
|
|
258
|
+
lines.push(` ${c.cyan}${name}${c.reset}${s.description ? ` ${c.dim}${s.description}${c.reset}` : ""}`);
|
|
259
|
+
if (s.components.length > 0) {
|
|
260
|
+
lines.push(` ${c.bold}components:${c.reset} ${s.components.join(", ")}`);
|
|
261
|
+
}
|
|
262
|
+
if (s.aliases.length > 0) {
|
|
263
|
+
lines.push(` ${c.dim}aliases: ${s.aliases.join(", ")}${c.reset}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const installs = Object.entries(entities.installations ?? {});
|
|
268
|
+
if (installs.length > 0) {
|
|
269
|
+
lines.push(`\n ${c.bold}Installations:${c.reset}`);
|
|
270
|
+
for (const [name, inst] of installs) {
|
|
271
|
+
const statusIcon = inst.status === "running" ? `${c.green}✓` : inst.status === "stopped" ? `${c.yellow}○` : `${c.dim}?`;
|
|
272
|
+
lines.push(` ${statusIcon}${c.reset} ${c.cyan}${name}${c.reset} — ${c.bold}${inst.service}${c.reset} on ${inst.environment}${inst.version ? ` v${inst.version}` : ""}${inst.model ? ` [${inst.model}]` : ""}${inst.port ? ` :${inst.port}` : ""}`);
|
|
273
|
+
if (inst.path)
|
|
274
|
+
lines.push(` ${c.dim}path: ${inst.path}${c.reset}`);
|
|
275
|
+
if (inst.aliases.length > 0)
|
|
276
|
+
lines.push(` ${c.dim}aliases: ${inst.aliases.join(", ")}${c.reset}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (servers.length === 0 && dbs.length === 0 && svcs.length === 0 && installs.length === 0) {
|
|
280
|
+
lines.push(` ${c.dim}No entities defined yet.${c.reset}`);
|
|
281
|
+
lines.push(` ${c.dim}Define one: "metroplex is the 66.94.115.165 server"${c.reset}`);
|
|
282
|
+
}
|
|
283
|
+
return lines.join("\n");
|
|
284
|
+
}
|
|
285
|
+
// ─── Installation discovery ──────────────────────────────────────────────────
|
|
286
|
+
/**
|
|
287
|
+
* Register a discovered service installation.
|
|
288
|
+
*/
|
|
289
|
+
export function registerInstallation(id, install) {
|
|
290
|
+
const entities = loadEntities();
|
|
291
|
+
if (!entities.installations)
|
|
292
|
+
entities.installations = {};
|
|
293
|
+
entities.installations[id] = install;
|
|
294
|
+
saveEntities(entities);
|
|
295
|
+
return `${c.green}✓${c.reset} Registered ${c.bold}${id}${c.reset} — ${install.service} on ${install.environment}`;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Update an installation's status/version/model.
|
|
299
|
+
*/
|
|
300
|
+
export function updateInstallation(id, updates) {
|
|
301
|
+
const entities = loadEntities();
|
|
302
|
+
if (!entities.installations?.[id])
|
|
303
|
+
return;
|
|
304
|
+
Object.assign(entities.installations[id], updates);
|
|
305
|
+
entities.installations[id].lastSeen = new Date().toISOString();
|
|
306
|
+
saveEntities(entities);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Get all installations for a service.
|
|
310
|
+
*/
|
|
311
|
+
export function getInstallationsForService(service) {
|
|
312
|
+
const entities = loadEntities();
|
|
313
|
+
return Object.entries(entities.installations ?? {})
|
|
314
|
+
.filter(([_, inst]) => inst.service === service);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Auto-discover OpenClaw installations across WSL, Windows host, and remote servers.
|
|
318
|
+
* Registers them as entities with aliases like "the windows one", "openclaw #1".
|
|
319
|
+
*/
|
|
320
|
+
export async function discoverInstallations(service) {
|
|
321
|
+
const { execSync } = await import("node:child_process");
|
|
322
|
+
const lines = [];
|
|
323
|
+
const found = [];
|
|
324
|
+
function tryExec(cmd, timeout = 10_000) {
|
|
325
|
+
try {
|
|
326
|
+
return execSync(cmd, { timeout, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
return "";
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const isWSL = tryExec("grep -qi microsoft /proc/version 2>/dev/null && echo wsl || echo native") === "wsl";
|
|
333
|
+
if (service === "openclaw" || service === "all") {
|
|
334
|
+
// WSL / native Linux
|
|
335
|
+
const wslPath = tryExec("which openclaw 2>/dev/null");
|
|
336
|
+
if (wslPath) {
|
|
337
|
+
const wslVer = tryExec("bash -c 'for d in \"$HOME/.nvm\" \"/home/\"*\"/.nvm\" \"/root/.nvm\"; do [ -s \"$d/nvm.sh\" ] && export NVM_DIR=\"$d\" && . \"$d/nvm.sh\" && break; done 2>/dev/null; nvm use 22 > /dev/null 2>&1; openclaw --version 2>/dev/null | head -1'");
|
|
338
|
+
const wslRunning = !!tryExec("pgrep -f openclaw-gateway 2>/dev/null");
|
|
339
|
+
const wslConfig = tryExec("cat /root/.openclaw/openclaw.json 2>/dev/null");
|
|
340
|
+
const wslModel = wslConfig.match(/"primary"\s*:\s*"([^"]+)"/)?.[1];
|
|
341
|
+
found.push({
|
|
342
|
+
id: "openclaw-wsl",
|
|
343
|
+
inst: {
|
|
344
|
+
service: "openclaw", environment: "wsl", path: wslPath,
|
|
345
|
+
version: wslVer.replace(/^OpenClaw\s*/i, "").split(" ")[0] || undefined,
|
|
346
|
+
port: 18789, model: wslModel, status: wslRunning ? "running" : "stopped",
|
|
347
|
+
aliases: ["wsl openclaw", "the wsl one", "openclaw in wsl", "linux openclaw"],
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
// Windows host (only from WSL)
|
|
352
|
+
if (isWSL) {
|
|
353
|
+
const winPath = tryExec("/mnt/c/Windows/System32/cmd.exe /c \"where openclaw\" 2>/dev/null");
|
|
354
|
+
if (winPath.includes("openclaw")) {
|
|
355
|
+
const psExe = "/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe";
|
|
356
|
+
const winPs = tryExec(`${psExe} -Command "Get-WmiObject Win32_Process -Filter \\"Name='node.exe'\\" | Select -Exp CommandLine" 2>/dev/null`);
|
|
357
|
+
const winRunning = winPs.includes("openclaw") && winPs.includes("gateway");
|
|
358
|
+
const winConfig = tryExec(`${psExe} -Command "Get-Content \\"$env:USERPROFILE\\.openclaw\\openclaw.json\\"" 2>/dev/null`);
|
|
359
|
+
const winModel = winConfig.match(/"primary"\s*:\s*"([^"]+)"/)?.[1];
|
|
360
|
+
const winVer = tryExec("/mnt/c/Windows/System32/cmd.exe /c \"openclaw --version\" 2>/dev/null");
|
|
361
|
+
found.push({
|
|
362
|
+
id: "openclaw-windows",
|
|
363
|
+
inst: {
|
|
364
|
+
service: "openclaw", environment: "windows",
|
|
365
|
+
path: winPath.trim().split("\n")[0].replace(/\r/g, ""),
|
|
366
|
+
version: winVer.replace(/^OpenClaw\s*/i, "").split(" ")[0] || undefined,
|
|
367
|
+
port: 18789, model: winModel, status: winRunning ? "running" : "stopped",
|
|
368
|
+
aliases: ["windows openclaw", "the windows one", "openclaw on windows", "host openclaw", "the other openclaw"],
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (service === "ollama" || service === "all") {
|
|
375
|
+
// WSL Ollama
|
|
376
|
+
const ollamaPath = tryExec("which ollama 2>/dev/null");
|
|
377
|
+
if (ollamaPath) {
|
|
378
|
+
const ollamaVer = tryExec("ollama --version 2>/dev/null | head -1");
|
|
379
|
+
const ollamaRunning = !!tryExec("curl -sf http://localhost:11434/api/tags 2>/dev/null | head -1");
|
|
380
|
+
found.push({
|
|
381
|
+
id: "ollama-wsl",
|
|
382
|
+
inst: {
|
|
383
|
+
service: "ollama", environment: "wsl", path: ollamaPath,
|
|
384
|
+
version: ollamaVer.replace(/^ollama\s+version\s*/i, "").trim() || undefined,
|
|
385
|
+
port: 11434, status: ollamaRunning ? "running" : "stopped",
|
|
386
|
+
aliases: ["wsl ollama", "the wsl ollama", "ollama in wsl", "local ollama"],
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
// Windows Ollama
|
|
391
|
+
if (isWSL) {
|
|
392
|
+
const winOllama = tryExec("/mnt/c/Windows/System32/cmd.exe /c \"where ollama\" 2>/dev/null");
|
|
393
|
+
if (winOllama.includes("ollama")) {
|
|
394
|
+
const winOllamaRunning = tryExec("cmd.exe /c 'tasklist /FI \"IMAGENAME eq ollama.exe\" /NH' 2>/dev/null").includes("ollama");
|
|
395
|
+
found.push({
|
|
396
|
+
id: "ollama-windows",
|
|
397
|
+
inst: {
|
|
398
|
+
service: "ollama", environment: "windows",
|
|
399
|
+
path: winOllama.trim().split("\n")[0].replace(/\r/g, ""),
|
|
400
|
+
port: 11434, status: winOllamaRunning ? "running" : "stopped",
|
|
401
|
+
aliases: ["windows ollama", "the windows ollama", "ollama on windows"],
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
// Register all found installations
|
|
408
|
+
const entities = loadEntities();
|
|
409
|
+
if (!entities.installations)
|
|
410
|
+
entities.installations = {};
|
|
411
|
+
for (let i = 0; i < found.length; i++) {
|
|
412
|
+
const { id, inst } = found[i];
|
|
413
|
+
// Add numbered alias
|
|
414
|
+
inst.aliases.push(`${inst.service} #${i + 1}`);
|
|
415
|
+
inst.lastSeen = new Date().toISOString();
|
|
416
|
+
entities.installations[id] = inst;
|
|
417
|
+
}
|
|
418
|
+
saveEntities(entities);
|
|
419
|
+
_cached = null; // force reload
|
|
420
|
+
// Format output
|
|
421
|
+
if (found.length === 0) {
|
|
422
|
+
return `${c.dim}No ${service} installations found.${c.reset}`;
|
|
423
|
+
}
|
|
424
|
+
lines.push(`\n${c.bold}${c.cyan}── Discovered ${found.length} Installation${found.length > 1 ? "s" : ""} ──${c.reset}\n`);
|
|
425
|
+
for (const { id, inst } of found) {
|
|
426
|
+
const statusIcon = inst.status === "running" ? `${c.green}✓` : `${c.yellow}○`;
|
|
427
|
+
lines.push(` ${statusIcon}${c.reset} ${c.bold}${id}${c.reset}`);
|
|
428
|
+
lines.push(` ${inst.service} on ${c.bold}${inst.environment}${c.reset}${inst.version ? ` v${inst.version}` : ""}`);
|
|
429
|
+
if (inst.model)
|
|
430
|
+
lines.push(` model: ${inst.model}`);
|
|
431
|
+
if (inst.path)
|
|
432
|
+
lines.push(` ${c.dim}path: ${inst.path}${c.reset}`);
|
|
433
|
+
lines.push(` ${c.dim}aliases: ${inst.aliases.join(", ")}${c.reset}`);
|
|
434
|
+
}
|
|
435
|
+
lines.push(`\n ${c.dim}Reference by: "${found[0].inst.service} #1", "the windows one", "${found[0].id}"${c.reset}`);
|
|
436
|
+
return lines.join("\n");
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Try to learn a new entity from natural language.
|
|
440
|
+
* "metroplex is 66.94.115.165" → defineServer("metroplex", "66.94.115.165")
|
|
441
|
+
* "astrotrain is the 197 server at 197.168.1.10" → defineServer(...)
|
|
442
|
+
*/
|
|
443
|
+
export function learnEntity(rawText) {
|
|
444
|
+
const lower = rawText.toLowerCase();
|
|
445
|
+
// "<name> is <ip>" or "<name> is the <ip> server"
|
|
446
|
+
const serverMatch = lower.match(/^(\w+)\s+is\s+(?:the\s+)?(?:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:\s+server)?|(?:the\s+)?(\d{1,3})\s+server)/);
|
|
447
|
+
if (serverMatch) {
|
|
448
|
+
const name = serverMatch[1];
|
|
449
|
+
const fullIp = serverMatch[2];
|
|
450
|
+
const partialIp = serverMatch[3];
|
|
451
|
+
if (fullIp) {
|
|
452
|
+
return defineServer(name, fullIp);
|
|
453
|
+
}
|
|
454
|
+
if (partialIp) {
|
|
455
|
+
// Partial IP — store as a pattern
|
|
456
|
+
return defineServer(name, `*.*.*.${partialIp}`, undefined, `Server with IP ending in ${partialIp}`, [`the ${partialIp} server`, `${partialIp} server`]);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
// "<name> is a <type> database at <host>"
|
|
460
|
+
const dbMatch = lower.match(/^(\w+)\s+is\s+(?:a\s+)?(postgres|mysql|redis|mongo)\w*\s+(?:database|db)\s+(?:at\s+)?(\S+)?/);
|
|
461
|
+
if (dbMatch) {
|
|
462
|
+
const name = dbMatch[1];
|
|
463
|
+
const type = dbMatch[2];
|
|
464
|
+
const host = dbMatch[3] ?? "localhost";
|
|
465
|
+
return defineDatabase(name, type, host, name);
|
|
466
|
+
}
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Image Generation.
|
|
3
|
+
*
|
|
4
|
+
* Detects, installs, and interfaces with local image generation engines.
|
|
5
|
+
* Falls back to informing users about online services.
|
|
6
|
+
*
|
|
7
|
+
* Supported local engines:
|
|
8
|
+
* 1. AUTOMATIC1111 (Stable Diffusion Web UI) — API at :7860
|
|
9
|
+
* 2. ComfyUI — API at :8188
|
|
10
|
+
* 3. Fooocus — simplest UI
|
|
11
|
+
* 4. Docker (stable-diffusion-webui container)
|
|
12
|
+
*
|
|
13
|
+
* Online services (info only):
|
|
14
|
+
* - OpenAI DALL-E API
|
|
15
|
+
* - Midjourney (Discord-based)
|
|
16
|
+
* - Leonardo.ai
|
|
17
|
+
* - Stability AI API
|
|
18
|
+
*/
|
|
19
|
+
export interface DriveInfo {
|
|
20
|
+
path: string;
|
|
21
|
+
freeGB: number;
|
|
22
|
+
totalGB: number;
|
|
23
|
+
usedPct: number;
|
|
24
|
+
mount: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function getDriveInfo(path: string): DriveInfo | null;
|
|
27
|
+
export interface InstallDirChoice {
|
|
28
|
+
dir: string;
|
|
29
|
+
freeGB: number;
|
|
30
|
+
reasoning: string;
|
|
31
|
+
candidates: Array<{
|
|
32
|
+
path: string;
|
|
33
|
+
freeGB: number;
|
|
34
|
+
rejected?: string;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
/** Resolve a user-specified path like "D drive", "F:", "/mnt/f", "/opt/mydir" */
|
|
38
|
+
export declare function resolveUserPath(input: string): string | null;
|
|
39
|
+
export type ImageEngine = "auto1111" | "comfyui" | "fooocus" | "docker" | "stability-matrix" | "easy-diffusion" | "none";
|
|
40
|
+
export interface ImageEngineStatus {
|
|
41
|
+
engine: ImageEngine;
|
|
42
|
+
installed: boolean;
|
|
43
|
+
running: boolean;
|
|
44
|
+
path?: string;
|
|
45
|
+
url?: string;
|
|
46
|
+
version?: string;
|
|
47
|
+
platform?: "wsl" | "windows" | "linux" | "macos";
|
|
48
|
+
pid?: number;
|
|
49
|
+
port?: number;
|
|
50
|
+
portConflict?: boolean;
|
|
51
|
+
}
|
|
52
|
+
export interface GpuInfo {
|
|
53
|
+
hasNvidia: boolean;
|
|
54
|
+
hasAmd: boolean;
|
|
55
|
+
gpuName?: string;
|
|
56
|
+
vram?: string;
|
|
57
|
+
vramFree?: string;
|
|
58
|
+
gpuTemp?: string;
|
|
59
|
+
gpuUtil?: string;
|
|
60
|
+
driverVersion?: string;
|
|
61
|
+
maxCudaVersion?: string;
|
|
62
|
+
gpuError?: string;
|
|
63
|
+
wslCuda?: boolean;
|
|
64
|
+
recommendedTorch?: string;
|
|
65
|
+
cudaVersion?: string;
|
|
66
|
+
cpuOnly: boolean;
|
|
67
|
+
}
|
|
68
|
+
export interface GenerateResult {
|
|
69
|
+
success: boolean;
|
|
70
|
+
engine?: ImageEngine;
|
|
71
|
+
imagePath?: string;
|
|
72
|
+
prompt?: string;
|
|
73
|
+
error?: string;
|
|
74
|
+
message?: string;
|
|
75
|
+
}
|
|
76
|
+
export declare function detectGpu(): GpuInfo;
|
|
77
|
+
export declare function detectImageEngines(): ImageEngineStatus[];
|
|
78
|
+
export declare function getBestImageEngine(): ImageEngineStatus | null;
|
|
79
|
+
export declare function generateImage(prompt: string): Promise<GenerateResult>;
|
|
80
|
+
export interface InstallPlan {
|
|
81
|
+
engine: string;
|
|
82
|
+
steps: string[];
|
|
83
|
+
requirements: string[];
|
|
84
|
+
estimatedTime: string;
|
|
85
|
+
diskSpace: string;
|
|
86
|
+
}
|
|
87
|
+
export declare function getInstallPlan(engine: "auto1111" | "comfyui" | "fooocus" | "docker"): InstallPlan;
|
|
88
|
+
export declare function installImageEngine(engine: "auto1111" | "comfyui" | "fooocus" | "docker"): Promise<{
|
|
89
|
+
success: boolean;
|
|
90
|
+
message: string;
|
|
91
|
+
}>;
|
|
92
|
+
export declare function formatImageEngineStatus(engines: ImageEngineStatus[]): string;
|