@defai.digital/ax-cli 3.6.0 → 3.6.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 +447 -102
- package/config-defaults/settings.yaml +24 -0
- package/dist/analyzers/code-smells/detectors/data-clumps-detector.js +7 -9
- package/dist/analyzers/code-smells/detectors/data-clumps-detector.js.map +1 -1
- package/dist/analyzers/code-smells/detectors/dead-code-detector.js +1 -1
- package/dist/analyzers/code-smells/detectors/dead-code-detector.js.map +1 -1
- package/dist/analyzers/code-smells/detectors/duplicate-code-detector.js +22 -10
- package/dist/analyzers/code-smells/detectors/duplicate-code-detector.js.map +1 -1
- package/dist/analyzers/code-smells/detectors/feature-envy-detector.js +1 -1
- package/dist/analyzers/code-smells/detectors/feature-envy-detector.js.map +1 -1
- package/dist/analyzers/code-smells/detectors/inappropriate-intimacy-detector.js +1 -1
- package/dist/analyzers/code-smells/detectors/inappropriate-intimacy-detector.js.map +1 -1
- package/dist/analyzers/code-smells/detectors/large-class-detector.js +4 -1
- package/dist/analyzers/code-smells/detectors/large-class-detector.js.map +1 -1
- package/dist/analyzers/code-smells/detectors/long-method-detector.js +4 -1
- package/dist/analyzers/code-smells/detectors/long-method-detector.js.map +1 -1
- package/dist/analyzers/code-smells/detectors/long-parameter-list-detector.js +4 -1
- package/dist/analyzers/code-smells/detectors/long-parameter-list-detector.js.map +1 -1
- package/dist/analyzers/code-smells/detectors/magic-numbers-detector.js +4 -5
- package/dist/analyzers/code-smells/detectors/magic-numbers-detector.js.map +1 -1
- package/dist/analyzers/code-smells/detectors/nested-conditionals-detector.js +4 -1
- package/dist/analyzers/code-smells/detectors/nested-conditionals-detector.js.map +1 -1
- package/dist/constants.d.ts +13 -0
- package/dist/constants.js +28 -0
- package/dist/constants.js.map +1 -1
- package/dist/schemas/settings-schemas.d.ts +40 -0
- package/dist/schemas/settings-schemas.js +32 -0
- package/dist/schemas/settings-schemas.js.map +1 -1
- package/dist/tools/web-search/index.d.ts +0 -2
- package/dist/tools/web-search/index.js +0 -2
- package/dist/tools/web-search/index.js.map +1 -1
- package/dist/tools/web-search/router.d.ts +0 -2
- package/dist/tools/web-search/router.js +2 -37
- package/dist/tools/web-search/router.js.map +1 -1
- package/dist/tools/web-search/web-search-tool.js +2 -12
- package/dist/tools/web-search/web-search-tool.js.map +1 -1
- package/dist/ui/components/chat-history.d.ts +2 -0
- package/dist/ui/components/chat-history.js +32 -81
- package/dist/ui/components/chat-history.js.map +1 -1
- package/dist/ui/components/chat-input.d.ts +4 -1
- package/dist/ui/components/chat-input.js +133 -52
- package/dist/ui/components/chat-input.js.map +1 -1
- package/dist/ui/components/chat-interface.js +4 -4
- package/dist/ui/components/chat-interface.js.map +1 -1
- package/dist/ui/components/keyboard-hints.js +2 -0
- package/dist/ui/components/keyboard-hints.js.map +1 -1
- package/dist/ui/components/status-bar.d.ts +2 -0
- package/dist/ui/components/status-bar.js +31 -17
- package/dist/ui/components/status-bar.js.map +1 -1
- package/dist/ui/components/tool-group-display.d.ts +15 -0
- package/dist/ui/components/tool-group-display.js +91 -0
- package/dist/ui/components/tool-group-display.js.map +1 -0
- package/dist/ui/components/welcome-panel.js +4 -0
- package/dist/ui/components/welcome-panel.js.map +1 -1
- package/dist/ui/hooks/use-enhanced-input.d.ts +4 -0
- package/dist/ui/hooks/use-enhanced-input.js +156 -9
- package/dist/ui/hooks/use-enhanced-input.js.map +1 -1
- package/dist/ui/hooks/use-input-handler.d.ts +4 -0
- package/dist/ui/hooks/use-input-handler.js +28 -10
- package/dist/ui/hooks/use-input-handler.js.map +1 -1
- package/dist/ui/utils/change-summarizer.d.ts +20 -0
- package/dist/ui/utils/change-summarizer.js +193 -0
- package/dist/ui/utils/change-summarizer.js.map +1 -0
- package/dist/ui/utils/tool-grouper.d.ts +62 -0
- package/dist/ui/utils/tool-grouper.js +224 -0
- package/dist/ui/utils/tool-grouper.js.map +1 -0
- package/dist/utils/audit-logger.d.ts +28 -70
- package/dist/utils/audit-logger.js +30 -135
- package/dist/utils/audit-logger.js.map +1 -1
- package/dist/utils/config-loader.d.ts +4 -0
- package/dist/utils/config-loader.js.map +1 -1
- package/dist/utils/paste-utils.d.ts +99 -0
- package/dist/utils/paste-utils.js +265 -0
- package/dist/utils/paste-utils.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts +15 -1
- package/dist/utils/rate-limiter.js +15 -1
- package/dist/utils/rate-limiter.js.map +1 -1
- package/package.json +4 -8
- package/packages/schemas/package.json +1 -1
- package/.ax-cli/CUSTOM.md +0 -269
- package/.ax-cli/checkpoints/2025-11-20/checkpoint-2dd84869-e62d-46c8-9885-7e45f37f36e2.json +0 -69
- package/.ax-cli/checkpoints/2025-11-20/checkpoint-484dc350-353f-4808-9ed1-ebb3cefdab37.json +0 -24
- package/.ax-cli/checkpoints/2025-11-20/checkpoint-74a18b87-6172-4215-962b-44bb9f46a662.json +0 -69
- package/.ax-cli/checkpoints/2025-11-20/checkpoint-870a5fb9-6e82-4ff2-8ec8-af4c251cc514.json +0 -44
- package/.ax-cli/checkpoints/2025-11-20/checkpoint-93946601-0e83-456c-ba47-def9713124dd.json +0 -24
- package/.ax-cli/checkpoints/metadata.json +0 -62
- package/.ax-cli/index.json +0 -44
- package/.ax-cli/memory.json +0 -55
- package/.ax-cli/settings.json +0 -1
- package/dist/agent/chat-history-manager.d.ts +0 -56
- package/dist/agent/chat-history-manager.js +0 -150
- package/dist/agent/chat-history-manager.js.map +0 -1
- package/dist/agent/tool-manager.d.ts +0 -39
- package/dist/agent/tool-manager.js +0 -76
- package/dist/agent/tool-manager.js.map +0 -1
- package/dist/tools/web-search/engines/brave.d.ts +0 -16
- package/dist/tools/web-search/engines/brave.js +0 -99
- package/dist/tools/web-search/engines/brave.js.map +0 -1
- package/dist/tools/web-search/engines/tavily.d.ts +0 -17
- package/dist/tools/web-search/engines/tavily.js +0 -73
- package/dist/tools/web-search/engines/tavily.js.map +0 -1
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Grouping Utilities
|
|
3
|
+
* Groups consecutive tool operations for cleaner UX in quiet mode
|
|
4
|
+
*/
|
|
5
|
+
import { UI_CONFIG } from '../../constants.js';
|
|
6
|
+
/**
|
|
7
|
+
* Check if an entry is a ToolGroup
|
|
8
|
+
*/
|
|
9
|
+
export function isToolGroup(entry) {
|
|
10
|
+
return 'operations' in entry && Array.isArray(entry.operations);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Extract file path from tool call arguments
|
|
14
|
+
*/
|
|
15
|
+
function getResourceFromToolCall(entry) {
|
|
16
|
+
if (entry.type !== 'tool_call' && entry.type !== 'tool_result') {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
const toolName = entry.toolCall?.function?.name;
|
|
20
|
+
if (!toolName)
|
|
21
|
+
return '';
|
|
22
|
+
try {
|
|
23
|
+
const argsStr = entry.toolCall?.function?.arguments;
|
|
24
|
+
if (!argsStr)
|
|
25
|
+
return '';
|
|
26
|
+
const args = JSON.parse(argsStr);
|
|
27
|
+
// File operations
|
|
28
|
+
if (toolName === 'view_file' || toolName === 'str_replace_editor' || toolName === 'create_file') {
|
|
29
|
+
return args.path || args.file_path || '';
|
|
30
|
+
}
|
|
31
|
+
// Search operations
|
|
32
|
+
if (toolName === 'search') {
|
|
33
|
+
return `search:${args.query || ''}`;
|
|
34
|
+
}
|
|
35
|
+
// Bash operations
|
|
36
|
+
if (toolName === 'bash') {
|
|
37
|
+
const cmd = args.command || '';
|
|
38
|
+
// Group by command type (git, npm, etc)
|
|
39
|
+
const cmdType = cmd.split(' ')[0];
|
|
40
|
+
return `bash:${cmdType}`;
|
|
41
|
+
}
|
|
42
|
+
// Todo operations
|
|
43
|
+
if (toolName === 'create_todo_list' || toolName === 'update_todo_list') {
|
|
44
|
+
return 'todo';
|
|
45
|
+
}
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Determine group type based on tool name
|
|
54
|
+
*/
|
|
55
|
+
function getGroupType(toolName) {
|
|
56
|
+
if (toolName === 'bash')
|
|
57
|
+
return 'bash';
|
|
58
|
+
if (toolName === 'search')
|
|
59
|
+
return 'search';
|
|
60
|
+
if (toolName === 'create_todo_list' || toolName === 'update_todo_list')
|
|
61
|
+
return 'todo';
|
|
62
|
+
if (toolName === 'view_file' || toolName === 'str_replace_editor' || toolName === 'create_file')
|
|
63
|
+
return 'file';
|
|
64
|
+
return 'mixed';
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Check if two entries should be grouped together
|
|
68
|
+
*/
|
|
69
|
+
function shouldGroup(current, previous, groupSize, maxGroupSize, timeWindow) {
|
|
70
|
+
// Don't group if we've hit the max size
|
|
71
|
+
if (groupSize >= maxGroupSize) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
// Only group tool operations
|
|
75
|
+
if ((current.type !== 'tool_call' && current.type !== 'tool_result') ||
|
|
76
|
+
(previous.type !== 'tool_call' && previous.type !== 'tool_result')) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
// Check time window
|
|
80
|
+
const timeDiff = current.timestamp.getTime() - previous.timestamp.getTime();
|
|
81
|
+
if (timeDiff > timeWindow) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
// Must operate on the same resource
|
|
85
|
+
const currentResource = getResourceFromToolCall(current);
|
|
86
|
+
const previousResource = getResourceFromToolCall(previous);
|
|
87
|
+
if (!currentResource || !previousResource) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
return currentResource === previousResource;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Group consecutive tool operations
|
|
94
|
+
*
|
|
95
|
+
* @param entries - Chat history entries
|
|
96
|
+
* @param options - Grouping configuration
|
|
97
|
+
* @returns Array of grouped or individual entries
|
|
98
|
+
*/
|
|
99
|
+
export function groupConsecutiveTools(entries, options = {}) {
|
|
100
|
+
const { enabled = UI_CONFIG.GROUP_TOOL_CALLS, maxGroupSize = UI_CONFIG.MAX_GROUP_SIZE, timeWindow = UI_CONFIG.GROUP_TIME_WINDOW, } = options;
|
|
101
|
+
// If grouping disabled, return entries as-is
|
|
102
|
+
if (!enabled) {
|
|
103
|
+
return entries;
|
|
104
|
+
}
|
|
105
|
+
const result = [];
|
|
106
|
+
let currentGroup = null;
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
// Non-tool entries always break the group
|
|
109
|
+
if (entry.type !== 'tool_call' && entry.type !== 'tool_result') {
|
|
110
|
+
if (currentGroup) {
|
|
111
|
+
result.push(currentGroup);
|
|
112
|
+
currentGroup = null;
|
|
113
|
+
}
|
|
114
|
+
result.push(entry);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const resource = getResourceFromToolCall(entry);
|
|
118
|
+
const toolName = entry.toolCall?.function?.name || 'unknown';
|
|
119
|
+
const groupType = getGroupType(toolName);
|
|
120
|
+
// Start new group or continue existing
|
|
121
|
+
if (!currentGroup) {
|
|
122
|
+
// Start new group
|
|
123
|
+
currentGroup = {
|
|
124
|
+
resource,
|
|
125
|
+
operations: [entry],
|
|
126
|
+
startTime: entry.timestamp instanceof Date ? entry.timestamp : new Date(entry.timestamp),
|
|
127
|
+
endTime: entry.timestamp instanceof Date ? entry.timestamp : new Date(entry.timestamp),
|
|
128
|
+
hasError: entry.toolResult ? !entry.toolResult.success : false,
|
|
129
|
+
groupType,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
else if (shouldGroup(entry, currentGroup.operations[currentGroup.operations.length - 1], currentGroup.operations.length, maxGroupSize, timeWindow)) {
|
|
133
|
+
// Add to existing group
|
|
134
|
+
currentGroup.operations.push(entry);
|
|
135
|
+
currentGroup.endTime = entry.timestamp instanceof Date ? entry.timestamp : new Date(entry.timestamp);
|
|
136
|
+
if (entry.toolResult && !entry.toolResult.success) {
|
|
137
|
+
currentGroup.hasError = true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
// Can't group, flush current and start new
|
|
142
|
+
result.push(currentGroup);
|
|
143
|
+
currentGroup = {
|
|
144
|
+
resource,
|
|
145
|
+
operations: [entry],
|
|
146
|
+
startTime: entry.timestamp instanceof Date ? entry.timestamp : new Date(entry.timestamp),
|
|
147
|
+
endTime: entry.timestamp instanceof Date ? entry.timestamp : new Date(entry.timestamp),
|
|
148
|
+
hasError: entry.toolResult ? !entry.toolResult.success : false,
|
|
149
|
+
groupType,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Flush final group
|
|
154
|
+
if (currentGroup) {
|
|
155
|
+
result.push(currentGroup);
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Get operation counts by type from a group
|
|
161
|
+
*/
|
|
162
|
+
export function getOperationCounts(group) {
|
|
163
|
+
const counts = {
|
|
164
|
+
reads: 0,
|
|
165
|
+
updates: 0,
|
|
166
|
+
creates: 0,
|
|
167
|
+
searches: 0,
|
|
168
|
+
bash: 0,
|
|
169
|
+
todos: 0,
|
|
170
|
+
other: 0,
|
|
171
|
+
};
|
|
172
|
+
for (const op of group.operations) {
|
|
173
|
+
const toolName = op.toolCall?.function?.name;
|
|
174
|
+
if (!toolName)
|
|
175
|
+
continue;
|
|
176
|
+
switch (toolName) {
|
|
177
|
+
case 'view_file':
|
|
178
|
+
counts.reads++;
|
|
179
|
+
break;
|
|
180
|
+
case 'str_replace_editor':
|
|
181
|
+
counts.updates++;
|
|
182
|
+
break;
|
|
183
|
+
case 'create_file':
|
|
184
|
+
counts.creates++;
|
|
185
|
+
break;
|
|
186
|
+
case 'search':
|
|
187
|
+
counts.searches++;
|
|
188
|
+
break;
|
|
189
|
+
case 'bash':
|
|
190
|
+
counts.bash++;
|
|
191
|
+
break;
|
|
192
|
+
case 'create_todo_list':
|
|
193
|
+
case 'update_todo_list':
|
|
194
|
+
counts.todos++;
|
|
195
|
+
break;
|
|
196
|
+
default:
|
|
197
|
+
counts.other++;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return counts;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Get execution duration for a group in milliseconds
|
|
204
|
+
*/
|
|
205
|
+
export function getGroupDuration(group) {
|
|
206
|
+
return group.endTime.getTime() - group.startTime.getTime();
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Format duration in human-readable form
|
|
210
|
+
*/
|
|
211
|
+
export function formatDuration(ms) {
|
|
212
|
+
if (ms < 1000) {
|
|
213
|
+
return `${ms}ms`;
|
|
214
|
+
}
|
|
215
|
+
else if (ms < 60000) {
|
|
216
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
const minutes = Math.floor(ms / 60000);
|
|
220
|
+
const seconds = Math.floor((ms % 60000) / 1000);
|
|
221
|
+
return `${minutes}m ${seconds}s`;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=tool-grouper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-grouper.js","sourceRoot":"","sources":["../../../src/ui/utils/tool-grouper.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Basic Audit Logger (Free Tier)
|
|
3
3
|
*
|
|
4
|
-
* Provides
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Provides simple JSON logging for security events.
|
|
5
|
+
*
|
|
6
|
+
* Free Tier Features:
|
|
7
|
+
* ✅ Simple JSON logging to file
|
|
8
|
+
* ✅ 30-day retention (auto-cleanup)
|
|
9
|
+
* ✅ Basic event tracking
|
|
10
|
+
* ✅ Lightweight (no encryption, no hash chains)
|
|
11
|
+
*
|
|
12
|
+
* Enterprise features (requires @ax-cli/enterprise):
|
|
13
|
+
* - Compliance reports (SOC2, HIPAA, PCI-DSS)
|
|
14
|
+
* - Tamper-proof encrypted logs with hash chains
|
|
15
|
+
* - Real-time dashboards
|
|
16
|
+
* - Custom retention (1yr, 7yr, forever)
|
|
17
|
+
* - Incident response workflows
|
|
18
|
+
* - Anomaly detection
|
|
10
19
|
*
|
|
11
20
|
* Security: CVSS 6.1 (Medium Priority)
|
|
12
21
|
*/
|
|
@@ -37,7 +46,7 @@ export declare enum AuditCategory {
|
|
|
37
46
|
SYSTEM_EVENT = "SYSTEM_EVENT"
|
|
38
47
|
}
|
|
39
48
|
/**
|
|
40
|
-
* Audit event structure
|
|
49
|
+
* Audit event structure (basic version)
|
|
41
50
|
*/
|
|
42
51
|
export interface AuditEvent {
|
|
43
52
|
/**
|
|
@@ -84,17 +93,9 @@ export interface AuditEvent {
|
|
|
84
93
|
* Source IP address (if applicable)
|
|
85
94
|
*/
|
|
86
95
|
sourceIp?: string;
|
|
87
|
-
/**
|
|
88
|
-
* Previous log entry hash (for tamper detection)
|
|
89
|
-
*/
|
|
90
|
-
previousHash?: string;
|
|
91
|
-
/**
|
|
92
|
-
* Current entry hash (SHA-256 of all fields)
|
|
93
|
-
*/
|
|
94
|
-
hash?: string;
|
|
95
96
|
}
|
|
96
97
|
/**
|
|
97
|
-
* Audit log configuration
|
|
98
|
+
* Audit log configuration (basic version)
|
|
98
99
|
*/
|
|
99
100
|
export interface AuditLogConfig {
|
|
100
101
|
/**
|
|
@@ -102,26 +103,18 @@ export interface AuditLogConfig {
|
|
|
102
103
|
*/
|
|
103
104
|
logDirectory?: string;
|
|
104
105
|
/**
|
|
105
|
-
* Log retention period in days (
|
|
106
|
+
* Log retention period in days (max 30 days in free tier)
|
|
106
107
|
*/
|
|
107
108
|
retentionDays?: number;
|
|
108
|
-
/**
|
|
109
|
-
* Whether to emit critical event alerts
|
|
110
|
-
*/
|
|
111
|
-
enableAlerts?: boolean;
|
|
112
|
-
/**
|
|
113
|
-
* Whether to enable tamper-proof chaining
|
|
114
|
-
*/
|
|
115
|
-
enableChaining?: boolean;
|
|
116
109
|
/**
|
|
117
110
|
* Maximum log file size in bytes (default: 10MB)
|
|
118
111
|
*/
|
|
119
112
|
maxFileSize?: number;
|
|
120
113
|
}
|
|
121
114
|
/**
|
|
122
|
-
* Audit Logger
|
|
115
|
+
* Basic Audit Logger (Free Tier)
|
|
123
116
|
*
|
|
124
|
-
*
|
|
117
|
+
* Simple JSON logging for security events.
|
|
125
118
|
*
|
|
126
119
|
* @example
|
|
127
120
|
* ```typescript
|
|
@@ -136,23 +129,13 @@ export interface AuditLogConfig {
|
|
|
136
129
|
* outcome: 'failure',
|
|
137
130
|
* details: { limit: 20, attempts: 25 },
|
|
138
131
|
* });
|
|
139
|
-
*
|
|
140
|
-
* // Log critical event (triggers alert)
|
|
141
|
-
* logger.logCritical({
|
|
142
|
-
* category: AuditCategory.COMMAND_EXECUTION,
|
|
143
|
-
* action: 'shell_injection_attempt',
|
|
144
|
-
* outcome: 'failure',
|
|
145
|
-
* details: { command: 'ls; rm -rf /' },
|
|
146
|
-
* });
|
|
147
132
|
* ```
|
|
148
133
|
*/
|
|
149
134
|
export declare class AuditLogger {
|
|
150
135
|
private static instance;
|
|
151
136
|
private config;
|
|
152
137
|
private currentLogFile;
|
|
153
|
-
private lastHash;
|
|
154
138
|
private eventCount;
|
|
155
|
-
private alertCallbacks;
|
|
156
139
|
private constructor();
|
|
157
140
|
/**
|
|
158
141
|
* Get singleton instance
|
|
@@ -163,38 +146,25 @@ export declare class AuditLogger {
|
|
|
163
146
|
*/
|
|
164
147
|
static resetInstance(): void;
|
|
165
148
|
/**
|
|
166
|
-
*
|
|
167
|
-
*/
|
|
168
|
-
onCriticalEvent(callback: (event: AuditEvent) => void): void;
|
|
169
|
-
/**
|
|
170
|
-
* Log an audit event
|
|
149
|
+
* Log an audit event (basic version - no hash chains)
|
|
171
150
|
*/
|
|
172
|
-
log(event: Omit<AuditEvent, 'id' | 'timestamp'
|
|
151
|
+
log(event: Omit<AuditEvent, 'id' | 'timestamp'>): void;
|
|
173
152
|
/**
|
|
174
153
|
* Log critical security event (convenience method)
|
|
175
154
|
*/
|
|
176
|
-
logCritical(event: Omit<AuditEvent, 'id' | 'timestamp' | 'severity'
|
|
155
|
+
logCritical(event: Omit<AuditEvent, 'id' | 'timestamp' | 'severity'>): void;
|
|
177
156
|
/**
|
|
178
157
|
* Log warning event (convenience method)
|
|
179
158
|
*/
|
|
180
|
-
logWarning(event: Omit<AuditEvent, 'id' | 'timestamp' | 'severity'
|
|
159
|
+
logWarning(event: Omit<AuditEvent, 'id' | 'timestamp' | 'severity'>): void;
|
|
181
160
|
/**
|
|
182
161
|
* Log error event (convenience method)
|
|
183
162
|
*/
|
|
184
|
-
logError(event: Omit<AuditEvent, 'id' | 'timestamp' | 'severity'
|
|
163
|
+
logError(event: Omit<AuditEvent, 'id' | 'timestamp' | 'severity'>): void;
|
|
185
164
|
/**
|
|
186
165
|
* Log info event (convenience method)
|
|
187
166
|
*/
|
|
188
|
-
logInfo(event: Omit<AuditEvent, 'id' | 'timestamp' | 'severity'
|
|
189
|
-
/**
|
|
190
|
-
* Verify log integrity
|
|
191
|
-
*
|
|
192
|
-
* Checks the hash chain to detect tampering
|
|
193
|
-
*/
|
|
194
|
-
verifyIntegrity(logFile?: string): {
|
|
195
|
-
valid: boolean;
|
|
196
|
-
errors: string[];
|
|
197
|
-
};
|
|
167
|
+
logInfo(event: Omit<AuditEvent, 'id' | 'timestamp' | 'severity'>): void;
|
|
198
168
|
/**
|
|
199
169
|
* Get audit statistics
|
|
200
170
|
*/
|
|
@@ -208,10 +178,6 @@ export declare class AuditLogger {
|
|
|
208
178
|
* Generate unique event ID
|
|
209
179
|
*/
|
|
210
180
|
private generateEventId;
|
|
211
|
-
/**
|
|
212
|
-
* Calculate SHA-256 hash of event (excluding hash field itself)
|
|
213
|
-
*/
|
|
214
|
-
private calculateHash;
|
|
215
181
|
/**
|
|
216
182
|
* Write event to log file
|
|
217
183
|
*/
|
|
@@ -220,10 +186,6 @@ export declare class AuditLogger {
|
|
|
220
186
|
* Get current log file path
|
|
221
187
|
*/
|
|
222
188
|
private getCurrentLogFile;
|
|
223
|
-
/**
|
|
224
|
-
* Load last hash from current log file
|
|
225
|
-
*/
|
|
226
|
-
private loadLastHash;
|
|
227
189
|
/**
|
|
228
190
|
* Check if log file needs rotation
|
|
229
191
|
*/
|
|
@@ -233,13 +195,9 @@ export declare class AuditLogger {
|
|
|
233
195
|
*/
|
|
234
196
|
private rotateLogFile;
|
|
235
197
|
/**
|
|
236
|
-
* Clean up old log files (retention
|
|
198
|
+
* Clean up old log files (30-day retention in free tier)
|
|
237
199
|
*/
|
|
238
200
|
private cleanupOldLogs;
|
|
239
|
-
/**
|
|
240
|
-
* Emit alert for critical events
|
|
241
|
-
*/
|
|
242
|
-
private emitAlert;
|
|
243
201
|
}
|
|
244
202
|
/**
|
|
245
203
|
* Get audit logger singleton
|
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Basic Audit Logger (Free Tier)
|
|
3
3
|
*
|
|
4
|
-
* Provides
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Provides simple JSON logging for security events.
|
|
5
|
+
*
|
|
6
|
+
* Free Tier Features:
|
|
7
|
+
* ✅ Simple JSON logging to file
|
|
8
|
+
* ✅ 30-day retention (auto-cleanup)
|
|
9
|
+
* ✅ Basic event tracking
|
|
10
|
+
* ✅ Lightweight (no encryption, no hash chains)
|
|
11
|
+
*
|
|
12
|
+
* Enterprise features (requires @ax-cli/enterprise):
|
|
13
|
+
* - Compliance reports (SOC2, HIPAA, PCI-DSS)
|
|
14
|
+
* - Tamper-proof encrypted logs with hash chains
|
|
15
|
+
* - Real-time dashboards
|
|
16
|
+
* - Custom retention (1yr, 7yr, forever)
|
|
17
|
+
* - Incident response workflows
|
|
18
|
+
* - Anomaly detection
|
|
10
19
|
*
|
|
11
20
|
* Security: CVSS 6.1 (Medium Priority)
|
|
12
21
|
*/
|
|
13
|
-
import {
|
|
14
|
-
import { writeFileSync, readFileSync, existsSync, mkdirSync, readdirSync, unlinkSync, statSync } from 'fs';
|
|
22
|
+
import { writeFileSync, existsSync, mkdirSync, readdirSync, unlinkSync, statSync } from 'fs';
|
|
15
23
|
import { join } from 'path';
|
|
16
24
|
import { homedir } from 'os';
|
|
17
25
|
/**
|
|
@@ -43,19 +51,17 @@ export var AuditCategory;
|
|
|
43
51
|
AuditCategory["SYSTEM_EVENT"] = "SYSTEM_EVENT";
|
|
44
52
|
})(AuditCategory || (AuditCategory = {}));
|
|
45
53
|
/**
|
|
46
|
-
* Default configuration
|
|
54
|
+
* Default configuration (free tier)
|
|
47
55
|
*/
|
|
48
56
|
const DEFAULT_CONFIG = {
|
|
49
57
|
logDirectory: join(homedir(), '.ax-cli', 'audit-logs'),
|
|
50
|
-
retentionDays:
|
|
51
|
-
enableAlerts: true,
|
|
52
|
-
enableChaining: true,
|
|
58
|
+
retentionDays: 30, // Free tier: max 30 days
|
|
53
59
|
maxFileSize: 10 * 1024 * 1024, // 10MB
|
|
54
60
|
};
|
|
55
61
|
/**
|
|
56
|
-
* Audit Logger
|
|
62
|
+
* Basic Audit Logger (Free Tier)
|
|
57
63
|
*
|
|
58
|
-
*
|
|
64
|
+
* Simple JSON logging for security events.
|
|
59
65
|
*
|
|
60
66
|
* @example
|
|
61
67
|
* ```typescript
|
|
@@ -70,35 +76,27 @@ const DEFAULT_CONFIG = {
|
|
|
70
76
|
* outcome: 'failure',
|
|
71
77
|
* details: { limit: 20, attempts: 25 },
|
|
72
78
|
* });
|
|
73
|
-
*
|
|
74
|
-
* // Log critical event (triggers alert)
|
|
75
|
-
* logger.logCritical({
|
|
76
|
-
* category: AuditCategory.COMMAND_EXECUTION,
|
|
77
|
-
* action: 'shell_injection_attempt',
|
|
78
|
-
* outcome: 'failure',
|
|
79
|
-
* details: { command: 'ls; rm -rf /' },
|
|
80
|
-
* });
|
|
81
79
|
* ```
|
|
82
80
|
*/
|
|
83
81
|
export class AuditLogger {
|
|
84
82
|
static instance = null;
|
|
85
83
|
config;
|
|
86
84
|
currentLogFile;
|
|
87
|
-
lastHash = null;
|
|
88
85
|
eventCount = 0;
|
|
89
|
-
alertCallbacks = [];
|
|
90
86
|
constructor(config = {}) {
|
|
91
|
-
|
|
87
|
+
// Enforce free tier limits
|
|
88
|
+
const retentionDays = Math.min(config.retentionDays || 30, 30);
|
|
89
|
+
this.config = {
|
|
90
|
+
...DEFAULT_CONFIG,
|
|
91
|
+
...config,
|
|
92
|
+
retentionDays
|
|
93
|
+
};
|
|
92
94
|
// Ensure log directory exists
|
|
93
95
|
if (!existsSync(this.config.logDirectory)) {
|
|
94
96
|
mkdirSync(this.config.logDirectory, { recursive: true });
|
|
95
97
|
}
|
|
96
98
|
// Initialize log file
|
|
97
99
|
this.currentLogFile = this.getCurrentLogFile();
|
|
98
|
-
// Load last hash for chaining
|
|
99
|
-
if (this.config.enableChaining) {
|
|
100
|
-
this.lastHash = this.loadLastHash();
|
|
101
|
-
}
|
|
102
100
|
// Start retention cleanup (run once on init)
|
|
103
101
|
this.cleanupOldLogs();
|
|
104
102
|
}
|
|
@@ -118,13 +116,7 @@ export class AuditLogger {
|
|
|
118
116
|
AuditLogger.instance = null;
|
|
119
117
|
}
|
|
120
118
|
/**
|
|
121
|
-
*
|
|
122
|
-
*/
|
|
123
|
-
onCriticalEvent(callback) {
|
|
124
|
-
this.alertCallbacks.push(callback);
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Log an audit event
|
|
119
|
+
* Log an audit event (basic version - no hash chains)
|
|
128
120
|
*/
|
|
129
121
|
log(event) {
|
|
130
122
|
const fullEvent = {
|
|
@@ -132,18 +124,8 @@ export class AuditLogger {
|
|
|
132
124
|
timestamp: new Date().toISOString(),
|
|
133
125
|
...event,
|
|
134
126
|
};
|
|
135
|
-
// Add hash chain for tamper detection
|
|
136
|
-
if (this.config.enableChaining) {
|
|
137
|
-
fullEvent.previousHash = this.lastHash || undefined;
|
|
138
|
-
fullEvent.hash = this.calculateHash(fullEvent);
|
|
139
|
-
this.lastHash = fullEvent.hash;
|
|
140
|
-
}
|
|
141
127
|
// Write to log file
|
|
142
128
|
this.writeEvent(fullEvent);
|
|
143
|
-
// Emit alert for critical events
|
|
144
|
-
if (this.config.enableAlerts && fullEvent.severity === AuditSeverity.CRITICAL) {
|
|
145
|
-
this.emitAlert(fullEvent);
|
|
146
|
-
}
|
|
147
129
|
// Check if we need to rotate log file
|
|
148
130
|
this.checkLogRotation();
|
|
149
131
|
this.eventCount++;
|
|
@@ -184,45 +166,6 @@ export class AuditLogger {
|
|
|
184
166
|
severity: AuditSeverity.INFO,
|
|
185
167
|
});
|
|
186
168
|
}
|
|
187
|
-
/**
|
|
188
|
-
* Verify log integrity
|
|
189
|
-
*
|
|
190
|
-
* Checks the hash chain to detect tampering
|
|
191
|
-
*/
|
|
192
|
-
verifyIntegrity(logFile) {
|
|
193
|
-
const file = logFile || this.currentLogFile;
|
|
194
|
-
if (!existsSync(file)) {
|
|
195
|
-
return { valid: false, errors: ['Log file does not exist'] };
|
|
196
|
-
}
|
|
197
|
-
const errors = [];
|
|
198
|
-
const lines = readFileSync(file, 'utf8').split('\n').filter(l => l.trim());
|
|
199
|
-
let previousHash = null;
|
|
200
|
-
for (let i = 0; i < lines.length; i++) {
|
|
201
|
-
try {
|
|
202
|
-
const event = JSON.parse(lines[i]);
|
|
203
|
-
// Verify hash chain (normalize undefined to null for comparison)
|
|
204
|
-
const eventPrevHash = event.previousHash || null;
|
|
205
|
-
if (eventPrevHash !== previousHash) {
|
|
206
|
-
errors.push(`Event ${event.id} (line ${i + 1}): Hash chain broken. ` +
|
|
207
|
-
`Expected previous hash: ${previousHash}, got: ${eventPrevHash}`);
|
|
208
|
-
}
|
|
209
|
-
// Verify event hash
|
|
210
|
-
const calculatedHash = this.calculateHash(event);
|
|
211
|
-
if (event.hash !== calculatedHash) {
|
|
212
|
-
errors.push(`Event ${event.id} (line ${i + 1}): Hash mismatch. ` +
|
|
213
|
-
`Event may have been tampered with.`);
|
|
214
|
-
}
|
|
215
|
-
previousHash = event.hash || null;
|
|
216
|
-
}
|
|
217
|
-
catch (error) {
|
|
218
|
-
errors.push(`Line ${i + 1}: Invalid JSON - ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
return {
|
|
222
|
-
valid: errors.length === 0,
|
|
223
|
-
errors,
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
169
|
/**
|
|
227
170
|
* Get audit statistics
|
|
228
171
|
*/
|
|
@@ -242,22 +185,6 @@ export class AuditLogger {
|
|
|
242
185
|
const random = Math.random().toString(36).substring(2, 11);
|
|
243
186
|
return `evt_${timestamp}_${random}`;
|
|
244
187
|
}
|
|
245
|
-
/**
|
|
246
|
-
* Calculate SHA-256 hash of event (excluding hash field itself)
|
|
247
|
-
*/
|
|
248
|
-
calculateHash(event) {
|
|
249
|
-
// Create copy without hash field
|
|
250
|
-
const { hash, ...eventWithoutHash } = event;
|
|
251
|
-
// Sort keys for consistent hashing, filter out undefined values
|
|
252
|
-
const sortedKeys = Object.keys(eventWithoutHash)
|
|
253
|
-
.filter(key => eventWithoutHash[key] !== undefined)
|
|
254
|
-
.sort();
|
|
255
|
-
const data = sortedKeys.map(key => {
|
|
256
|
-
const value = eventWithoutHash[key];
|
|
257
|
-
return `${key}:${JSON.stringify(value)}`;
|
|
258
|
-
}).join('|');
|
|
259
|
-
return createHash('sha256').update(data).digest('hex');
|
|
260
|
-
}
|
|
261
188
|
/**
|
|
262
189
|
* Write event to log file
|
|
263
190
|
*/
|
|
@@ -280,24 +207,6 @@ export class AuditLogger {
|
|
|
280
207
|
const date = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
|
|
281
208
|
return join(this.config.logDirectory, `audit-${date}.jsonl`);
|
|
282
209
|
}
|
|
283
|
-
/**
|
|
284
|
-
* Load last hash from current log file
|
|
285
|
-
*/
|
|
286
|
-
loadLastHash() {
|
|
287
|
-
if (!existsSync(this.currentLogFile)) {
|
|
288
|
-
return null;
|
|
289
|
-
}
|
|
290
|
-
try {
|
|
291
|
-
const lines = readFileSync(this.currentLogFile, 'utf8').split('\n').filter(l => l.trim());
|
|
292
|
-
if (lines.length === 0)
|
|
293
|
-
return null;
|
|
294
|
-
const lastEvent = JSON.parse(lines[lines.length - 1]);
|
|
295
|
-
return lastEvent.hash || null;
|
|
296
|
-
}
|
|
297
|
-
catch {
|
|
298
|
-
return null;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
210
|
/**
|
|
302
211
|
* Check if log file needs rotation
|
|
303
212
|
*/
|
|
@@ -322,10 +231,9 @@ export class AuditLogger {
|
|
|
322
231
|
*/
|
|
323
232
|
rotateLogFile() {
|
|
324
233
|
this.currentLogFile = this.getCurrentLogFile();
|
|
325
|
-
this.lastHash = this.loadLastHash();
|
|
326
234
|
}
|
|
327
235
|
/**
|
|
328
|
-
* Clean up old log files (retention
|
|
236
|
+
* Clean up old log files (30-day retention in free tier)
|
|
329
237
|
*/
|
|
330
238
|
cleanupOldLogs() {
|
|
331
239
|
try {
|
|
@@ -351,19 +259,6 @@ export class AuditLogger {
|
|
|
351
259
|
console.error('Failed to clean up old audit logs:', error);
|
|
352
260
|
}
|
|
353
261
|
}
|
|
354
|
-
/**
|
|
355
|
-
* Emit alert for critical events
|
|
356
|
-
*/
|
|
357
|
-
emitAlert(event) {
|
|
358
|
-
for (const callback of this.alertCallbacks) {
|
|
359
|
-
try {
|
|
360
|
-
callback(event);
|
|
361
|
-
}
|
|
362
|
-
catch (error) {
|
|
363
|
-
console.error('Alert callback failed:', error);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
262
|
}
|
|
368
263
|
/**
|
|
369
264
|
* Get audit logger singleton
|