vigthoria-cli 1.10.1 → 1.10.37

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.
@@ -95,10 +95,15 @@ export declare class AgenticTools {
95
95
  private requireNonEmptyString;
96
96
  private requireArgsObject;
97
97
  private sessionApprovedTools;
98
+ private indexedCodebaseSearch;
98
99
  private static permissionsFile;
99
100
  constructor(logger: Logger, cwd: string, permissionCallback: (action: string, options?: {
100
101
  batchApproval?: boolean;
101
102
  }) => Promise<boolean | 'batch' | 'persist'>, autoApprove?: boolean);
103
+ /** Rebind tool execution to a different local workspace root (e.g. prompt path override). */
104
+ setWorkspaceRoot(cwd: string): void;
105
+ setIndexedCodebaseSearch(handler: ((query: string, maxResults: number) => string) | null): void;
106
+ getWorkspaceRoot(): string;
102
107
  private getErrorMessage;
103
108
  private assertToolCall;
104
109
  /**
@@ -183,6 +188,8 @@ export declare class AgenticTools {
183
188
  */
184
189
  private formatPermissionRequest;
185
190
  private sleep;
191
+ private bridgeToolsEnabled;
192
+ private tryBridgeReadFile;
186
193
  /**
187
194
  * Read file with enhanced error handling and suggestions
188
195
  */
@@ -433,6 +433,7 @@ export class AgenticTools {
433
433
  }
434
434
  // Session-based tool approvals - remembers which tools user approved for this turn
435
435
  sessionApprovedTools = new Set();
436
+ indexedCodebaseSearch = null;
436
437
  // Persistent permissions - tool allowlists per project
437
438
  static permissionsFile = path.join(process.env.HOME || process.env.USERPROFILE || '~', '.vigthoria', 'permissions.json');
438
439
  constructor(logger, cwd, permissionCallback, autoApprove = false) {
@@ -449,10 +450,23 @@ export class AgenticTools {
449
450
  throw new Error('AgenticTools initialization failed: autoApprove must be a boolean.');
450
451
  }
451
452
  this.logger = logger;
452
- this.cwd = cwd;
453
+ this.cwd = path.resolve(cwd);
453
454
  this.permissionCallback = permissionCallback;
454
455
  this.autoApprove = autoApprove;
455
456
  }
457
+ /** Rebind tool execution to a different local workspace root (e.g. prompt path override). */
458
+ setWorkspaceRoot(cwd) {
459
+ if (typeof cwd !== 'string' || cwd.trim().length === 0) {
460
+ throw new Error('AgenticTools.setWorkspaceRoot failed: cwd must be a non-empty string.');
461
+ }
462
+ this.cwd = path.resolve(cwd);
463
+ }
464
+ setIndexedCodebaseSearch(handler) {
465
+ this.indexedCodebaseSearch = handler;
466
+ }
467
+ getWorkspaceRoot() {
468
+ return this.cwd;
469
+ }
456
470
  getErrorMessage(error) {
457
471
  if (error instanceof Error) {
458
472
  return error.message;
@@ -1234,10 +1248,52 @@ export class AgenticTools {
1234
1248
  return new Promise(resolve => setTimeout(resolve, ms));
1235
1249
  }
1236
1250
  // Tool implementations with enhanced error handling and undo support
1251
+ bridgeToolsEnabled() {
1252
+ const flag = String(process.env.VIGTHORIA_BRIDGE_TOOLS || '').trim().toLowerCase();
1253
+ return flag === '1' || flag === 'true' || flag === 'yes' || flag === 'on';
1254
+ }
1255
+ tryBridgeReadFile(args) {
1256
+ if (!this.bridgeToolsEnabled()) {
1257
+ return null;
1258
+ }
1259
+ const bridgeBase = String(process.env.VIGTHORIA_BRIDGE_HTTP
1260
+ || process.env.VIGTHORIA_DESKTOP_BRIDGE_URL
1261
+ || 'http://127.0.0.1:49160').replace(/\/$/, '');
1262
+ const targetPath = args.path || '';
1263
+ if (!targetPath) {
1264
+ return null;
1265
+ }
1266
+ try {
1267
+ const response = execSync(`curl -sS -m 15 -X POST "${bridgeBase}/tools/read_file" -H "Content-Type: application/json" -d ${JSON.stringify(JSON.stringify({
1268
+ path: targetPath,
1269
+ start_line: args.start_line ? Number(args.start_line) : undefined,
1270
+ end_line: args.end_line ? Number(args.end_line) : undefined,
1271
+ }))}`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
1272
+ const payload = JSON.parse(response);
1273
+ if (payload?.success === true) {
1274
+ return {
1275
+ success: true,
1276
+ output: String(payload.output || payload.content || ''),
1277
+ metadata: { source: 'desktop-bridge', path: targetPath },
1278
+ };
1279
+ }
1280
+ if (payload?.error) {
1281
+ this.logger.debug(`Desktop bridge read failed for ${targetPath}: ${payload.error}`);
1282
+ }
1283
+ }
1284
+ catch (error) {
1285
+ this.logger.debug(`Desktop bridge read unavailable for ${targetPath}: ${this.formatExternalToolError('read_file', 'bridge delegation', error)}`);
1286
+ }
1287
+ return null;
1288
+ }
1237
1289
  /**
1238
1290
  * Read file with enhanced error handling and suggestions
1239
1291
  */
1240
1292
  readFile(args) {
1293
+ const bridgeRead = this.tryBridgeReadFile(args);
1294
+ if (bridgeRead) {
1295
+ return bridgeRead;
1296
+ }
1241
1297
  const filePath = this.resolvePath(args.path);
1242
1298
  if (!fs.existsSync(filePath)) {
1243
1299
  // Try to find similar files
@@ -2763,6 +2819,16 @@ export class AgenticTools {
2763
2819
  const scope = args.scope || 'all';
2764
2820
  const includePattern = args.include || '';
2765
2821
  const maxResults = Math.min(parseInt(args.max_results || '30', 10), 100);
2822
+ if (this.indexedCodebaseSearch && (scope === 'all' || scope === 'content')) {
2823
+ const indexedOutput = this.indexedCodebaseSearch(query, maxResults);
2824
+ if (indexedOutput && !indexedOutput.includes('No indexed codebase matches found')) {
2825
+ return {
2826
+ success: true,
2827
+ output: indexedOutput,
2828
+ metadata: { searchStatus: 'search_matches_found', source: 'tfidf-index' },
2829
+ };
2830
+ }
2831
+ }
2766
2832
  const results = [];
2767
2833
  const seen = new Set();
2768
2834
  // Helper: collect files recursively respecting gitignore-like patterns
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Workspace Brain — local codebase index + Brain Hub sync for CLI.
3
+ */
4
+ import { CodebaseIndexer, IndexMeta } from './codebase-indexer.js';
5
+ export type WorkspaceBrainOptions = {
6
+ workspacePath: string;
7
+ apiBase?: string;
8
+ getAuthToken: () => string | null | Promise<string | null>;
9
+ autoIndex?: boolean;
10
+ brainSyncEnabled?: boolean;
11
+ };
12
+ export type EnsureIndexedResult = {
13
+ indexed: boolean;
14
+ fileCount: number;
15
+ chunkCount: number;
16
+ prompted?: 'index_now' | 'later' | 'skipped';
17
+ };
18
+ export declare class WorkspaceBrainService {
19
+ private workspacePath;
20
+ private indexer;
21
+ private brainClient;
22
+ private autoIndex;
23
+ private brainSyncEnabled;
24
+ private accountContextCache;
25
+ constructor(options: WorkspaceBrainOptions);
26
+ getIndexer(): CodebaseIndexer;
27
+ getStatus(): {
28
+ workspacePath: string;
29
+ indexedFileCount: number;
30
+ totalChunks: number;
31
+ isIndexing: boolean;
32
+ meta: IndexMeta | null;
33
+ };
34
+ ensureIndexed(options?: {
35
+ promptIfMissing?: boolean;
36
+ askToIndex?: (fileCount: number, workspaceName: string) => Promise<'index_now' | 'later'>;
37
+ }): Promise<EnsureIndexedResult>;
38
+ reindexWorkspace(): Promise<IndexMeta>;
39
+ buildCodebaseContext(prompt: string): string;
40
+ searchCodebase(query: string, maxResults?: number): string;
41
+ fetchAccountBrainContext(force?: boolean): Promise<string>;
42
+ private syncIndexToHub;
43
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Workspace Brain — local codebase index + Brain Hub sync for CLI.
3
+ */
4
+ import * as path from 'path';
5
+ import { BrainHubClient } from './brain-hub-client.js';
6
+ import { CodebaseIndexer } from './codebase-indexer.js';
7
+ export class WorkspaceBrainService {
8
+ workspacePath;
9
+ indexer;
10
+ brainClient;
11
+ autoIndex;
12
+ brainSyncEnabled;
13
+ accountContextCache = null;
14
+ constructor(options) {
15
+ this.workspacePath = path.resolve(options.workspacePath);
16
+ this.indexer = new CodebaseIndexer(this.workspacePath);
17
+ this.brainClient = new BrainHubClient({
18
+ apiBase: options.apiBase,
19
+ getAuthToken: options.getAuthToken,
20
+ });
21
+ this.autoIndex = options.autoIndex ?? !/^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_NO_AUTO_INDEX || ''));
22
+ this.brainSyncEnabled = options.brainSyncEnabled ?? !/^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_NO_BRAIN_SYNC || ''));
23
+ }
24
+ getIndexer() {
25
+ return this.indexer;
26
+ }
27
+ getStatus() {
28
+ const status = this.indexer.getStatus();
29
+ return {
30
+ workspacePath: this.workspacePath,
31
+ indexedFileCount: status.indexedFileCount,
32
+ totalChunks: status.totalChunks,
33
+ isIndexing: status.isIndexing,
34
+ meta: CodebaseIndexer.loadMeta(this.workspacePath),
35
+ };
36
+ }
37
+ async ensureIndexed(options = {}) {
38
+ if (this.indexer.hasLocalIndex() && this.indexer.getStatus().totalChunks === 0) {
39
+ const meta = await this.indexer.indexWorkspace();
40
+ await this.syncIndexToHub(meta);
41
+ return { indexed: true, fileCount: meta.indexedFileCount, chunkCount: meta.totalChunks };
42
+ }
43
+ if (this.indexer.hasLocalIndex()) {
44
+ const meta = CodebaseIndexer.loadMeta(this.workspacePath);
45
+ return {
46
+ indexed: true,
47
+ fileCount: meta?.indexedFileCount || this.indexer.getStatus().indexedFileCount,
48
+ chunkCount: meta?.totalChunks || this.indexer.getStatus().totalChunks,
49
+ };
50
+ }
51
+ if (!this.autoIndex && !options.promptIfMissing) {
52
+ return { indexed: false, fileCount: 0, chunkCount: 0, prompted: 'skipped' };
53
+ }
54
+ if (options.promptIfMissing && options.askToIndex) {
55
+ const wsName = path.basename(this.workspacePath);
56
+ const fileCount = this.indexer.countIndexableFiles();
57
+ if (fileCount === 0) {
58
+ return { indexed: false, fileCount: 0, chunkCount: 0, prompted: 'skipped' };
59
+ }
60
+ const choice = await options.askToIndex(fileCount, wsName);
61
+ if (choice === 'later') {
62
+ return { indexed: false, fileCount: 0, chunkCount: 0, prompted: 'later' };
63
+ }
64
+ }
65
+ const meta = await this.indexer.indexWorkspace();
66
+ await this.syncIndexToHub(meta);
67
+ return {
68
+ indexed: true,
69
+ fileCount: meta.indexedFileCount,
70
+ chunkCount: meta.totalChunks,
71
+ prompted: options.promptIfMissing ? 'index_now' : undefined,
72
+ };
73
+ }
74
+ async reindexWorkspace() {
75
+ const meta = await this.indexer.indexWorkspace();
76
+ await this.syncIndexToHub(meta);
77
+ return meta;
78
+ }
79
+ buildCodebaseContext(prompt) {
80
+ if (!prompt.trim() || !this.indexer.hasLocalIndex()) {
81
+ return '';
82
+ }
83
+ const context = this.indexer.getContextForQuery(prompt, 3500);
84
+ if (!context) {
85
+ return '';
86
+ }
87
+ return `Vigthoria codebase index context.\n${context}`;
88
+ }
89
+ searchCodebase(query, maxResults = 30) {
90
+ return this.indexer.formatSearchResults(query, maxResults);
91
+ }
92
+ async fetchAccountBrainContext(force = false) {
93
+ if (!this.brainSyncEnabled) {
94
+ return '';
95
+ }
96
+ const cacheTtlMs = 5 * 60 * 1000;
97
+ if (!force && this.accountContextCache && Date.now() - this.accountContextCache.fetchedAt < cacheTtlMs) {
98
+ return this.accountContextCache.text;
99
+ }
100
+ const response = await this.brainClient.fetchAccountContext(25);
101
+ const text = String(response.formattedText || '').trim();
102
+ this.accountContextCache = { text, fetchedAt: Date.now() };
103
+ return text;
104
+ }
105
+ async syncIndexToHub(meta) {
106
+ if (!this.brainSyncEnabled) {
107
+ return;
108
+ }
109
+ const wsName = path.basename(this.workspacePath);
110
+ const summary = `Workspace "${wsName}" indexed in Vigthoria CLI: ${meta.indexedFileCount} files, ${meta.totalChunks} chunks. Key paths: ${meta.topFiles.slice(0, 6).join(', ')}`;
111
+ await this.brainClient.syncWorkspaceIndex({
112
+ workspaceName: wsName,
113
+ workspacePath: this.workspacePath,
114
+ fileCount: meta.indexedFileCount,
115
+ chunkCount: meta.totalChunks,
116
+ topFiles: meta.topFiles,
117
+ summary,
118
+ indexHash: meta.indexHash,
119
+ }).catch(() => undefined);
120
+ }
121
+ }
@@ -37,6 +37,17 @@ const IGNORE_PATTERNS = [
37
37
  '**/.vigthoria/**',
38
38
  '**/coverage/**',
39
39
  '**/*.pyc',
40
+ // CRITICAL: Windows AppData & system directories (prevents EPERM errors)
41
+ '**/Anwendungsdaten/**', // German AppData
42
+ '**/AppData/**', // English AppData
43
+ '**/Local Settings/**', // Legacy Windows
44
+ '**/Application Data/**', // Legacy Windows
45
+ '**/$RECYCLE.BIN/**', // Windows Recycle Bin
46
+ '**/System Volume Information/**',
47
+ '**/Temp/**',
48
+ '**/tmp/**',
49
+ '**/OneDrive/**', // OneDrive cache
50
+ '**/MicrosoftEdgeBackups/**',
40
51
  ];
41
52
  const BINARY_EXTENSIONS = new Set([
42
53
  '.png', '.jpg', '.jpeg', '.gif', '.ico', '.svg', '.webp', '.bmp',
@@ -103,24 +114,54 @@ export class WorkspaceWatcher {
103
114
  start() {
104
115
  if (this.watcher)
105
116
  return;
106
- this.watcher = chokidar.watch(this.workspaceRoot, {
107
- ignored: IGNORE_PATTERNS,
108
- persistent: true,
109
- ignoreInitial: true,
110
- awaitWriteFinish: { stabilityThreshold: 300, pollInterval: 100 },
111
- });
112
- this.watcher
113
- .on('ready', () => {
114
- this._ready = true;
115
- logger.debug('Workspace watcher ready');
116
- })
117
- .on('add', (filePath) => this._handleChange(filePath, 'write'))
118
- .on('change', (filePath) => this._handleChange(filePath, 'write'))
119
- .on('unlink', (filePath) => this._handleChange(filePath, 'delete'));
117
+ try {
118
+ this.watcher = chokidar.watch(this.workspaceRoot, {
119
+ ignored: IGNORE_PATTERNS,
120
+ persistent: false,
121
+ ignoreInitial: true,
122
+ awaitWriteFinish: { stabilityThreshold: 300, pollInterval: 100 },
123
+ // Windows-specific settings to prevent EPERM on system directories
124
+ usePolling: false,
125
+ interval: 100,
126
+ binaryInterval: 300,
127
+ depth: 50, // Limit traversal depth
128
+ });
129
+ this.watcher
130
+ .on('ready', () => {
131
+ this._ready = true;
132
+ logger.debug('Workspace watcher ready');
133
+ })
134
+ .on('add', (filePath) => this._handleChange(filePath, 'write'))
135
+ .on('change', (filePath) => this._handleChange(filePath, 'write'))
136
+ .on('unlink', (filePath) => this._handleChange(filePath, 'delete'))
137
+ // CRITICAL: Handle permission errors gracefully (Windows AppData EPERM)
138
+ .on('error', (error) => {
139
+ if (error?.code === 'EPERM' || error?.errno === -4048) {
140
+ logger.warn(`Workspace watcher permission error (ignored): ${error.message}`);
141
+ // Continue without watcher; don't crash
142
+ }
143
+ else {
144
+ logger.error(`Workspace watcher error: ${error?.message || error}`);
145
+ }
146
+ });
147
+ }
148
+ catch (err) {
149
+ // Handle initialization errors (e.g., EPERM during chokidar.watch setup)
150
+ if (err?.code === 'EPERM' || err?.errno === -4048 || err?.message?.includes('EPERM')) {
151
+ logger.warn(`Failed to initialize workspace watcher (EPERM): ${err.message}`);
152
+ logger.info('Continuing without file watcher; local file changes will not sync in real-time');
153
+ this.watcher = null;
154
+ }
155
+ else {
156
+ logger.error(`Failed to initialize workspace watcher: ${err.message}`);
157
+ throw err;
158
+ }
159
+ }
120
160
  }
121
161
  stop() {
122
162
  if (this.watcher) {
123
- this.watcher.close();
163
+ // close() returns a Promise; fire-and-forget so handles are released
164
+ this.watcher.close().catch(() => { });
124
165
  this.watcher = null;
125
166
  this._ready = false;
126
167
  }
package/install.ps1 CHANGED
@@ -5,7 +5,7 @@
5
5
  $ErrorActionPreference = "Stop"
6
6
 
7
7
  # Configuration
8
- $CLI_VERSION = "1.9.22"
8
+ $CLI_VERSION = "1.10.18"
9
9
  $INSTALL_DIR = "$env:USERPROFILE\.vigthoria"
10
10
  $NPM_PACKAGE = "vigthoria-cli"
11
11
  $GIT_PACKAGE_URL = "git+https://market.vigthoria.io/vigthoria/vigthoria-cli.git"
package/install.sh CHANGED
@@ -26,7 +26,7 @@ else
26
26
  fi
27
27
 
28
28
  # Configuration
29
- CLI_VERSION="1.9.22"
29
+ CLI_VERSION="1.10.18"
30
30
  INSTALL_DIR="$HOME/.vigthoria"
31
31
  REPO_URL="https://market.vigthoria.io/vigthoria/vigthoria-cli"
32
32
  GIT_PACKAGE_URL="git+https://market.vigthoria.io/vigthoria/vigthoria-cli.git"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigthoria-cli",
3
- "version": "1.10.1",
3
+ "version": "1.10.37",
4
4
  "description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -65,7 +65,9 @@
65
65
  "test:model:governance": "npm run build && node scripts/test-model-governance-no-agent.js",
66
66
  "validate:no-go": "bash scripts/release/validate-no-go-gates.sh",
67
67
  "test:legion:billing:e2e": "npm run build && node scripts/test-legion-godmode-billing-e2e.js",
68
- "test:windows:v3-sync": "npm run build && node scripts/test-windows-v3-sync-recovery.js"
68
+ "test:windows:v3-sync": "npm run build && node scripts/test-windows-v3-sync-recovery.js",
69
+ "test:persona": "npm run build && node scripts/test-persona-mode.mjs",
70
+ "test:error-wording": "npm run build && node scripts/test-error-wording.mjs"
69
71
  },
70
72
  "keywords": [
71
73
  "ai",
@@ -46,7 +46,7 @@ import('./dist/utils/context-ranker.js').then((m)=>{
46
46
  EOF2
47
47
 
48
48
  echo "[2.1/2.4] tsc validator emits passing result"
49
- node - << 'EOF2'
49
+ node --input-type=module - << 'EOF2'
50
50
  import fs from 'node:fs';
51
51
  import os from 'node:os';
52
52
  import path from 'node:path';