rackmind-cli 0.2.0 → 0.2.1

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.
@@ -3,20 +3,172 @@
3
3
  // ---------------------------------------------------------------------------
4
4
  // Shared between `exec` command and AI tool executor to prevent accidental
5
5
  // or AI-initiated destructive operations without explicit user consent.
6
+ //
7
+ // The blocklist is intentionally regex-based (not literal-string-match) so
8
+ // that flag reordering (`pct destroy --purge 100` vs `pct destroy 100 --purge`)
9
+ // cannot trivially bypass the gate. Patterns are evaluated in order; the
10
+ // first match wins, so more-specific patterns are listed first when label
11
+ // specificity matters.
6
12
  // ---------------------------------------------------------------------------
7
13
  /** Patterns that match potentially destructive shell commands */
8
14
  export const DANGEROUS_PATTERNS = [
15
+ // -----------------------------------------------------------------------
16
+ // Filesystem destruction
17
+ // -----------------------------------------------------------------------
18
+ // `rm -rf /` and "rm root" variants — match `rm` with at least one
19
+ // recursive/force flag AND a path that targets the system root.
20
+ // Listed BEFORE the generic rm pattern so this more-specific label wins.
9
21
  {
10
- pattern: /\brm\s+(-[a-zA-Z]*f|-[a-zA-Z]*r|--force|--recursive)/i,
22
+ pattern: /\brm\s+(?:-[a-zA-Z]*[rfR][a-zA-Z]*|--recursive|--force|--no-preserve-root)(?:\s+(?:-[a-zA-Z-]+|--[a-zA-Z-]+))*\s+\/(?:\s|$|\*|;|&|\||>)/i,
23
+ label: 'rm -rf / (root filesystem destruction)',
24
+ },
25
+ // Generic rm -rf / rm -r / rm -f / rm --force / rm --recursive
26
+ {
27
+ pattern: /\brm\s+(?:-[a-zA-Z]*(?:f|r|R)[a-zA-Z]*|--force|--recursive)\b/i,
11
28
  label: 'rm with force/recursive',
12
29
  },
13
- { pattern: /\bmkfs\b/i, label: 'mkfs (format filesystem)' },
30
+ // find -delete or find -exec rm — recursive deletion via find
31
+ {
32
+ pattern: /\bfind\b[^|;&]*\s(?:-delete\b|-exec\s+rm\b)/i,
33
+ label: 'find with -delete or -exec rm (mass deletion)',
34
+ },
35
+ // chmod 000 -R / chmod -R 000 / chmod 0 recursive — permission wipe
36
+ {
37
+ pattern: /\bchmod\b[^|;&]*\s(?:-R\b|--recursive\b)[^|;&]*\s0{1,4}\b/i,
38
+ label: 'chmod recursive 000 (permission wipe)',
39
+ },
40
+ {
41
+ pattern: /\bchmod\b[^|;&]*\s0{2,4}\s+(?:-R\b|--recursive\b)/i,
42
+ label: 'chmod recursive 000 (permission wipe)',
43
+ },
44
+ // chown -R … / — recursive ownership change starting at /
45
+ {
46
+ pattern: /\bchown\b[^|;&]*\s(?:-R\b|--recursive\b)[^|;&]*\s\/(?:\s|$)/i,
47
+ label: 'chown -R targeting / (recursive ownership change)',
48
+ },
49
+ // -----------------------------------------------------------------------
50
+ // Disk / partition / filesystem operations
51
+ // -----------------------------------------------------------------------
52
+ { pattern: /\bmkfs(?:\.[a-z0-9]+)?\b/i, label: 'mkfs (format filesystem)' },
14
53
  { pattern: /\bdd\s+.*of=/i, label: 'dd (raw disk write)' },
15
54
  { pattern: /\bfdisk\b/i, label: 'fdisk (partition table)' },
16
- { pattern: /:\(\)\s*\{/, label: 'fork bomb' },
17
- { pattern: /\b(shutdown|reboot|poweroff|halt)\b/i, label: 'shutdown/reboot/poweroff' },
18
- { pattern: /\bsystemctl\s+(stop|disable|mask)\b/i, label: 'systemctl stop/disable/mask' },
55
+ { pattern: /\bsgdisk\b/i, label: 'sgdisk (partition table)' },
56
+ { pattern: /\bparted\b/i, label: 'parted (partition table)' },
57
+ { pattern: /\bwipefs\b/i, label: 'wipefs (wipe filesystem signature)' },
58
+ { pattern: /\b(?:lvremove|vgremove|pvremove)\b/i, label: 'LVM remove (storage destruction)' },
59
+ { pattern: /\bzpool\s+destroy\b/i, label: 'zpool destroy (ZFS pool destruction)' },
60
+ { pattern: /\bzfs\s+destroy\b/i, label: 'zfs destroy (ZFS dataset destruction)' },
61
+ { pattern: /\bmdadm\s+(?:--stop|--remove|--zero-superblock)\b/i, label: 'mdadm stop/remove' },
62
+ // -----------------------------------------------------------------------
63
+ // Container / VM destruction (Proxmox + Docker + libvirt)
64
+ // -----------------------------------------------------------------------
65
+ { pattern: /\bpct\s+(?:destroy|delete)\b/i, label: 'pct destroy/delete (LXC destruction)' },
66
+ { pattern: /\bqm\s+(?:destroy|delete)\b/i, label: 'qm destroy/delete (QEMU VM destruction)' },
67
+ { pattern: /\bvirsh\s+(?:destroy|undefine)\b/i, label: 'virsh destroy/undefine' },
68
+ { pattern: /\bdocker\s+(?:system\s+)?prune\b[^|;&]*-[a-z]*a/i, label: 'docker prune -a' },
69
+ { pattern: /\bdocker\s+rm\s+-f\b/i, label: 'docker rm -f' },
70
+ { pattern: /\bdocker\s+rmi\s+-f\b/i, label: 'docker rmi -f' },
71
+ // -----------------------------------------------------------------------
72
+ // Network blackholing
73
+ // -----------------------------------------------------------------------
74
+ {
75
+ pattern: /\biptables\s+(?:-F|--flush|-X|--delete-chain|-Z|--zero)\b/i,
76
+ label: 'iptables flush/delete chain (network blackhole)',
77
+ },
78
+ {
79
+ pattern: /\bip6tables\s+(?:-F|--flush|-X|--delete-chain|-Z|--zero)\b/i,
80
+ label: 'ip6tables flush/delete chain',
81
+ },
82
+ { pattern: /\bnft\s+flush\b/i, label: 'nft flush (nftables ruleset wipe)' },
83
+ {
84
+ pattern: /\bip\s+link\s+set\s+\S+\s+down\b/i,
85
+ label: 'ip link set <iface> down (network interface shutdown)',
86
+ },
87
+ {
88
+ pattern: /\bifconfig\s+\S+\s+down\b/i,
89
+ label: 'ifconfig <iface> down (network interface shutdown)',
90
+ },
91
+ { pattern: /\bip\s+route\s+(?:flush|del)\b/i, label: 'ip route flush/del (routing wipe)' },
92
+ // -----------------------------------------------------------------------
93
+ // User / authentication destruction
94
+ // -----------------------------------------------------------------------
95
+ { pattern: /\buserdel\b/i, label: 'userdel (delete user account)' },
96
+ { pattern: /\bgroupdel\b/i, label: 'groupdel (delete group)' },
97
+ { pattern: /\bdeluser\b/i, label: 'deluser (delete user account)' },
98
+ { pattern: /\bdelgroup\b/i, label: 'delgroup (delete group)' },
99
+ {
100
+ pattern: /\bpasswd\s+(?:-l|--lock|-d|--delete)\b/i,
101
+ label: 'passwd lock/delete (lock or wipe password)',
102
+ },
103
+ {
104
+ pattern: /\bchage\s+(?:-E\s+0|-E0)\b/i,
105
+ label: 'chage -E 0 (expire account immediately)',
106
+ },
107
+ { pattern: /\busermod\s+(?:-L|--lock)\b/i, label: 'usermod --lock (lock account)' },
108
+ {
109
+ pattern: />\s*\/etc\/(?:passwd|shadow|sudoers|group|gshadow)\b/i,
110
+ label: 'write to /etc/passwd|shadow|sudoers (auth file overwrite)',
111
+ },
112
+ {
113
+ pattern: /\brm\b[^|;&]*\/etc\/(?:passwd|shadow|sudoers|group|gshadow)\b/i,
114
+ label: 'rm /etc/passwd|shadow|sudoers (auth file deletion)',
115
+ },
116
+ // -----------------------------------------------------------------------
117
+ // Process disruption — kill init / PID 1
118
+ // -----------------------------------------------------------------------
119
+ { pattern: /\bkill\s+(?:-[a-zA-Z0-9]+\s+)?-?1\b(?!\d)/i, label: 'kill PID 1 (init kill)' },
120
+ { pattern: /\bkillall\s+(?:-[a-zA-Z0-9]+\s+)?init\b/i, label: 'killall init' },
121
+ { pattern: /\bkillall\s+(?:-[a-zA-Z0-9]+\s+)?systemd\b/i, label: 'killall systemd' },
122
+ { pattern: /\bpkill\b[^|;&]*\s-1\b(?!\d)/i, label: 'pkill -1 (signal all processes)' },
123
+ { pattern: /\bpkill\s+(?:-[a-zA-Z0-9]+\s+)?init\b/i, label: 'pkill init' },
124
+ { pattern: /\bpkill\s+(?:-[a-zA-Z0-9]+\s+)?systemd\b/i, label: 'pkill systemd' },
125
+ // -----------------------------------------------------------------------
126
+ // System shutdown / reboot
127
+ // -----------------------------------------------------------------------
128
+ { pattern: /\b(?:shutdown|reboot|poweroff|halt)\b/i, label: 'shutdown/reboot/poweroff' },
129
+ {
130
+ pattern: /\bsystemctl\s+(?:stop|disable|mask|isolate)\b/i,
131
+ label: 'systemctl stop/disable/mask',
132
+ },
133
+ {
134
+ pattern: /\bsystemctl\s+(?:reboot|poweroff|halt|emergency|rescue)\b/i,
135
+ label: 'systemctl reboot/poweroff/halt',
136
+ },
137
+ { pattern: /\binit\s+0\b/i, label: 'init 0 (shutdown)' },
138
+ { pattern: /\binit\s+6\b/i, label: 'init 6 (reboot)' },
139
+ { pattern: /\btelinit\s+(?:0|6)\b/i, label: 'telinit 0/6 (shutdown/reboot)' },
140
+ // -----------------------------------------------------------------------
141
+ // Raw device writes
142
+ // -----------------------------------------------------------------------
19
143
  { pattern: />\s*\/dev\//i, label: 'write to /dev/' },
144
+ // -----------------------------------------------------------------------
145
+ // Fork bomb
146
+ // -----------------------------------------------------------------------
147
+ // Matches the classic `:(){ :|:& };:` shape — a function named `:` that
148
+ // pipes itself into itself recursively in the background. Tolerates
149
+ // whitespace and minor formatting variation.
150
+ {
151
+ pattern: /:\s*\(\s*\)\s*\{[^}]*:\s*\|\s*:[^}]*&[^}]*\}\s*;\s*:/,
152
+ label: 'fork bomb',
153
+ },
154
+ // Backstop: matches the bare function-definition shape that pipes itself
155
+ // in background. Catches variants where the inner body is condensed.
156
+ { pattern: /:\(\)\s*\{[\s\S]{0,40}:\|:&/, label: 'fork bomb' },
157
+ // -----------------------------------------------------------------------
158
+ // Curl/wget piped to a shell — remote code execution
159
+ // -----------------------------------------------------------------------
160
+ {
161
+ pattern: /\b(?:curl|wget)\b[^|;&]*\|\s*(?:sudo\s+)?(?:bash|sh|zsh|ksh|dash|python|perl)\b/i,
162
+ label: 'curl|wget piped to shell (remote code execution)',
163
+ },
164
+ // -----------------------------------------------------------------------
165
+ // Crypto / key destruction
166
+ // -----------------------------------------------------------------------
167
+ { pattern: /\brm\b[^|;&]*~\/\.ssh\b/i, label: 'rm ~/.ssh (SSH key destruction)' },
168
+ {
169
+ pattern: /\brm\b[^|;&]*\/root\/\.ssh\b/i,
170
+ label: 'rm /root/.ssh (root SSH key destruction)',
171
+ },
20
172
  ];
21
173
  /**
22
174
  * Check a command string against the dangerous patterns blocklist.
@@ -1 +1 @@
1
- {"version":3,"file":"command-safety.js","sourceRoot":"","sources":["../../src/utils/command-safety.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,yDAAyD;AACzD,8EAA8E;AAC9E,2EAA2E;AAC3E,wEAAwE;AACxE,8EAA8E;AAE9E,iEAAiE;AACjE,MAAM,CAAC,MAAM,kBAAkB,GAAyC;IACpE;QACI,OAAO,EAAE,uDAAuD;QAChE,KAAK,EAAE,yBAAyB;KACnC;IACD,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,0BAA0B,EAAE;IAC3D,EAAE,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,qBAAqB,EAAE;IAC1D,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,yBAAyB,EAAE;IAC3D,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE;IAC7C,EAAE,OAAO,EAAE,sCAAsC,EAAE,KAAK,EAAE,0BAA0B,EAAE;IACtF,EAAE,OAAO,EAAE,sCAAsC,EAAE,KAAK,EAAE,6BAA6B,EAAE;IACzF,EAAE,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,gBAAgB,EAAE;CACvD,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAe;IACjD,KAAK,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,kBAAkB,EAAE,CAAC;QAClD,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACxB,OAAO,KAAK,CAAC;QACjB,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"command-safety.js","sourceRoot":"","sources":["../../src/utils/command-safety.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,16 @@
1
+ declare function getLogFile(): string;
2
+ /**
3
+ * Rotate `logFile` if it exceeds MAX_FILE_BYTES.
4
+ *
5
+ * Strategy: shift `.N -> .N+1`, then rename the live file to `.1`. The next
6
+ * write to `logFile` will create a fresh, empty file.
7
+ *
8
+ * This is intentionally synchronous (CLI hot path, rare event, tiny rename
9
+ * cost) and best-effort — any failure here must NOT throw, so that a broken
10
+ * filesystem state cannot crash the CLI or the MCP. We swallow errors and let
11
+ * the caller fall back to appending to the (possibly oversized) file.
12
+ */
13
+ declare function rotateIfNeeded(logFile: string): void;
1
14
  export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
2
15
  export declare function setLogLevel(level: LogLevel): void;
3
16
  export declare const logger: {
@@ -6,4 +19,11 @@ export declare const logger: {
6
19
  warn: (message: string, meta?: Record<string, unknown>) => void;
7
20
  error: (message: string, meta?: Record<string, unknown>) => void;
8
21
  };
22
+ export declare const __testing: {
23
+ MAX_FILE_BYTES: number;
24
+ MAX_BACKUPS: number;
25
+ rotateIfNeeded: typeof rotateIfNeeded;
26
+ getLogFile: typeof getLogFile;
27
+ };
28
+ export {};
9
29
  //# sourceMappingURL=logger.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAmBA,iBAAS,UAAU,IAAI,MAAM,CAG5B;AAMD;;;;;;;;;;GAUG;AACH,iBAAS,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAmC7C;AAED,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAW3D,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAEjD;AAyBD,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;AAIF,eAAO,MAAM,SAAS;;;;;CAKrB,CAAC"}
@@ -2,6 +2,14 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import os from 'node:os';
4
4
  const LOG_DIR = path.join(os.homedir(), '.rackmind', 'logs');
5
+ // Rotation policy: cap each log file at MAX_FILE_BYTES, keep MAX_BACKUPS rotated
6
+ // copies (`<file>.1` is the most recent rotation, `<file>.<MAX_BACKUPS>` is the
7
+ // oldest and is deleted on the next rotation).
8
+ // One file maxed out + N backups bounds disk usage at roughly
9
+ // MAX_FILE_BYTES * (MAX_BACKUPS + 1)
10
+ // = 10 MB * 6 = 60 MB per log basename.
11
+ const MAX_FILE_BYTES = 10 * 1024 * 1024;
12
+ const MAX_BACKUPS = 5;
5
13
  function ensureLogDir() {
6
14
  fs.mkdirSync(LOG_DIR, { recursive: true });
7
15
  }
@@ -12,6 +20,55 @@ function getLogFile() {
12
20
  function formatTimestamp() {
13
21
  return new Date().toISOString();
14
22
  }
23
+ /**
24
+ * Rotate `logFile` if it exceeds MAX_FILE_BYTES.
25
+ *
26
+ * Strategy: shift `.N -> .N+1`, then rename the live file to `.1`. The next
27
+ * write to `logFile` will create a fresh, empty file.
28
+ *
29
+ * This is intentionally synchronous (CLI hot path, rare event, tiny rename
30
+ * cost) and best-effort — any failure here must NOT throw, so that a broken
31
+ * filesystem state cannot crash the CLI or the MCP. We swallow errors and let
32
+ * the caller fall back to appending to the (possibly oversized) file.
33
+ */
34
+ function rotateIfNeeded(logFile) {
35
+ try {
36
+ const stat = fs.statSync(logFile);
37
+ if (stat.size <= MAX_FILE_BYTES)
38
+ return;
39
+ }
40
+ catch {
41
+ // File doesn't exist yet — nothing to rotate.
42
+ return;
43
+ }
44
+ try {
45
+ // Drop the oldest backup if it exists.
46
+ const oldest = `${logFile}.${MAX_BACKUPS}`;
47
+ try {
48
+ fs.unlinkSync(oldest);
49
+ }
50
+ catch {
51
+ // ignore — may not exist
52
+ }
53
+ // Shift .N -> .N+1, starting from the second-oldest.
54
+ for (let i = MAX_BACKUPS - 1; i >= 1; i--) {
55
+ const src = `${logFile}.${i}`;
56
+ const dest = `${logFile}.${i + 1}`;
57
+ try {
58
+ fs.renameSync(src, dest);
59
+ }
60
+ catch {
61
+ // ignore — backup at this index doesn't exist
62
+ }
63
+ }
64
+ // Live file -> .1. Atomic rename on POSIX.
65
+ fs.renameSync(logFile, `${logFile}.1`);
66
+ }
67
+ catch {
68
+ // Rotation failed — fall through, caller will append to the existing
69
+ // file. Bounded growth is preferred to a crashed CLI.
70
+ }
71
+ }
15
72
  const LOG_LEVELS = {
16
73
  debug: 0,
17
74
  info: 1,
@@ -30,13 +87,15 @@ function writeLog(level, message, meta) {
30
87
  return;
31
88
  try {
32
89
  ensureLogDir();
90
+ const file = getLogFile();
91
+ rotateIfNeeded(file);
33
92
  const entry = {
34
93
  timestamp: formatTimestamp(),
35
94
  level,
36
95
  message,
37
96
  ...(meta ? { meta } : {}),
38
97
  };
39
- fs.appendFileSync(getLogFile(), JSON.stringify(entry) + '\n');
98
+ fs.appendFileSync(file, JSON.stringify(entry) + '\n');
40
99
  }
41
100
  catch {
42
101
  // Silently fail — logging should never crash the CLI
@@ -48,4 +107,12 @@ export const logger = {
48
107
  warn: (message, meta) => writeLog('warn', message, meta),
49
108
  error: (message, meta) => writeLog('error', message, meta),
50
109
  };
110
+ // Exported for tests only. Keep undocumented in public README — the rotation
111
+ // policy is an implementation detail.
112
+ export const __testing = {
113
+ MAX_FILE_BYTES,
114
+ MAX_BACKUPS,
115
+ rotateIfNeeded,
116
+ getLogFile,
117
+ };
51
118
  //# sourceMappingURL=logger.js.map
@@ -1 +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"}
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rackmind-cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "CLI interface for RackMind — Claude Code-style terminal experience for managing Proxmox servers, LXC containers, and QEMU VMs from your terminal.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -74,6 +74,7 @@
74
74
  "format:check": "prettier --check .",
75
75
  "type-check": "tsc --noEmit -p tsconfig.typecheck.json",
76
76
  "test": "vitest run",
77
+ "test:homelab": "./scripts/test-against-homelab.sh",
77
78
  "check": "npm run lint && npm run format:check && npm run type-check && npm test",
78
79
  "prepare": "husky || true",
79
80
  "prepublishOnly": "npm run check && npm run build"