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.
Files changed (99) hide show
  1. package/config/chat-responses.json +767 -0
  2. package/config/concept-clusters.json +31 -0
  3. package/config/entities.json +93 -0
  4. package/config/image-prompts.json +20 -0
  5. package/config/intent-vectors.json +1 -0
  6. package/config/intents.json +4946 -83
  7. package/config/ollama-models.json +193 -0
  8. package/config/rules.json +32 -1
  9. package/dist/automation/discordPatchright.d.ts +35 -0
  10. package/dist/automation/discordPatchright.js +424 -0
  11. package/dist/automation/discordSetup.d.ts +31 -0
  12. package/dist/automation/discordSetup.js +338 -0
  13. package/dist/conversation/coreference.js +44 -4
  14. package/dist/conversation/pendingActions.d.ts +55 -0
  15. package/dist/conversation/pendingActions.js +127 -0
  16. package/dist/conversation/store.d.ts +72 -0
  17. package/dist/conversation/store.js +140 -1
  18. package/dist/conversation/topicTracker.d.ts +36 -0
  19. package/dist/conversation/topicTracker.js +141 -0
  20. package/dist/execution/ssh.d.ts +42 -1
  21. package/dist/execution/ssh.js +532 -3
  22. package/dist/handlers/executor.js +3981 -16
  23. package/dist/index.d.ts +25 -3
  24. package/dist/index.js +36 -2
  25. package/dist/nlp/batchParser.d.ts +30 -0
  26. package/dist/nlp/batchParser.js +77 -0
  27. package/dist/nlp/conceptExpansion.d.ts +54 -0
  28. package/dist/nlp/conceptExpansion.js +136 -0
  29. package/dist/nlp/conceptRouter.d.ts +49 -0
  30. package/dist/nlp/conceptRouter.js +302 -0
  31. package/dist/nlp/confidenceCalibrator.d.ts +62 -0
  32. package/dist/nlp/confidenceCalibrator.js +116 -0
  33. package/dist/nlp/correctionLearner.d.ts +45 -0
  34. package/dist/nlp/correctionLearner.js +207 -0
  35. package/dist/nlp/entitySpellCorrect.d.ts +35 -0
  36. package/dist/nlp/entitySpellCorrect.js +141 -0
  37. package/dist/nlp/knowledgeGraph.d.ts +70 -0
  38. package/dist/nlp/knowledgeGraph.js +380 -0
  39. package/dist/nlp/llmFallback.js +28 -1
  40. package/dist/nlp/multiClassifier.js +91 -6
  41. package/dist/nlp/multiIntent.d.ts +43 -0
  42. package/dist/nlp/multiIntent.js +154 -0
  43. package/dist/nlp/parseIntent.d.ts +6 -1
  44. package/dist/nlp/parseIntent.js +180 -5
  45. package/dist/nlp/ruleParser.js +315 -0
  46. package/dist/nlp/semanticSimilarity.d.ts +30 -0
  47. package/dist/nlp/semanticSimilarity.js +174 -0
  48. package/dist/nlp/vocabularyBuilder.d.ts +43 -0
  49. package/dist/nlp/vocabularyBuilder.js +224 -0
  50. package/dist/nlp/wikidata.d.ts +49 -0
  51. package/dist/nlp/wikidata.js +228 -0
  52. package/dist/policy/confirm.d.ts +10 -0
  53. package/dist/policy/confirm.js +39 -0
  54. package/dist/policy/safety.js +6 -4
  55. package/dist/utils/aliases.d.ts +5 -0
  56. package/dist/utils/aliases.js +39 -0
  57. package/dist/utils/analysis.js +71 -15
  58. package/dist/utils/browser.d.ts +64 -0
  59. package/dist/utils/browser.js +364 -0
  60. package/dist/utils/commandHistory.d.ts +20 -0
  61. package/dist/utils/commandHistory.js +108 -0
  62. package/dist/utils/completer.d.ts +17 -0
  63. package/dist/utils/completer.js +79 -0
  64. package/dist/utils/config.js +32 -2
  65. package/dist/utils/dbQuery.d.ts +25 -0
  66. package/dist/utils/dbQuery.js +248 -0
  67. package/dist/utils/discordDiag.d.ts +35 -0
  68. package/dist/utils/discordDiag.js +826 -0
  69. package/dist/utils/diskCleanup.d.ts +36 -0
  70. package/dist/utils/diskCleanup.js +775 -0
  71. package/dist/utils/entityResolver.d.ts +107 -0
  72. package/dist/utils/entityResolver.js +468 -0
  73. package/dist/utils/imageGen.d.ts +92 -0
  74. package/dist/utils/imageGen.js +2031 -0
  75. package/dist/utils/installTracker.d.ts +57 -0
  76. package/dist/utils/installTracker.js +160 -0
  77. package/dist/utils/multiExec.d.ts +21 -0
  78. package/dist/utils/multiExec.js +141 -0
  79. package/dist/utils/openclawDiag.d.ts +29 -0
  80. package/dist/utils/openclawDiag.js +1035 -0
  81. package/dist/utils/output.js +4 -0
  82. package/dist/utils/platform.js +2 -1
  83. package/dist/utils/progressReporter.d.ts +50 -0
  84. package/dist/utils/progressReporter.js +58 -0
  85. package/dist/utils/projectDetect.d.ts +44 -0
  86. package/dist/utils/projectDetect.js +319 -0
  87. package/dist/utils/projectScanner.d.ts +44 -0
  88. package/dist/utils/projectScanner.js +312 -0
  89. package/dist/utils/shellCompat.d.ts +78 -0
  90. package/dist/utils/shellCompat.js +186 -0
  91. package/dist/utils/smartArchive.d.ts +16 -0
  92. package/dist/utils/smartArchive.js +172 -0
  93. package/dist/utils/smartRetry.d.ts +26 -0
  94. package/dist/utils/smartRetry.js +114 -0
  95. package/dist/utils/updater.d.ts +1 -0
  96. package/dist/utils/updater.js +1 -1
  97. package/dist/utils/version.d.ts +20 -0
  98. package/dist/utils/version.js +212 -0
  99. package/package.json +6 -3
@@ -27,6 +27,22 @@ export interface UncertaintyReport {
27
27
  }>;
28
28
  overallConfidence: number;
29
29
  }
30
+ export interface EntityFocus {
31
+ /** The entity/installation currently being discussed */
32
+ entityId: string;
33
+ /** What type — "installation", "server", "database", "service" */
34
+ entityType: string;
35
+ /** When it became the focus */
36
+ focusedAt: string;
37
+ /** Turn ID when it became the focus */
38
+ focusedAtTurn: number;
39
+ /** Conversation history of discussed entities (most recent first) */
40
+ history: Array<{
41
+ entityId: string;
42
+ entityType: string;
43
+ turnId: number;
44
+ }>;
45
+ }
30
46
  export interface Conversation {
31
47
  id: string;
32
48
  folderPath: string;
@@ -35,6 +51,15 @@ export interface Conversation {
35
51
  turns: ConversationTurn[];
36
52
  /** Running knowledge of entities mentioned across turns */
37
53
  knowledgeTree: KnowledgeNode[];
54
+ /** Currently focused entity — for "it", "that one", "restart it" resolution */
55
+ focus?: EntityFocus;
56
+ /** Files loaded into context — content available to LLM and command analysis */
57
+ contextFiles?: Array<{
58
+ path: string;
59
+ name: string;
60
+ content: string;
61
+ loadedAt: string;
62
+ }>;
38
63
  }
39
64
  export interface KnowledgeNode {
40
65
  entity: string;
@@ -80,6 +105,53 @@ export declare function addUserTurn(conv: Conversation, rawText: string, intent?
80
105
  * Add a system result turn.
81
106
  */
82
107
  export declare function addSystemTurn(conv: Conversation, rawText: string, result?: string, error?: string): ConversationTurn;
108
+ /**
109
+ * Set the currently discussed entity. Called when the user explicitly
110
+ * references an entity/installation (e.g., "the windows openclaw",
111
+ * "openclaw #2", "start openclaw on windows").
112
+ */
113
+ export declare function setEntityFocus(conv: Conversation, entityId: string, entityType: string, turnId?: number): void;
114
+ /**
115
+ * Get the currently focused entity.
116
+ * Returns null if nothing is focused or focus is stale.
117
+ * Staleness = too many turns have passed without re-mentioning the entity.
118
+ * (Not wall-clock time — if user leaves and comes back, focus is still valid.)
119
+ */
120
+ export declare function getEntityFocus(conv: Conversation, maxTurnsSinceFocus?: number): EntityFocus | null;
121
+ /**
122
+ * Get the previously discussed entity (for "the other one" resolution).
123
+ * Returns the entity that was focused before the current one.
124
+ */
125
+ export declare function getPreviousFocus(conv: Conversation): {
126
+ entityId: string;
127
+ entityType: string;
128
+ } | null;
129
+ /**
130
+ * Resolve "it", "that one", "this one" to the focused entity.
131
+ * Resolve "the other one" to the previously focused entity.
132
+ */
133
+ export declare function resolveFocusReference(conv: Conversation, text: string): {
134
+ entityId: string;
135
+ entityType: string;
136
+ } | null;
137
+ /**
138
+ * Load a file into the conversation context.
139
+ * Content is stored and available to LLM fallback and analysis.
140
+ * Max 50KB per file to avoid bloating conversation store.
141
+ */
142
+ export declare function loadContextFile(conv: Conversation, filePath: string): string;
143
+ /**
144
+ * Unload a file from context.
145
+ */
146
+ export declare function unloadContextFile(conv: Conversation, fileName: string): string;
147
+ /**
148
+ * List loaded context files.
149
+ */
150
+ export declare function listContextFiles(conv: Conversation): string;
151
+ /**
152
+ * Get all context file contents as a single string for LLM prompts.
153
+ */
154
+ export declare function getContextFilesContent(conv: Conversation): string;
83
155
  /**
84
156
  * Get the most recently mentioned entity of a given type.
85
157
  */
@@ -1,5 +1,5 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from "node:fs";
2
- import { resolve } from "node:path";
2
+ import { resolve, basename } from "node:path";
3
3
  import { homedir } from "node:os";
4
4
  const CONVERSATIONS_ROOT = resolve(homedir(), ".notoken", "conversations");
5
5
  // ─── Store ───────────────────────────────────────────────────────────────────
@@ -158,6 +158,145 @@ function updateKnowledge(conv, entity, turnId, fields) {
158
158
  }
159
159
  }
160
160
  }
161
+ // ─── Entity Focus ──────────────────────────────────────────────────────────
162
+ /**
163
+ * Set the currently discussed entity. Called when the user explicitly
164
+ * references an entity/installation (e.g., "the windows openclaw",
165
+ * "openclaw #2", "start openclaw on windows").
166
+ */
167
+ export function setEntityFocus(conv, entityId, entityType, turnId) {
168
+ const turn = turnId ?? conv.turns.length;
169
+ const entry = { entityId, entityType, turnId: turn };
170
+ if (!conv.focus) {
171
+ conv.focus = { entityId, entityType, focusedAt: new Date().toISOString(), focusedAtTurn: turn, history: [] };
172
+ }
173
+ else {
174
+ // Push previous focus to history
175
+ if (conv.focus.entityId !== entityId) {
176
+ conv.focus.history.unshift({ entityId: conv.focus.entityId, entityType: conv.focus.entityType, turnId: conv.focus.focusedAtTurn });
177
+ // Keep last 10
178
+ if (conv.focus.history.length > 10)
179
+ conv.focus.history = conv.focus.history.slice(0, 10);
180
+ }
181
+ conv.focus.entityId = entityId;
182
+ conv.focus.entityType = entityType;
183
+ conv.focus.focusedAt = new Date().toISOString();
184
+ conv.focus.focusedAtTurn = turn;
185
+ }
186
+ saveConversation(conv);
187
+ }
188
+ /**
189
+ * Get the currently focused entity.
190
+ * Returns null if nothing is focused or focus is stale.
191
+ * Staleness = too many turns have passed without re-mentioning the entity.
192
+ * (Not wall-clock time — if user leaves and comes back, focus is still valid.)
193
+ */
194
+ export function getEntityFocus(conv, maxTurnsSinceFocus = 8) {
195
+ if (!conv.focus)
196
+ return null;
197
+ // Focus expires after N turns without re-mentioning
198
+ const turnsSinceFocus = conv.turns.length - conv.focus.focusedAtTurn;
199
+ if (turnsSinceFocus > maxTurnsSinceFocus)
200
+ return null;
201
+ return conv.focus;
202
+ }
203
+ /**
204
+ * Get the previously discussed entity (for "the other one" resolution).
205
+ * Returns the entity that was focused before the current one.
206
+ */
207
+ export function getPreviousFocus(conv) {
208
+ if (!conv.focus?.history?.length)
209
+ return null;
210
+ return conv.focus.history[0];
211
+ }
212
+ /**
213
+ * Resolve "it", "that one", "this one" to the focused entity.
214
+ * Resolve "the other one" to the previously focused entity.
215
+ */
216
+ export function resolveFocusReference(conv, text) {
217
+ const lower = text.toLowerCase();
218
+ // "the other one", "the previous one", "not this one"
219
+ if (/\bthe\s+other\s+(one|entity|install|openclaw|ollama)\b|\bnot\s+this\s+one\b|\bthe\s+previous\s+one\b/.test(lower)) {
220
+ return getPreviousFocus(conv);
221
+ }
222
+ // "it", "this one", "that one", "the same one" — return current focus
223
+ if (/\b(restart|stop|start|check|update|configure|diagnose|fix)\s+(it|this|that)\b|\bthis\s+one\b|\bthat\s+one\b|\bthe\s+same\s+one\b/.test(lower)) {
224
+ const focus = getEntityFocus(conv);
225
+ if (focus)
226
+ return { entityId: focus.entityId, entityType: focus.entityType };
227
+ }
228
+ return null;
229
+ }
230
+ // ─── Context Files ──────────────────────────────────────────────────────────
231
+ /**
232
+ * Load a file into the conversation context.
233
+ * Content is stored and available to LLM fallback and analysis.
234
+ * Max 50KB per file to avoid bloating conversation store.
235
+ */
236
+ export function loadContextFile(conv, filePath) {
237
+ const absPath = resolve(filePath);
238
+ if (!existsSync(absPath)) {
239
+ return `File not found: ${absPath}`;
240
+ }
241
+ const stat = require("node:fs").statSync(absPath);
242
+ if (stat.size > 50 * 1024) {
243
+ return `File too large (${(stat.size / 1024).toFixed(0)} KB). Max 50 KB for context files. Use file.read for large files.`;
244
+ }
245
+ const content = readFileSync(absPath, "utf-8");
246
+ const name = basename(absPath);
247
+ if (!conv.contextFiles)
248
+ conv.contextFiles = [];
249
+ // Replace if already loaded
250
+ const existing = conv.contextFiles.findIndex((f) => f.path === absPath);
251
+ if (existing >= 0) {
252
+ conv.contextFiles[existing] = { path: absPath, name, content, loadedAt: new Date().toISOString() };
253
+ }
254
+ else {
255
+ conv.contextFiles.push({ path: absPath, name, content, loadedAt: new Date().toISOString() });
256
+ }
257
+ saveConversation(conv);
258
+ return `Loaded ${name} (${content.split("\n").length} lines, ${(stat.size / 1024).toFixed(1)} KB) into context.`;
259
+ }
260
+ /**
261
+ * Unload a file from context.
262
+ */
263
+ export function unloadContextFile(conv, fileName) {
264
+ if (!conv.contextFiles?.length)
265
+ return "No context files loaded.";
266
+ const idx = conv.contextFiles.findIndex((f) => f.name === fileName || f.path.endsWith(fileName));
267
+ if (idx < 0)
268
+ return `Not in context: ${fileName}`;
269
+ const removed = conv.contextFiles.splice(idx, 1)[0];
270
+ saveConversation(conv);
271
+ return `Removed ${removed.name} from context.`;
272
+ }
273
+ /**
274
+ * List loaded context files.
275
+ */
276
+ export function listContextFiles(conv) {
277
+ if (!conv.contextFiles?.length)
278
+ return "No context files loaded.";
279
+ const c = { reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m", cyan: "\x1b[36m", green: "\x1b[32m" };
280
+ const lines = [`\n${c.bold}${c.cyan}── Context Files ──${c.reset}\n`];
281
+ for (const f of conv.contextFiles) {
282
+ const lineCount = f.content.split("\n").length;
283
+ const sizeKB = (Buffer.byteLength(f.content) / 1024).toFixed(1);
284
+ lines.push(` ${c.green}✓${c.reset} ${c.bold}${f.name}${c.reset} ${c.dim}(${lineCount} lines, ${sizeKB} KB, loaded ${new Date(f.loadedAt).toLocaleString()})${c.reset}`);
285
+ lines.push(` ${c.dim}${f.path}${c.reset}`);
286
+ }
287
+ lines.push(`\n${c.dim}To unload: :context unload <filename>${c.reset}`);
288
+ return lines.join("\n");
289
+ }
290
+ /**
291
+ * Get all context file contents as a single string for LLM prompts.
292
+ */
293
+ export function getContextFilesContent(conv) {
294
+ if (!conv.contextFiles?.length)
295
+ return "";
296
+ return conv.contextFiles
297
+ .map((f) => `=== ${f.name} ===\n${f.content}`)
298
+ .join("\n\n");
299
+ }
161
300
  /**
162
301
  * Get the most recently mentioned entity of a given type.
163
302
  */
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Topic Tracker — tracks what domain the conversation is about.
3
+ *
4
+ * If the user has been talking about Docker for 5 turns, bare commands
5
+ * like "check status" or "restart" should default to Docker context.
6
+ *
7
+ * Also provides suggested follow-up commands based on what was just done.
8
+ */
9
+ import type { Conversation } from "./store.js";
10
+ export interface TopicContext {
11
+ /** Current dominant topic */
12
+ topic: string | null;
13
+ /** How many consecutive turns on this topic */
14
+ depth: number;
15
+ /** Confidence that this is the active topic (0-1) */
16
+ confidence: number;
17
+ /** Recent topics with counts */
18
+ recentTopics: Array<{
19
+ topic: string;
20
+ count: number;
21
+ }>;
22
+ }
23
+ /**
24
+ * Analyze recent conversation to determine the current topic.
25
+ */
26
+ export declare function getCurrentTopic(conv: Conversation, lookback?: number): TopicContext;
27
+ /**
28
+ * Suggest follow-up commands based on what was just executed.
29
+ */
30
+ export declare function suggestFollowups(intent: string): string[];
31
+ /**
32
+ * Get topic-aware default for ambiguous commands.
33
+ * "check status" during a Docker conversation → docker.list
34
+ * "restart" during a services conversation → service.restart
35
+ */
36
+ export declare function getTopicDefault(ambiguousVerb: string, topic: TopicContext): string | null;
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Topic Tracker — tracks what domain the conversation is about.
3
+ *
4
+ * If the user has been talking about Docker for 5 turns, bare commands
5
+ * like "check status" or "restart" should default to Docker context.
6
+ *
7
+ * Also provides suggested follow-up commands based on what was just done.
8
+ */
9
+ // ─── Topic detection ────────────────────────────────────────────────────────
10
+ const INTENT_TO_TOPIC = {
11
+ "docker.": "docker",
12
+ "service.": "services",
13
+ "server.": "system",
14
+ "disk.": "disk",
15
+ "logs.": "logs",
16
+ "network.": "network",
17
+ "git.": "git",
18
+ "deploy.": "deploy",
19
+ "openclaw.": "openclaw",
20
+ "discord.": "discord",
21
+ "ollama.": "ollama",
22
+ "security.": "security",
23
+ "db.": "database",
24
+ "backup.": "backup",
25
+ "user.": "users",
26
+ "firewall.": "firewall",
27
+ "cron.": "cron",
28
+ "files.": "files",
29
+ "process.": "processes",
30
+ "ai.": "ai",
31
+ "notoken.": "notoken",
32
+ "weather.": "general",
33
+ "news.": "general",
34
+ };
35
+ function intentToTopic(intent) {
36
+ for (const [prefix, topic] of Object.entries(INTENT_TO_TOPIC)) {
37
+ if (intent.startsWith(prefix))
38
+ return topic;
39
+ }
40
+ return null;
41
+ }
42
+ /**
43
+ * Analyze recent conversation to determine the current topic.
44
+ */
45
+ export function getCurrentTopic(conv, lookback = 8) {
46
+ const recent = conv.turns.slice(-lookback).filter(t => t.role === "user" && t.intent);
47
+ if (recent.length === 0)
48
+ return { topic: null, depth: 0, confidence: 0, recentTopics: [] };
49
+ // Count topics in recent turns
50
+ const topicCounts = new Map();
51
+ let consecutiveCount = 0;
52
+ let lastTopic = null;
53
+ for (const turn of recent.reverse()) {
54
+ const topic = intentToTopic(turn.intent);
55
+ if (!topic)
56
+ continue;
57
+ topicCounts.set(topic, (topicCounts.get(topic) ?? 0) + 1);
58
+ if (lastTopic === null) {
59
+ lastTopic = topic;
60
+ consecutiveCount = 1;
61
+ }
62
+ else if (topic === lastTopic) {
63
+ consecutiveCount++;
64
+ }
65
+ }
66
+ if (!lastTopic)
67
+ return { topic: null, depth: 0, confidence: 0, recentTopics: [] };
68
+ // Most frequent topic
69
+ const sorted = [...topicCounts.entries()].sort((a, b) => b[1] - a[1]);
70
+ const dominantTopic = sorted[0][0];
71
+ const dominantCount = sorted[0][1];
72
+ // Confidence based on dominance ratio and consecutive turns
73
+ const confidence = Math.min(1.0, (dominantCount / recent.length) * 0.6 +
74
+ (consecutiveCount / Math.min(recent.length, 5)) * 0.4);
75
+ return {
76
+ topic: dominantTopic,
77
+ depth: consecutiveCount,
78
+ confidence,
79
+ recentTopics: sorted.map(([topic, count]) => ({ topic, count })),
80
+ };
81
+ }
82
+ // ─── Suggested commands ─────────────────────────────────────────────────────
83
+ const SUGGESTIONS = {
84
+ "service.restart": ["check service status", "show logs", "is it running now"],
85
+ "service.status": ["restart service", "show logs", "check memory"],
86
+ "server.check_disk": ["free up space", "scan drives", "show large files"],
87
+ "server.uptime": ["check memory", "list processes", "check disk"],
88
+ "server.check_memory": ["list processes", "check disk", "kill process"],
89
+ "disk.cleanup": ["check disk space", "scan drives"],
90
+ "disk.scan": ["free up space", "check disk space"],
91
+ "docker.list": ["docker logs", "restart container", "docker images"],
92
+ "docker.restart": ["docker list", "docker logs", "check status"],
93
+ "logs.tail": ["search logs for errors", "show error logs"],
94
+ "logs.errors": ["show full logs", "restart service", "check status"],
95
+ "deploy.run": ["check status", "rollback deploy", "show logs"],
96
+ "network.ports": ["check firewall rules", "block ip", "scan for attacks"],
97
+ "security.scan": ["check firewall rules", "install fail2ban", "block ip"],
98
+ "git.status": ["git log", "git pull", "git diff"],
99
+ "git.pull": ["git status", "git log"],
100
+ "process.list": ["kill process", "check memory", "check load"],
101
+ "process.kill": ["list processes", "check memory"],
102
+ "backup.create": ["list backups", "check disk space"],
103
+ "ai.generate_image": ["check image status", "generate another image"],
104
+ "openclaw.status": ["diagnose openclaw", "restart openclaw"],
105
+ "openclaw.diagnose": ["fix openclaw", "restart openclaw"],
106
+ "weather.current": ["news headlines"],
107
+ "notoken.status": ["check disk", "check load", "show running services"],
108
+ };
109
+ /**
110
+ * Suggest follow-up commands based on what was just executed.
111
+ */
112
+ export function suggestFollowups(intent) {
113
+ return SUGGESTIONS[intent] ?? [];
114
+ }
115
+ /**
116
+ * Get topic-aware default for ambiguous commands.
117
+ * "check status" during a Docker conversation → docker.list
118
+ * "restart" during a services conversation → service.restart
119
+ */
120
+ export function getTopicDefault(ambiguousVerb, topic) {
121
+ if (!topic.topic || topic.confidence < 0.4)
122
+ return null;
123
+ const defaults = {
124
+ docker: { status: "docker.list", restart: "docker.restart", stop: "docker.stop", logs: "docker.logs", list: "docker.list" },
125
+ services: { status: "service.status", restart: "service.restart", stop: "service.stop", start: "service.start", check: "service.status" },
126
+ system: { status: "server.uptime", check: "server.uptime" },
127
+ disk: { status: "server.check_disk", check: "server.check_disk", clean: "disk.cleanup", scan: "disk.scan" },
128
+ logs: { show: "logs.tail", check: "logs.errors", search: "logs.search" },
129
+ network: { check: "network.ports", scan: "network.ports", status: "network.ports" },
130
+ git: { status: "git.status", check: "git.status" },
131
+ security: { check: "security.scan", scan: "security.scan" },
132
+ database: { check: "db.size", status: "db.tables" },
133
+ openclaw: { status: "openclaw.status", check: "openclaw.status", restart: "openclaw.restart", diagnose: "openclaw.diagnose" },
134
+ discord: { status: "discord.check", check: "discord.check", diagnose: "discord.diagnose" },
135
+ };
136
+ const topicDefaults = defaults[topic.topic];
137
+ if (!topicDefaults)
138
+ return null;
139
+ const verb = ambiguousVerb.toLowerCase().trim();
140
+ return topicDefaults[verb] ?? null;
141
+ }
@@ -1,2 +1,43 @@
1
+ /**
2
+ * SSH / local / Docker execution layer.
3
+ *
4
+ * Uses the `ssh2` npm package for all remote connections:
5
+ * - Password auth (no sshpass/expect/plink needed)
6
+ * - Key-based auth (reads key file directly)
7
+ * - SSH agent forwarding
8
+ * - Reads ~/.ssh/config for host aliases, keys, ports
9
+ *
10
+ * Falls back to system `ssh` binary only if ssh2 fails unexpectedly.
11
+ */
12
+ export interface HostEntry {
13
+ host: string;
14
+ description: string;
15
+ port?: number;
16
+ key?: string;
17
+ password?: string;
18
+ /** Path to a credentials file. Format: first line = username, second line = password */
19
+ credentialsFile?: string;
20
+ }
1
21
  export declare function runRemoteCommand(environment: string, command: string): Promise<string>;
2
- export declare function runLocalCommand(command: string): Promise<string>;
22
+ export declare function runLocalCommand(command: string, timeout?: number): Promise<string>;
23
+ /**
24
+ * Run a command inside a Docker container.
25
+ */
26
+ export declare function runDockerExec(container: string, command: string): Promise<string>;
27
+ /**
28
+ * Test SSH connectivity to a host. Returns formatted status.
29
+ */
30
+ export declare function testSshConnection(environment: string): Promise<string>;
31
+ export interface TransferProgress {
32
+ file: string;
33
+ bytesTransferred: number;
34
+ totalBytes: number;
35
+ filesCompleted: number;
36
+ totalFiles: number;
37
+ }
38
+ export type TransferDirection = "upload" | "download";
39
+ /**
40
+ * Transfer files to/from a remote host via SFTP.
41
+ * Supports single files and recursive directories.
42
+ */
43
+ export declare function sftpTransfer(environment: string, localPath: string, remotePath: string, direction: TransferDirection, onProgress?: (progress: TransferProgress) => void): Promise<string>;