vigthoria-cli 1.10.37 → 1.10.47

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 (58) hide show
  1. package/dist/commands/agent-session-menu.d.ts +19 -0
  2. package/dist/commands/agent-session-menu.js +155 -0
  3. package/dist/commands/auth.js +68 -51
  4. package/dist/commands/bridge.js +19 -12
  5. package/dist/commands/cancel.js +22 -15
  6. package/dist/commands/chat.d.ts +0 -28
  7. package/dist/commands/chat.js +407 -1254
  8. package/dist/commands/config.js +73 -33
  9. package/dist/commands/deploy.js +123 -83
  10. package/dist/commands/device.js +61 -21
  11. package/dist/commands/edit.js +39 -32
  12. package/dist/commands/explain.js +25 -18
  13. package/dist/commands/generate.js +44 -37
  14. package/dist/commands/hub.js +102 -95
  15. package/dist/commands/index.js +46 -41
  16. package/dist/commands/legion.js +186 -146
  17. package/dist/commands/review.js +36 -29
  18. package/dist/commands/security.js +12 -5
  19. package/dist/commands/wallet.js +35 -28
  20. package/dist/commands/workflow.js +20 -13
  21. package/dist/utils/brain-hub-client.js +5 -1
  22. package/dist/utils/bridge-client.js +52 -11
  23. package/dist/utils/codebase-indexer.js +41 -4
  24. package/dist/utils/context-ranker.js +21 -15
  25. package/dist/utils/files.js +42 -5
  26. package/dist/utils/logger.js +50 -42
  27. package/dist/utils/persona.js +8 -3
  28. package/dist/utils/post-write-validator.js +29 -22
  29. package/dist/utils/project-memory.js +23 -16
  30. package/dist/utils/task-display.js +20 -13
  31. package/dist/utils/workspace-brain-service.js +45 -8
  32. package/dist/utils/workspace-cache.js +26 -18
  33. package/dist/utils/workspace-stream.js +63 -21
  34. package/package.json +3 -6
  35. package/dist/commands/fork.d.ts +0 -17
  36. package/dist/commands/fork.js +0 -164
  37. package/dist/commands/history.d.ts +0 -17
  38. package/dist/commands/history.js +0 -113
  39. package/dist/commands/preview.d.ts +0 -55
  40. package/dist/commands/preview.js +0 -467
  41. package/dist/commands/replay.d.ts +0 -18
  42. package/dist/commands/replay.js +0 -156
  43. package/dist/commands/repo.d.ts +0 -97
  44. package/dist/commands/repo.js +0 -773
  45. package/dist/commands/update.d.ts +0 -9
  46. package/dist/commands/update.js +0 -201
  47. package/dist/index.d.ts +0 -21
  48. package/dist/index.js +0 -1823
  49. package/dist/utils/api.d.ts +0 -572
  50. package/dist/utils/api.js +0 -6548
  51. package/dist/utils/cli-state.d.ts +0 -54
  52. package/dist/utils/cli-state.js +0 -185
  53. package/dist/utils/config.d.ts +0 -85
  54. package/dist/utils/config.js +0 -267
  55. package/dist/utils/session.d.ts +0 -118
  56. package/dist/utils/session.js +0 -423
  57. package/dist/utils/tools.d.ts +0 -276
  58. package/dist/utils/tools.js +0 -3516
@@ -1,118 +0,0 @@
1
- /**
2
- * Session Manager - Persist and resume conversations
3
- * Similar to Vigthoria's session persistence
4
- */
5
- import { ChatMessage } from './api.js';
6
- export type AuthState = {
7
- token: string | null;
8
- expiresAt: number | null;
9
- isValid: boolean;
10
- };
11
- export type CliError = {
12
- code: string;
13
- message: string;
14
- details?: any;
15
- isCritical: boolean;
16
- };
17
- /**
18
- * Validate persisted authentication state without assuming any field is present.
19
- *
20
- * Production hygiene: by default this helper is SILENT. Earlier revisions
21
- * wrote ``console.warn`` for every missing-token branch, which polluted
22
- * stderr on PowerShell (NativeCommandError styling) and surfaced as noise
23
- * on first-run installs. Pass ``{ silent: false }`` only when the caller
24
- * actually wants the warning visible.
25
- */
26
- export declare function validateSession(session: AuthState, options?: {
27
- silent?: boolean;
28
- }): boolean;
29
- /**
30
- * Load persisted authentication state and normalize nullable token fields safely.
31
- *
32
- * Production behaviour: first-run users (no session file yet) get a clean
33
- * ``{ token: null, expiresAt: null, isValid: false }`` instead of an
34
- * exception. Callers that need a hard failure can inspect ``isValid``.
35
- */
36
- export declare function loadSession(): Promise<AuthState>;
37
- export interface Session {
38
- id: string;
39
- name: string;
40
- project: string;
41
- model: string;
42
- messages: ChatMessage[];
43
- memorySummary?: string;
44
- compactedAt?: string | null;
45
- createdAt: string;
46
- updatedAt: string;
47
- agentMode: boolean;
48
- operatorMode?: boolean;
49
- }
50
- export declare class SessionManager {
51
- private sessionsDir;
52
- private readonly compactThreshold;
53
- private readonly retainRecentMessages;
54
- /** Keep at most this many session files on disk per CLI install. */
55
- private readonly maxRetainedSessions;
56
- /** Sessions older than this are pruned (90 days). */
57
- private readonly retentionWindowMs;
58
- /** Avoid running prune on every save — at most once per CLI process. */
59
- private prunedThisProcess;
60
- constructor();
61
- private ensureDir;
62
- /**
63
- * Prune sessions older than the retention window AND cap the number of
64
- * retained files. This is best-effort: any unreadable file is treated
65
- * as stale and removed. Pruning is bounded to once per process.
66
- */
67
- private pruneIfNeeded;
68
- /**
69
- * Generate unique session ID
70
- */
71
- private generateId;
72
- /**
73
- * Create a new session
74
- */
75
- create(project: string, model: string, agentMode?: boolean, operatorMode?: boolean): Session;
76
- /**
77
- * Save session to disk — atomic + 0600 mode so transcripts cannot be
78
- * read by other local users on a shared POSIX host.
79
- */
80
- save(session: Session): void;
81
- /**
82
- * Load session by ID
83
- */
84
- load(id: string): Session | null;
85
- /**
86
- * Get the most recent session for a project
87
- */
88
- getLatest(project: string): Session | null;
89
- /**
90
- * List all sessions (metadata only) — corrupt or unreadable files are
91
- * skipped silently unless VIGTHORIA_DEBUG=1.
92
- */
93
- list(): Omit<Session, 'messages'>[];
94
- /**
95
- * Delete a session
96
- */
97
- delete(id: string): boolean;
98
- /**
99
- * Clear all sessions
100
- */
101
- clearAll(): number;
102
- /**
103
- * Add message to session
104
- */
105
- addMessage(session: Session, message: ChatMessage): void;
106
- compactInMemory(session: Session): Session;
107
- buildMemoryContext(session: Session | null): string;
108
- private compactSession;
109
- private isPersistentSystemMessage;
110
- private mergePersistentAndRecent;
111
- private mergeSummaries;
112
- private summarizeMessages;
113
- private normalizeMessageContent;
114
- /**
115
- * Get session summary for display
116
- */
117
- getSummary(session: Session): string;
118
- }
@@ -1,423 +0,0 @@
1
- /**
2
- * Session Manager - Persist and resume conversations
3
- * Similar to Vigthoria's session persistence
4
- */
5
- import * as fs from 'fs';
6
- import * as path from 'path';
7
- import * as os from 'os';
8
- import { atomicWriteJson, secureFileMode } from './cli-state.js';
9
- function createSessionLoadError(message, details) {
10
- return {
11
- code: 'SESSION_LOAD_FAILED',
12
- message,
13
- details,
14
- isCritical: true,
15
- };
16
- }
17
- function normalizeAuthState(raw) {
18
- if (!raw || typeof raw !== 'object') {
19
- return { token: null, expiresAt: null, isValid: false };
20
- }
21
- const source = raw.auth && typeof raw.auth === 'object' ? raw.auth : raw;
22
- const rawToken = source.token ?? source.accessToken ?? source.jwt ?? null;
23
- const rawExpiresAt = source.expiresAt ?? null;
24
- const token = typeof rawToken === 'string' && rawToken.trim().length > 0 ? rawToken.trim() : null;
25
- const expiresAt = typeof rawExpiresAt === 'number' && Number.isFinite(rawExpiresAt) ? rawExpiresAt : null;
26
- const session = { token, expiresAt, isValid: false };
27
- session.isValid = validateSession(session, { silent: true });
28
- return session;
29
- }
30
- /**
31
- * Validate persisted authentication state without assuming any field is present.
32
- *
33
- * Production hygiene: by default this helper is SILENT. Earlier revisions
34
- * wrote ``console.warn`` for every missing-token branch, which polluted
35
- * stderr on PowerShell (NativeCommandError styling) and surfaced as noise
36
- * on first-run installs. Pass ``{ silent: false }`` only when the caller
37
- * actually wants the warning visible.
38
- */
39
- export function validateSession(session, options = {}) {
40
- const silent = options.silent !== false;
41
- if (!session || typeof session !== 'object') {
42
- if (!silent)
43
- console.warn('Invalid session: session state is missing.');
44
- return false;
45
- }
46
- if (typeof session.token !== 'string' || session.token.trim().length === 0) {
47
- if (!silent)
48
- console.warn('Invalid session: authentication token is missing.');
49
- return false;
50
- }
51
- if (typeof session.expiresAt !== 'number' || !Number.isFinite(session.expiresAt)) {
52
- if (!silent)
53
- console.warn('Invalid session: expiration timestamp is missing.');
54
- return false;
55
- }
56
- if (session.expiresAt <= Date.now()) {
57
- if (!silent)
58
- console.warn('Invalid session: authentication token has expired.');
59
- return false;
60
- }
61
- return true;
62
- }
63
- /**
64
- * Load persisted authentication state and normalize nullable token fields safely.
65
- *
66
- * Production behaviour: first-run users (no session file yet) get a clean
67
- * ``{ token: null, expiresAt: null, isValid: false }`` instead of an
68
- * exception. Callers that need a hard failure can inspect ``isValid``.
69
- */
70
- export async function loadSession() {
71
- const configDir = path.join(os.homedir(), '.vigthoria');
72
- const candidateFiles = [
73
- path.join(configDir, 'auth.json'),
74
- path.join(configDir, 'session.json'),
75
- path.join(configDir, 'config.json'),
76
- ];
77
- const sessionFile = candidateFiles.find((filePath) => {
78
- try {
79
- return fs.existsSync(filePath);
80
- }
81
- catch {
82
- return false;
83
- }
84
- });
85
- if (!sessionFile) {
86
- return { token: null, expiresAt: null, isValid: false };
87
- }
88
- try {
89
- const content = fs.readFileSync(sessionFile, 'utf-8');
90
- const parsed = JSON.parse(content);
91
- return normalizeAuthState(parsed);
92
- }
93
- catch (error) {
94
- if (process.env.VIGTHORIA_DEBUG === '1') {
95
- console.error('[vigthoria] Could not read session file:', error?.message ?? error);
96
- }
97
- return { token: null, expiresAt: null, isValid: false };
98
- }
99
- }
100
- export class SessionManager {
101
- sessionsDir;
102
- compactThreshold = 40;
103
- retainRecentMessages = 18;
104
- /** Keep at most this many session files on disk per CLI install. */
105
- maxRetainedSessions = 200;
106
- /** Sessions older than this are pruned (90 days). */
107
- retentionWindowMs = 90 * 24 * 60 * 60 * 1000;
108
- /** Avoid running prune on every save — at most once per CLI process. */
109
- prunedThisProcess = false;
110
- constructor() {
111
- this.sessionsDir = path.join(os.homedir(), '.vigthoria', 'sessions');
112
- this.ensureDir();
113
- }
114
- ensureDir() {
115
- try {
116
- if (!fs.existsSync(this.sessionsDir)) {
117
- fs.mkdirSync(this.sessionsDir, { recursive: true, mode: 0o700 });
118
- }
119
- if (process.platform !== 'win32') {
120
- try {
121
- fs.chmodSync(this.sessionsDir, 0o700);
122
- }
123
- catch { /* best-effort */ }
124
- }
125
- }
126
- catch (error) {
127
- if (error?.code === 'EACCES' || error?.code === 'EPERM') {
128
- this.sessionsDir = path.join(os.tmpdir(), 'vigthoria-sessions');
129
- try {
130
- if (!fs.existsSync(this.sessionsDir)) {
131
- fs.mkdirSync(this.sessionsDir, { recursive: true });
132
- }
133
- }
134
- catch {
135
- // If even tmpdir is unwritable we'll fail at write time with a useful error.
136
- }
137
- }
138
- }
139
- }
140
- /**
141
- * Prune sessions older than the retention window AND cap the number of
142
- * retained files. This is best-effort: any unreadable file is treated
143
- * as stale and removed. Pruning is bounded to once per process.
144
- */
145
- pruneIfNeeded() {
146
- if (this.prunedThisProcess)
147
- return;
148
- this.prunedThisProcess = true;
149
- try {
150
- const now = Date.now();
151
- const files = fs.readdirSync(this.sessionsDir)
152
- .filter((f) => f.endsWith('.json'))
153
- .map((f) => {
154
- const full = path.join(this.sessionsDir, f);
155
- let mtimeMs = 0;
156
- try {
157
- mtimeMs = fs.statSync(full).mtimeMs;
158
- }
159
- catch { /* treat as stale */ }
160
- return { file: f, full, mtimeMs };
161
- });
162
- // Drop everything older than the retention window.
163
- for (const entry of files) {
164
- if (entry.mtimeMs === 0 || now - entry.mtimeMs > this.retentionWindowMs) {
165
- try {
166
- fs.unlinkSync(entry.full);
167
- }
168
- catch { /* ignore */ }
169
- }
170
- }
171
- // Cap retained-session count (keep most recent).
172
- const remaining = files
173
- .filter((e) => e.mtimeMs > 0 && now - e.mtimeMs <= this.retentionWindowMs)
174
- .sort((a, b) => b.mtimeMs - a.mtimeMs);
175
- if (remaining.length > this.maxRetainedSessions) {
176
- for (const entry of remaining.slice(this.maxRetainedSessions)) {
177
- try {
178
- fs.unlinkSync(entry.full);
179
- }
180
- catch { /* ignore */ }
181
- }
182
- }
183
- }
184
- catch {
185
- // Pruning is purely a hygiene step — never block on failure.
186
- }
187
- }
188
- /**
189
- * Generate unique session ID
190
- */
191
- generateId() {
192
- const timestamp = Date.now().toString(36);
193
- const random = Math.random().toString(36).substring(2, 8);
194
- return `${timestamp}-${random}`;
195
- }
196
- /**
197
- * Create a new session
198
- */
199
- create(project, model, agentMode = false, operatorMode = false) {
200
- const session = {
201
- id: this.generateId(),
202
- name: `Session ${new Date().toLocaleString()}`,
203
- project,
204
- model,
205
- messages: [],
206
- memorySummary: '',
207
- compactedAt: null,
208
- createdAt: new Date().toISOString(),
209
- updatedAt: new Date().toISOString(),
210
- agentMode,
211
- operatorMode,
212
- };
213
- this.save(session);
214
- return session;
215
- }
216
- /**
217
- * Save session to disk — atomic + 0600 mode so transcripts cannot be
218
- * read by other local users on a shared POSIX host.
219
- */
220
- save(session) {
221
- const compacted = this.compactSession(session);
222
- const targetSession = compacted.session;
223
- targetSession.updatedAt = new Date().toISOString();
224
- const filePath = path.join(this.sessionsDir, `${session.id}.json`);
225
- try {
226
- atomicWriteJson(filePath, targetSession, 0o600);
227
- secureFileMode(filePath);
228
- }
229
- catch (error) {
230
- // Surface a best-effort hint rather than crashing the active chat.
231
- if (process.env.VIGTHORIA_DEBUG === '1') {
232
- console.error('[vigthoria] session save failed:', error?.message ?? error);
233
- }
234
- }
235
- this.pruneIfNeeded();
236
- }
237
- /**
238
- * Load session by ID
239
- */
240
- load(id) {
241
- const filePath = path.join(this.sessionsDir, `${id}.json`);
242
- if (!fs.existsSync(filePath)) {
243
- return null;
244
- }
245
- try {
246
- const content = fs.readFileSync(filePath, 'utf-8');
247
- return JSON.parse(content);
248
- }
249
- catch (error) {
250
- console.warn(`Failed to load session ${id}:`, error);
251
- return null;
252
- }
253
- }
254
- /**
255
- * Get the most recent session for a project
256
- */
257
- getLatest(project) {
258
- const sessions = this.list();
259
- const projectSessions = sessions
260
- .filter(s => s.project === project)
261
- .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
262
- return projectSessions.length > 0 ? this.load(projectSessions[0].id) : null;
263
- }
264
- /**
265
- * List all sessions (metadata only) — corrupt or unreadable files are
266
- * skipped silently unless VIGTHORIA_DEBUG=1.
267
- */
268
- list() {
269
- let files = [];
270
- try {
271
- files = fs.readdirSync(this.sessionsDir).filter(f => f.endsWith('.json'));
272
- }
273
- catch {
274
- return [];
275
- }
276
- return files.map(f => {
277
- try {
278
- const content = fs.readFileSync(path.join(this.sessionsDir, f), 'utf-8');
279
- const session = JSON.parse(content);
280
- const { messages, ...metadata } = session;
281
- return { ...metadata, messages: [] };
282
- }
283
- catch (error) {
284
- if (process.env.VIGTHORIA_DEBUG === '1') {
285
- console.warn(`Failed to read session metadata from ${f}:`, error);
286
- }
287
- return null;
288
- }
289
- }).filter(Boolean);
290
- }
291
- /**
292
- * Delete a session
293
- */
294
- delete(id) {
295
- const filePath = path.join(this.sessionsDir, `${id}.json`);
296
- if (fs.existsSync(filePath)) {
297
- fs.unlinkSync(filePath);
298
- return true;
299
- }
300
- return false;
301
- }
302
- /**
303
- * Clear all sessions
304
- */
305
- clearAll() {
306
- const files = fs.readdirSync(this.sessionsDir)
307
- .filter(f => f.endsWith('.json'));
308
- files.forEach(f => fs.unlinkSync(path.join(this.sessionsDir, f)));
309
- return files.length;
310
- }
311
- /**
312
- * Add message to session
313
- */
314
- addMessage(session, message) {
315
- session.messages.push(message);
316
- this.save(session);
317
- }
318
- compactInMemory(session) {
319
- return this.compactSession(session).session;
320
- }
321
- buildMemoryContext(session) {
322
- if (!session?.memorySummary || !session.memorySummary.trim()) {
323
- return '';
324
- }
325
- return [
326
- 'Session memory summary from earlier conversation turns.',
327
- 'Use this as compressed context for follow-up questions and resume behavior.',
328
- session.memorySummary.trim(),
329
- ].join('\n');
330
- }
331
- compactSession(session) {
332
- if (session.messages.length <= this.compactThreshold) {
333
- return { session, compacted: false };
334
- }
335
- const systemMessages = session.messages.filter((message) => message.role === 'system' && this.isPersistentSystemMessage(message.content));
336
- const recentMessages = session.messages.slice(-this.retainRecentMessages);
337
- const olderMessages = session.messages.slice(0, -this.retainRecentMessages);
338
- const nextSummary = this.mergeSummaries(session.memorySummary || '', this.summarizeMessages(olderMessages));
339
- session.memorySummary = nextSummary;
340
- session.compactedAt = new Date().toISOString();
341
- session.messages = this.mergePersistentAndRecent(systemMessages, recentMessages);
342
- return { session, compacted: true };
343
- }
344
- isPersistentSystemMessage(content) {
345
- return content.includes('Vigthoria CLI agent operating contract')
346
- || content.includes('Session memory summary from earlier conversation turns.');
347
- }
348
- mergePersistentAndRecent(systemMessages, recentMessages) {
349
- const merged = [];
350
- const seen = new Set();
351
- for (const message of [...systemMessages, ...recentMessages]) {
352
- const key = `${message.role}:${message.content}`;
353
- if (seen.has(key)) {
354
- continue;
355
- }
356
- seen.add(key);
357
- merged.push(message);
358
- }
359
- return merged;
360
- }
361
- mergeSummaries(existingSummary, newSummary) {
362
- const blocks = [existingSummary.trim(), newSummary.trim()].filter(Boolean);
363
- if (blocks.length === 0) {
364
- return '';
365
- }
366
- const mergedLines = new Set();
367
- for (const block of blocks) {
368
- for (const line of block.split('\n')) {
369
- const trimmed = line.trim();
370
- if (trimmed) {
371
- mergedLines.add(trimmed);
372
- }
373
- }
374
- }
375
- return Array.from(mergedLines).slice(-18).join('\n');
376
- }
377
- summarizeMessages(messages) {
378
- const summaryLines = [];
379
- let userCount = 0;
380
- let assistantCount = 0;
381
- for (const message of messages) {
382
- const normalized = this.normalizeMessageContent(message.content);
383
- if (!normalized) {
384
- continue;
385
- }
386
- if (message.role === 'user' && userCount < 8) {
387
- summaryLines.push(`- User intent: ${normalized}`);
388
- userCount += 1;
389
- continue;
390
- }
391
- if (message.role === 'assistant' && assistantCount < 6) {
392
- summaryLines.push(`- Assistant outcome: ${normalized}`);
393
- assistantCount += 1;
394
- continue;
395
- }
396
- if (message.role === 'system' && /Tool .* (succeeded|failed)\./.test(normalized) && assistantCount < 10) {
397
- summaryLines.push(`- Tool evidence: ${normalized}`);
398
- assistantCount += 1;
399
- }
400
- }
401
- return summaryLines.slice(-14).join('\n');
402
- }
403
- normalizeMessageContent(content) {
404
- const collapsed = String(content || '')
405
- .replace(/<tool_call>[\s\S]*?<\/tool_call>/g, ' ')
406
- .replace(/\s+/g, ' ')
407
- .trim();
408
- if (!collapsed) {
409
- return '';
410
- }
411
- return collapsed.length > 220 ? `${collapsed.slice(0, 220)}...` : collapsed;
412
- }
413
- /**
414
- * Get session summary for display
415
- */
416
- getSummary(session) {
417
- const userMessages = session.messages.filter(m => m.role === 'user');
418
- const preview = userMessages.length > 0
419
- ? userMessages[0].content.substring(0, 50) + '...'
420
- : 'Empty session';
421
- return `[${session.id}] ${preview} (${userMessages.length} messages)`;
422
- }
423
- }