@kernel.chat/kbot 3.86.0 → 3.87.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/dist/tools/index.js
CHANGED
|
@@ -318,6 +318,7 @@ const LAZY_MODULE_IMPORTS = [
|
|
|
318
318
|
{ path: './stream-character.js', registerFn: 'registerStreamCharacterTools' },
|
|
319
319
|
{ path: './stream-renderer.js', registerFn: 'registerStreamRendererTools' },
|
|
320
320
|
{ path: './kbot-browser.js', registerFn: 'registerKBotBrowserTools' },
|
|
321
|
+
{ path: './kbot-terminal.js', registerFn: 'registerKBotTerminalTools' },
|
|
321
322
|
];
|
|
322
323
|
/** Track whether lazy tools have been registered */
|
|
323
324
|
let lazyToolsRegistered = false;
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
// kbot Persistent Terminal — kbot's own shell environment
|
|
2
|
+
//
|
|
3
|
+
// A persistent terminal that survives session disconnects.
|
|
4
|
+
// Shell state (cwd, env, history) persists to disk.
|
|
5
|
+
// Command queue enables autonomous unattended execution.
|
|
6
|
+
// Multiple named sessions with independent state.
|
|
7
|
+
//
|
|
8
|
+
// State: ~/.kbot/terminal-state.json
|
|
9
|
+
// Queue: ~/.kbot/terminal-queue.json
|
|
10
|
+
import { spawn } from 'node:child_process';
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
12
|
+
import { join, resolve } from 'node:path';
|
|
13
|
+
import { homedir } from 'node:os';
|
|
14
|
+
import { registerTool } from './index.js';
|
|
15
|
+
// ─── State ────────────────────────────────────────────────────────────────
|
|
16
|
+
const KBOT_DIR = join(homedir(), '.kbot');
|
|
17
|
+
const TERMINAL_STATE = join(KBOT_DIR, 'terminal-state.json');
|
|
18
|
+
const COMMAND_QUEUE = join(KBOT_DIR, 'terminal-queue.json');
|
|
19
|
+
const terminal = {
|
|
20
|
+
sessions: new Map(),
|
|
21
|
+
defaultSession: '',
|
|
22
|
+
};
|
|
23
|
+
let queueInterval = null;
|
|
24
|
+
// ─── Session Management ───────────────────────────────────────────────────
|
|
25
|
+
function createShellSession(name) {
|
|
26
|
+
const id = `term_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
27
|
+
const session = {
|
|
28
|
+
id,
|
|
29
|
+
name,
|
|
30
|
+
cwd: homedir(),
|
|
31
|
+
env: { ...process.env },
|
|
32
|
+
history: [],
|
|
33
|
+
running: true,
|
|
34
|
+
pid: null,
|
|
35
|
+
createdAt: Date.now(),
|
|
36
|
+
lastActivity: Date.now(),
|
|
37
|
+
outputBuffer: [],
|
|
38
|
+
pendingCommand: null,
|
|
39
|
+
exitCode: null,
|
|
40
|
+
};
|
|
41
|
+
return session;
|
|
42
|
+
}
|
|
43
|
+
function findSession(nameOrId) {
|
|
44
|
+
if (terminal.sessions.has(nameOrId))
|
|
45
|
+
return nameOrId;
|
|
46
|
+
for (const [id, s] of terminal.sessions) {
|
|
47
|
+
if (s.name === nameOrId)
|
|
48
|
+
return id;
|
|
49
|
+
}
|
|
50
|
+
return terminal.defaultSession;
|
|
51
|
+
}
|
|
52
|
+
// ─── Command Execution ────────────────────────────────────────────────────
|
|
53
|
+
async function executeInSession(session, command) {
|
|
54
|
+
const start = Date.now();
|
|
55
|
+
return new Promise((resolve_) => {
|
|
56
|
+
const proc = spawn('bash', ['-c', command], {
|
|
57
|
+
cwd: session.cwd,
|
|
58
|
+
env: session.env,
|
|
59
|
+
timeout: 300_000, // 5 min timeout
|
|
60
|
+
});
|
|
61
|
+
session.pid = proc.pid ?? null;
|
|
62
|
+
session.pendingCommand = command;
|
|
63
|
+
let stdout = '';
|
|
64
|
+
let stderr = '';
|
|
65
|
+
proc.stdout?.on('data', (d) => {
|
|
66
|
+
const text = d.toString();
|
|
67
|
+
stdout += text;
|
|
68
|
+
// Add to rolling buffer
|
|
69
|
+
const lines = text.split('\n');
|
|
70
|
+
session.outputBuffer.push(...lines);
|
|
71
|
+
if (session.outputBuffer.length > 500) {
|
|
72
|
+
session.outputBuffer = session.outputBuffer.slice(-500);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
proc.stderr?.on('data', (d) => {
|
|
76
|
+
const text = d.toString();
|
|
77
|
+
stderr += text;
|
|
78
|
+
// Also add stderr to output buffer for visibility
|
|
79
|
+
const lines = text.split('\n');
|
|
80
|
+
session.outputBuffer.push(...lines);
|
|
81
|
+
if (session.outputBuffer.length > 500) {
|
|
82
|
+
session.outputBuffer = session.outputBuffer.slice(-500);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
proc.on('close', (code) => {
|
|
86
|
+
const entry = {
|
|
87
|
+
command,
|
|
88
|
+
output: (stdout + stderr).slice(0, 50_000), // cap at 50KB
|
|
89
|
+
exitCode: code ?? 1,
|
|
90
|
+
timestamp: start,
|
|
91
|
+
duration: Date.now() - start,
|
|
92
|
+
};
|
|
93
|
+
session.history.push(entry);
|
|
94
|
+
if (session.history.length > 200)
|
|
95
|
+
session.history = session.history.slice(-200);
|
|
96
|
+
session.lastActivity = Date.now();
|
|
97
|
+
session.exitCode = code;
|
|
98
|
+
session.pendingCommand = null;
|
|
99
|
+
session.pid = null;
|
|
100
|
+
// Track cwd changes
|
|
101
|
+
if (/^\s*cd\s/.test(command) && !command.includes('&&') && !command.includes(';')) {
|
|
102
|
+
const dir = command.replace(/^\s*cd\s+/, '').trim().replace(/^['"]|['"]$/g, '');
|
|
103
|
+
const expanded = dir.replace(/^~/, homedir());
|
|
104
|
+
try {
|
|
105
|
+
const resolved = resolve(session.cwd, expanded);
|
|
106
|
+
if (existsSync(resolved))
|
|
107
|
+
session.cwd = resolved;
|
|
108
|
+
}
|
|
109
|
+
catch { /* keep current cwd */ }
|
|
110
|
+
}
|
|
111
|
+
// Save to disk
|
|
112
|
+
saveTerminalState();
|
|
113
|
+
resolve_(entry);
|
|
114
|
+
});
|
|
115
|
+
proc.on('error', (err) => {
|
|
116
|
+
const entry = {
|
|
117
|
+
command,
|
|
118
|
+
output: `Error: ${err.message}`,
|
|
119
|
+
exitCode: 1,
|
|
120
|
+
timestamp: start,
|
|
121
|
+
duration: Date.now() - start,
|
|
122
|
+
};
|
|
123
|
+
session.history.push(entry);
|
|
124
|
+
session.lastActivity = Date.now();
|
|
125
|
+
session.pendingCommand = null;
|
|
126
|
+
session.pid = null;
|
|
127
|
+
saveTerminalState();
|
|
128
|
+
resolve_(entry);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
// ─── State Persistence ────────────────────────────────────────────────────
|
|
133
|
+
function saveTerminalState() {
|
|
134
|
+
try {
|
|
135
|
+
if (!existsSync(KBOT_DIR))
|
|
136
|
+
mkdirSync(KBOT_DIR, { recursive: true });
|
|
137
|
+
const state = {
|
|
138
|
+
sessions: Array.from(terminal.sessions.entries()).map(([_id, s]) => ({
|
|
139
|
+
id: s.id,
|
|
140
|
+
name: s.name,
|
|
141
|
+
cwd: s.cwd,
|
|
142
|
+
createdAt: s.createdAt,
|
|
143
|
+
lastActivity: s.lastActivity,
|
|
144
|
+
history: s.history.slice(-50), // save last 50 commands
|
|
145
|
+
outputBuffer: s.outputBuffer.slice(-100), // save last 100 lines
|
|
146
|
+
})),
|
|
147
|
+
defaultSession: terminal.defaultSession,
|
|
148
|
+
savedAt: Date.now(),
|
|
149
|
+
};
|
|
150
|
+
writeFileSync(TERMINAL_STATE, JSON.stringify(state, null, 2));
|
|
151
|
+
}
|
|
152
|
+
catch { /* best-effort persistence */ }
|
|
153
|
+
}
|
|
154
|
+
function loadTerminalState() {
|
|
155
|
+
try {
|
|
156
|
+
if (existsSync(TERMINAL_STATE)) {
|
|
157
|
+
const raw = readFileSync(TERMINAL_STATE, 'utf-8');
|
|
158
|
+
const state = JSON.parse(raw);
|
|
159
|
+
for (const s of state.sessions || []) {
|
|
160
|
+
terminal.sessions.set(s.id, {
|
|
161
|
+
id: s.id,
|
|
162
|
+
name: s.name,
|
|
163
|
+
cwd: s.cwd || homedir(),
|
|
164
|
+
env: { ...process.env },
|
|
165
|
+
history: s.history || [],
|
|
166
|
+
running: true,
|
|
167
|
+
pid: null,
|
|
168
|
+
createdAt: s.createdAt || Date.now(),
|
|
169
|
+
lastActivity: s.lastActivity || Date.now(),
|
|
170
|
+
outputBuffer: s.outputBuffer || [],
|
|
171
|
+
pendingCommand: null,
|
|
172
|
+
exitCode: null,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
if (state.defaultSession && terminal.sessions.has(state.defaultSession)) {
|
|
176
|
+
terminal.defaultSession = state.defaultSession;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch { /* start fresh if state is corrupted */ }
|
|
181
|
+
}
|
|
182
|
+
// ─── Command Queue ────────────────────────────────────────────────────────
|
|
183
|
+
function loadQueue() {
|
|
184
|
+
try {
|
|
185
|
+
if (existsSync(COMMAND_QUEUE)) {
|
|
186
|
+
return JSON.parse(readFileSync(COMMAND_QUEUE, 'utf-8'));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch { /* empty queue on corruption */ }
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
function saveQueue(queue) {
|
|
193
|
+
try {
|
|
194
|
+
if (!existsSync(KBOT_DIR))
|
|
195
|
+
mkdirSync(KBOT_DIR, { recursive: true });
|
|
196
|
+
writeFileSync(COMMAND_QUEUE, JSON.stringify(queue, null, 2));
|
|
197
|
+
}
|
|
198
|
+
catch { /* best-effort */ }
|
|
199
|
+
}
|
|
200
|
+
function queueCommand(command, sessionId, delay) {
|
|
201
|
+
const queue = loadQueue();
|
|
202
|
+
queue.push({
|
|
203
|
+
command,
|
|
204
|
+
session: sessionId,
|
|
205
|
+
scheduledAt: Date.now(),
|
|
206
|
+
runAt: delay ? Date.now() + delay : undefined,
|
|
207
|
+
});
|
|
208
|
+
saveQueue(queue);
|
|
209
|
+
}
|
|
210
|
+
async function processQueue() {
|
|
211
|
+
const queue = loadQueue();
|
|
212
|
+
if (queue.length === 0)
|
|
213
|
+
return;
|
|
214
|
+
const now = Date.now();
|
|
215
|
+
const ready = queue.filter(q => !q.runAt || q.runAt <= now);
|
|
216
|
+
const remaining = queue.filter(q => q.runAt != null && q.runAt > now);
|
|
217
|
+
for (const cmd of ready) {
|
|
218
|
+
const session = terminal.sessions.get(cmd.session);
|
|
219
|
+
if (session) {
|
|
220
|
+
await executeInSession(session, cmd.command);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
saveQueue(remaining);
|
|
224
|
+
}
|
|
225
|
+
function startQueueProcessor() {
|
|
226
|
+
if (queueInterval)
|
|
227
|
+
return;
|
|
228
|
+
queueInterval = setInterval(() => {
|
|
229
|
+
processQueue().catch(() => { });
|
|
230
|
+
}, 10_000);
|
|
231
|
+
// Don't keep the process alive just for the queue
|
|
232
|
+
if (queueInterval.unref)
|
|
233
|
+
queueInterval.unref();
|
|
234
|
+
}
|
|
235
|
+
// ─── Tool Registration ────────────────────────────────────────────────────
|
|
236
|
+
export function registerKBotTerminalTools() {
|
|
237
|
+
// Initialize: load persisted state
|
|
238
|
+
loadTerminalState();
|
|
239
|
+
// Ensure at least one session exists
|
|
240
|
+
if (terminal.sessions.size === 0) {
|
|
241
|
+
const defaultSession = createShellSession('main');
|
|
242
|
+
terminal.sessions.set(defaultSession.id, defaultSession);
|
|
243
|
+
terminal.defaultSession = defaultSession.id;
|
|
244
|
+
saveTerminalState();
|
|
245
|
+
}
|
|
246
|
+
else if (!terminal.defaultSession || !terminal.sessions.has(terminal.defaultSession)) {
|
|
247
|
+
terminal.defaultSession = terminal.sessions.keys().next().value;
|
|
248
|
+
}
|
|
249
|
+
// Start background queue processor
|
|
250
|
+
// Queue processor disabled by default — only runs when explicitly commanded
|
|
251
|
+
// startQueueProcessor()
|
|
252
|
+
registerTool({
|
|
253
|
+
name: 'terminal_exec',
|
|
254
|
+
description: 'Execute a command in kbot\'s persistent terminal. Shell state (cwd, history) persists across calls and sessions. Output is stored for later retrieval.',
|
|
255
|
+
parameters: {
|
|
256
|
+
command: { type: 'string', description: 'Shell command to execute', required: true },
|
|
257
|
+
session: { type: 'string', description: 'Session name or ID (default: current default session)' },
|
|
258
|
+
},
|
|
259
|
+
tier: 'free',
|
|
260
|
+
timeout: 300_000,
|
|
261
|
+
async execute(args) {
|
|
262
|
+
const sessionId = findSession(String(args.session || 'main'));
|
|
263
|
+
const session = terminal.sessions.get(sessionId);
|
|
264
|
+
if (!session)
|
|
265
|
+
return 'Session not found. Use terminal_sessions action="list" to see available sessions.';
|
|
266
|
+
const result = await executeInSession(session, String(args.command));
|
|
267
|
+
return `[${session.name}:${session.cwd}] $ ${result.command}\n${result.output}\n[exit: ${result.exitCode}, ${result.duration}ms]`;
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
registerTool({
|
|
271
|
+
name: 'terminal_history',
|
|
272
|
+
description: 'View command history from kbot\'s persistent terminal. Shows recent commands and their output. History persists across process restarts.',
|
|
273
|
+
parameters: {
|
|
274
|
+
count: { type: 'string', description: 'Number of recent commands to show (default: 10)' },
|
|
275
|
+
session: { type: 'string', description: 'Session name or ID' },
|
|
276
|
+
},
|
|
277
|
+
tier: 'free',
|
|
278
|
+
async execute(args) {
|
|
279
|
+
const sessionId = findSession(String(args.session || 'main'));
|
|
280
|
+
const session = terminal.sessions.get(sessionId);
|
|
281
|
+
if (!session)
|
|
282
|
+
return 'Session not found';
|
|
283
|
+
const count = parseInt(String(args.count || '10'), 10) || 10;
|
|
284
|
+
const recent = session.history.slice(-count);
|
|
285
|
+
if (recent.length === 0)
|
|
286
|
+
return `[${session.name}] No command history yet.`;
|
|
287
|
+
return recent.map(h => `[${new Date(h.timestamp).toLocaleTimeString()}] $ ${h.command}\n${h.output.slice(0, 200)}${h.output.length > 200 ? '...' : ''}\n[exit: ${h.exitCode}, ${h.duration}ms]`).join('\n\n');
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
registerTool({
|
|
291
|
+
name: 'terminal_output',
|
|
292
|
+
description: 'Get the current output buffer from a terminal session. Shows the last N lines of combined stdout/stderr output across all commands.',
|
|
293
|
+
parameters: {
|
|
294
|
+
lines: { type: 'string', description: 'Number of lines to show (default: 50)' },
|
|
295
|
+
session: { type: 'string', description: 'Session name or ID' },
|
|
296
|
+
},
|
|
297
|
+
tier: 'free',
|
|
298
|
+
async execute(args) {
|
|
299
|
+
const sessionId = findSession(String(args.session || 'main'));
|
|
300
|
+
const session = terminal.sessions.get(sessionId);
|
|
301
|
+
if (!session)
|
|
302
|
+
return 'Session not found';
|
|
303
|
+
const lines = parseInt(String(args.lines || '50'), 10) || 50;
|
|
304
|
+
const buf = session.outputBuffer.slice(-lines);
|
|
305
|
+
if (buf.length === 0)
|
|
306
|
+
return `[${session.name}] Output buffer empty.`;
|
|
307
|
+
return buf.join('\n');
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
registerTool({
|
|
311
|
+
name: 'terminal_sessions',
|
|
312
|
+
description: 'List, create, or close terminal sessions. Each session has its own cwd, history, and output buffer.',
|
|
313
|
+
parameters: {
|
|
314
|
+
action: { type: 'string', description: '"list", "new", or "close"', required: true },
|
|
315
|
+
name: { type: 'string', description: 'Session name (for new/close)' },
|
|
316
|
+
},
|
|
317
|
+
tier: 'free',
|
|
318
|
+
async execute(args) {
|
|
319
|
+
const action = String(args.action);
|
|
320
|
+
if (action === 'list') {
|
|
321
|
+
const sessions = Array.from(terminal.sessions.values());
|
|
322
|
+
if (sessions.length === 0)
|
|
323
|
+
return 'No sessions.';
|
|
324
|
+
return sessions.map(s => {
|
|
325
|
+
const isDefault = s.id === terminal.defaultSession ? ' [default]' : '';
|
|
326
|
+
const pending = s.pendingCommand ? `\n running: ${s.pendingCommand}` : '';
|
|
327
|
+
return `${s.name} (${s.id})${isDefault}\n cwd: ${s.cwd}\n commands: ${s.history.length}\n last: ${new Date(s.lastActivity).toLocaleString()}${pending}`;
|
|
328
|
+
}).join('\n\n');
|
|
329
|
+
}
|
|
330
|
+
if (action === 'new') {
|
|
331
|
+
const name = String(args.name || `session-${terminal.sessions.size + 1}`);
|
|
332
|
+
// Check for duplicate names
|
|
333
|
+
for (const s of terminal.sessions.values()) {
|
|
334
|
+
if (s.name === name)
|
|
335
|
+
return `Session "${name}" already exists. Choose a different name.`;
|
|
336
|
+
}
|
|
337
|
+
const session = createShellSession(name);
|
|
338
|
+
terminal.sessions.set(session.id, session);
|
|
339
|
+
terminal.defaultSession = session.id;
|
|
340
|
+
saveTerminalState();
|
|
341
|
+
return `Created session: ${name} (${session.id}) — now the default session.`;
|
|
342
|
+
}
|
|
343
|
+
if (action === 'close') {
|
|
344
|
+
if (!args.name)
|
|
345
|
+
return 'Specify session name to close.';
|
|
346
|
+
const sessionId = findSession(String(args.name));
|
|
347
|
+
if (!terminal.sessions.has(sessionId))
|
|
348
|
+
return `Session "${args.name}" not found.`;
|
|
349
|
+
if (sessionId === terminal.defaultSession && terminal.sessions.size === 1) {
|
|
350
|
+
return 'Cannot close the last session.';
|
|
351
|
+
}
|
|
352
|
+
const closedName = terminal.sessions.get(sessionId)?.name;
|
|
353
|
+
terminal.sessions.delete(sessionId);
|
|
354
|
+
if (sessionId === terminal.defaultSession) {
|
|
355
|
+
terminal.defaultSession = terminal.sessions.keys().next().value;
|
|
356
|
+
}
|
|
357
|
+
saveTerminalState();
|
|
358
|
+
return `Closed session: ${closedName}`;
|
|
359
|
+
}
|
|
360
|
+
return 'Unknown action. Use: list, new, close';
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
registerTool({
|
|
364
|
+
name: 'terminal_queue',
|
|
365
|
+
description: 'Queue a command for later execution in kbot\'s terminal. Queued commands only run when explicitly triggered with terminal_exec "process_queue". Use for batching commands you want to run together.',
|
|
366
|
+
parameters: {
|
|
367
|
+
command: { type: 'string', description: 'Command to queue (omit to list pending queue)', required: false },
|
|
368
|
+
delay_seconds: { type: 'string', description: 'Delay in seconds before execution (default: 0 = next cycle)' },
|
|
369
|
+
session: { type: 'string', description: 'Session name or ID' },
|
|
370
|
+
action: { type: 'string', description: '"add" (default), "list", or "clear"' },
|
|
371
|
+
},
|
|
372
|
+
tier: 'free',
|
|
373
|
+
async execute(args) {
|
|
374
|
+
const action = String(args.action || 'add');
|
|
375
|
+
if (action === 'list') {
|
|
376
|
+
const queue = loadQueue();
|
|
377
|
+
if (queue.length === 0)
|
|
378
|
+
return 'Queue is empty.';
|
|
379
|
+
return queue.map((q, i) => {
|
|
380
|
+
const delay = q.runAt ? `runs at ${new Date(q.runAt).toLocaleTimeString()}` : 'runs next cycle';
|
|
381
|
+
return `${i + 1}. [${q.session}] ${q.command}\n scheduled: ${new Date(q.scheduledAt).toLocaleTimeString()}, ${delay}`;
|
|
382
|
+
}).join('\n\n');
|
|
383
|
+
}
|
|
384
|
+
if (action === 'clear') {
|
|
385
|
+
saveQueue([]);
|
|
386
|
+
return 'Queue cleared.';
|
|
387
|
+
}
|
|
388
|
+
// action === 'add'
|
|
389
|
+
if (!args.command)
|
|
390
|
+
return 'Specify a command to queue, or use action="list" to view pending commands.';
|
|
391
|
+
const delay = parseInt(String(args.delay_seconds || '0'), 10) * 1000;
|
|
392
|
+
const sessionId = findSession(String(args.session || 'main'));
|
|
393
|
+
queueCommand(String(args.command), sessionId, delay || undefined);
|
|
394
|
+
return `Queued: ${args.command}${delay ? ` (runs in ${args.delay_seconds}s)` : ' (runs next cycle ~10s)'}`;
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
registerTool({
|
|
398
|
+
name: 'terminal_cwd',
|
|
399
|
+
description: 'Get or change the working directory of a terminal session.',
|
|
400
|
+
parameters: {
|
|
401
|
+
path: { type: 'string', description: 'New directory path (omit to show current cwd)' },
|
|
402
|
+
session: { type: 'string', description: 'Session name or ID' },
|
|
403
|
+
},
|
|
404
|
+
tier: 'free',
|
|
405
|
+
async execute(args) {
|
|
406
|
+
const sessionId = findSession(String(args.session || 'main'));
|
|
407
|
+
const session = terminal.sessions.get(sessionId);
|
|
408
|
+
if (!session)
|
|
409
|
+
return 'Session not found';
|
|
410
|
+
if (args.path) {
|
|
411
|
+
const target = String(args.path).replace(/^~/, homedir());
|
|
412
|
+
const resolved = resolve(session.cwd, target);
|
|
413
|
+
if (existsSync(resolved)) {
|
|
414
|
+
session.cwd = resolved;
|
|
415
|
+
saveTerminalState();
|
|
416
|
+
return `[${session.name}] cwd: ${resolved}`;
|
|
417
|
+
}
|
|
418
|
+
return `Directory not found: ${resolved}`;
|
|
419
|
+
}
|
|
420
|
+
return `[${session.name}] cwd: ${session.cwd}`;
|
|
421
|
+
},
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
//# sourceMappingURL=kbot-terminal.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kernel.chat/kbot",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.87.0",
|
|
4
4
|
"description": "Open-source terminal AI agent. 764+ tools, 35 agents, 20 providers. Dreams, learns, watches your system. Controls your phone. Fully local, fully sovereign. MIT.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|