vigthoria-cli 1.6.20 → 1.6.22
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/dist/commands/chat.d.ts +8 -0
- package/dist/commands/chat.js +129 -12
- package/dist/commands/deploy.js +16 -8
- package/dist/commands/edit.js +22 -5
- package/dist/commands/generate.d.ts +5 -0
- package/dist/commands/generate.js +30 -7
- package/dist/commands/repo.js +14 -7
- package/dist/index.js +9 -1
- package/dist/utils/api.d.ts +12 -0
- package/dist/utils/api.js +161 -27
- package/dist/utils/bridge-client.d.ts +110 -0
- package/dist/utils/bridge-client.js +278 -0
- package/dist/utils/logger.d.ts +9 -1
- package/dist/utils/logger.js +13 -2
- package/dist/utils/tools.js +4 -0
- package/package.json +2 -1
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Vigthoria CLI → DevTools Bridge Telemetry Client
|
|
4
|
+
*
|
|
5
|
+
* Connects the local CLI to the remote bridge server in "commando" mode,
|
|
6
|
+
* streaming real-time activity (commands, tool calls, model responses,
|
|
7
|
+
* file edits, errors) and receiving admin-issued commands.
|
|
8
|
+
*
|
|
9
|
+
* Design principles:
|
|
10
|
+
* - Fire-and-forget: never blocks the CLI main flow
|
|
11
|
+
* - Auto-reconnects with exponential back-off
|
|
12
|
+
* - Opt-in via --bridge <url> flag
|
|
13
|
+
* - Sensitive data (API keys, tokens) is never transmitted
|
|
14
|
+
*/
|
|
15
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
18
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
19
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
20
|
+
}
|
|
21
|
+
Object.defineProperty(o, k2, desc);
|
|
22
|
+
}) : (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
o[k2] = m[k];
|
|
25
|
+
}));
|
|
26
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
27
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
28
|
+
}) : function(o, v) {
|
|
29
|
+
o["default"] = v;
|
|
30
|
+
});
|
|
31
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
32
|
+
var ownKeys = function(o) {
|
|
33
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
34
|
+
var ar = [];
|
|
35
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
36
|
+
return ar;
|
|
37
|
+
};
|
|
38
|
+
return ownKeys(o);
|
|
39
|
+
};
|
|
40
|
+
return function (mod) {
|
|
41
|
+
if (mod && mod.__esModule) return mod;
|
|
42
|
+
var result = {};
|
|
43
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
44
|
+
__setModuleDefault(result, mod);
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
})();
|
|
48
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
49
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
50
|
+
};
|
|
51
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
+
exports.BridgeClient = void 0;
|
|
53
|
+
exports.getBridgeClient = getBridgeClient;
|
|
54
|
+
const ws_1 = __importDefault(require("ws"));
|
|
55
|
+
const os = __importStar(require("os"));
|
|
56
|
+
// ── Singleton accessor ───────────────────────────────────────────────
|
|
57
|
+
let _instance = null;
|
|
58
|
+
/** Get the active bridge client (may be null if --bridge was not used). */
|
|
59
|
+
function getBridgeClient() {
|
|
60
|
+
return _instance;
|
|
61
|
+
}
|
|
62
|
+
// ── BridgeClient ─────────────────────────────────────────────────────
|
|
63
|
+
class BridgeClient {
|
|
64
|
+
ws = null;
|
|
65
|
+
url;
|
|
66
|
+
apiKey;
|
|
67
|
+
machineLabel;
|
|
68
|
+
clientId;
|
|
69
|
+
connected = false;
|
|
70
|
+
reconnectTimer = null;
|
|
71
|
+
heartbeatTimer = null;
|
|
72
|
+
queue = []; // buffered events while disconnected
|
|
73
|
+
maxQueueSize = 500;
|
|
74
|
+
reconnectDelay = 2000; // ms, doubles on failure up to 30 s
|
|
75
|
+
destroyed = false;
|
|
76
|
+
onAdminCommand;
|
|
77
|
+
constructor(opts) {
|
|
78
|
+
this.url = opts.bridgeUrl.replace(/\/$/, '');
|
|
79
|
+
if (!this.url.includes('/ws')) {
|
|
80
|
+
this.url = this.url.replace(/^http/, 'ws') + '/ws';
|
|
81
|
+
}
|
|
82
|
+
this.apiKey = opts.apiKey;
|
|
83
|
+
this.machineLabel = opts.machineLabel || os.hostname();
|
|
84
|
+
this.clientId = `cli-${this.machineLabel}-${Date.now().toString(36)}`;
|
|
85
|
+
this.onAdminCommand = opts.onAdminCommand;
|
|
86
|
+
_instance = this;
|
|
87
|
+
}
|
|
88
|
+
// ── Lifecycle ────────────────────────────────────────────────────
|
|
89
|
+
async connect() {
|
|
90
|
+
if (this.destroyed)
|
|
91
|
+
return;
|
|
92
|
+
return new Promise((resolve) => {
|
|
93
|
+
try {
|
|
94
|
+
this.ws = new ws_1.default(this.url, { handshakeTimeout: 8000 });
|
|
95
|
+
this.ws.on('open', () => {
|
|
96
|
+
this.connected = true;
|
|
97
|
+
this.reconnectDelay = 2000;
|
|
98
|
+
// Authenticate as CLI commando client
|
|
99
|
+
this.sendRaw({
|
|
100
|
+
type: 'auth',
|
|
101
|
+
payload: {
|
|
102
|
+
source: 'cli-commando',
|
|
103
|
+
clientId: this.clientId,
|
|
104
|
+
machineLabel: this.machineLabel,
|
|
105
|
+
hostname: os.hostname(),
|
|
106
|
+
platform: os.platform(),
|
|
107
|
+
arch: os.arch(),
|
|
108
|
+
nodeVersion: process.version,
|
|
109
|
+
auth: this.apiKey ? { apiKey: this.apiKey } : undefined,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
// Flush buffered events
|
|
113
|
+
this.flushQueue();
|
|
114
|
+
this.startHeartbeat();
|
|
115
|
+
resolve();
|
|
116
|
+
});
|
|
117
|
+
this.ws.on('message', (raw) => {
|
|
118
|
+
try {
|
|
119
|
+
const msg = JSON.parse(raw.toString());
|
|
120
|
+
if (msg.type === 'admin:command' && this.onAdminCommand) {
|
|
121
|
+
this.onAdminCommand(msg);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// ignore unparseable messages
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
this.ws.on('close', () => {
|
|
129
|
+
this.connected = false;
|
|
130
|
+
this.stopHeartbeat();
|
|
131
|
+
this.scheduleReconnect();
|
|
132
|
+
});
|
|
133
|
+
this.ws.on('error', () => {
|
|
134
|
+
this.connected = false;
|
|
135
|
+
this.stopHeartbeat();
|
|
136
|
+
this.scheduleReconnect();
|
|
137
|
+
resolve(); // resolve even on failure – must never block CLI
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
resolve(); // swallow – bridge is optional
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
destroy() {
|
|
146
|
+
this.destroyed = true;
|
|
147
|
+
this.stopHeartbeat();
|
|
148
|
+
if (this.reconnectTimer)
|
|
149
|
+
clearTimeout(this.reconnectTimer);
|
|
150
|
+
if (this.ws) {
|
|
151
|
+
try {
|
|
152
|
+
this.ws.close();
|
|
153
|
+
}
|
|
154
|
+
catch { /* ignore */ }
|
|
155
|
+
}
|
|
156
|
+
this.ws = null;
|
|
157
|
+
this.connected = false;
|
|
158
|
+
if (_instance === this)
|
|
159
|
+
_instance = null;
|
|
160
|
+
}
|
|
161
|
+
get isConnected() {
|
|
162
|
+
return this.connected;
|
|
163
|
+
}
|
|
164
|
+
// ── Telemetry emitters (public API used by CLI code) ─────────────
|
|
165
|
+
/** CLI session started (command, flags, cwd). */
|
|
166
|
+
emitStart(data) {
|
|
167
|
+
this.emit('cli:start', data);
|
|
168
|
+
}
|
|
169
|
+
/** User entered a prompt / message. */
|
|
170
|
+
emitPrompt(data) {
|
|
171
|
+
this.emit('cli:prompt', data);
|
|
172
|
+
}
|
|
173
|
+
/** Model response received (summary only, not full content). */
|
|
174
|
+
emitModelResponse(data) {
|
|
175
|
+
this.emit('cli:model-response', data);
|
|
176
|
+
}
|
|
177
|
+
/** Tool is being called. */
|
|
178
|
+
emitToolCall(data) {
|
|
179
|
+
// Redact sensitive arg values
|
|
180
|
+
const safeArgs = {};
|
|
181
|
+
for (const [k, v] of Object.entries(data.args)) {
|
|
182
|
+
if (/key|token|password|secret|auth/i.test(k)) {
|
|
183
|
+
safeArgs[k] = '***';
|
|
184
|
+
}
|
|
185
|
+
else if (typeof v === 'string' && v.length > 2000) {
|
|
186
|
+
safeArgs[k] = v.slice(0, 200) + `...[${v.length} chars]`;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
safeArgs[k] = v;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
this.emit('cli:tool-call', { tool: data.tool, args: safeArgs });
|
|
193
|
+
}
|
|
194
|
+
/** Tool finished executing. */
|
|
195
|
+
emitToolResult(data) {
|
|
196
|
+
this.emit('cli:tool-result', data);
|
|
197
|
+
}
|
|
198
|
+
/** File was written or edited. */
|
|
199
|
+
emitFileEdit(data) {
|
|
200
|
+
this.emit('cli:file-edit', data);
|
|
201
|
+
}
|
|
202
|
+
/** Error occurred. */
|
|
203
|
+
emitError(data) {
|
|
204
|
+
this.emit('cli:error', data);
|
|
205
|
+
}
|
|
206
|
+
/** Mode changed (agent / operator / chat). */
|
|
207
|
+
emitModeChange(data) {
|
|
208
|
+
this.emit('cli:mode-change', data);
|
|
209
|
+
}
|
|
210
|
+
/** Session ended. */
|
|
211
|
+
emitEnd(data) {
|
|
212
|
+
this.emit('cli:end', data);
|
|
213
|
+
}
|
|
214
|
+
// ── Internals ────────────────────────────────────────────────────
|
|
215
|
+
emit(type, payload) {
|
|
216
|
+
const event = {
|
|
217
|
+
type,
|
|
218
|
+
payload,
|
|
219
|
+
ts: Date.now(),
|
|
220
|
+
clientId: this.clientId,
|
|
221
|
+
};
|
|
222
|
+
const json = JSON.stringify(event);
|
|
223
|
+
if (this.connected && this.ws?.readyState === ws_1.default.OPEN) {
|
|
224
|
+
try {
|
|
225
|
+
this.ws.send(json);
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
this.bufferEvent(json);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
this.bufferEvent(json);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
sendRaw(obj) {
|
|
236
|
+
if (this.ws?.readyState === ws_1.default.OPEN) {
|
|
237
|
+
this.ws.send(JSON.stringify(obj));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
bufferEvent(json) {
|
|
241
|
+
if (this.queue.length >= this.maxQueueSize) {
|
|
242
|
+
this.queue.shift(); // drop oldest
|
|
243
|
+
}
|
|
244
|
+
this.queue.push(json);
|
|
245
|
+
}
|
|
246
|
+
flushQueue() {
|
|
247
|
+
while (this.queue.length > 0 && this.ws?.readyState === ws_1.default.OPEN) {
|
|
248
|
+
const msg = this.queue.shift();
|
|
249
|
+
try {
|
|
250
|
+
this.ws.send(msg);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
scheduleReconnect() {
|
|
258
|
+
if (this.destroyed || this.reconnectTimer)
|
|
259
|
+
return;
|
|
260
|
+
this.reconnectTimer = setTimeout(() => {
|
|
261
|
+
this.reconnectTimer = null;
|
|
262
|
+
this.connect();
|
|
263
|
+
}, this.reconnectDelay);
|
|
264
|
+
this.reconnectDelay = Math.min(this.reconnectDelay * 2, 30000);
|
|
265
|
+
}
|
|
266
|
+
startHeartbeat() {
|
|
267
|
+
this.heartbeatTimer = setInterval(() => {
|
|
268
|
+
this.emit('cli:heartbeat', { uptime: process.uptime() });
|
|
269
|
+
}, 30000);
|
|
270
|
+
}
|
|
271
|
+
stopHeartbeat() {
|
|
272
|
+
if (this.heartbeatTimer) {
|
|
273
|
+
clearInterval(this.heartbeatTimer);
|
|
274
|
+
this.heartbeatTimer = null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
exports.BridgeClient = BridgeClient;
|
package/dist/utils/logger.d.ts
CHANGED
|
@@ -5,7 +5,15 @@ import { type Options as OraOptions, type Ora } from 'ora';
|
|
|
5
5
|
export type { Ora };
|
|
6
6
|
/**
|
|
7
7
|
* Create an ora spinner that writes to stderr so it never
|
|
8
|
-
* pollutes stdout JSON output
|
|
8
|
+
* pollutes stdout JSON output.
|
|
9
|
+
*
|
|
10
|
+
* IMPORTANT: On Windows PowerShell, any stderr output triggers
|
|
11
|
+
* NativeCommandError styling. The spinner animation itself is
|
|
12
|
+
* tolerable, but `spinner.fail(msg)` writes the message to stderr
|
|
13
|
+
* which produces ugly red PowerShell errors.
|
|
14
|
+
*
|
|
15
|
+
* Prefer: spinner.stop() then Logger.error(msg) — which writes to
|
|
16
|
+
* stdout — instead of spinner.fail(msg).
|
|
9
17
|
*/
|
|
10
18
|
export declare function createSpinner(textOrOpts: string | OraOptions): Ora;
|
|
11
19
|
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'success';
|
package/dist/utils/logger.js
CHANGED
|
@@ -12,7 +12,15 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
12
12
|
const ora_1 = __importDefault(require("ora"));
|
|
13
13
|
/**
|
|
14
14
|
* Create an ora spinner that writes to stderr so it never
|
|
15
|
-
* pollutes stdout JSON output
|
|
15
|
+
* pollutes stdout JSON output.
|
|
16
|
+
*
|
|
17
|
+
* IMPORTANT: On Windows PowerShell, any stderr output triggers
|
|
18
|
+
* NativeCommandError styling. The spinner animation itself is
|
|
19
|
+
* tolerable, but `spinner.fail(msg)` writes the message to stderr
|
|
20
|
+
* which produces ugly red PowerShell errors.
|
|
21
|
+
*
|
|
22
|
+
* Prefer: spinner.stop() then Logger.error(msg) — which writes to
|
|
23
|
+
* stdout — instead of spinner.fail(msg).
|
|
16
24
|
*/
|
|
17
25
|
function createSpinner(textOrOpts) {
|
|
18
26
|
const opts = typeof textOrOpts === 'string' ? { text: textOrOpts } : textOrOpts;
|
|
@@ -35,7 +43,10 @@ class Logger {
|
|
|
35
43
|
console.log(chalk_1.default.yellow('⚠'), ...args);
|
|
36
44
|
}
|
|
37
45
|
error(...args) {
|
|
38
|
-
|
|
46
|
+
// Write error messages to stdout (not stderr) to avoid triggering
|
|
47
|
+
// PowerShell NativeCommandError styling. The red ✗ prefix already
|
|
48
|
+
// signals an error visually; stderr redirection is unnecessary.
|
|
49
|
+
console.log(chalk_1.default.red('✗'), ...args);
|
|
39
50
|
}
|
|
40
51
|
success(...args) {
|
|
41
52
|
console.log(chalk_1.default.green('✓'), ...args);
|
package/dist/utils/tools.js
CHANGED
|
@@ -356,6 +356,10 @@ class AgenticTools {
|
|
|
356
356
|
* Execute a tool call with enhanced error handling and retry logic
|
|
357
357
|
*/
|
|
358
358
|
async execute(call) {
|
|
359
|
+
// Guard against malformed tool calls with undefined or empty tool names
|
|
360
|
+
if (!call.tool || typeof call.tool !== 'string' || !call.tool.trim()) {
|
|
361
|
+
return this.createErrorResult(ToolErrorType.INVALID_ARGS, `Invalid tool call: tool name is ${call.tool === undefined ? 'undefined' : 'empty'}`, `Provide a valid tool name. Available tools: ${AgenticTools.getToolDefinitions().map(t => t.name).join(', ')}`);
|
|
362
|
+
}
|
|
359
363
|
const normalizedCall = this.normalizeToolCall(call);
|
|
360
364
|
const tool = AgenticTools.getToolDefinitions().find(t => t.name === normalizedCall.tool);
|
|
361
365
|
if (!tool) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vigthoria-cli",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.22",
|
|
4
4
|
"description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"files": [
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"dev": "ts-node src/index.ts",
|
|
22
22
|
"test": "npm run test:cli",
|
|
23
23
|
"test:cli": "npm run build && node scripts/test-cli-suite.js",
|
|
24
|
+
"test:regression": "npm run build && node scripts/test-regression-1.6.22.js",
|
|
24
25
|
"test:agent:smoke": "npm run build && node scripts/test-agent-smoke.js",
|
|
25
26
|
"test:agent:routing": "npm run build && node scripts/test-agent-routing-policy.js",
|
|
26
27
|
"test:agent:context": "npm run build && node scripts/test-agent-context-trace-e2e.js",
|