@tracehound/cli 1.2.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 (45) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +17 -0
  3. package/dist/commands/inspect.d.ts +6 -0
  4. package/dist/commands/inspect.d.ts.map +1 -0
  5. package/dist/commands/inspect.js +108 -0
  6. package/dist/commands/stats.d.ts +6 -0
  7. package/dist/commands/stats.d.ts.map +1 -0
  8. package/dist/commands/stats.js +80 -0
  9. package/dist/commands/status.d.ts +6 -0
  10. package/dist/commands/status.d.ts.map +1 -0
  11. package/dist/commands/status.js +98 -0
  12. package/dist/commands/watch.d.ts +45 -0
  13. package/dist/commands/watch.d.ts.map +1 -0
  14. package/dist/commands/watch.js +184 -0
  15. package/dist/index.d.ts +13 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +31 -0
  18. package/dist/lib/theme.d.ts +39 -0
  19. package/dist/lib/theme.d.ts.map +1 -0
  20. package/dist/lib/theme.js +95 -0
  21. package/dist/tui/App.d.ts +10 -0
  22. package/dist/tui/App.d.ts.map +1 -0
  23. package/dist/tui/App.js +19 -0
  24. package/dist/tui/hooks/useSnapshot.d.ts +29 -0
  25. package/dist/tui/hooks/useSnapshot.d.ts.map +1 -0
  26. package/dist/tui/hooks/useSnapshot.js +41 -0
  27. package/dist/tui/panels/Audit.d.ts +15 -0
  28. package/dist/tui/panels/Audit.d.ts.map +1 -0
  29. package/dist/tui/panels/Audit.js +9 -0
  30. package/dist/tui/panels/HoundPool.d.ts +16 -0
  31. package/dist/tui/panels/HoundPool.d.ts.map +1 -0
  32. package/dist/tui/panels/HoundPool.js +15 -0
  33. package/dist/tui/panels/Quarantine.d.ts +21 -0
  34. package/dist/tui/panels/Quarantine.d.ts.map +1 -0
  35. package/dist/tui/panels/Quarantine.js +18 -0
  36. package/package.json +48 -0
  37. package/src/commands/inspect.ts +142 -0
  38. package/src/commands/stats.ts +124 -0
  39. package/src/commands/status.ts +144 -0
  40. package/src/commands/watch.ts +273 -0
  41. package/src/index.ts +40 -0
  42. package/src/lib/theme.ts +117 -0
  43. package/tests/commands.test.ts +226 -0
  44. package/tests/smoke.test.ts +27 -0
  45. package/tsconfig.json +23 -0
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Watch command - Live dashboard (Pure ANSI, no React)
3
+ */
4
+ import Table from 'cli-table3';
5
+ import { Command } from 'commander';
6
+ import { createRequire } from 'module';
7
+ import { bold, clearScreen, hideCursor, muted, primary, progressBar, secondary, severity, showCursor, theme, } from '../lib/theme.js';
8
+ const require = createRequire(import.meta.url);
9
+ const { version } = require('../../package.json');
10
+ export const watchCommand = new Command('watch')
11
+ .description('Launch live dashboard')
12
+ .option('-r, --refresh <ms>', 'Refresh interval in ms', '1000')
13
+ .action((options) => {
14
+ const refreshMs = parseInt(options.refresh);
15
+ startDashboard(refreshMs);
16
+ });
17
+ export function getSnapshot() {
18
+ // TODO: Connect to real core
19
+ return {
20
+ timestamp: new Date().toISOString(),
21
+ system: {
22
+ version: version,
23
+ uptime: formatUptime(Math.floor(process.uptime())),
24
+ health: 'healthy',
25
+ memory: { used: 45, total: 256 },
26
+ },
27
+ quarantine: {
28
+ count: 0,
29
+ capacity: 1000,
30
+ bytes: 0,
31
+ bySeverity: { critical: 0, high: 0, medium: 0, low: 0 },
32
+ },
33
+ houndPool: {
34
+ active: 0,
35
+ dormant: 0,
36
+ total: 0,
37
+ status: 'ok',
38
+ },
39
+ recentThreats: [],
40
+ };
41
+ }
42
+ function startDashboard(refreshMs) {
43
+ hideCursor();
44
+ // Handle Ctrl+C gracefully
45
+ process.on('SIGINT', () => {
46
+ showCursor();
47
+ clearScreen();
48
+ console.log(muted('\n Dashboard closed.\n'));
49
+ process.exit(0);
50
+ });
51
+ const render = () => {
52
+ clearScreen();
53
+ const snapshot = getSnapshot();
54
+ renderDashboard(snapshot, refreshMs);
55
+ };
56
+ render();
57
+ setInterval(render, refreshMs);
58
+ }
59
+ export function renderDashboard(s, refreshMs) {
60
+ const width = 76;
61
+ // Header
62
+ console.log();
63
+ console.log(primary(` ╔${'═'.repeat(width)}╗`));
64
+ console.log(primary(` ║${' '.repeat(20)}`) +
65
+ bold('🐕 TRACEHOUND LIVE DASHBOARD') +
66
+ primary(`${' '.repeat(28)}║`));
67
+ console.log(primary(` ║${' '.repeat(24)}`) + muted(s.timestamp) + primary(`${' '.repeat(28)}║`));
68
+ console.log(primary(` ╚${'═'.repeat(width)}╝`));
69
+ console.log();
70
+ // System Status
71
+ const systemTable = new Table({
72
+ chars: getTableChars(),
73
+ style: { head: [], border: [] },
74
+ head: [secondary('Version'), secondary('Uptime'), secondary('Health'), secondary('Memory')],
75
+ });
76
+ const healthIcon = s.system.health === 'healthy' ? '✅' : s.system.health === 'degraded' ? '⚠️' : '🔴';
77
+ const memBar = progressBar(s.system.memory.used, s.system.memory.total, 10);
78
+ systemTable.push([
79
+ s.system.version,
80
+ s.system.uptime,
81
+ `${healthIcon} ${s.system.health}`,
82
+ `${memBar} ${s.system.memory.used}/${s.system.memory.total} MB`,
83
+ ]);
84
+ console.log(muted(' SYSTEM'));
85
+ console.log(indent(systemTable.toString()));
86
+ console.log();
87
+ // Quarantine & Hound Pool side by side
88
+ const quarantineTable = new Table({
89
+ chars: getTableChars(),
90
+ style: { head: [], border: [] },
91
+ head: [secondary('QUARANTINE'), secondary('Value')],
92
+ });
93
+ const qUsage = s.quarantine.capacity > 0 ? (s.quarantine.count / s.quarantine.capacity) * 100 : 0;
94
+ const qBar = progressBar(s.quarantine.count, s.quarantine.capacity, 8);
95
+ quarantineTable.push(['Count', `${s.quarantine.count} / ${s.quarantine.capacity}`], ['Usage', `${qBar} ${qUsage.toFixed(1)}%`], ['Bytes', formatBytes(s.quarantine.bytes)], [
96
+ 'Split',
97
+ `${severity('critical').slice(0, 15)} ${s.quarantine.bySeverity.critical} ${severity('high').slice(0, 12)} ${s.quarantine.bySeverity.high} ${severity('medium').slice(0, 12)} ${s.quarantine.bySeverity.medium} ${severity('low').slice(0, 10)} ${s.quarantine.bySeverity.low}`,
98
+ ]);
99
+ const poolTable = new Table({
100
+ chars: getTableChars(),
101
+ style: { head: [], border: [] },
102
+ head: [secondary('HOUND POOL'), secondary('Value')],
103
+ });
104
+ const poolBar = progressBar(s.houndPool.active, s.houndPool.total || 1, 8);
105
+ const poolStatus = s.houndPool.status === 'ok' ? '✅ OK' : '🔴 EXHAUSTED';
106
+ poolTable.push(['Active', `${poolBar} ${s.houndPool.active}/${s.houndPool.total}`], ['Dormant', String(s.houndPool.dormant)], ['Status', poolStatus]);
107
+ console.log(indent(quarantineTable.toString()));
108
+ console.log();
109
+ console.log(indent(poolTable.toString()));
110
+ console.log();
111
+ // Recent Threats
112
+ if (s.recentThreats.length > 0) {
113
+ const threatTable = new Table({
114
+ chars: getTableChars(),
115
+ style: { head: [], border: [] },
116
+ head: [
117
+ secondary('Signature'),
118
+ secondary('Severity'),
119
+ secondary('Category'),
120
+ secondary('Size'),
121
+ secondary('Time'),
122
+ ],
123
+ });
124
+ for (const t of s.recentThreats.slice(0, 5)) {
125
+ threatTable.push([
126
+ t.signature.slice(0, 12) + '...',
127
+ severity(t.severity),
128
+ t.category,
129
+ t.size,
130
+ t.time,
131
+ ]);
132
+ }
133
+ console.log(muted(' RECENT THREATS'));
134
+ console.log(indent(threatTable.toString()));
135
+ console.log();
136
+ }
137
+ else {
138
+ console.log(muted(' RECENT THREATS'));
139
+ console.log(muted(' 📭 No recent threats'));
140
+ console.log();
141
+ }
142
+ // Footer
143
+ console.log(muted(` ${'─'.repeat(width)}`));
144
+ console.log(muted(` Press Ctrl+C to exit │ Refresh: ${refreshMs}ms │ ${theme.reset}${secondary(new Date().toLocaleTimeString())}`));
145
+ }
146
+ function getTableChars() {
147
+ return {
148
+ top: '─',
149
+ 'top-mid': '┬',
150
+ 'top-left': '┌',
151
+ 'top-right': '┐',
152
+ bottom: '─',
153
+ 'bottom-mid': '┴',
154
+ 'bottom-left': '└',
155
+ 'bottom-right': '┘',
156
+ left: '│',
157
+ 'left-mid': '├',
158
+ mid: '─',
159
+ 'mid-mid': '┼',
160
+ right: '│',
161
+ 'right-mid': '┤',
162
+ middle: '│',
163
+ };
164
+ }
165
+ function indent(text, spaces = 2) {
166
+ return text
167
+ .split('\n')
168
+ .map((line) => ' '.repeat(spaces) + line)
169
+ .join('\n');
170
+ }
171
+ function formatUptime(seconds) {
172
+ const h = Math.floor(seconds / 3600);
173
+ const m = Math.floor((seconds % 3600) / 60);
174
+ const s = seconds % 60;
175
+ return `${h}h ${m}m ${s}s`;
176
+ }
177
+ function formatBytes(bytes) {
178
+ if (bytes === 0)
179
+ return '0 B';
180
+ const k = 1024;
181
+ const sizes = ['B', 'KB', 'MB', 'GB'];
182
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
183
+ return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`;
184
+ }
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Tracehound CLI - Evaluation Runtime
4
+ *
5
+ * Commands:
6
+ * - tracehound status : Show current system status
7
+ * - tracehound stats : Show threat statistics
8
+ * - tracehound inspect : Inspect quarantine
9
+ * - tracehound watch : Live TUI dashboard
10
+ */
11
+ import { Command } from 'commander';
12
+ export declare const program: Command;
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAYnC,eAAO,MAAM,OAAO,SAAgB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Tracehound CLI - Evaluation Runtime
4
+ *
5
+ * Commands:
6
+ * - tracehound status : Show current system status
7
+ * - tracehound stats : Show threat statistics
8
+ * - tracehound inspect : Inspect quarantine
9
+ * - tracehound watch : Live TUI dashboard
10
+ */
11
+ import { Command } from 'commander';
12
+ import { inspectCommand } from './commands/inspect.js';
13
+ import { statsCommand } from './commands/stats.js';
14
+ import { statusCommand } from './commands/status.js';
15
+ import { watchCommand } from './commands/watch.js';
16
+ import { fileURLToPath } from 'url';
17
+ import { createRequire } from 'module';
18
+ const require = createRequire(import.meta.url);
19
+ const { version } = require('../package.json');
20
+ export const program = new Command();
21
+ program.name('tracehound').description('Tracehound CLI - Runtime Security Buffer').version(version);
22
+ // Register commands
23
+ program.addCommand(statusCommand);
24
+ program.addCommand(statsCommand);
25
+ program.addCommand(inspectCommand);
26
+ program.addCommand(watchCommand);
27
+ // Only parse if executed directly
28
+ const isMain = process.argv[1] && fileURLToPath(import.meta.url).endsWith(process.argv[1].replace(/\\/g, '/'));
29
+ if (isMain || process.env.NODE_ENV === 'cli-run') {
30
+ program.parse();
31
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * ANSI Theme - Soft Dark Material
3
+ *
4
+ * Terminal color utilities with a beautiful dark theme
5
+ */
6
+ export declare const theme: {
7
+ bg: string;
8
+ fg: string;
9
+ primary: string;
10
+ secondary: string;
11
+ accent: string;
12
+ critical: string;
13
+ high: string;
14
+ medium: string;
15
+ low: string;
16
+ border: string;
17
+ muted: string;
18
+ success: string;
19
+ warning: string;
20
+ error: string;
21
+ bold: string;
22
+ dim: string;
23
+ reset: string;
24
+ };
25
+ export declare function colorize(text: string, ...styles: string[]): string;
26
+ export declare function primary(text: string): string;
27
+ export declare function secondary(text: string): string;
28
+ export declare function accent(text: string): string;
29
+ export declare function muted(text: string): string;
30
+ export declare function bold(text: string): string;
31
+ export declare function success(text: string): string;
32
+ export declare function warning(text: string): string;
33
+ export declare function error(text: string): string;
34
+ export declare function severity(level: string): string;
35
+ export declare function progressBar(current: number, max: number, width?: number): string;
36
+ export declare function clearScreen(): void;
37
+ export declare function hideCursor(): void;
38
+ export declare function showCursor(): void;
39
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/lib/theme.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;CA2BjB,CAAA;AAGD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAElE;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1C;AAED,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzC;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1C;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAa9C;AAGD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,MAAM,CAS5E;AAGD,wBAAgB,WAAW,IAAI,IAAI,CAElC;AAGD,wBAAgB,UAAU,IAAI,IAAI,CAEjC;AAED,wBAAgB,UAAU,IAAI,IAAI,CAEjC"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * ANSI Theme - Soft Dark Material
3
+ *
4
+ * Terminal color utilities with a beautiful dark theme
5
+ */
6
+ // ANSI escape codes
7
+ const ESC = '\x1b[';
8
+ const RESET = `${ESC}0m`;
9
+ // 256-color palette for soft dark material theme
10
+ export const theme = {
11
+ // Base colors
12
+ bg: `${ESC}48;5;235m`, // Soft dark gray background
13
+ fg: `${ESC}38;5;253m`, // Light gray text
14
+ // Accent colors (Material Design inspired)
15
+ primary: `${ESC}38;5;75m`, // Soft blue
16
+ secondary: `${ESC}38;5;183m`, // Soft purple
17
+ accent: `${ESC}38;5;114m`, // Soft green
18
+ // Severity colors
19
+ critical: `${ESC}38;5;203m`, // Soft red
20
+ high: `${ESC}38;5;215m`, // Soft orange
21
+ medium: `${ESC}38;5;221m`, // Soft yellow
22
+ low: `${ESC}38;5;114m`, // Soft green
23
+ // UI elements
24
+ border: `${ESC}38;5;240m`, // Dim gray for borders
25
+ muted: `${ESC}38;5;245m`, // Muted text
26
+ success: `${ESC}38;5;114m`, // Green
27
+ warning: `${ESC}38;5;215m`, // Orange
28
+ error: `${ESC}38;5;203m`, // Red
29
+ // Styles
30
+ bold: `${ESC}1m`,
31
+ dim: `${ESC}2m`,
32
+ reset: RESET,
33
+ };
34
+ // Color helper functions
35
+ export function colorize(text, ...styles) {
36
+ return `${styles.join('')}${text}${RESET}`;
37
+ }
38
+ export function primary(text) {
39
+ return colorize(text, theme.primary);
40
+ }
41
+ export function secondary(text) {
42
+ return colorize(text, theme.secondary);
43
+ }
44
+ export function accent(text) {
45
+ return colorize(text, theme.accent);
46
+ }
47
+ export function muted(text) {
48
+ return colorize(text, theme.muted);
49
+ }
50
+ export function bold(text) {
51
+ return colorize(text, theme.bold);
52
+ }
53
+ export function success(text) {
54
+ return colorize(text, theme.success);
55
+ }
56
+ export function warning(text) {
57
+ return colorize(text, theme.warning);
58
+ }
59
+ export function error(text) {
60
+ return colorize(text, theme.error);
61
+ }
62
+ export function severity(level) {
63
+ switch (level) {
64
+ case 'critical':
65
+ return colorize(`● ${level}`, theme.critical);
66
+ case 'high':
67
+ return colorize(`● ${level}`, theme.high);
68
+ case 'medium':
69
+ return colorize(`● ${level}`, theme.medium);
70
+ case 'low':
71
+ return colorize(`● ${level}`, theme.low);
72
+ default:
73
+ return level;
74
+ }
75
+ }
76
+ // Progress bar
77
+ export function progressBar(current, max, width = 20) {
78
+ const ratio = max > 0 ? current / max : 0;
79
+ const filled = Math.round(ratio * width);
80
+ const empty = width - filled;
81
+ const filledColor = ratio > 0.9 ? theme.critical : ratio > 0.7 ? theme.warning : theme.accent;
82
+ const bar = `${filledColor}${'█'.repeat(filled)}${theme.muted}${'░'.repeat(empty)}${RESET}`;
83
+ return bar;
84
+ }
85
+ // Clear screen
86
+ export function clearScreen() {
87
+ process.stdout.write('\x1b[2J\x1b[H');
88
+ }
89
+ // Hide/show cursor
90
+ export function hideCursor() {
91
+ process.stdout.write('\x1b[?25l');
92
+ }
93
+ export function showCursor() {
94
+ process.stdout.write('\x1b[?25h');
95
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * TUI Application - Main Ink component
3
+ */
4
+ import React from 'react';
5
+ interface AppProps {
6
+ refreshMs: number;
7
+ }
8
+ export declare function App({ refreshMs }: AppProps): React.ReactElement;
9
+ export {};
10
+ //# sourceMappingURL=App.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../src/tui/App.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAA8B,MAAM,OAAO,CAAA;AAMlD,UAAU,QAAQ;IAChB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,GAAG,CAAC,EAAE,SAAS,EAAE,EAAE,QAAQ,GAAG,KAAK,CAAC,YAAY,CAiD/D"}
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * TUI Application - Main Ink component
4
+ */
5
+ import { Box, Text } from 'ink';
6
+ import { useEffect, useState } from 'react';
7
+ import { useSnapshot } from './hooks/useSnapshot.js';
8
+ import { AuditPanel } from './panels/Audit.js';
9
+ import { HoundPoolPanel } from './panels/HoundPool.js';
10
+ import { QuarantinePanel } from './panels/Quarantine.js';
11
+ export function App({ refreshMs }) {
12
+ const snapshot = useSnapshot(refreshMs);
13
+ const [time, setTime] = useState(new Date());
14
+ useEffect(() => {
15
+ const timer = setInterval(() => setTime(new Date()), 1000);
16
+ return () => clearInterval(timer);
17
+ }, []);
18
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557" }) }), _jsxs(Box, { children: [_jsx(Text, { bold: true, color: "cyan", children: "\u2551" }), _jsxs(Text, { bold: true, color: "white", children: [' ', "TRACEHOUND LIVE", ' '] }), _jsx(Text, { color: "gray", children: time.toISOString() }), _jsxs(Text, { bold: true, color: "cyan", children: [' ', "\u2551"] })] }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D" }) }), _jsxs(Box, { flexDirection: "row", gap: 2, children: [_jsx(QuarantinePanel, { data: snapshot.quarantine }), _jsx(HoundPoolPanel, { data: snapshot.houndPool }), _jsx(AuditPanel, { data: snapshot.audit })] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "gray", children: ["Press Ctrl+C to exit | Refresh: ", refreshMs, "ms"] }) })] }));
19
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * useSnapshot hook - Fetches system snapshot at interval
3
+ */
4
+ export interface Snapshot {
5
+ quarantine: {
6
+ count: number;
7
+ bytes: number;
8
+ capacity: number;
9
+ bySeverity: {
10
+ critical: number;
11
+ high: number;
12
+ medium: number;
13
+ low: number;
14
+ };
15
+ };
16
+ houndPool: {
17
+ active: number;
18
+ dormant: number;
19
+ total: number;
20
+ exhausted: boolean;
21
+ };
22
+ audit: {
23
+ records: number;
24
+ lastHash: string;
25
+ integrity: 'valid' | 'invalid' | 'empty';
26
+ };
27
+ }
28
+ export declare function useSnapshot(refreshMs: number): Snapshot;
29
+ //# sourceMappingURL=useSnapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSnapshot.d.ts","sourceRoot":"","sources":["../../../src/tui/hooks/useSnapshot.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,WAAW,QAAQ;IACvB,UAAU,EAAE;QACV,KAAK,EAAE,MAAM,CAAA;QACb,KAAK,EAAE,MAAM,CAAA;QACb,QAAQ,EAAE,MAAM,CAAA;QAChB,UAAU,EAAE;YACV,QAAQ,EAAE,MAAM,CAAA;YAChB,IAAI,EAAE,MAAM,CAAA;YACZ,MAAM,EAAE,MAAM,CAAA;YACd,GAAG,EAAE,MAAM,CAAA;SACZ,CAAA;KACF,CAAA;IACD,SAAS,EAAE;QACT,MAAM,EAAE,MAAM,CAAA;QACd,OAAO,EAAE,MAAM,CAAA;QACf,KAAK,EAAE,MAAM,CAAA;QACb,SAAS,EAAE,OAAO,CAAA;KACnB,CAAA;IACD,KAAK,EAAE;QACL,OAAO,EAAE,MAAM,CAAA;QACf,QAAQ,EAAE,MAAM,CAAA;QAChB,SAAS,EAAE,OAAO,GAAG,SAAS,GAAG,OAAO,CAAA;KACzC,CAAA;CACF;AA8BD,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,CAYvD"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * useSnapshot hook - Fetches system snapshot at interval
3
+ */
4
+ import { useEffect, useState } from 'react';
5
+ function getSnapshot() {
6
+ // TODO: Connect to real core when available
7
+ return {
8
+ quarantine: {
9
+ count: 0,
10
+ bytes: 0,
11
+ capacity: 1000,
12
+ bySeverity: {
13
+ critical: 0,
14
+ high: 0,
15
+ medium: 0,
16
+ low: 0,
17
+ },
18
+ },
19
+ houndPool: {
20
+ active: 0,
21
+ dormant: 0,
22
+ total: 0,
23
+ exhausted: false,
24
+ },
25
+ audit: {
26
+ records: 0,
27
+ lastHash: '',
28
+ integrity: 'empty',
29
+ },
30
+ };
31
+ }
32
+ export function useSnapshot(refreshMs) {
33
+ const [snapshot, setSnapshot] = useState(getSnapshot);
34
+ useEffect(() => {
35
+ const interval = setInterval(() => {
36
+ setSnapshot(getSnapshot());
37
+ }, refreshMs);
38
+ return () => clearInterval(interval);
39
+ }, [refreshMs]);
40
+ return snapshot;
41
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Audit Panel - Shows audit chain status
3
+ */
4
+ import React from 'react';
5
+ interface AuditData {
6
+ records: number;
7
+ lastHash: string;
8
+ integrity: 'valid' | 'invalid' | 'empty';
9
+ }
10
+ interface Props {
11
+ data: AuditData;
12
+ }
13
+ export declare function AuditPanel({ data }: Props): React.ReactElement;
14
+ export {};
15
+ //# sourceMappingURL=Audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Audit.d.ts","sourceRoot":"","sources":["../../../src/tui/panels/Audit.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,UAAU,SAAS;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,OAAO,GAAG,SAAS,GAAG,OAAO,CAAA;CACzC;AAED,UAAU,KAAK;IACb,IAAI,EAAE,SAAS,CAAA;CAChB;AAED,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,EAAE,KAAK,GAAG,KAAK,CAAC,YAAY,CA6B9D"}
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Audit Panel - Shows audit chain status
4
+ */
5
+ import { Box, Text } from 'ink';
6
+ export function AuditPanel({ data }) {
7
+ const integrityColor = data.integrity === 'valid' ? 'green' : data.integrity === 'invalid' ? 'red' : 'gray';
8
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", padding: 1, width: 25, children: [_jsx(Text, { bold: true, color: "cyan", children: "AUDIT CHAIN" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Records: " }), _jsx(Text, { bold: true, children: data.records })] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Integrity: " }), _jsx(Text, { color: integrityColor, bold: true, children: data.integrity.toUpperCase() })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { bold: true, children: "Last Hash:" }) }), _jsx(Box, { children: _jsxs(Text, { color: "gray", children: [data.lastHash.slice(0, 16) || '(empty)', "..."] }) })] }));
9
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Hound Pool Panel - Shows pool status
3
+ */
4
+ import React from 'react';
5
+ interface HoundPoolData {
6
+ active: number;
7
+ dormant: number;
8
+ total: number;
9
+ exhausted: boolean;
10
+ }
11
+ interface Props {
12
+ data: HoundPoolData;
13
+ }
14
+ export declare function HoundPoolPanel({ data }: Props): React.ReactElement;
15
+ export {};
16
+ //# sourceMappingURL=HoundPool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HoundPool.d.ts","sourceRoot":"","sources":["../../../src/tui/panels/HoundPool.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,UAAU,aAAa;IACrB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,OAAO,CAAA;CACnB;AAED,UAAU,KAAK;IACb,IAAI,EAAE,aAAa,CAAA;CACpB;AAED,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,EAAE,KAAK,GAAG,KAAK,CAAC,YAAY,CAuClE"}
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Hound Pool Panel - Shows pool status
4
+ */
5
+ import { Box, Text } from 'ink';
6
+ export function HoundPoolPanel({ data }) {
7
+ const healthColor = data.exhausted ? 'red' : data.active === data.total ? 'yellow' : 'green';
8
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", padding: 1, width: 25, children: [_jsx(Text, { bold: true, color: "cyan", children: "HOUND POOL" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Status: " }), _jsx(Text, { color: healthColor, bold: true, children: data.exhausted ? 'EXHAUSTED' : 'OK' })] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "green", children: "\u25CF Active: " }), _jsx(Text, { bold: true, children: data.active })] }), _jsxs(Box, { children: [_jsx(Text, { color: "gray", children: "\u25CB Dormant: " }), _jsx(Text, { children: data.dormant })] }), _jsxs(Box, { children: [_jsx(Text, { children: "Total: " }), _jsx(Text, { children: data.total })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { bold: true, children: "Utilization:" }) }), _jsx(Box, { children: _jsx(Text, { children: renderBar(data.active, data.total) }) })] }));
9
+ }
10
+ function renderBar(current, max) {
11
+ const width = 15;
12
+ const filled = max > 0 ? Math.round((current / max) * width) : 0;
13
+ const empty = width - filled;
14
+ return `[${'█'.repeat(filled)}${'░'.repeat(empty)}]`;
15
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Quarantine Panel - Shows quarantine status
3
+ */
4
+ import React from 'react';
5
+ interface QuarantineData {
6
+ count: number;
7
+ bytes: number;
8
+ capacity: number;
9
+ bySeverity: {
10
+ critical: number;
11
+ high: number;
12
+ medium: number;
13
+ low: number;
14
+ };
15
+ }
16
+ interface Props {
17
+ data: QuarantineData;
18
+ }
19
+ export declare function QuarantinePanel({ data }: Props): React.ReactElement;
20
+ export {};
21
+ //# sourceMappingURL=Quarantine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Quarantine.d.ts","sourceRoot":"","sources":["../../../src/tui/panels/Quarantine.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE;QACV,QAAQ,EAAE,MAAM,CAAA;QAChB,IAAI,EAAE,MAAM,CAAA;QACZ,MAAM,EAAE,MAAM,CAAA;QACd,GAAG,EAAE,MAAM,CAAA;KACZ,CAAA;CACF;AAED,UAAU,KAAK;IACb,IAAI,EAAE,cAAc,CAAA;CACrB;AAED,wBAAgB,eAAe,CAAC,EAAE,IAAI,EAAE,EAAE,KAAK,GAAG,KAAK,CAAC,YAAY,CAuCnE"}
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Quarantine Panel - Shows quarantine status
4
+ */
5
+ import { Box, Text } from 'ink';
6
+ export function QuarantinePanel({ data }) {
7
+ const usage = data.capacity > 0 ? (data.count / data.capacity) * 100 : 0;
8
+ const usageColor = usage > 90 ? 'red' : usage > 70 ? 'yellow' : 'green';
9
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", padding: 1, width: 30, children: [_jsx(Text, { bold: true, color: "cyan", children: "QUARANTINE" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Count: " }), _jsx(Text, { color: usageColor, bold: true, children: data.count }), _jsxs(Text, { color: "gray", children: [" / ", data.capacity] })] }), _jsxs(Box, { children: [_jsx(Text, { children: "Usage: " }), _jsxs(Text, { color: usageColor, children: [usage.toFixed(1), "%"] })] }), _jsxs(Box, { children: [_jsx(Text, { children: "Bytes: " }), _jsx(Text, { children: formatBytes(data.bytes) })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { bold: true, children: "By Severity:" }) }), _jsxs(Box, { children: [_jsxs(Text, { color: "red", children: ["\u25CF ", data.bySeverity.critical, " "] }), _jsxs(Text, { color: "yellow", children: ["\u25CF ", data.bySeverity.high, " "] }), _jsxs(Text, { color: "cyan", children: ["\u25CF ", data.bySeverity.medium, " "] }), _jsxs(Text, { color: "green", children: ["\u25CF ", data.bySeverity.low] })] })] }));
10
+ }
11
+ function formatBytes(bytes) {
12
+ if (bytes === 0)
13
+ return '0 B';
14
+ const k = 1024;
15
+ const sizes = ['B', 'KB', 'MB', 'GB'];
16
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
17
+ return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`;
18
+ }
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@tracehound/cli",
3
+ "version": "1.2.0",
4
+ "description": "Tracehound CLI evaluation runtime",
5
+ "license": "Apache-2.0",
6
+ "author": "Erdem Arslan <me@erdem.work>",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/tracehound/tracehound.git",
10
+ "directory": "packages/cli"
11
+ },
12
+ "homepage": "https://github.com/tracehound/tracehound#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/tracehound/tracehound/issues"
15
+ },
16
+ "keywords": [
17
+ "security",
18
+ "runtime",
19
+ "cli",
20
+ "forensics",
21
+ "audit"
22
+ ],
23
+ "type": "module",
24
+ "main": "dist/index.js",
25
+ "bin": {
26
+ "tracehound": "dist/index.js"
27
+ },
28
+ "dependencies": {
29
+ "cli-table3": "^0.6.5",
30
+ "commander": "^12.0.0",
31
+ "@tracehound/core": "1.2.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^20.0.0",
35
+ "tsx": "^4.0.0",
36
+ "typescript": "^5.0.0",
37
+ "vitest": "^2.0.0"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "scripts": {
43
+ "build": "tsc",
44
+ "dev": "tsx src/index.ts",
45
+ "test": "vitest run",
46
+ "lint": "tsc --noEmit"
47
+ }
48
+ }