maiass 5.7.31
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/LICENSE +26 -0
- package/README.md +347 -0
- package/build.js +127 -0
- package/lib/account-info.js +476 -0
- package/lib/colors.js +49 -0
- package/lib/commit.js +885 -0
- package/lib/config-command.js +310 -0
- package/lib/config-manager.js +344 -0
- package/lib/config.js +150 -0
- package/lib/devlog.js +182 -0
- package/lib/env-display.js +162 -0
- package/lib/git-info.js +509 -0
- package/lib/header.js +152 -0
- package/lib/input-utils.js +116 -0
- package/lib/logger.js +285 -0
- package/lib/machine-fingerprint.js +229 -0
- package/lib/maiass-command.js +79 -0
- package/lib/maiass-pipeline.js +1204 -0
- package/lib/maiass-variables.js +152 -0
- package/lib/secure-storage.js +256 -0
- package/lib/symbols.js +200 -0
- package/lib/token-validator.js +184 -0
- package/lib/version-command.js +256 -0
- package/lib/version-manager.js +902 -0
- package/maiass-standalone.cjs +148 -0
- package/maiass.cjs +34 -0
- package/maiass.mjs +167 -0
- package/package.json +45 -0
- package/setup-env.js +83 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { createInterface } from 'readline';
|
|
2
|
+
import colors from './colors.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Format a user input prompt with distinct visual styling
|
|
6
|
+
* @param {string} message - The prompt message
|
|
7
|
+
* @returns {string} Formatted prompt
|
|
8
|
+
*/
|
|
9
|
+
function formatPrompt(message) {
|
|
10
|
+
// Format: |)) ? message (bold yellow for calls to action)
|
|
11
|
+
return `\n${colors.BSoftPink('|))')} ${colors.BBlueOnWhite(' ? ')} ${colors.BYellow(message)}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get single character input from user
|
|
16
|
+
* @param {string} prompt - Prompt message to display
|
|
17
|
+
* @returns {Promise<string>} Single character input
|
|
18
|
+
*/
|
|
19
|
+
export function getSingleCharInput(prompt) {
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
// Use formatted prompt for better visibility
|
|
22
|
+
process.stdout.write(formatPrompt(prompt));
|
|
23
|
+
|
|
24
|
+
// Store original state
|
|
25
|
+
const wasRawMode = process.stdin.isRaw;
|
|
26
|
+
|
|
27
|
+
// Set raw mode to capture single characters
|
|
28
|
+
process.stdin.setRawMode(true);
|
|
29
|
+
process.stdin.resume();
|
|
30
|
+
|
|
31
|
+
const onData = (key) => {
|
|
32
|
+
// Handle Ctrl+C
|
|
33
|
+
if (key[0] === 3) {
|
|
34
|
+
console.log('\n');
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Get only the first character and convert to lowercase
|
|
39
|
+
const char = key.toString().charAt(0).toLowerCase();
|
|
40
|
+
|
|
41
|
+
// Echo the character and newline
|
|
42
|
+
process.stdout.write(char + '\n');
|
|
43
|
+
|
|
44
|
+
// Clean up
|
|
45
|
+
process.stdin.removeListener('data', onData);
|
|
46
|
+
process.stdin.setRawMode(wasRawMode);
|
|
47
|
+
process.stdin.pause();
|
|
48
|
+
|
|
49
|
+
resolve(char);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
process.stdin.on('data', onData);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get multi-line input from user
|
|
58
|
+
* @param {string} prompt - Prompt message to display
|
|
59
|
+
* @returns {Promise<string>} Multi-line input
|
|
60
|
+
*/
|
|
61
|
+
export function getMultiLineInput(prompt) {
|
|
62
|
+
return new Promise((resolve) => {
|
|
63
|
+
const rl = createInterface({
|
|
64
|
+
input: process.stdin,
|
|
65
|
+
output: process.stdout
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
console.log(formatPrompt(prompt));
|
|
69
|
+
|
|
70
|
+
const lines = [];
|
|
71
|
+
let emptyLineCount = 0;
|
|
72
|
+
|
|
73
|
+
rl.on('line', (line) => {
|
|
74
|
+
if (line.trim() === '') {
|
|
75
|
+
emptyLineCount++;
|
|
76
|
+
if (emptyLineCount >= 3) {
|
|
77
|
+
rl.close();
|
|
78
|
+
resolve(lines.join('\n').trim());
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
emptyLineCount = 0;
|
|
83
|
+
lines.push(line);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
rl.on('SIGINT', () => {
|
|
88
|
+
console.log('\n');
|
|
89
|
+
process.exit(0);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get simple line input from user
|
|
96
|
+
* @param {string} prompt - Prompt message to display
|
|
97
|
+
* @returns {Promise<string>} Single line input
|
|
98
|
+
*/
|
|
99
|
+
export function getLineInput(prompt) {
|
|
100
|
+
return new Promise((resolve) => {
|
|
101
|
+
const rl = createInterface({
|
|
102
|
+
input: process.stdin,
|
|
103
|
+
output: process.stdout
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
rl.question(formatPrompt(prompt), (answer) => {
|
|
107
|
+
rl.close();
|
|
108
|
+
resolve(answer.trim());
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
rl.on('SIGINT', () => {
|
|
112
|
+
console.log('\n');
|
|
113
|
+
process.exit(0);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
package/lib/logger.js
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// Centralized logger for MAIASS CLI with consistent branding
|
|
2
|
+
import colors from './colors.js';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import os from 'os';
|
|
7
|
+
|
|
8
|
+
// Store environment variables
|
|
9
|
+
let env = {};
|
|
10
|
+
|
|
11
|
+
// Debug collection system
|
|
12
|
+
let debugBuffer = [];
|
|
13
|
+
let sessionId = null;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Initialize debug collection session
|
|
17
|
+
*/
|
|
18
|
+
function initDebugSession() {
|
|
19
|
+
sessionId = `maiass-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
20
|
+
debugBuffer = [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Add entry to debug buffer
|
|
25
|
+
* @param {string} level - Log level
|
|
26
|
+
* @param {string} message - Log message
|
|
27
|
+
* @param {Object} metadata - Additional metadata
|
|
28
|
+
*/
|
|
29
|
+
function addToDebugBuffer(level, message, metadata = {}) {
|
|
30
|
+
if (!sessionId) initDebugSession();
|
|
31
|
+
|
|
32
|
+
debugBuffer.push({
|
|
33
|
+
timestamp: new Date().toISOString(),
|
|
34
|
+
level,
|
|
35
|
+
message,
|
|
36
|
+
...metadata
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Write debug buffer to temporary file
|
|
42
|
+
* @returns {string} Path to debug file
|
|
43
|
+
*/
|
|
44
|
+
export function writeDebugFile() {
|
|
45
|
+
if (!sessionId || debugBuffer.length === 0) return null;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const tempDir = os.tmpdir();
|
|
49
|
+
const debugFile = path.join(tempDir, `${sessionId}.debug.log`);
|
|
50
|
+
|
|
51
|
+
const debugContent = debugBuffer
|
|
52
|
+
.map(entry => `[${entry.timestamp}] ${entry.level.toUpperCase()}: ${entry.message}`)
|
|
53
|
+
.join('\n');
|
|
54
|
+
|
|
55
|
+
fs.writeFileSync(debugFile, debugContent, 'utf8');
|
|
56
|
+
return debugFile;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('Failed to write debug file:', error.message);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get current debug session info
|
|
65
|
+
* @returns {Object} Session info
|
|
66
|
+
*/
|
|
67
|
+
export function getDebugSession() {
|
|
68
|
+
return {
|
|
69
|
+
sessionId,
|
|
70
|
+
entryCount: debugBuffer.length,
|
|
71
|
+
hasErrors: debugBuffer.some(entry => entry.level === 'error')
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Initialize logger with environment variables
|
|
77
|
+
* @param {Object} environment - Environment variables
|
|
78
|
+
*/
|
|
79
|
+
export function initLogger(environment) {
|
|
80
|
+
env = { ...environment };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Check if message should be shown based on verbosity level
|
|
85
|
+
* @param {string} level - Message level: 'error', 'warning', 'info', 'debug', 'critical', 'prompt'
|
|
86
|
+
* @returns {boolean} - Whether to show the message
|
|
87
|
+
*/
|
|
88
|
+
function shouldLog(level) {
|
|
89
|
+
const verbosity = env.MAIASS_VERBOSITY || process.env.MAIASS_VERBOSITY || 'brief';
|
|
90
|
+
|
|
91
|
+
// Always show errors, prompts, and critical messages
|
|
92
|
+
if (['error', 'critical', 'prompt'].includes(level)) return true;
|
|
93
|
+
|
|
94
|
+
// For brief: only errors, critical messages, prompts, final results, and important warnings
|
|
95
|
+
if (verbosity === 'brief') {
|
|
96
|
+
return ['error', 'critical', 'prompt', 'success', 'warning'].includes(level);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// For normal: errors, warnings, success, and important info
|
|
100
|
+
if (verbosity === 'normal') {
|
|
101
|
+
return ['error', 'warning', 'success', 'info', 'critical', 'prompt'].includes(level);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// For verbose/debug: show everything
|
|
105
|
+
if (verbosity === 'verbose' || verbosity === 'debug') {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Bold soft pink prefix for MAIASS branding
|
|
113
|
+
const MAIASS_PREFIX = colors.BSoftPink('|))');
|
|
114
|
+
|
|
115
|
+
export const log = {
|
|
116
|
+
logThis: (prefix, icon, message) => {
|
|
117
|
+
// Format: |)) icon message (prefix always first)
|
|
118
|
+
const prefixPart = prefix ? `${prefix} ` : '';
|
|
119
|
+
const iconPart = icon ? `${icon} ` : '';
|
|
120
|
+
console.log(`${prefixPart}${iconPart}${message}`);
|
|
121
|
+
addToDebugBuffer('info', `${icon} ${message}`);
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
// Main branded message types with icons
|
|
125
|
+
info: (icon, message, bold=false) => {
|
|
126
|
+
if (!shouldLog('info')) return;
|
|
127
|
+
log.logThis(MAIASS_PREFIX, colors.BCyan(icon), bold?colors.BCyan(message):colors.Cyan(message));
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
success: (icon, message, bold=false) => {
|
|
131
|
+
if (!shouldLog('success')) return;
|
|
132
|
+
log.logThis(MAIASS_PREFIX, colors.BGreen(icon), bold?colors.BGreen(message):colors.Green(message));
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
aisuggestion: (icon, message, bold=true) => {
|
|
136
|
+
if (!shouldLog('prompt')) return;
|
|
137
|
+
log.logThis('', colors.BYellowBright(icon), bold?colors.BYellowBright(message):colors.BYellowBright(message));
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
warning: (icon, message, bold=false) => {
|
|
141
|
+
if (!shouldLog('warning')) return;
|
|
142
|
+
log.logThis(MAIASS_PREFIX, colors.BOrange(icon), bold?colors.BOrange(message):colors.Orange(message));
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
error: (icon, message, bold=false) => {
|
|
146
|
+
const msg = bold ? colors.BRed(message) : colors.Red(message);
|
|
147
|
+
const fullMessage = `${MAIASS_PREFIX} ${colors.BRed(icon)} ${msg}`;
|
|
148
|
+
console.error(fullMessage);
|
|
149
|
+
addToDebugBuffer('error', `${icon} ${message}`);
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
blue: (icon, message, bold=false) => {
|
|
153
|
+
if (!shouldLog('info')) return;
|
|
154
|
+
log.logThis(MAIASS_PREFIX, colors.BBlue(icon), bold?colors.BBlue(message):colors.Blue(message));
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
purple: (icon, message, bold=false) => {
|
|
158
|
+
if (!shouldLog('info')) return;
|
|
159
|
+
log.logThis(MAIASS_PREFIX, colors.BPurple(icon), bold?colors.BPurple(message):colors.Purple(message));
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
BWhite: (icon, message, bold=false) => {
|
|
163
|
+
if (!shouldLog('info')) return;
|
|
164
|
+
log.logThis(MAIASS_PREFIX, colors.BWhite(icon), bold?colors.BWhite(message):colors.White(message));
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
blueOnWhite: (icon, message, bold=false) => {
|
|
168
|
+
if (!shouldLog('info')) return;
|
|
169
|
+
log.logThis(MAIASS_PREFIX, colors.BBlueOnWhite(icon), bold?colors.BBlueOnWhite(message):colors.BlueOnWhite(message));
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
// Custom color variants
|
|
173
|
+
custom: (icon, message, colorFn) => {
|
|
174
|
+
if (!shouldLog('info')) return;
|
|
175
|
+
log.logThis(MAIASS_PREFIX, colorFn(icon), colorFn(message));
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
// Plain messages without icons (for indented content, spacing, etc.)
|
|
179
|
+
plain: (message) => {
|
|
180
|
+
if (!shouldLog('info')) return;
|
|
181
|
+
console.log(message);
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
// Branded plain message (no icon, but with | )) prefix)
|
|
185
|
+
branded: (message, colorFn = colors.White) => {
|
|
186
|
+
if (!shouldLog('info')) return;
|
|
187
|
+
console.log(`${MAIASS_PREFIX} ${colorFn(message)}`);
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
// Empty line
|
|
191
|
+
space: () => {
|
|
192
|
+
if (!shouldLog('info')) return;
|
|
193
|
+
console.log();
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
// Indented content (for sub-items under main messages)
|
|
197
|
+
indent: (message, colorFn = colors.Gray) => {
|
|
198
|
+
if (!shouldLog('info')) return;
|
|
199
|
+
console.log(` ${colorFn(message)}`);
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
// Critical messages that should always show regardless of verbosity
|
|
203
|
+
critical: (icon, message, bold=false) => {
|
|
204
|
+
log.logThis(MAIASS_PREFIX, colors.BCyan(icon), bold?colors.BCyan(message):colors.Cyan(message));
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
// User prompts that should always show
|
|
208
|
+
prompt: (message, colorFn = colors.White) => {
|
|
209
|
+
console.log(colorFn(message));
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
// debug message (with MAIASS branding)
|
|
213
|
+
debug: (label, data) => {
|
|
214
|
+
const debugEnabled = env.MAIASS_DEBUG === 'true' || env.MAIASS_DEBUG === true ||
|
|
215
|
+
process.env.MAIASS_DEBUG === 'true' || process.env.MAIASS_DEBUG === true;
|
|
216
|
+
if (!debugEnabled) return;
|
|
217
|
+
|
|
218
|
+
const timestamp = new Date().toISOString();
|
|
219
|
+
const debugPrefix = colors.Blue('🐛 |)) ');
|
|
220
|
+
const timestampStr = colors.Gray(`[${timestamp}]`);
|
|
221
|
+
|
|
222
|
+
// Ensure we're writing to stderr to avoid mixing with other output
|
|
223
|
+
const output = process.stderr;
|
|
224
|
+
|
|
225
|
+
// Write the debug message header
|
|
226
|
+
output.write(`${debugPrefix} ${timestampStr} ${colors.Blue(label)}\n`);
|
|
227
|
+
|
|
228
|
+
// If we have data, pretty-print it with 2-space indentation
|
|
229
|
+
if (data !== undefined) {
|
|
230
|
+
const jsonStr = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
|
|
231
|
+
// Split into lines and indent each line
|
|
232
|
+
const lines = jsonStr.split('\n');
|
|
233
|
+
for (const line of lines) {
|
|
234
|
+
output.write(` ${colors.Gray(line)}\n`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Ensure everything is flushed
|
|
239
|
+
output.write('');
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
// Indented with arrow (for config values, etc.)
|
|
243
|
+
indentArrow: (message, colorFn = colors.Gray) => {
|
|
244
|
+
if (!shouldLog('info')) return;
|
|
245
|
+
console.log(` ${colorFn('→')} ${colorFn(message)}`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// Convenience methods for common patterns
|
|
251
|
+
export const logger = {
|
|
252
|
+
...log,
|
|
253
|
+
|
|
254
|
+
// Common CLI patterns
|
|
255
|
+
header: (icon, title) => {
|
|
256
|
+
log.info(icon, title);
|
|
257
|
+
log.space();
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
section: (title, colorFn = colors.BBlue) => {
|
|
261
|
+
log.custom('📁', title, colorFn);
|
|
262
|
+
log.space();
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
configItem: (key, value, source = '', description = '') => {
|
|
266
|
+
const keyColor = colors.BWhite;
|
|
267
|
+
const valueColor = colors.White;
|
|
268
|
+
const sourceColor = colors.Gray;
|
|
269
|
+
|
|
270
|
+
log.plain(` ${keyColor(key.padEnd(25))} = ${valueColor(value)}`);
|
|
271
|
+
if (source) {
|
|
272
|
+
log.plain(` ${' '.repeat(25)} ${sourceColor(`→ ${source}`)} ${colors.Gray(`(${description})`)}`);
|
|
273
|
+
}
|
|
274
|
+
log.space();
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
usage: (examples) => {
|
|
278
|
+
log.blue('ℹ️', 'Usage examples:');
|
|
279
|
+
examples.forEach(example => {
|
|
280
|
+
log.plain(` ${example}`);
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
export default logger;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
// Machine fingerprinting for abuse prevention
|
|
2
|
+
// Generates stable device identifiers using hardware characteristics
|
|
3
|
+
// Matches bashmaiass implementation: MAC|CPU|Disk|Kernel → SHA-256 hex
|
|
4
|
+
|
|
5
|
+
import os from 'os';
|
|
6
|
+
import crypto from 'crypto';
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
import { logger } from './logger.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get MAC address for fingerprinting
|
|
12
|
+
* @returns {string} MAC address of primary network interface
|
|
13
|
+
*/
|
|
14
|
+
function getMacAddress() {
|
|
15
|
+
try {
|
|
16
|
+
const platform = os.platform();
|
|
17
|
+
|
|
18
|
+
if (platform === 'linux') {
|
|
19
|
+
// Use ip command on Linux
|
|
20
|
+
const result = execSync('ip link | awk \'/ether/ {print $2; exit}\'',
|
|
21
|
+
{ encoding: 'utf8', timeout: 3000 }).trim();
|
|
22
|
+
if (result && result !== '') {
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
} else if (platform === 'darwin') {
|
|
26
|
+
// macOS - match bashmaiass exactly
|
|
27
|
+
const result = execSync('networksetup -listallhardwareports | awk \'/Wi-Fi|Ethernet/{getline; print $2; exit}\'',
|
|
28
|
+
{ encoding: 'utf8', timeout: 3000 }).trim();
|
|
29
|
+
if (result && result !== '') {
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Fallback: try to get from network interfaces
|
|
35
|
+
const interfaces = os.networkInterfaces();
|
|
36
|
+
for (const name of Object.keys(interfaces)) {
|
|
37
|
+
const iface = interfaces[name];
|
|
38
|
+
if (iface) {
|
|
39
|
+
for (const alias of iface) {
|
|
40
|
+
if (!alias.internal && alias.mac && alias.mac !== '00:00:00:00:00:00') {
|
|
41
|
+
return alias.mac;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} catch (error) {
|
|
47
|
+
logger.debug('Could not get MAC address:', error.message);
|
|
48
|
+
}
|
|
49
|
+
return 'unknown-mac';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get CPU information for fingerprinting
|
|
54
|
+
* @returns {string} CPU brand string
|
|
55
|
+
*/
|
|
56
|
+
function getCpuInfo() {
|
|
57
|
+
try {
|
|
58
|
+
const platform = os.platform();
|
|
59
|
+
|
|
60
|
+
if (platform === 'darwin') {
|
|
61
|
+
// Use sysctl on macOS to match bashmaiass
|
|
62
|
+
const result = execSync('sysctl -n machdep.cpu.brand_string',
|
|
63
|
+
{ encoding: 'utf8', timeout: 3000 }).trim();
|
|
64
|
+
if (result && result !== '') {
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
} else if (platform === 'linux') {
|
|
68
|
+
// Use /proc/cpuinfo on Linux
|
|
69
|
+
const result = execSync('grep -m1 "model name" /proc/cpuinfo | cut -d ":" -f 2 | xargs',
|
|
70
|
+
{ encoding: 'utf8', timeout: 3000 }).trim();
|
|
71
|
+
if (result && result !== '') {
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Fallback to Node.js os.cpus()
|
|
77
|
+
const cpus = os.cpus();
|
|
78
|
+
if (cpus && cpus.length > 0) {
|
|
79
|
+
return cpus[0].model;
|
|
80
|
+
}
|
|
81
|
+
} catch (error) {
|
|
82
|
+
logger.debug('Could not get CPU info:', error.message);
|
|
83
|
+
}
|
|
84
|
+
return 'unknown-cpu';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get disk identifier for fingerprinting
|
|
89
|
+
* @returns {string} Disk/volume identifier
|
|
90
|
+
*/
|
|
91
|
+
function getDiskIdentifier() {
|
|
92
|
+
try {
|
|
93
|
+
const platform = os.platform();
|
|
94
|
+
|
|
95
|
+
if (platform === 'darwin') {
|
|
96
|
+
// Get volume UUID on macOS - match bashmaiass exactly
|
|
97
|
+
const result = execSync('diskutil info / | grep "Volume UUID" | awk \'{print $3}\'',
|
|
98
|
+
{ encoding: 'utf8', timeout: 3000 }).trim();
|
|
99
|
+
if (result && result !== '') {
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
} else if (platform === 'linux') {
|
|
103
|
+
// Get disk serial on Linux
|
|
104
|
+
const rootDisk = execSync('df / | tail -1 | awk \'{print $1}\' | sed \'s/[0-9]*$//\'',
|
|
105
|
+
{ encoding: 'utf8', timeout: 3000 }).trim();
|
|
106
|
+
if (rootDisk) {
|
|
107
|
+
const serial = execSync(`lsblk -no SERIAL "${rootDisk}" 2>/dev/null || echo "unknown-serial"`,
|
|
108
|
+
{ encoding: 'utf8', timeout: 3000 }).trim();
|
|
109
|
+
if (serial && serial !== '') {
|
|
110
|
+
return serial;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} catch (error) {
|
|
115
|
+
logger.debug('Could not get disk identifier:', error.message);
|
|
116
|
+
}
|
|
117
|
+
return 'unknown-disk';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get kernel information for fingerprinting
|
|
122
|
+
* @returns {string} Kernel info (system, release, machine)
|
|
123
|
+
*/
|
|
124
|
+
function getKernelInfo() {
|
|
125
|
+
try {
|
|
126
|
+
// Use uname -srm to match bashmaiass
|
|
127
|
+
const result = execSync('uname -srm',
|
|
128
|
+
{ encoding: 'utf8', timeout: 3000 }).trim();
|
|
129
|
+
if (result && result !== '') {
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
logger.debug('Could not get kernel info:', error.message);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Fallback using Node.js os module
|
|
137
|
+
return `${os.type()} ${os.release()} ${os.arch()}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Generate a stable machine fingerprint for abuse prevention - V1 (LEGACY)
|
|
142
|
+
* Algorithm: MAC|CPU|Disk|Kernel → SHA-256 hex
|
|
143
|
+
* WARNING: Includes kernel version which changes on OS updates
|
|
144
|
+
* @returns {string} SHA-256 hex fingerprint hash
|
|
145
|
+
* @deprecated Use generateMachineFingerprint() (V2) instead
|
|
146
|
+
*/
|
|
147
|
+
export function generateMachineFingerprintV1() {
|
|
148
|
+
try {
|
|
149
|
+
const mac = getMacAddress();
|
|
150
|
+
const cpu = getCpuInfo();
|
|
151
|
+
const disk = getDiskIdentifier();
|
|
152
|
+
const kernel = getKernelInfo();
|
|
153
|
+
|
|
154
|
+
// V1: Includes kernel (causes issues on OS updates)
|
|
155
|
+
const fingerprintInput = `${mac}|${cpu}|${disk}|${kernel}`;
|
|
156
|
+
|
|
157
|
+
const hash = crypto.createHash('sha256').update(fingerprintInput).digest('hex');
|
|
158
|
+
|
|
159
|
+
logger.debug('Machine fingerprint V1 components:', {
|
|
160
|
+
mac: mac,
|
|
161
|
+
cpu: cpu,
|
|
162
|
+
disk: disk,
|
|
163
|
+
kernel: kernel,
|
|
164
|
+
input: fingerprintInput
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return hash;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
logger.error('Failed to generate machine fingerprint V1:', error.message);
|
|
170
|
+
const fallback = `${os.platform()}-${os.arch()}-${os.userInfo().username}-FALLBACK`;
|
|
171
|
+
return crypto.createHash('sha256').update(fallback).digest('hex');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Generate a stable machine fingerprint for abuse prevention - V2 (CURRENT)
|
|
177
|
+
* Algorithm: MAC|CPU|Disk → SHA-256 hex (hardware-only, no kernel)
|
|
178
|
+
* This version excludes kernel to prevent fingerprint changes on OS updates
|
|
179
|
+
* Matches bashmaiass V2 implementation
|
|
180
|
+
* @returns {string} SHA-256 hex fingerprint hash
|
|
181
|
+
*/
|
|
182
|
+
export function generateMachineFingerprint() {
|
|
183
|
+
try {
|
|
184
|
+
// Get hardware components only (no kernel)
|
|
185
|
+
const mac = getMacAddress();
|
|
186
|
+
const cpu = getCpuInfo();
|
|
187
|
+
const disk = getDiskIdentifier();
|
|
188
|
+
|
|
189
|
+
// V2: Hardware-only (no kernel version)
|
|
190
|
+
// This prevents fingerprint changes on OS updates
|
|
191
|
+
const fingerprintInput = `${mac}|${cpu}|${disk}`;
|
|
192
|
+
|
|
193
|
+
// Generate SHA-256 hash in hex format to match bashmaiass
|
|
194
|
+
const hash = crypto.createHash('sha256').update(fingerprintInput).digest('hex');
|
|
195
|
+
|
|
196
|
+
logger.debug('Machine fingerprint V2 components:', {
|
|
197
|
+
mac: mac,
|
|
198
|
+
cpu: cpu,
|
|
199
|
+
disk: disk,
|
|
200
|
+
input: fingerprintInput,
|
|
201
|
+
version: 'V2 (hardware-only)'
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
return hash;
|
|
205
|
+
} catch (error) {
|
|
206
|
+
logger.error('Failed to generate machine fingerprint V2:', error.message);
|
|
207
|
+
logger.warn('SECURITY WARNING: Using minimal fallback fingerprint');
|
|
208
|
+
const fallback = `${os.platform()}-${os.arch()}-${os.userInfo().username}-FALLBACK`;
|
|
209
|
+
return crypto.createHash('sha256').update(fallback).digest('hex');
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Get a shortened machine fingerprint for display/logging
|
|
215
|
+
* @returns {string} First 12 characters of the fingerprint
|
|
216
|
+
*/
|
|
217
|
+
export function getShortFingerprint() {
|
|
218
|
+
return generateMachineFingerprint().substring(0, 12);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Validate that the current machine fingerprint matches a stored one
|
|
223
|
+
* @param {string} storedFingerprint - Previously stored fingerprint
|
|
224
|
+
* @returns {boolean} True if fingerprints match
|
|
225
|
+
*/
|
|
226
|
+
export function validateMachineFingerprint(storedFingerprint) {
|
|
227
|
+
const currentFingerprint = generateMachineFingerprint();
|
|
228
|
+
return currentFingerprint === storedFingerprint;
|
|
229
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { log, logger } from './logger.js';
|
|
2
|
+
import { SYMBOLS } from './symbols.js';
|
|
3
|
+
import { runMaiassPipeline } from './maiass-pipeline.js';
|
|
4
|
+
import colors from './colors.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Handle the main MAIASS command
|
|
8
|
+
* @param {Object} args - Command arguments from yargs
|
|
9
|
+
*/
|
|
10
|
+
export async function handleMaiassCommand(args) {
|
|
11
|
+
const {
|
|
12
|
+
_: positionalArgs,
|
|
13
|
+
'commits-only': commitsOnly,
|
|
14
|
+
'auto-stage': autoStage,
|
|
15
|
+
'version-bump': versionBump,
|
|
16
|
+
'dry-run': dryRun,
|
|
17
|
+
tag,
|
|
18
|
+
force,
|
|
19
|
+
silent
|
|
20
|
+
} = args;
|
|
21
|
+
|
|
22
|
+
// Extract version bump from positional arguments if provided
|
|
23
|
+
let bumpType = versionBump;
|
|
24
|
+
if (positionalArgs.length > 0 && !bumpType) {
|
|
25
|
+
bumpType = positionalArgs[0];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Default to 'patch' if no version bump specified and not commits-only
|
|
29
|
+
if (!bumpType && !commitsOnly) {
|
|
30
|
+
bumpType = 'patch';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
logger.header('', 'MAIASS - Modular AI-Assisted Semantic Scribe');
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// Run the complete MAIASS pipeline
|
|
37
|
+
const result = await runMaiassPipeline({
|
|
38
|
+
commitsOnly,
|
|
39
|
+
autoStage,
|
|
40
|
+
versionBump: bumpType,
|
|
41
|
+
dryRun,
|
|
42
|
+
tag,
|
|
43
|
+
force,
|
|
44
|
+
silent
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (result.success) {
|
|
48
|
+
// Success message already shown in pipeline
|
|
49
|
+
|
|
50
|
+
// Display summary if not commits-only
|
|
51
|
+
if (!commitsOnly && result.versionResult && !result.versionResult.skipped) {
|
|
52
|
+
log.space();
|
|
53
|
+
log.info(SYMBOLS.INFO, 'Workflow Summary:');
|
|
54
|
+
log.indent(`${colors.Gray('Phase:')} ${colors.White('Complete Pipeline')}`);
|
|
55
|
+
if (result.versionResult.bumpType) {
|
|
56
|
+
log.indent(`${colors.Gray('Version Bump:')} ${colors.White(result.versionResult.bumpType)}`);
|
|
57
|
+
}
|
|
58
|
+
if (result.mergeResult && result.mergeResult.merged) {
|
|
59
|
+
log.indent(`${colors.Gray('Merge:')} ${colors.White('Completed')}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
log.space();
|
|
64
|
+
log.blue(SYMBOLS.INFO, 'Thank you for using MAIASS!');
|
|
65
|
+
} else if (result.cancelled) {
|
|
66
|
+
log.warning(SYMBOLS.INFO, 'Workflow cancelled by user');
|
|
67
|
+
} else {
|
|
68
|
+
console.error(colors.Red(`${SYMBOLS.CROSS} Workflow failed: ${result.error || 'Unknown error'}`));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error(colors.Red(`${SYMBOLS.CROSS} MAIASS command failed: ${error.message}`));
|
|
74
|
+
if (process.env.MAIASS_DEBUG === 'true') {
|
|
75
|
+
console.error(colors.Gray(error.stack));
|
|
76
|
+
}
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|