rackmind-cli 0.1.2
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/README.md +179 -0
- package/bin/rackmind.js +6 -0
- package/dist/ai/client.d.ts +71 -0
- package/dist/ai/client.d.ts.map +1 -0
- package/dist/ai/client.js +198 -0
- package/dist/ai/client.js.map +1 -0
- package/dist/ai/system-prompt.d.ts +20 -0
- package/dist/ai/system-prompt.d.ts.map +1 -0
- package/dist/ai/system-prompt.js +169 -0
- package/dist/ai/system-prompt.js.map +1 -0
- package/dist/ai/tool-executor.d.ts +27 -0
- package/dist/ai/tool-executor.d.ts.map +1 -0
- package/dist/ai/tool-executor.js +184 -0
- package/dist/ai/tool-executor.js.map +1 -0
- package/dist/ai/tools.d.ts +7 -0
- package/dist/ai/tools.d.ts.map +1 -0
- package/dist/ai/tools.js +203 -0
- package/dist/ai/tools.js.map +1 -0
- package/dist/commands/config.d.ts +21 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +169 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/connect.d.ts +7 -0
- package/dist/commands/connect.d.ts.map +1 -0
- package/dist/commands/connect.js +117 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/containers.d.ts +5 -0
- package/dist/commands/containers.d.ts.map +1 -0
- package/dist/commands/containers.js +83 -0
- package/dist/commands/containers.js.map +1 -0
- package/dist/commands/exec.d.ts +5 -0
- package/dist/commands/exec.d.ts.map +1 -0
- package/dist/commands/exec.js +133 -0
- package/dist/commands/exec.js.map +1 -0
- package/dist/commands/lifecycle.d.ts +7 -0
- package/dist/commands/lifecycle.d.ts.map +1 -0
- package/dist/commands/lifecycle.js +213 -0
- package/dist/commands/lifecycle.js.map +1 -0
- package/dist/commands/logs.d.ts +5 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +117 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/report.d.ts +6 -0
- package/dist/commands/report.d.ts.map +1 -0
- package/dist/commands/report.js +203 -0
- package/dist/commands/report.js.map +1 -0
- package/dist/commands/servers.d.ts +17 -0
- package/dist/commands/servers.d.ts.map +1 -0
- package/dist/commands/servers.js +116 -0
- package/dist/commands/servers.js.map +1 -0
- package/dist/commands/status.d.ts +5 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +174 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/vms.d.ts +5 -0
- package/dist/commands/vms.d.ts.map +1 -0
- package/dist/commands/vms.js +83 -0
- package/dist/commands/vms.js.map +1 -0
- package/dist/config/crypto.d.ts +20 -0
- package/dist/config/crypto.d.ts.map +1 -0
- package/dist/config/crypto.js +78 -0
- package/dist/config/crypto.js.map +1 -0
- package/dist/config/index.d.ts +21 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +158 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/types.d.ts +263 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +83 -0
- package/dist/config/types.js.map +1 -0
- package/dist/globals.d.ts +22 -0
- package/dist/globals.d.ts.map +1 -0
- package/dist/globals.js +43 -0
- package/dist/globals.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +399 -0
- package/dist/index.js.map +1 -0
- package/dist/interactive/App.d.ts +3 -0
- package/dist/interactive/App.d.ts.map +1 -0
- package/dist/interactive/App.js +103 -0
- package/dist/interactive/App.js.map +1 -0
- package/dist/interactive/components/Header.d.ts +8 -0
- package/dist/interactive/components/Header.d.ts.map +1 -0
- package/dist/interactive/components/Header.js +20 -0
- package/dist/interactive/components/Header.js.map +1 -0
- package/dist/interactive/components/InputBar.d.ts +9 -0
- package/dist/interactive/components/InputBar.d.ts.map +1 -0
- package/dist/interactive/components/InputBar.js +89 -0
- package/dist/interactive/components/InputBar.js.map +1 -0
- package/dist/interactive/components/MessageList.d.ts +9 -0
- package/dist/interactive/components/MessageList.d.ts.map +1 -0
- package/dist/interactive/components/MessageList.js +28 -0
- package/dist/interactive/components/MessageList.js.map +1 -0
- package/dist/interactive/components/Spinner.d.ts +7 -0
- package/dist/interactive/components/Spinner.d.ts.map +1 -0
- package/dist/interactive/components/Spinner.js +19 -0
- package/dist/interactive/components/Spinner.js.map +1 -0
- package/dist/interactive/components/StatusBar.d.ts +10 -0
- package/dist/interactive/components/StatusBar.d.ts.map +1 -0
- package/dist/interactive/components/StatusBar.js +7 -0
- package/dist/interactive/components/StatusBar.js.map +1 -0
- package/dist/interactive/components/StreamingMessage.d.ts +7 -0
- package/dist/interactive/components/StreamingMessage.d.ts.map +1 -0
- package/dist/interactive/components/StreamingMessage.js +9 -0
- package/dist/interactive/components/StreamingMessage.js.map +1 -0
- package/dist/interactive/components/ToolIndicator.d.ts +7 -0
- package/dist/interactive/components/ToolIndicator.d.ts.map +1 -0
- package/dist/interactive/components/ToolIndicator.js +10 -0
- package/dist/interactive/components/ToolIndicator.js.map +1 -0
- package/dist/interactive/components/ToolOutput.d.ts +8 -0
- package/dist/interactive/components/ToolOutput.d.ts.map +1 -0
- package/dist/interactive/components/ToolOutput.js +9 -0
- package/dist/interactive/components/ToolOutput.js.map +1 -0
- package/dist/interactive/components/WelcomeScreen.d.ts +9 -0
- package/dist/interactive/components/WelcomeScreen.d.ts.map +1 -0
- package/dist/interactive/components/WelcomeScreen.js +8 -0
- package/dist/interactive/components/WelcomeScreen.js.map +1 -0
- package/dist/interactive/launch.d.ts +6 -0
- package/dist/interactive/launch.d.ts.map +1 -0
- package/dist/interactive/launch.js +30 -0
- package/dist/interactive/launch.js.map +1 -0
- package/dist/interactive/slashCommands.d.ts +13 -0
- package/dist/interactive/slashCommands.d.ts.map +1 -0
- package/dist/interactive/slashCommands.js +102 -0
- package/dist/interactive/slashCommands.js.map +1 -0
- package/dist/interactive/useRackMind.d.ts +30 -0
- package/dist/interactive/useRackMind.d.ts.map +1 -0
- package/dist/interactive/useRackMind.js +241 -0
- package/dist/interactive/useRackMind.js.map +1 -0
- package/dist/oneshot/run.d.ts +2 -0
- package/dist/oneshot/run.d.ts.map +1 -0
- package/dist/oneshot/run.js +195 -0
- package/dist/oneshot/run.js.map +1 -0
- package/dist/server/client.d.ts +89 -0
- package/dist/server/client.d.ts.map +1 -0
- package/dist/server/client.js +239 -0
- package/dist/server/client.js.map +1 -0
- package/dist/server/proxmox.d.ts +160 -0
- package/dist/server/proxmox.d.ts.map +1 -0
- package/dist/server/proxmox.js +219 -0
- package/dist/server/proxmox.js.map +1 -0
- package/dist/server/ssh.d.ts +80 -0
- package/dist/server/ssh.d.ts.map +1 -0
- package/dist/server/ssh.js +262 -0
- package/dist/server/ssh.js.map +1 -0
- package/dist/utils/ascii.d.ts +3 -0
- package/dist/utils/ascii.d.ts.map +1 -0
- package/dist/utils/ascii.js +16 -0
- package/dist/utils/ascii.js.map +1 -0
- package/dist/utils/format.d.ts +7 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +20 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/history.d.ts +23 -0
- package/dist/utils/history.d.ts.map +1 -0
- package/dist/utils/history.js +107 -0
- package/dist/utils/history.js.map +1 -0
- package/dist/utils/logger.d.ts +9 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +51 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/markdown.d.ts +12 -0
- package/dist/utils/markdown.d.ts.map +1 -0
- package/dist/utils/markdown.js +76 -0
- package/dist/utils/markdown.js.map +1 -0
- package/dist/utils/prompt.d.ts +25 -0
- package/dist/utils/prompt.d.ts.map +1 -0
- package/dist/utils/prompt.js +133 -0
- package/dist/utils/prompt.js.map +1 -0
- package/dist/utils/retry.d.ts +37 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +122 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/shutdown.d.ts +22 -0
- package/dist/utils/shutdown.d.ts.map +1 -0
- package/dist/utils/shutdown.js +94 -0
- package/dist/utils/shutdown.js.map +1 -0
- package/dist/utils/table.d.ts +38 -0
- package/dist/utils/table.d.ts.map +1 -0
- package/dist/utils/table.js +150 -0
- package/dist/utils/table.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Client as SSHClient } from 'ssh2';
|
|
2
|
+
export interface SSHClientOptions {
|
|
3
|
+
/** SSH host */
|
|
4
|
+
host: string;
|
|
5
|
+
/** SSH port (default: 22) */
|
|
6
|
+
port: number;
|
|
7
|
+
/** SSH username */
|
|
8
|
+
username: string;
|
|
9
|
+
/** SSH auth method */
|
|
10
|
+
authType: 'password' | 'key';
|
|
11
|
+
/** Decrypted SSH password (when authType is 'password') */
|
|
12
|
+
password?: string;
|
|
13
|
+
/** Path to SSH private key (when authType is 'key') */
|
|
14
|
+
privateKeyPath?: string;
|
|
15
|
+
/** Connection timeout in milliseconds (default: 10000) */
|
|
16
|
+
timeout?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface SSHExecResult {
|
|
19
|
+
/** Exit code of the command (null if signal-killed) */
|
|
20
|
+
exitCode: number | null;
|
|
21
|
+
/** Combined stdout output */
|
|
22
|
+
stdout: string;
|
|
23
|
+
/** Combined stderr output */
|
|
24
|
+
stderr: string;
|
|
25
|
+
}
|
|
26
|
+
export type StreamCallback = (data: string, stream: 'stdout' | 'stderr') => void;
|
|
27
|
+
export declare class SSHConnection {
|
|
28
|
+
private readonly options;
|
|
29
|
+
private client;
|
|
30
|
+
private connected;
|
|
31
|
+
constructor(options: SSHClientOptions);
|
|
32
|
+
/** Establish the SSH connection */
|
|
33
|
+
connect(): Promise<void>;
|
|
34
|
+
/** Disconnect the SSH session */
|
|
35
|
+
disconnect(): void;
|
|
36
|
+
/** Check whether the SSH connection is active */
|
|
37
|
+
isConnected(): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Reconnect the SSH session if it has been dropped.
|
|
40
|
+
* Attempts to close the existing connection first, then re-establishes.
|
|
41
|
+
*/
|
|
42
|
+
reconnect(): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Ensure connected, auto-reconnecting if the connection was dropped.
|
|
45
|
+
* This is the resilient version of ensureConnected — use it for
|
|
46
|
+
* operations that should survive transient disconnects.
|
|
47
|
+
*/
|
|
48
|
+
ensureConnectedOrReconnect(): Promise<SSHClient>;
|
|
49
|
+
/**
|
|
50
|
+
* Execute a command on the SSH host and return the full result.
|
|
51
|
+
* Buffers all output — suitable for short-lived commands.
|
|
52
|
+
*/
|
|
53
|
+
exec(command: string): Promise<SSHExecResult>;
|
|
54
|
+
/**
|
|
55
|
+
* Execute a command with streaming output.
|
|
56
|
+
* Calls the callback for each chunk of data as it arrives.
|
|
57
|
+
* Returns the exit code when the command completes.
|
|
58
|
+
*/
|
|
59
|
+
execStream(command: string, onData: StreamCallback): Promise<number | null>;
|
|
60
|
+
/**
|
|
61
|
+
* Execute a command inside an LXC container via `pct exec`.
|
|
62
|
+
* Runs on the Proxmox host: `pct exec <vmid> -- <command>`
|
|
63
|
+
*/
|
|
64
|
+
execInContainer(vmid: number, command: string): Promise<SSHExecResult>;
|
|
65
|
+
/**
|
|
66
|
+
* Execute a command inside an LXC container with streaming output.
|
|
67
|
+
*/
|
|
68
|
+
execInContainerStream(vmid: number, command: string, onData: StreamCallback): Promise<number | null>;
|
|
69
|
+
/**
|
|
70
|
+
* Test the SSH connection by running a simple command.
|
|
71
|
+
* Returns the hostname of the remote machine.
|
|
72
|
+
*/
|
|
73
|
+
testConnection(): Promise<string>;
|
|
74
|
+
private buildConnectConfig;
|
|
75
|
+
private ensureConnected;
|
|
76
|
+
}
|
|
77
|
+
export declare class SSHConnectionError extends Error {
|
|
78
|
+
constructor(message: string);
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=ssh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssh.d.ts","sourceRoot":"","sources":["../../src/server/ssh.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,MAAM,IAAI,SAAS,EAA0C,MAAM,MAAM,CAAC;AAUnF,MAAM,WAAW,gBAAgB;IAC7B,eAAe;IACf,IAAI,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,mBAAmB;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,sBAAsB;IACtB,QAAQ,EAAE,UAAU,GAAG,KAAK,CAAC;IAC7B,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC1B,uDAAuD;IACvD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAG,QAAQ,KAAK,IAAI,CAAC;AAMjF,qBAAa,aAAa;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAC3C,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,SAAS,CAAS;gBAEd,OAAO,EAAE,gBAAgB;IAQrC,mCAAmC;IACnC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA8DxB,iCAAiC;IACjC,UAAU,IAAI,IAAI;IASlB,iDAAiD;IACjD,WAAW,IAAI,OAAO;IAQtB;;;OAGG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAMhC;;;;OAIG;IACG,0BAA0B,IAAI,OAAO,CAAC,SAAS,CAAC;IAStD;;;OAGG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAoC7C;;;;OAIG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IA6B3E;;;OAGG;IACG,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAM5E;;OAEG;IACG,qBAAqB,CACvB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,cAAc,GACvB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAKzB;;;OAGG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAcvC,OAAO,CAAC,kBAAkB;IA0B1B,OAAO,CAAC,eAAe;CAM1B;AA8BD,qBAAa,kBAAmB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI9B"}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// SSH client — execute commands on Proxmox host and inside LXC containers
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Uses the ssh2 package for SSH connections. Supports:
|
|
5
|
+
// - Direct command execution on the Proxmox host
|
|
6
|
+
// - Command execution inside LXC containers via `pct exec <vmid> -- <cmd>`
|
|
7
|
+
// - Streaming output for long-running commands
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
import { Client as SSHClient } from 'ssh2';
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import os from 'node:os';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { logger } from '../utils/logger.js';
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// SSH Connection Manager
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
export class SSHConnection {
|
|
18
|
+
options;
|
|
19
|
+
client = null;
|
|
20
|
+
connected = false;
|
|
21
|
+
constructor(options) {
|
|
22
|
+
this.options = options;
|
|
23
|
+
}
|
|
24
|
+
// -----------------------------------------------------------------------
|
|
25
|
+
// Connection lifecycle
|
|
26
|
+
// -----------------------------------------------------------------------
|
|
27
|
+
/** Establish the SSH connection */
|
|
28
|
+
connect() {
|
|
29
|
+
if (this.connected && this.client) {
|
|
30
|
+
return Promise.resolve();
|
|
31
|
+
}
|
|
32
|
+
const config = this.buildConnectConfig();
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const client = new SSHClient();
|
|
35
|
+
const timeout = this.options.timeout ?? 10_000;
|
|
36
|
+
const timer = setTimeout(() => {
|
|
37
|
+
client.end();
|
|
38
|
+
reject(new SSHConnectionError('SSH connection timed out'));
|
|
39
|
+
}, timeout);
|
|
40
|
+
client.on('ready', () => {
|
|
41
|
+
clearTimeout(timer);
|
|
42
|
+
this.client = client;
|
|
43
|
+
this.connected = true;
|
|
44
|
+
logger.info('SSH connection established', {
|
|
45
|
+
host: this.options.host,
|
|
46
|
+
port: this.options.port,
|
|
47
|
+
});
|
|
48
|
+
resolve();
|
|
49
|
+
});
|
|
50
|
+
client.on('error', (err) => {
|
|
51
|
+
clearTimeout(timer);
|
|
52
|
+
this.connected = false;
|
|
53
|
+
this.client = null;
|
|
54
|
+
logger.error('SSH connection error', { error: err.message });
|
|
55
|
+
if (err.message.includes('ECONNREFUSED')) {
|
|
56
|
+
reject(new SSHConnectionError(`SSH connection refused at ${this.options.host}:${this.options.port}`));
|
|
57
|
+
}
|
|
58
|
+
else if (err.message.includes('Authentication failed') ||
|
|
59
|
+
err.message.includes('auth')) {
|
|
60
|
+
reject(new SSHConnectionError('SSH authentication failed — check username and password/key'));
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
reject(new SSHConnectionError(`SSH connection failed: ${err.message}`));
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
client.on('close', () => {
|
|
67
|
+
this.connected = false;
|
|
68
|
+
this.client = null;
|
|
69
|
+
});
|
|
70
|
+
client.connect(config);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/** Disconnect the SSH session */
|
|
74
|
+
disconnect() {
|
|
75
|
+
if (this.client) {
|
|
76
|
+
this.client.end();
|
|
77
|
+
this.client = null;
|
|
78
|
+
this.connected = false;
|
|
79
|
+
logger.debug('SSH connection closed');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Check whether the SSH connection is active */
|
|
83
|
+
isConnected() {
|
|
84
|
+
return this.connected && this.client !== null;
|
|
85
|
+
}
|
|
86
|
+
// -----------------------------------------------------------------------
|
|
87
|
+
// Command execution
|
|
88
|
+
// -----------------------------------------------------------------------
|
|
89
|
+
/**
|
|
90
|
+
* Reconnect the SSH session if it has been dropped.
|
|
91
|
+
* Attempts to close the existing connection first, then re-establishes.
|
|
92
|
+
*/
|
|
93
|
+
async reconnect() {
|
|
94
|
+
logger.info('Attempting SSH reconnect');
|
|
95
|
+
this.disconnect();
|
|
96
|
+
await this.connect();
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Ensure connected, auto-reconnecting if the connection was dropped.
|
|
100
|
+
* This is the resilient version of ensureConnected — use it for
|
|
101
|
+
* operations that should survive transient disconnects.
|
|
102
|
+
*/
|
|
103
|
+
async ensureConnectedOrReconnect() {
|
|
104
|
+
if (this.connected && this.client) {
|
|
105
|
+
return this.client;
|
|
106
|
+
}
|
|
107
|
+
logger.info('SSH connection dropped, reconnecting');
|
|
108
|
+
await this.reconnect();
|
|
109
|
+
return this.ensureConnected();
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Execute a command on the SSH host and return the full result.
|
|
113
|
+
* Buffers all output — suitable for short-lived commands.
|
|
114
|
+
*/
|
|
115
|
+
exec(command) {
|
|
116
|
+
const client = this.ensureConnected();
|
|
117
|
+
return new Promise((resolve, reject) => {
|
|
118
|
+
client.exec(command, (err, channel) => {
|
|
119
|
+
if (err) {
|
|
120
|
+
reject(new SSHConnectionError(`Failed to execute command: ${err.message}`));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
let stdout = '';
|
|
124
|
+
let stderr = '';
|
|
125
|
+
channel.on('data', (data) => {
|
|
126
|
+
stdout += data.toString('utf8');
|
|
127
|
+
});
|
|
128
|
+
channel.stderr.on('data', (data) => {
|
|
129
|
+
stderr += data.toString('utf8');
|
|
130
|
+
});
|
|
131
|
+
channel.on('close', (code) => {
|
|
132
|
+
resolve({
|
|
133
|
+
exitCode: code,
|
|
134
|
+
stdout,
|
|
135
|
+
stderr,
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
channel.on('error', (channelErr) => {
|
|
139
|
+
reject(new SSHConnectionError(`SSH channel error: ${channelErr.message}`));
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Execute a command with streaming output.
|
|
146
|
+
* Calls the callback for each chunk of data as it arrives.
|
|
147
|
+
* Returns the exit code when the command completes.
|
|
148
|
+
*/
|
|
149
|
+
execStream(command, onData) {
|
|
150
|
+
const client = this.ensureConnected();
|
|
151
|
+
return new Promise((resolve, reject) => {
|
|
152
|
+
client.exec(command, (err, channel) => {
|
|
153
|
+
if (err) {
|
|
154
|
+
reject(new SSHConnectionError(`Failed to execute command: ${err.message}`));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
channel.on('data', (data) => {
|
|
158
|
+
onData(data.toString('utf8'), 'stdout');
|
|
159
|
+
});
|
|
160
|
+
channel.stderr.on('data', (data) => {
|
|
161
|
+
onData(data.toString('utf8'), 'stderr');
|
|
162
|
+
});
|
|
163
|
+
channel.on('close', (code) => {
|
|
164
|
+
resolve(code);
|
|
165
|
+
});
|
|
166
|
+
channel.on('error', (channelErr) => {
|
|
167
|
+
reject(new SSHConnectionError(`SSH channel error: ${channelErr.message}`));
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Execute a command inside an LXC container via `pct exec`.
|
|
174
|
+
* Runs on the Proxmox host: `pct exec <vmid> -- <command>`
|
|
175
|
+
*/
|
|
176
|
+
async execInContainer(vmid, command) {
|
|
177
|
+
// Use bash -c to handle complex commands with pipes, redirects, etc.
|
|
178
|
+
const wrappedCommand = `pct exec ${vmid} -- bash -c ${shellEscape(command)}`;
|
|
179
|
+
return await this.exec(wrappedCommand);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Execute a command inside an LXC container with streaming output.
|
|
183
|
+
*/
|
|
184
|
+
async execInContainerStream(vmid, command, onData) {
|
|
185
|
+
const wrappedCommand = `pct exec ${vmid} -- bash -c ${shellEscape(command)}`;
|
|
186
|
+
return await this.execStream(wrappedCommand, onData);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Test the SSH connection by running a simple command.
|
|
190
|
+
* Returns the hostname of the remote machine.
|
|
191
|
+
*/
|
|
192
|
+
async testConnection() {
|
|
193
|
+
const result = await this.exec('hostname');
|
|
194
|
+
if (result.exitCode !== 0) {
|
|
195
|
+
throw new SSHConnectionError(`SSH test command failed with exit code ${result.exitCode}: ${result.stderr}`);
|
|
196
|
+
}
|
|
197
|
+
return result.stdout.trim();
|
|
198
|
+
}
|
|
199
|
+
// -----------------------------------------------------------------------
|
|
200
|
+
// Private helpers
|
|
201
|
+
// -----------------------------------------------------------------------
|
|
202
|
+
buildConnectConfig() {
|
|
203
|
+
const config = {
|
|
204
|
+
host: this.options.host,
|
|
205
|
+
port: this.options.port,
|
|
206
|
+
username: this.options.username,
|
|
207
|
+
readyTimeout: this.options.timeout ?? 10_000,
|
|
208
|
+
keepaliveInterval: 30_000,
|
|
209
|
+
};
|
|
210
|
+
if (this.options.authType === 'password') {
|
|
211
|
+
config.password = this.options.password;
|
|
212
|
+
}
|
|
213
|
+
else if (this.options.authType === 'key') {
|
|
214
|
+
const keyPath = resolveKeyPath(this.options.privateKeyPath ?? '~/.ssh/id_rsa');
|
|
215
|
+
try {
|
|
216
|
+
config.privateKey = fs.readFileSync(keyPath);
|
|
217
|
+
}
|
|
218
|
+
catch (readErr) {
|
|
219
|
+
const message = readErr instanceof Error ? readErr.message : String(readErr);
|
|
220
|
+
throw new SSHConnectionError(`Cannot read SSH private key at ${keyPath}: ${message}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return config;
|
|
224
|
+
}
|
|
225
|
+
ensureConnected() {
|
|
226
|
+
if (!this.connected || !this.client) {
|
|
227
|
+
throw new SSHConnectionError('SSH not connected — call connect() first');
|
|
228
|
+
}
|
|
229
|
+
return this.client;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
// Utility functions
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
/**
|
|
236
|
+
* Resolve a key path, expanding ~ to the user's home directory.
|
|
237
|
+
*/
|
|
238
|
+
function resolveKeyPath(keyPath) {
|
|
239
|
+
if (keyPath.startsWith('~')) {
|
|
240
|
+
return path.join(os.homedir(), keyPath.slice(1));
|
|
241
|
+
}
|
|
242
|
+
return path.resolve(keyPath);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Shell-escape a string for use in a bash -c argument.
|
|
246
|
+
* Wraps in single quotes and escapes any embedded single quotes.
|
|
247
|
+
*/
|
|
248
|
+
function shellEscape(str) {
|
|
249
|
+
// Replace single quotes with '\'' (end quote, escaped quote, start quote)
|
|
250
|
+
const escaped = str.replace(/'/g, "'\\''");
|
|
251
|
+
return `'${escaped}'`;
|
|
252
|
+
}
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
// Custom error class
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
export class SSHConnectionError extends Error {
|
|
257
|
+
constructor(message) {
|
|
258
|
+
super(message);
|
|
259
|
+
this.name = 'SSHConnectionError';
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=ssh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssh.js","sourceRoot":"","sources":["../../src/server/ssh.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ascii.d.ts","sourceRoot":"","sources":["../../src/utils/ascii.ts"],"names":[],"mappings":"AAUA,wBAAgB,SAAS,IAAI,IAAI,CAGhC;AAED,wBAAgB,OAAO,IAAI,MAAM,CAEhC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
const LOGO = `
|
|
3
|
+
____ _ __ __ _ _
|
|
4
|
+
| _ \\ __ _ ___| | _| \\/ (_)_ __ __| |
|
|
5
|
+
| |_) / _\` |/ __| |/ / |\\/| | | '_ \\ / _\` |
|
|
6
|
+
| _ < (_| | (__| <| | | | | | | | (_| |
|
|
7
|
+
|_| \\_\\__,_|\\___|_|\\_\\_| |_|_|_| |_|\\__,_|
|
|
8
|
+
`;
|
|
9
|
+
export function printLogo() {
|
|
10
|
+
process.stdout.write(chalk.cyan(LOGO));
|
|
11
|
+
process.stdout.write(chalk.dim(' AI-powered infrastructure management for MSPs\n\n'));
|
|
12
|
+
}
|
|
13
|
+
export function getLogo() {
|
|
14
|
+
return LOGO.trim();
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=ascii.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ascii.js","sourceRoot":"","sources":["../../src/utils/ascii.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,IAAI,GAAG;;;;;;CAMZ,CAAC;AAEF,MAAM,UAAU,SAAS;IACrB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACvC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,qDAAqD,CAAC,CAAC,CAAC;AAC3F,CAAC;AAED,MAAM,UAAU,OAAO;IACnB,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;AACvB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function formatError(message: string): string;
|
|
2
|
+
export declare function formatSuccess(message: string): string;
|
|
3
|
+
export declare function formatWarning(message: string): string;
|
|
4
|
+
export declare function formatDim(message: string): string;
|
|
5
|
+
export declare function formatBold(message: string): string;
|
|
6
|
+
export declare function formatKeyValue(key: string, value: string): string;
|
|
7
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/utils/format.ts"],"names":[],"mappings":"AAEA,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjE"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
export function formatError(message) {
|
|
3
|
+
return chalk.red(`Error: ${message}`);
|
|
4
|
+
}
|
|
5
|
+
export function formatSuccess(message) {
|
|
6
|
+
return chalk.green(message);
|
|
7
|
+
}
|
|
8
|
+
export function formatWarning(message) {
|
|
9
|
+
return chalk.yellow(message);
|
|
10
|
+
}
|
|
11
|
+
export function formatDim(message) {
|
|
12
|
+
return chalk.dim(message);
|
|
13
|
+
}
|
|
14
|
+
export function formatBold(message) {
|
|
15
|
+
return chalk.bold(message);
|
|
16
|
+
}
|
|
17
|
+
export function formatKeyValue(key, value) {
|
|
18
|
+
return `${chalk.bold(key)}: ${value}`;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/utils/format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,UAAU,WAAW,CAAC,OAAe;IACvC,OAAO,KAAK,CAAC,GAAG,CAAC,UAAU,OAAO,EAAE,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAe;IACzC,OAAO,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAe;IACzC,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAe;IACrC,OAAO,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,OAAe;IACtC,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,GAAW,EAAE,KAAa;IACrD,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,KAAK,EAAE,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface HistoryEntry {
|
|
2
|
+
role: 'user' | 'assistant' | 'system';
|
|
3
|
+
content: string;
|
|
4
|
+
timestamp: number;
|
|
5
|
+
}
|
|
6
|
+
export interface HistorySession {
|
|
7
|
+
sessionId: string;
|
|
8
|
+
serverAlias: string | null;
|
|
9
|
+
model: string;
|
|
10
|
+
startedAt: string;
|
|
11
|
+
entries: HistoryEntry[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Save a conversation session to disk.
|
|
15
|
+
* Creates a timestamped JSON file in ~/.config/rackmind/history/.
|
|
16
|
+
* Silently fails on write errors — history is optional.
|
|
17
|
+
*/
|
|
18
|
+
export declare function saveSession(session: HistorySession): void;
|
|
19
|
+
/**
|
|
20
|
+
* Generate a unique session ID based on the current timestamp.
|
|
21
|
+
*/
|
|
22
|
+
export declare function generateSessionId(): string;
|
|
23
|
+
//# sourceMappingURL=history.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../src/utils/history.ts"],"names":[],"mappings":"AAyBA,MAAM,WAAW,YAAY;IACzB,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,YAAY,EAAE,CAAC;CAC3B;AAmBD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAmCzD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAK1C"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Conversation history — optional persistence of chat sessions
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Saves conversation history to ~/.config/rackmind/history/ as JSON files.
|
|
5
|
+
// Each session gets a timestamped file. Old sessions are cleaned up
|
|
6
|
+
// automatically to prevent unbounded disk usage.
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { getConfigDir } from '../config/index.js';
|
|
11
|
+
import { logger } from './logger.js';
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Constants
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
const HISTORY_DIR_NAME = 'history';
|
|
16
|
+
const MAX_HISTORY_FILES = 50;
|
|
17
|
+
const MAX_FILE_SIZE_BYTES = 1024 * 1024; // 1 MB per file
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Directory management
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
function getHistoryDir() {
|
|
22
|
+
return path.join(getConfigDir(), HISTORY_DIR_NAME);
|
|
23
|
+
}
|
|
24
|
+
function ensureHistoryDir() {
|
|
25
|
+
const dir = getHistoryDir();
|
|
26
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Save session
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
/**
|
|
32
|
+
* Save a conversation session to disk.
|
|
33
|
+
* Creates a timestamped JSON file in ~/.config/rackmind/history/.
|
|
34
|
+
* Silently fails on write errors — history is optional.
|
|
35
|
+
*/
|
|
36
|
+
export function saveSession(session) {
|
|
37
|
+
try {
|
|
38
|
+
ensureHistoryDir();
|
|
39
|
+
const filename = `${session.sessionId}.json`;
|
|
40
|
+
const filepath = path.join(getHistoryDir(), filename);
|
|
41
|
+
const json = JSON.stringify(session, null, 2);
|
|
42
|
+
// Don't save if it would exceed the size limit
|
|
43
|
+
if (Buffer.byteLength(json, 'utf-8') > MAX_FILE_SIZE_BYTES) {
|
|
44
|
+
logger.warn('Session history too large to save', {
|
|
45
|
+
sessionId: session.sessionId,
|
|
46
|
+
entries: session.entries.length,
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// Atomic write
|
|
51
|
+
const tmpFile = filepath + '.tmp';
|
|
52
|
+
fs.writeFileSync(tmpFile, json, 'utf-8');
|
|
53
|
+
fs.renameSync(tmpFile, filepath);
|
|
54
|
+
logger.debug('Session history saved', {
|
|
55
|
+
sessionId: session.sessionId,
|
|
56
|
+
entries: session.entries.length,
|
|
57
|
+
});
|
|
58
|
+
// Clean up old history files
|
|
59
|
+
cleanupOldHistory();
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
63
|
+
logger.warn('Failed to save session history', { error: message });
|
|
64
|
+
// Silently fail — history is optional
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Generate a unique session ID based on the current timestamp.
|
|
69
|
+
*/
|
|
70
|
+
export function generateSessionId() {
|
|
71
|
+
const now = new Date();
|
|
72
|
+
const date = now.toISOString().split('T')[0];
|
|
73
|
+
const time = now.toTimeString().split(' ')[0].replace(/:/g, '');
|
|
74
|
+
return `session-${date}-${time}`;
|
|
75
|
+
}
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// Cleanup
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
/**
|
|
80
|
+
* Remove old history files if we exceed the max count.
|
|
81
|
+
* Keeps the most recent files.
|
|
82
|
+
*/
|
|
83
|
+
function cleanupOldHistory() {
|
|
84
|
+
try {
|
|
85
|
+
const dir = getHistoryDir();
|
|
86
|
+
const files = fs
|
|
87
|
+
.readdirSync(dir)
|
|
88
|
+
.filter((f) => f.endsWith('.json'))
|
|
89
|
+
.map((f) => ({
|
|
90
|
+
name: f,
|
|
91
|
+
path: path.join(dir, f),
|
|
92
|
+
mtime: fs.statSync(path.join(dir, f)).mtimeMs,
|
|
93
|
+
}))
|
|
94
|
+
.sort((a, b) => b.mtime - a.mtime); // newest first
|
|
95
|
+
if (files.length > MAX_HISTORY_FILES) {
|
|
96
|
+
const toDelete = files.slice(MAX_HISTORY_FILES);
|
|
97
|
+
for (const file of toDelete) {
|
|
98
|
+
fs.unlinkSync(file.path);
|
|
99
|
+
logger.debug('Deleted old history file', { file: file.name });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// Silently fail
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.js","sourceRoot":"","sources":["../../src/utils/history.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
2
|
+
export declare function setLogLevel(level: LogLevel): void;
|
|
3
|
+
export declare const logger: {
|
|
4
|
+
debug: (message: string, meta?: Record<string, unknown>) => void;
|
|
5
|
+
info: (message: string, meta?: Record<string, unknown>) => void;
|
|
6
|
+
warn: (message: string, meta?: Record<string, unknown>) => void;
|
|
7
|
+
error: (message: string, meta?: Record<string, unknown>) => void;
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAmBA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAW3D,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAEjD;AAuBD,eAAO,MAAM,MAAM;qBACE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;oBACvC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;oBACtC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;qBACrC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1D,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
const LOG_DIR = path.join(os.homedir(), '.rackmind', 'logs');
|
|
5
|
+
function ensureLogDir() {
|
|
6
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
7
|
+
}
|
|
8
|
+
function getLogFile() {
|
|
9
|
+
const date = new Date().toISOString().split('T')[0];
|
|
10
|
+
return path.join(LOG_DIR, `rackmind-${date}.log`);
|
|
11
|
+
}
|
|
12
|
+
function formatTimestamp() {
|
|
13
|
+
return new Date().toISOString();
|
|
14
|
+
}
|
|
15
|
+
const LOG_LEVELS = {
|
|
16
|
+
debug: 0,
|
|
17
|
+
info: 1,
|
|
18
|
+
warn: 2,
|
|
19
|
+
error: 3,
|
|
20
|
+
};
|
|
21
|
+
let currentLevel = 'info';
|
|
22
|
+
export function setLogLevel(level) {
|
|
23
|
+
currentLevel = level;
|
|
24
|
+
}
|
|
25
|
+
function shouldLog(level) {
|
|
26
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel];
|
|
27
|
+
}
|
|
28
|
+
function writeLog(level, message, meta) {
|
|
29
|
+
if (!shouldLog(level))
|
|
30
|
+
return;
|
|
31
|
+
try {
|
|
32
|
+
ensureLogDir();
|
|
33
|
+
const entry = {
|
|
34
|
+
timestamp: formatTimestamp(),
|
|
35
|
+
level,
|
|
36
|
+
message,
|
|
37
|
+
...(meta ? { meta } : {}),
|
|
38
|
+
};
|
|
39
|
+
fs.appendFileSync(getLogFile(), JSON.stringify(entry) + '\n');
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Silently fail — logging should never crash the CLI
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export const logger = {
|
|
46
|
+
debug: (message, meta) => writeLog('debug', message, meta),
|
|
47
|
+
info: (message, meta) => writeLog('info', message, meta),
|
|
48
|
+
warn: (message, meta) => writeLog('warn', message, meta),
|
|
49
|
+
error: (message, meta) => writeLog('error', message, meta),
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;AAE7D,SAAS,YAAY;IACjB,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC/C,CAAC;AAED,SAAS,UAAU;IACf,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,IAAI,MAAM,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,eAAe;IACpB,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AACpC,CAAC;AAID,MAAM,UAAU,GAA6B;IACzC,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;CACX,CAAC;AAEF,IAAI,YAAY,GAAa,MAAM,CAAC;AAEpC,MAAM,UAAU,WAAW,CAAC,KAAe;IACvC,YAAY,GAAG,KAAK,CAAC;AACzB,CAAC;AAED,SAAS,SAAS,CAAC,KAAe;IAC9B,OAAO,UAAU,CAAC,KAAK,CAAC,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC;AACzD,CAAC;AAED,SAAS,QAAQ,CAAC,KAAe,EAAE,OAAe,EAAE,IAA8B;IAC9E,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;QAAE,OAAO;IAE9B,IAAI,CAAC;QACD,YAAY,EAAE,CAAC;QACf,MAAM,KAAK,GAAG;YACV,SAAS,EAAE,eAAe,EAAE;YAC5B,KAAK;YACL,OAAO;YACP,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5B,CAAC;QACF,EAAE,CAAC,cAAc,CAAC,UAAU,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACL,qDAAqD;IACzD,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG;IAClB,KAAK,EAAE,CAAC,OAAe,EAAE,IAA8B,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;IAC5F,IAAI,EAAE,CAAC,OAAe,EAAE,IAA8B,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;IAC1F,IAAI,EAAE,CAAC,OAAe,EAAE,IAA8B,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;IAC1F,KAAK,EAAE,CAAC,OAAe,EAAE,IAA8B,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;CAC/F,CAAC"}
|