@vohongtho.infotech/code-intel 1.0.1 → 1.0.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/dist/cli/main.js CHANGED
@@ -9,7 +9,7 @@ import winston from 'winston';
9
9
  import DailyRotateFile from 'winston-daily-rotate-file';
10
10
  import fs39, { readFileSync, existsSync } from 'fs';
11
11
  import path39, { dirname, join } from 'path';
12
- import os13 from 'os';
12
+ import os18 from 'os';
13
13
  import { Registry, collectDefaultMetrics, Counter, Histogram, Gauge } from 'prom-client';
14
14
  import { createRequire } from 'module';
15
15
  import { fileURLToPath } from 'url';
@@ -38,6 +38,7 @@ import https from 'https';
38
38
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
39
39
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
40
40
  import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
41
+ import process2 from 'process';
41
42
 
42
43
  var __defProp = Object.defineProperty;
43
44
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -327,13 +328,13 @@ var init_logger = __esm({
327
328
  };
328
329
  }
329
330
  /** Global log directory: ~/.code-intel/logs */
330
- static LOG_DIR = path39.join(os13.homedir(), ".code-intel", "logs");
331
+ static LOG_DIR = path39.join(os18.homedir(), ".code-intel", "logs");
331
332
  static getLogger() {
332
333
  if (!_Logger.instance) {
333
334
  const isProduction = process.env.NODE_ENV === "production";
334
335
  const logLevel = process.env.LOG_LEVEL ?? "info";
335
336
  const transports = [];
336
- transports.push(new winston.transports.Console());
337
+ transports.push(new winston.transports.Console({ stderrLevels: ["error", "warn", "info", "http", "verbose", "debug", "silly"] }));
337
338
  if (!isProduction) {
338
339
  try {
339
340
  if (!fs39.existsSync(_Logger.LOG_DIR)) {
@@ -3115,7 +3116,7 @@ var init_llm_governance = __esm({
3115
3116
  }
3116
3117
  /** Path to the JSONL log file. */
3117
3118
  getLogPath() {
3118
- return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ?? path39.join(os13.homedir(), ".code-intel", "llm-governance.jsonl");
3119
+ return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ?? path39.join(os18.homedir(), ".code-intel", "llm-governance.jsonl");
3119
3120
  }
3120
3121
  /**
3121
3122
  * Append an entry to the governance log.
@@ -3276,6 +3277,49 @@ var init_anthropic = __esm({
3276
3277
  }
3277
3278
  });
3278
3279
 
3280
+ // src/llm/providers/custom.ts
3281
+ var custom_exports = {};
3282
+ __export(custom_exports, {
3283
+ CustomProvider: () => CustomProvider
3284
+ });
3285
+ var CustomProvider;
3286
+ var init_custom = __esm({
3287
+ "src/llm/providers/custom.ts"() {
3288
+ CustomProvider = class {
3289
+ modelName;
3290
+ baseUrl;
3291
+ apiKey;
3292
+ constructor(baseUrl, model, apiKey = "") {
3293
+ this.baseUrl = baseUrl.replace(/\/$/, "");
3294
+ this.modelName = model;
3295
+ this.apiKey = apiKey;
3296
+ }
3297
+ async summarize(prompt2) {
3298
+ const url = `${this.baseUrl}/chat/completions`;
3299
+ const headers = {
3300
+ "Content-Type": "application/json"
3301
+ };
3302
+ if (this.apiKey) {
3303
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
3304
+ }
3305
+ const body = JSON.stringify({
3306
+ model: this.modelName,
3307
+ messages: [{ role: "user", content: prompt2 }],
3308
+ max_tokens: 200,
3309
+ temperature: 0.3
3310
+ });
3311
+ const res = await fetch(url, { method: "POST", headers, body });
3312
+ if (!res.ok) {
3313
+ const text = await res.text().catch(() => res.statusText);
3314
+ throw new Error(`Custom LLM API error ${res.status}: ${text}`);
3315
+ }
3316
+ const data = await res.json();
3317
+ return data.choices?.[0]?.message?.content?.trim() ?? "";
3318
+ }
3319
+ };
3320
+ }
3321
+ });
3322
+
3279
3323
  // src/llm/providers/ollama.ts
3280
3324
  var ollama_exports = {};
3281
3325
  __export(ollama_exports, {
@@ -3319,7 +3363,7 @@ __export(factory_exports, {
3319
3363
  createLLMProvider: () => createLLMProvider
3320
3364
  });
3321
3365
  async function createLLMProvider(config = {}) {
3322
- const { provider = "ollama", model } = config;
3366
+ const { provider = "ollama", model, baseUrl, apiKey } = config;
3323
3367
  switch (provider) {
3324
3368
  case "openai": {
3325
3369
  const { OpenAIProvider: OpenAIProvider2 } = await Promise.resolve().then(() => (init_openai(), openai_exports));
@@ -3329,6 +3373,13 @@ async function createLLMProvider(config = {}) {
3329
3373
  const { AnthropicProvider: AnthropicProvider2 } = await Promise.resolve().then(() => (init_anthropic(), anthropic_exports));
3330
3374
  return new AnthropicProvider2(model);
3331
3375
  }
3376
+ case "custom": {
3377
+ const { CustomProvider: CustomProvider2 } = await Promise.resolve().then(() => (init_custom(), custom_exports));
3378
+ const url = baseUrl ?? "http://localhost:1234/v1";
3379
+ const mdl = model ?? "default";
3380
+ const key = apiKey ?? "";
3381
+ return new CustomProvider2(url, mdl, key);
3382
+ }
3332
3383
  case "ollama":
3333
3384
  default: {
3334
3385
  const { OllamaProvider: OllamaProvider2 } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
@@ -3482,7 +3533,7 @@ var init_worker_pool = __esm({
3482
3533
  constructor(opts) {
3483
3534
  super();
3484
3535
  this.workerScript = opts.workerScript;
3485
- this.workerCount = opts.workerCount ?? Math.max(1, os13.cpus().length - 1);
3536
+ this.workerCount = opts.workerCount ?? Math.max(1, os18.cpus().length - 1);
3486
3537
  this.maxQueueSize = opts.maxQueueSize ?? 200;
3487
3538
  this.maxTaskRetries = opts.maxTaskRetries ?? 2;
3488
3539
  }
@@ -3610,7 +3661,7 @@ var init_parse_phase_parallel = __esm({
3610
3661
  if (!context2.fileCache) context2.fileCache = /* @__PURE__ */ new Map();
3611
3662
  if (!context2.fileFunctionIndex) context2.fileFunctionIndex = /* @__PURE__ */ new Map();
3612
3663
  const filePaths = context2.filePaths;
3613
- const workerCount = parseInt(process.env["PARSE_WORKERS"] ?? "", 10) || Math.max(1, os13.cpus().length - 1);
3664
+ const workerCount = parseInt(process.env["PARSE_WORKERS"] ?? "", 10) || Math.max(1, os18.cpus().length - 1);
3614
3665
  const CONCURRENCY = 64;
3615
3666
  for (let i = 0; i < filePaths.length; i += CONCURRENCY) {
3616
3667
  const batch = filePaths.slice(i, i + CONCURRENCY);
@@ -3739,7 +3790,7 @@ var init_resolve_phase_parallel = __esm({
3739
3790
  const snapshot = { symbolIndex, fileSymbolIndex, fileIndex, workspaceRoot };
3740
3791
  const workerScript = workerScriptPath2();
3741
3792
  const workerScriptExists = fs39.existsSync(workerScript);
3742
- const workerCount = parseInt(process.env["PARSE_WORKERS"] ?? "", 10) || Math.max(1, os13.cpus().length - 1);
3793
+ const workerCount = parseInt(process.env["PARSE_WORKERS"] ?? "", 10) || Math.max(1, os18.cpus().length - 1);
3743
3794
  if (!workerScriptExists || workerCount === 1) {
3744
3795
  logger_default.info(`[resolve-parallel] falling back to sequential`);
3745
3796
  const { resolvePhase: resolvePhase2 } = await Promise.resolve().then(() => (init_resolve_phase(), resolve_phase_exports));
@@ -4531,7 +4582,7 @@ async function loadGraphToDB(graph, dbManager) {
4531
4582
  } catch {
4532
4583
  }
4533
4584
  }
4534
- const tmpDir = fs39.mkdtempSync(path39.join(os13.tmpdir(), "code-intel-csv-"));
4585
+ const tmpDir = fs39.mkdtempSync(path39.join(os18.tmpdir(), "code-intel-csv-"));
4535
4586
  try {
4536
4587
  const nodeTableFiles = writeNodeCSVs(graph, tmpDir);
4537
4588
  const edgeGroups = writeEdgeCSV(graph, tmpDir);
@@ -4717,7 +4768,7 @@ function removeRepo(repoPath) {
4717
4768
  var GLOBAL_DIR, REPOS_FILE;
4718
4769
  var init_repo_registry = __esm({
4719
4770
  "src/storage/repo-registry.ts"() {
4720
- GLOBAL_DIR = path39.join(os13.homedir(), ".code-intel");
4771
+ GLOBAL_DIR = path39.join(os18.homedir(), ".code-intel");
4721
4772
  REPOS_FILE = path39.join(GLOBAL_DIR, "repos.json");
4722
4773
  }
4723
4774
  });
@@ -4888,7 +4939,7 @@ function loadSyncResult(groupName) {
4888
4939
  var GROUPS_DIR;
4889
4940
  var init_group_registry = __esm({
4890
4941
  "src/multi-repo/group-registry.ts"() {
4891
- GROUPS_DIR = path39.join(os13.homedir(), ".code-intel", "groups");
4942
+ GROUPS_DIR = path39.join(os18.homedir(), ".code-intel", "groups");
4892
4943
  }
4893
4944
  });
4894
4945
 
@@ -5447,14 +5498,14 @@ function runPrerequisiteChecks() {
5447
5498
  });
5448
5499
  }
5449
5500
  try {
5450
- const out = execSync(`df -BM "${os13.homedir()}" 2>/dev/null | tail -1 | awk '{print $4}'`, { encoding: "utf8" });
5501
+ const out = execSync(`df -BM "${os18.homedir()}" 2>/dev/null | tail -1 | awk '{print $4}'`, { encoding: "utf8" });
5451
5502
  const availMB = parseInt(out.trim().replace("M", ""), 10);
5452
5503
  if (Number.isFinite(availMB) && availMB < 500) {
5453
5504
  results.push({
5454
5505
  name: "Disk space",
5455
5506
  ok: false,
5456
5507
  level: "warn",
5457
- message: `Low disk space: ${availMB} MB available in ${os13.homedir()} (500 MB recommended)`
5508
+ message: `Low disk space: ${availMB} MB available in ${os18.homedir()} (500 MB recommended)`
5458
5509
  });
5459
5510
  }
5460
5511
  } catch {
@@ -5475,6 +5526,7 @@ var init_codes = __esm({
5475
5526
  RATE_LIMIT_EXCEEDED: "CI-1100",
5476
5527
  PAYLOAD_TOO_LARGE: "CI-1101",
5477
5528
  INVALID_REQUEST: "CI-1200",
5529
+ CONFLICT: "CI-1409",
5478
5530
  // Config (CI-2xxx)
5479
5531
  CONFIG_INVALID: "CI-2000",
5480
5532
  CONFIG_NOT_FOUND: "CI-2001",
@@ -5544,7 +5596,7 @@ var init_fs_secure = __esm({
5544
5596
  }
5545
5597
  });
5546
5598
  function getUsersDBPath() {
5547
- return process.env["CODE_INTEL_USERS_DB_PATH"] ?? path39.join(os13.homedir(), ".code-intel", "users.db");
5599
+ return process.env["CODE_INTEL_USERS_DB_PATH"] ?? path39.join(os18.homedir(), ".code-intel", "users.db");
5548
5600
  }
5549
5601
  function getOrCreateUsersDB() {
5550
5602
  if (!_usersDB) {
@@ -5837,17 +5889,17 @@ function getScryptN() {
5837
5889
  return Number.isInteger(v) && v >= 1024 ? v : 1 << 14;
5838
5890
  }
5839
5891
  function getSecretsPath() {
5840
- return process.env["CODE_INTEL_SECRETS_PATH"] ?? path39.join(os13.homedir(), ".code-intel", ".secrets");
5892
+ return process.env["CODE_INTEL_SECRETS_PATH"] ?? path39.join(os18.homedir(), ".code-intel", ".secrets");
5841
5893
  }
5842
5894
  function getMasterPassword() {
5843
5895
  const fromEnv = process.env["CODE_INTEL_SECRET_KEY"];
5844
5896
  if (fromEnv && fromEnv.length >= 16) return fromEnv;
5845
5897
  let username = "unknown";
5846
5898
  try {
5847
- username = os13.userInfo().username;
5899
+ username = os18.userInfo().username;
5848
5900
  } catch {
5849
5901
  }
5850
- return `code-intel-machine:${os13.hostname()}:${username}:${os13.platform()}`;
5902
+ return `code-intel-machine:${os18.hostname()}:${username}:${os18.platform()}`;
5851
5903
  }
5852
5904
  function deriveKey(password, salt) {
5853
5905
  return crypto5.scryptSync(password, salt, KEY_LEN, { N: getScryptN(), r: 8, p: 1 });
@@ -8313,6 +8365,7 @@ async function runInitWizard(opts = {}) {
8313
8365
  { label: "Ollama (local, free, requires Ollama running)", value: "ollama" },
8314
8366
  { label: "OpenAI (requires OPENAI_API_KEY env var)", value: "openai" },
8315
8367
  { label: "Anthropic (requires ANTHROPIC_API_KEY env var)", value: "anthropic" },
8368
+ { label: "Custom (OpenAI-compatible API \u2014 enter URL, token & model)", value: "custom" },
8316
8369
  { label: "Skip (configure later)", value: "none" }
8317
8370
  ], 0);
8318
8371
  cfg.llm.provider = llmProvider;
@@ -8328,6 +8381,19 @@ async function runInitWizard(opts = {}) {
8328
8381
  cfg.llm.model = "llama3";
8329
8382
  cfg.llm.apiKey = "";
8330
8383
  console.log(" Make sure Ollama is running: https://ollama.com");
8384
+ } else if (llmProvider === "custom") {
8385
+ console.log(" Configure your OpenAI-compatible provider (e.g. LM Studio, vLLM, Together, Groq, Azure).\n");
8386
+ const baseUrl = (await prompt(rl, " API Base URL (e.g. http://localhost:1234/v1): ")).trim();
8387
+ cfg.llm.baseUrl = baseUrl || "http://localhost:1234/v1";
8388
+ const apiKey = (await prompt(rl, " API Token/Key (leave blank if not required): ")).trim();
8389
+ cfg.llm.apiKey = apiKey || "";
8390
+ const model = (await prompt(rl, " Model name (e.g. mistral-7b-instruct): ")).trim();
8391
+ cfg.llm.model = model || "default";
8392
+ console.log(`
8393
+ \u2705 Custom provider configured:`);
8394
+ console.log(` URL: ${cfg.llm.baseUrl}`);
8395
+ console.log(` Model: ${cfg.llm.model}`);
8396
+ console.log(` Token: ${cfg.llm.apiKey ? "(set)" : "(none)"}`);
8331
8397
  } else {
8332
8398
  cfg.llm.apiKey = "";
8333
8399
  console.log(" Skipped. Run `code-intel config set llm.provider openai` later.");
@@ -8392,7 +8458,7 @@ function printNextSteps() {
8392
8458
  var GLOBAL_DIR2, CONFIG_PATH, DEFAULT_CONFIG, EDITORS;
8393
8459
  var init_init_wizard = __esm({
8394
8460
  "src/cli/init-wizard.ts"() {
8395
- GLOBAL_DIR2 = path39.join(os13.homedir(), ".code-intel");
8461
+ GLOBAL_DIR2 = path39.join(os18.homedir(), ".code-intel");
8396
8462
  CONFIG_PATH = path39.join(GLOBAL_DIR2, "config.json");
8397
8463
  DEFAULT_CONFIG = {
8398
8464
  $schema: "https://code-intel.dev/config-schema.json",
@@ -8705,9 +8771,10 @@ var init_config_manager = __esm({
8705
8771
  /access[_-]?token/i
8706
8772
  ];
8707
8773
  CONFIG_SCHEMA = {
8708
- "llm.provider": { type: "string", enum: ["openai", "anthropic", "ollama", "none"], default: "ollama", description: "LLM provider for AI summaries" },
8774
+ "llm.provider": { type: "string", enum: ["openai", "anthropic", "ollama", "custom", "none"], default: "ollama", description: "LLM provider for AI summaries" },
8709
8775
  "llm.model": { type: "string", default: "llama3", description: "LLM model name" },
8710
8776
  "llm.apiKey": { type: "string", default: "", description: "API key \u2014 use $ENV_VAR syntax, e.g. $OPENAI_API_KEY" },
8777
+ "llm.baseUrl": { type: "string", default: "", description: "Base URL for custom OpenAI-compatible API (e.g. http://localhost:1234/v1)" },
8711
8778
  "llm.batchSize": { type: "number", minimum: 1, maximum: 100, default: 20, description: "Concurrent LLM calls per batch" },
8712
8779
  "llm.maxTokensPerSummary": { type: "number", minimum: 10, maximum: 2e3, default: 100, description: "Max tokens per AI summary" },
8713
8780
  "embeddings.model": { type: "string", default: "all-MiniLM-L6-v2", description: "Embedding model name" },
@@ -10394,7 +10461,7 @@ var JobsDB = class {
10394
10461
  }
10395
10462
  };
10396
10463
  function getJobsDBPath() {
10397
- return path39.join(os13.homedir(), ".code-intel", "jobs.db");
10464
+ return path39.join(os18.homedir(), ".code-intel", "jobs.db");
10398
10465
  }
10399
10466
  var _jobsDB = null;
10400
10467
  function getOrCreateJobsDB() {
@@ -10489,14 +10556,14 @@ var BACKUP_VERSION = "1.0";
10489
10556
  var ALGORITHM = "aes-256-gcm";
10490
10557
  var IV_LENGTH = 16;
10491
10558
  function getBackupDir() {
10492
- return path39.join(os13.homedir(), ".code-intel", "backups");
10559
+ return path39.join(os18.homedir(), ".code-intel", "backups");
10493
10560
  }
10494
10561
  function getBackupKey() {
10495
10562
  const keyHex = process.env["CODE_INTEL_BACKUP_KEY"];
10496
10563
  if (keyHex && keyHex.length >= 64) {
10497
10564
  return Buffer.from(keyHex.slice(0, 64), "hex");
10498
10565
  }
10499
- const seed = `code-intel-backup-${os13.hostname()}-${os13.homedir()}`;
10566
+ const seed = `code-intel-backup-${os18.hostname()}-${os18.homedir()}`;
10500
10567
  return crypto5.createHash("sha256").update(seed).digest();
10501
10568
  }
10502
10569
  function encryptBuffer(data, key) {
@@ -10538,11 +10605,11 @@ var BackupService = class {
10538
10605
  filesToBackup.push({ name: f, localPath: fp });
10539
10606
  }
10540
10607
  }
10541
- const registryPath = path39.join(os13.homedir(), ".code-intel", "registry.json");
10608
+ const registryPath = path39.join(os18.homedir(), ".code-intel", "registry.json");
10542
10609
  if (fs39.existsSync(registryPath)) {
10543
10610
  filesToBackup.push({ name: "registry.json", localPath: registryPath });
10544
10611
  }
10545
- const usersDbPath = path39.join(os13.homedir(), ".code-intel", "users.db");
10612
+ const usersDbPath = path39.join(os18.homedir(), ".code-intel", "users.db");
10546
10613
  if (fs39.existsSync(usersDbPath)) {
10547
10614
  filesToBackup.push({ name: "users.db", localPath: usersDbPath });
10548
10615
  }
@@ -10708,7 +10775,7 @@ var BackupService = class {
10708
10775
  }
10709
10776
  let destPath;
10710
10777
  if (name === "registry.json" || name === "users.db") {
10711
- destPath = path39.join(os13.homedir(), ".code-intel", name);
10778
+ destPath = path39.join(os18.homedir(), ".code-intel", name);
10712
10779
  } else {
10713
10780
  destPath = path39.join(codeIntelDir, name);
10714
10781
  }
@@ -12387,6 +12454,97 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
12387
12454
  }
12388
12455
  res.json(result);
12389
12456
  });
12457
+ app.post("/api/v1/groups", requireAuth, requireRole("analyst"), (req, res) => {
12458
+ const { name } = req.body;
12459
+ if (!name || !name.trim()) {
12460
+ res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: "Group name is required" } });
12461
+ return;
12462
+ }
12463
+ const trimmed = name.trim();
12464
+ if (groupExists(trimmed)) {
12465
+ res.status(409).json({ error: { code: ErrorCodes.CONFLICT, message: `Group "${trimmed}" already exists` } });
12466
+ return;
12467
+ }
12468
+ const group = { name: trimmed, createdAt: (/* @__PURE__ */ new Date()).toISOString(), members: [] };
12469
+ saveGroup(group);
12470
+ res.status(201).json(group);
12471
+ });
12472
+ app.delete("/api/v1/groups/:name", requireAuth, requireRole("analyst"), (req, res) => {
12473
+ const groupName = req.params["name"];
12474
+ if (!groupExists(groupName)) {
12475
+ res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "Group not found" } });
12476
+ return;
12477
+ }
12478
+ deleteGroup(groupName);
12479
+ res.status(204).end();
12480
+ });
12481
+ app.patch("/api/v1/groups/:name", requireAuth, requireRole("analyst"), (req, res) => {
12482
+ const groupName = req.params["name"];
12483
+ const group = loadGroup(groupName);
12484
+ if (!group) {
12485
+ res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "Group not found" } });
12486
+ return;
12487
+ }
12488
+ const { name } = req.body;
12489
+ if (!name || !name.trim()) {
12490
+ res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: "New name is required" } });
12491
+ return;
12492
+ }
12493
+ const newName = name.trim();
12494
+ if (newName !== group.name && groupExists(newName)) {
12495
+ res.status(409).json({ error: { code: ErrorCodes.CONFLICT, message: `Group "${newName}" already exists` } });
12496
+ return;
12497
+ }
12498
+ if (newName !== group.name) {
12499
+ deleteGroup(group.name);
12500
+ group.name = newName;
12501
+ }
12502
+ saveGroup(group);
12503
+ res.json(group);
12504
+ });
12505
+ app.post("/api/v1/groups/:name/members", requireAuth, requireRole("analyst"), (req, res) => {
12506
+ const groupName = req.params["name"];
12507
+ const group = loadGroup(groupName);
12508
+ if (!group) {
12509
+ res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "Group not found" } });
12510
+ return;
12511
+ }
12512
+ const { groupPath, registryName } = req.body;
12513
+ if (!groupPath || !registryName) {
12514
+ res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: "groupPath and registryName are required" } });
12515
+ return;
12516
+ }
12517
+ const registry = loadRegistry();
12518
+ if (!registry.find((r) => r.name === registryName)) {
12519
+ res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: `Repo "${registryName}" not found in registry. Run code-intel analyze first.` } });
12520
+ return;
12521
+ }
12522
+ try {
12523
+ const updated = addMember(groupName, { groupPath, registryName });
12524
+ res.json(updated);
12525
+ } catch (err) {
12526
+ res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: err instanceof Error ? err.message : String(err) } });
12527
+ }
12528
+ });
12529
+ app.delete("/api/v1/groups/:name/members", requireAuth, requireRole("analyst"), (req, res) => {
12530
+ const groupName = req.params["name"];
12531
+ const group = loadGroup(groupName);
12532
+ if (!group) {
12533
+ res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "Group not found" } });
12534
+ return;
12535
+ }
12536
+ const { groupPath } = req.body;
12537
+ if (!groupPath) {
12538
+ res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: "groupPath is required" } });
12539
+ return;
12540
+ }
12541
+ try {
12542
+ const updated = removeMember(groupName, groupPath);
12543
+ res.json(updated);
12544
+ } catch (err) {
12545
+ res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: err instanceof Error ? err.message : String(err) } });
12546
+ }
12547
+ });
12390
12548
  app.post("/api/v1/groups/:name/sync", async (req, res) => {
12391
12549
  const group = loadGroup(req.params.name);
12392
12550
  if (!group) {
@@ -12397,8 +12555,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
12397
12555
  const result = await syncGroup(group);
12398
12556
  saveSyncResult(result);
12399
12557
  group.lastSync = result.syncedAt;
12400
- const { saveGroup: saveGroup2 } = await Promise.resolve().then(() => (init_group_registry(), group_registry_exports));
12401
- saveGroup2(group);
12558
+ saveGroup(group);
12402
12559
  res.json(result);
12403
12560
  } catch (err) {
12404
12561
  res.status(500).json({ error: { code: ErrorCodes.INTERNAL_ERROR, message: err instanceof Error ? err.message : String(err) } });
@@ -14954,6 +15111,14 @@ function writeContextFiles(workspaceRoot, projectName, stats, skills) {
14954
15111
  const kiroDir = path39.join(workspaceRoot, ".kiro", "steering");
14955
15112
  if (!fs39.existsSync(kiroDir)) fs39.mkdirSync(kiroDir, { recursive: true });
14956
15113
  upsertFile(path39.join(kiroDir, "code-intel.md"), block, "code-intel.md");
15114
+ upsertFile(path39.join(workspaceRoot, ".clinerules"), block, ".clinerules");
15115
+ upsertFile(path39.join(workspaceRoot, ".windsurfrules"), block, ".windsurfrules");
15116
+ const kilocodeDir = path39.join(workspaceRoot, ".kilocode", "rules");
15117
+ if (!fs39.existsSync(kilocodeDir)) fs39.mkdirSync(kilocodeDir, { recursive: true });
15118
+ upsertFile(path39.join(kilocodeDir, "code-intel-rules.md"), block, "code-intel-rules.md");
15119
+ const agentsDir = path39.join(workspaceRoot, ".agents", "rules");
15120
+ if (!fs39.existsSync(agentsDir)) fs39.mkdirSync(agentsDir, { recursive: true });
15121
+ upsertFile(path39.join(agentsDir, "code-intel-rules.md"), block, "code-intel-rules.md");
14957
15122
  }
14958
15123
  function buildBlock(projectName, stats, skills) {
14959
15124
  const skillTableRows = skills.map(
@@ -15451,7 +15616,7 @@ var MigrationRunner = class {
15451
15616
  try {
15452
15617
  const dbFile = this.db.name;
15453
15618
  if (!dbFile || !fs39.existsSync(dbFile)) return;
15454
- const backupDir = path39.join(os13.homedir(), ".code-intel", "backups", "pre-migration");
15619
+ const backupDir = path39.join(os18.homedir(), ".code-intel", "backups", "pre-migration");
15455
15620
  fs39.mkdirSync(backupDir, { recursive: true });
15456
15621
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
15457
15622
  const baseName = path39.basename(dbFile, ".db");
@@ -15607,7 +15772,7 @@ init_tracing();
15607
15772
  init_init_wizard();
15608
15773
  init_config_manager();
15609
15774
  init_codes();
15610
- var GLOBAL_DIR3 = path39.join(os13.homedir(), ".code-intel");
15775
+ var GLOBAL_DIR3 = path39.join(os18.homedir(), ".code-intel");
15611
15776
  function loadRepoPaths() {
15612
15777
  try {
15613
15778
  const data = fs39.readFileSync(path39.join(GLOBAL_DIR3, "repos.json"), "utf-8");
@@ -15881,7 +16046,7 @@ function autoInstallCompletion() {
15881
16046
  }
15882
16047
  console.log(` Detected shell: ${shell}`);
15883
16048
  if (shell === "fish") {
15884
- const dir = path39.join(os13.homedir(), ".config", "fish", "completions");
16049
+ const dir = path39.join(os18.homedir(), ".config", "fish", "completions");
15885
16050
  const dest = path39.join(dir, "code-intel.fish");
15886
16051
  fs39.mkdirSync(dir, { recursive: true });
15887
16052
  fs39.writeFileSync(dest, fishCompletion(), "utf-8");
@@ -15894,7 +16059,7 @@ source <(code-intel completion zsh)
15894
16059
  ` : `
15895
16060
  source <(code-intel completion bash)
15896
16061
  `;
15897
- const rcFile = shell === "zsh" ? path39.join(os13.homedir(), ".zshrc") : path39.join(os13.homedir(), ".bashrc");
16062
+ const rcFile = shell === "zsh" ? path39.join(os18.homedir(), ".zshrc") : path39.join(os18.homedir(), ".bashrc");
15898
16063
  try {
15899
16064
  const existing = fs39.existsSync(rcFile) ? fs39.readFileSync(rcFile, "utf-8") : "";
15900
16065
  if (existing.includes("code-intel completion")) {
@@ -15921,7 +16086,7 @@ function generateCompletion(shell) {
15921
16086
  return fishCompletion();
15922
16087
  }
15923
16088
  }
15924
- var GLOBAL_DIR4 = path39.join(os13.homedir(), ".code-intel");
16089
+ var GLOBAL_DIR4 = path39.join(os18.homedir(), ".code-intel");
15925
16090
  var META_PATH = path39.join(GLOBAL_DIR4, "update-meta.json");
15926
16091
  var PACKAGE_NAME = "code-intel";
15927
16092
  var NPM_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
@@ -16047,14 +16212,170 @@ async function runUpdate(opts = {}) {
16047
16212
  process.exit(1);
16048
16213
  }
16049
16214
  }
16215
+ var SOURCE_EXT = /* @__PURE__ */ new Set([
16216
+ "ts",
16217
+ "tsx",
16218
+ "js",
16219
+ "jsx",
16220
+ "mjs",
16221
+ "cjs",
16222
+ "py",
16223
+ "pyi",
16224
+ "rs",
16225
+ "go",
16226
+ "java",
16227
+ "kt",
16228
+ "kts",
16229
+ "rb",
16230
+ "cs",
16231
+ "cpp",
16232
+ "cc",
16233
+ "cxx",
16234
+ "c",
16235
+ "h",
16236
+ "hpp",
16237
+ "swift",
16238
+ "scala",
16239
+ "php"
16240
+ ]);
16241
+ var REGEX_META_RE = /[.*+?^${}()|[\]\\]/;
16242
+ var SYMBOL_ID_RE = /^[A-Za-z_$][A-Za-z0-9_$.-]*$/;
16243
+ function isSymbolLike(term) {
16244
+ return SYMBOL_ID_RE.test(term) && !REGEX_META_RE.test(term);
16245
+ }
16246
+ function isSourceFile(filePath) {
16247
+ const dot = filePath.lastIndexOf(".");
16248
+ if (dot === -1) return false;
16249
+ return SOURCE_EXT.has(filePath.slice(dot + 1).toLowerCase());
16250
+ }
16251
+ function fileStem(filePath) {
16252
+ const base = filePath.includes("/") ? filePath.slice(filePath.lastIndexOf("/") + 1) : filePath;
16253
+ const dot = base.lastIndexOf(".");
16254
+ return dot === -1 ? base : base.slice(0, dot);
16255
+ }
16256
+ function extractGrepSymbol(cmd) {
16257
+ if (/(?:^|\s)-[a-zA-Z]*[cvlLoZ]/.test(cmd)) return null;
16258
+ const quoted = cmd.match(/(?:^|\s)["']([^"']+)["'](?:\s|$)/);
16259
+ if (quoted) {
16260
+ const term = quoted[1];
16261
+ return isSymbolLike(term) ? term : null;
16262
+ }
16263
+ const tokens = cmd.split(/\s+/).slice(1);
16264
+ for (const tok of tokens) {
16265
+ if (tok.startsWith("-")) continue;
16266
+ if (tok.startsWith("/")) continue;
16267
+ if (tok.startsWith("./") || tok.startsWith("../")) continue;
16268
+ if (tok.includes("/")) continue;
16269
+ if (tok === "." || tok === "..") continue;
16270
+ return isSymbolLike(tok) ? tok : null;
16271
+ }
16272
+ return null;
16273
+ }
16274
+ function rewriteCommand(cmd) {
16275
+ try {
16276
+ const trimmed = cmd.trim();
16277
+ if (!trimmed) return null;
16278
+ if (trimmed.startsWith("code-intel ") || trimmed === "code-intel") return null;
16279
+ if (/(?:&&|\|\||;|\|)/.test(trimmed)) return null;
16280
+ if (/^grep\s/.test(trimmed)) {
16281
+ const sym = extractGrepSymbol(trimmed);
16282
+ if (sym) return `code-intel search "${sym}"`;
16283
+ return null;
16284
+ }
16285
+ if (/^rg\s/.test(trimmed)) {
16286
+ if (/\s--files(?:\s|$)/.test(trimmed)) return null;
16287
+ if (/\s--files-with-matches(?:\s|$)/.test(trimmed)) return null;
16288
+ if (/\s--type-not\b/.test(trimmed)) return null;
16289
+ const sym = extractGrepSymbol(trimmed);
16290
+ if (sym) return `code-intel search "${sym}"`;
16291
+ return null;
16292
+ }
16293
+ if (/^cat\s/.test(trimmed)) {
16294
+ const m = trimmed.match(/^cat\s+(\S+)$/);
16295
+ if (!m) return null;
16296
+ const filePath = m[1];
16297
+ if (filePath === "-") return null;
16298
+ if (filePath.startsWith(">")) return null;
16299
+ if (!isSourceFile(filePath)) return null;
16300
+ return `code-intel inspect ${fileStem(filePath)}`;
16301
+ }
16302
+ if (/^(?:head|tail)\s/.test(trimmed)) {
16303
+ if (/\s-f\b/.test(trimmed)) return null;
16304
+ const m = trimmed.match(
16305
+ /^(?:head|tail)\s+(?:-\d+\s+|-n\s+\d+\s+|--lines=\d+\s+)?(\S+)$/
16306
+ );
16307
+ if (!m) return null;
16308
+ const filePath = m[1];
16309
+ if (!isSourceFile(filePath)) return null;
16310
+ return `code-intel inspect ${fileStem(filePath)}`;
16311
+ }
16312
+ return null;
16313
+ } catch {
16314
+ return null;
16315
+ }
16316
+ }
16317
+ function runRewrite(cmd) {
16318
+ const rewritten = rewriteCommand(cmd.trim());
16319
+ if (rewritten === null) {
16320
+ process2.exit(1);
16321
+ }
16322
+ process2.stdout.write(rewritten);
16323
+ process2.exit(0);
16324
+ }
16325
+ function runClaudeHook() {
16326
+ process2.on("uncaughtException", () => process2.exit(0));
16327
+ process2.on("unhandledRejection", () => process2.exit(0));
16328
+ let input = "";
16329
+ process2.stdin.setEncoding("utf-8");
16330
+ process2.stdin.on("data", (chunk) => {
16331
+ input += chunk;
16332
+ });
16333
+ process2.stdin.on("end", () => {
16334
+ try {
16335
+ if (!input.trim()) {
16336
+ process2.exit(0);
16337
+ }
16338
+ const parsed = JSON.parse(input);
16339
+ const cmd = parsed?.tool_input?.command;
16340
+ if (typeof cmd !== "string" || !cmd.trim()) {
16341
+ process2.exit(0);
16342
+ }
16343
+ const rewritten = rewriteCommand(cmd);
16344
+ if (rewritten === null || rewritten === cmd) {
16345
+ process2.exit(0);
16346
+ }
16347
+ const response = {
16348
+ hookSpecificOutput: {
16349
+ hookEventName: "PreToolUse",
16350
+ permissionDecision: "allow",
16351
+ permissionDecisionReason: "code-intel: semantic search replaces grep/cat",
16352
+ updatedInput: {
16353
+ ...parsed.tool_input,
16354
+ command: rewritten
16355
+ }
16356
+ }
16357
+ };
16358
+ process2.stdout.write(JSON.stringify(response));
16359
+ process2.exit(0);
16360
+ } catch {
16361
+ process2.exit(0);
16362
+ }
16363
+ });
16364
+ process2.stdin.on("error", () => {
16365
+ process2.exit(0);
16366
+ });
16367
+ }
16050
16368
 
16051
16369
  // src/cli/main.ts
16052
16370
  var __filename$1 = fileURLToPath(import.meta.url);
16053
16371
  var __dirname2 = dirname(__filename$1);
16054
16372
  var _pkg = JSON.parse(readFileSync(join(__dirname2, "../../package.json"), "utf-8"));
16055
- initTracing();
16373
+ var IS_HOOK_MODE = process.argv[2] === "hook";
16374
+ if (!IS_HOOK_MODE) {
16375
+ initTracing();
16376
+ }
16056
16377
  var debugMode = process.argv.includes("--debug");
16057
- {
16378
+ if (!IS_HOOK_MODE) {
16058
16379
  const checks = runPrerequisiteChecks();
16059
16380
  for (const c of checks) {
16060
16381
  const icon = c.level === "error" ? "\u2717" : "\u26A0";
@@ -16062,20 +16383,28 @@ var debugMode = process.argv.includes("--debug");
16062
16383
  `);
16063
16384
  }
16064
16385
  }
16065
- if (!configExists()) {
16386
+ if (!IS_HOOK_MODE && !configExists()) {
16066
16387
  process.stderr.write(
16067
16388
  " \u2139 No config found. Run `code-intel init` to set up your environment.\n"
16068
16389
  );
16069
16390
  }
16070
16391
  process.on("uncaughtException", (err) => {
16392
+ if (IS_HOOK_MODE) {
16393
+ process.exit(0);
16394
+ }
16071
16395
  process.stderr.write(formatCliError(err, debugMode));
16072
16396
  process.exit(1);
16073
16397
  });
16074
16398
  process.on("unhandledRejection", (err) => {
16399
+ if (IS_HOOK_MODE) {
16400
+ process.exit(0);
16401
+ }
16075
16402
  process.stderr.write(formatCliError(err, debugMode));
16076
16403
  process.exit(1);
16077
16404
  });
16078
- backgroundVersionCheck(_pkg.version);
16405
+ if (!IS_HOOK_MODE) {
16406
+ backgroundVersionCheck(_pkg.version);
16407
+ }
16079
16408
  var program = new Command();
16080
16409
  var BANNER = `
16081
16410
  \u25C8 Code Intelligence Platform v${_pkg.version}
@@ -16125,6 +16454,8 @@ program.name("code-intel").description("Code Intelligence Platform \u2014 Static
16125
16454
  \u2502 server \u2502
16126
16455
  \u2502 code-intel mcp [path] Launch the MCP stdio server consumed by AI-enabled editors \u2502
16127
16456
  \u2502 code-intel serve [path] --port <n> Start the HTTP API and serve the interactive web UI (default :4747) \u2502
16457
+ \u2502 code-intel serve --detach Start the server in the background (frees the terminal) \u2502
16458
+ \u2502 code-intel stop [path] Stop a background server started with --detach \u2502
16128
16459
  \u2502 \u2502
16129
16460
  \u2502 registry \u2502
16130
16461
  \u2502 code-intel list Display all repositories that have been indexed \u2502
@@ -16272,7 +16603,9 @@ async function analyzeWorkspace(targetPath, options) {
16272
16603
  provider: options.llmProvider ?? "ollama",
16273
16604
  model: options.llmModel,
16274
16605
  batchSize: options.llmBatchSize,
16275
- maxNodesPerRun: options.llmMaxNodes
16606
+ maxNodesPerRun: options.llmMaxNodes,
16607
+ baseUrl: options.llmBaseUrl,
16608
+ apiKey: options.llmApiKey
16276
16609
  } : void 0,
16277
16610
  onProgress: options?.silent ? void 0 : (phase, msg) => {
16278
16611
  if (!options?.silent) {
@@ -16706,6 +17039,396 @@ program.command("completion").description("Generate shell completion scripts (ba
16706
17039
  }
16707
17040
  process.stdout.write(generateCompletion(shell));
16708
17041
  });
17042
+ var CODE_INTEL_HOOK_CMD = "code-intel-hook claude";
17043
+ var CODE_INTEL_CURSOR_HOOK_CMD = "code-intel-hook cursor";
17044
+ var CODE_INTEL_GEMINI_HOOK_CMD = "code-intel-hook gemini";
17045
+ function hookAlreadyPresent(root) {
17046
+ const entries = root?.hooks?.PreToolUse;
17047
+ if (!Array.isArray(entries)) return false;
17048
+ return entries.some(
17049
+ (entry) => Array.isArray(entry?.hooks) && entry.hooks.some(
17050
+ (h) => typeof h?.command === "string" && (h.command.includes("code-intel-hook") || h.command.includes("code-intel hook"))
17051
+ )
17052
+ );
17053
+ }
17054
+ function insertHookEntry(root) {
17055
+ const existingHooks = root.hooks ?? {};
17056
+ const existingPreToolUse = Array.isArray(
17057
+ existingHooks.PreToolUse
17058
+ ) ? existingHooks.PreToolUse : [];
17059
+ const newEntry = {
17060
+ matcher: "Bash",
17061
+ hooks: [{ type: "command", command: CODE_INTEL_HOOK_CMD }]
17062
+ };
17063
+ return {
17064
+ ...root,
17065
+ hooks: {
17066
+ ...existingHooks,
17067
+ PreToolUse: [newEntry, ...existingPreToolUse]
17068
+ }
17069
+ };
17070
+ }
17071
+ function installClaudeHook() {
17072
+ const settingsPath = path39.join(os18.homedir(), ".claude", "settings.json");
17073
+ let root = {};
17074
+ if (fs39.existsSync(settingsPath)) {
17075
+ try {
17076
+ const raw = fs39.readFileSync(settingsPath, "utf-8");
17077
+ root = JSON.parse(raw);
17078
+ } catch (err) {
17079
+ return {
17080
+ result: "skipped",
17081
+ reason: `Could not parse ~/.claude/settings.json: ${err instanceof Error ? err.message : String(err)}`
17082
+ };
17083
+ }
17084
+ }
17085
+ if (hookAlreadyPresent(root)) {
17086
+ return { result: "already-present" };
17087
+ }
17088
+ const updated = insertHookEntry(root);
17089
+ if (fs39.existsSync(settingsPath)) {
17090
+ try {
17091
+ fs39.copyFileSync(settingsPath, `${settingsPath}.bak`);
17092
+ } catch {
17093
+ logger_default.warn(" \u26A0 Could not create settings.json backup \u2014 proceeding anyway");
17094
+ }
17095
+ }
17096
+ try {
17097
+ fs39.mkdirSync(path39.dirname(settingsPath), { recursive: true });
17098
+ const tmp = `${settingsPath}.tmp.${Date.now()}`;
17099
+ fs39.writeFileSync(tmp, JSON.stringify(updated, null, 2) + "\n", "utf-8");
17100
+ fs39.renameSync(tmp, settingsPath);
17101
+ } catch (err) {
17102
+ return {
17103
+ result: "skipped",
17104
+ reason: `Could not write ~/.claude/settings.json: ${err instanceof Error ? err.message : String(err)}`
17105
+ };
17106
+ }
17107
+ return { result: "installed" };
17108
+ }
17109
+ var COPILOT_HOOK_JSON_CONTENT = JSON.stringify({
17110
+ hooks: {
17111
+ PreToolUse: [
17112
+ { type: "command", command: CODE_INTEL_HOOK_CMD.replace("claude", "copilot"), cwd: ".", timeout: 5 }
17113
+ ]
17114
+ }
17115
+ }, null, 2) + "\n";
17116
+ function installCopilotHook(workspaceRoot = ".") {
17117
+ const githubDir = path39.join(workspaceRoot, ".github");
17118
+ const hooksDir = path39.join(githubDir, "hooks");
17119
+ const hookFile = path39.join(hooksDir, "code-intel-rewrite.json");
17120
+ try {
17121
+ fs39.mkdirSync(hooksDir, { recursive: true });
17122
+ if (fs39.existsSync(hookFile)) {
17123
+ try {
17124
+ const existing = JSON.parse(fs39.readFileSync(hookFile, "utf-8"));
17125
+ const hooks = existing?.hooks?.PreToolUse;
17126
+ if (Array.isArray(hooks) && hooks.some(
17127
+ (h) => typeof h?.command === "string" && h.command.includes("code-intel")
17128
+ )) {
17129
+ return { result: "already-present" };
17130
+ }
17131
+ } catch {
17132
+ }
17133
+ }
17134
+ const tmp = `${hookFile}.tmp.${Date.now()}`;
17135
+ fs39.writeFileSync(tmp, COPILOT_HOOK_JSON_CONTENT, "utf-8");
17136
+ fs39.renameSync(tmp, hookFile);
17137
+ return { result: "installed" };
17138
+ } catch (err) {
17139
+ return { result: "skipped", reason: `Could not write .github/hooks/code-intel-rewrite.json: ${err instanceof Error ? err.message : String(err)}` };
17140
+ }
17141
+ }
17142
+ var OPENCODE_PLUGIN_CONTENT = `import type { Plugin } from "@opencode-ai/plugin"
17143
+ import { execSync } from "node:child_process"
17144
+
17145
+ // code-intel OpenCode plugin \u2014 rewrites symbol-discovery commands to semantic equivalents.
17146
+ // Requires: code-intel installed and in PATH.
17147
+ // To change rewrite rules, edit hook-rewriter.ts in the code-intel source.
17148
+
17149
+ let codeIntelAvailable: boolean | null = null
17150
+
17151
+ function checkCodeIntel(): boolean {
17152
+ if (codeIntelAvailable !== null) return codeIntelAvailable
17153
+ try {
17154
+ execSync("which code-intel", { stdio: "ignore" })
17155
+ codeIntelAvailable = true
17156
+ } catch {
17157
+ codeIntelAvailable = false
17158
+ }
17159
+ return codeIntelAvailable
17160
+ }
17161
+
17162
+ function tryRewrite(command: string): string | null {
17163
+ try {
17164
+ const result = execSync(\`code-intel rewrite \${JSON.stringify(command)}\`, {
17165
+ encoding: "utf-8",
17166
+ timeout: 2000,
17167
+ }).trim()
17168
+ return result && result !== command ? result : null
17169
+ } catch {
17170
+ return null
17171
+ }
17172
+ }
17173
+
17174
+ export const CodeIntelOpenCodePlugin: Plugin = async ({ $ }) => {
17175
+ if (!checkCodeIntel()) {
17176
+ console.warn("[code-intel] code-intel binary not found in PATH \u2014 plugin disabled")
17177
+ return {}
17178
+ }
17179
+ return {
17180
+ "tool.execute.before": async (input, output) => {
17181
+ const tool = String(input?.tool ?? "").toLowerCase()
17182
+ if (tool !== "bash" && tool !== "shell") return
17183
+ const args = output?.args
17184
+ if (!args || typeof args !== "object") return
17185
+ const command = (args as Record<string, unknown>).command
17186
+ if (typeof command !== "string" || !command) return
17187
+ try {
17188
+ const rewritten = tryRewrite(command)
17189
+ if (rewritten) {
17190
+ ;(args as Record<string, unknown>).command = rewritten
17191
+ }
17192
+ } catch {
17193
+ // non-blocking
17194
+ }
17195
+ },
17196
+ }
17197
+ }
17198
+ `;
17199
+ function installOpenCodePlugin() {
17200
+ const pluginDir = path39.join(os18.homedir(), ".config", "opencode", "plugins");
17201
+ const pluginFile = path39.join(pluginDir, "code-intel.ts");
17202
+ const opencodeCfg = path39.join(os18.homedir(), ".config", "opencode");
17203
+ if (!fs39.existsSync(opencodeCfg)) {
17204
+ return { result: "skipped", reason: "OpenCode not installed (~/.config/opencode not found)" };
17205
+ }
17206
+ if (fs39.existsSync(pluginFile)) {
17207
+ return { result: "already-present" };
17208
+ }
17209
+ try {
17210
+ fs39.mkdirSync(pluginDir, { recursive: true });
17211
+ const tmp = `${pluginFile}.tmp.${Date.now()}`;
17212
+ fs39.writeFileSync(tmp, OPENCODE_PLUGIN_CONTENT, "utf-8");
17213
+ fs39.renameSync(tmp, pluginFile);
17214
+ return { result: "installed" };
17215
+ } catch (err) {
17216
+ return { result: "skipped", reason: `Could not write plugin: ${err instanceof Error ? err.message : String(err)}` };
17217
+ }
17218
+ }
17219
+ var OPENCLAW_PLUGIN_CONTENT = `import { execSync } from "node:child_process";
17220
+
17221
+ // code-intel OpenClaw plugin \u2014 rewrites symbol-discovery commands to semantic equivalents.
17222
+ // Requires: code-intel installed and in PATH.
17223
+ // To change rewrite rules, edit hook-rewriter.ts in the code-intel source.
17224
+
17225
+ let codeIntelAvailable: boolean | null = null;
17226
+
17227
+ function checkCodeIntel(): boolean {
17228
+ if (codeIntelAvailable !== null) return codeIntelAvailable;
17229
+ try {
17230
+ execSync("which code-intel", { stdio: "ignore" });
17231
+ codeIntelAvailable = true;
17232
+ } catch {
17233
+ codeIntelAvailable = false;
17234
+ }
17235
+ return codeIntelAvailable;
17236
+ }
17237
+
17238
+ function tryRewrite(command: string): string | null {
17239
+ try {
17240
+ const result = execSync(\`code-intel rewrite \${JSON.stringify(command)}\`, {
17241
+ encoding: "utf-8",
17242
+ timeout: 2000,
17243
+ }).trim();
17244
+ return result && result !== command ? result : null;
17245
+ } catch {
17246
+ return null;
17247
+ }
17248
+ }
17249
+
17250
+ export default function register(api: any) {
17251
+ const pluginConfig = api.config ?? {};
17252
+ const enabled = pluginConfig.enabled !== false;
17253
+ const verbose = pluginConfig.verbose === true;
17254
+
17255
+ if (!enabled) return;
17256
+
17257
+ if (!checkCodeIntel()) {
17258
+ console.warn("[code-intel] code-intel binary not found in PATH \u2014 plugin disabled");
17259
+ return;
17260
+ }
17261
+
17262
+ api.on(
17263
+ "before_tool_call",
17264
+ (event: { toolName: string; params: Record<string, unknown> }) => {
17265
+ if (event.toolName !== "exec") return;
17266
+ const command = event.params?.command;
17267
+ if (typeof command !== "string") return;
17268
+ const rewritten = tryRewrite(command);
17269
+ if (!rewritten) return;
17270
+ if (verbose) console.log(\`[code-intel] \${command} -> \${rewritten}\`);
17271
+ return { params: { ...event.params, command: rewritten } };
17272
+ },
17273
+ { priority: 10 }
17274
+ );
17275
+
17276
+ if (verbose) console.log("[code-intel] OpenClaw plugin registered");
17277
+ }
17278
+ `;
17279
+ function installOpenClawPlugin() {
17280
+ const openclawDir = path39.join(os18.homedir(), ".openclaw");
17281
+ if (!fs39.existsSync(openclawDir)) {
17282
+ return { result: "skipped", reason: "OpenClaw not installed (~/.openclaw directory not found)" };
17283
+ }
17284
+ const pluginDir = path39.join(openclawDir, "extensions", "code-intel");
17285
+ const pluginFile = path39.join(pluginDir, "index.ts");
17286
+ if (fs39.existsSync(pluginFile)) {
17287
+ return { result: "already-present" };
17288
+ }
17289
+ try {
17290
+ fs39.mkdirSync(pluginDir, { recursive: true });
17291
+ const tmp = `${pluginFile}.tmp.${Date.now()}`;
17292
+ fs39.writeFileSync(tmp, OPENCLAW_PLUGIN_CONTENT, "utf-8");
17293
+ fs39.renameSync(tmp, pluginFile);
17294
+ return { result: "installed" };
17295
+ } catch (err) {
17296
+ return { result: "skipped", reason: `Could not write plugin: ${err instanceof Error ? err.message : String(err)}` };
17297
+ }
17298
+ }
17299
+ var CODE_INTEL_RULES_CONTENT = `# Code Intelligence \u2014 Tool Policy
17300
+
17301
+ ## MANDATORY: Use code-intel before any code action
17302
+
17303
+ **FORBIDDEN** (never use for symbol/code discovery): \`grep\`, \`rg\`, \`find\`, \`cat\`, \`sed\`, \`ls\`
17304
+
17305
+ **REQUIRED workflow**:
17306
+ \`\`\`bash
17307
+ code-intel search "<concept>" # replaces grep/rg/find
17308
+ code-intel inspect <symbol> # replaces cat + manual reading
17309
+ code-intel impact <symbol> # replaces manual caller tracing
17310
+ \`\`\`
17311
+
17312
+ Rules:
17313
+ - NEVER open a file cold \u2014 always \`code-intel search\` first
17314
+ - NEVER use grep for symbols \u2014 use \`code-intel search\` instead
17315
+ - If impact shows \u2265 5 callers \u2192 warn user: HIGH blast radius
17316
+ `;
17317
+ function installRulesFile(filePath, agentName) {
17318
+ try {
17319
+ const dir = path39.dirname(filePath);
17320
+ if (!fs39.existsSync(dir)) {
17321
+ fs39.mkdirSync(dir, { recursive: true });
17322
+ }
17323
+ if (fs39.existsSync(filePath)) {
17324
+ const existing = fs39.readFileSync(filePath, "utf-8");
17325
+ if (existing.includes("code-intel search") || existing.includes("code-intel inspect")) {
17326
+ return { result: "already-present" };
17327
+ }
17328
+ fs39.appendFileSync(filePath, "\n---\n\n" + CODE_INTEL_RULES_CONTENT);
17329
+ return { result: "installed" };
17330
+ }
17331
+ fs39.writeFileSync(filePath, CODE_INTEL_RULES_CONTENT, "utf-8");
17332
+ return { result: "installed" };
17333
+ } catch (err) {
17334
+ return { result: "skipped", reason: `Could not write ${agentName} rules: ${err instanceof Error ? err.message : String(err)}` };
17335
+ }
17336
+ }
17337
+ function cursorHookAlreadyPresent(root) {
17338
+ const hooks = root?.hooks;
17339
+ const preToolUse = hooks?.preToolUse;
17340
+ if (!Array.isArray(preToolUse)) return false;
17341
+ return preToolUse.some((entry) => {
17342
+ const cmd = entry?.command;
17343
+ return typeof cmd === "string" && (cmd.includes("code-intel-hook") || cmd.includes("code-intel hook"));
17344
+ });
17345
+ }
17346
+ function installCursorHook() {
17347
+ const cursorDir = path39.join(os18.homedir(), ".cursor");
17348
+ const hooksPath = path39.join(cursorDir, "hooks.json");
17349
+ let root = { version: 1 };
17350
+ if (fs39.existsSync(hooksPath)) {
17351
+ try {
17352
+ const raw = fs39.readFileSync(hooksPath, "utf-8");
17353
+ if (raw.trim()) root = JSON.parse(raw);
17354
+ } catch (err) {
17355
+ return { result: "skipped", reason: `Could not parse ~/.cursor/hooks.json: ${err instanceof Error ? err.message : String(err)}` };
17356
+ }
17357
+ } else if (!fs39.existsSync(cursorDir)) {
17358
+ return { result: "skipped", reason: "Cursor not installed (~/.cursor directory not found)" };
17359
+ }
17360
+ if (cursorHookAlreadyPresent(root)) return { result: "already-present" };
17361
+ if (fs39.existsSync(hooksPath)) {
17362
+ try {
17363
+ fs39.copyFileSync(hooksPath, `${hooksPath}.bak`);
17364
+ } catch {
17365
+ }
17366
+ }
17367
+ const existingHooks = root.hooks ?? {};
17368
+ const existingPreToolUse = Array.isArray(existingHooks.preToolUse) ? existingHooks.preToolUse : [];
17369
+ const newEntry = { command: CODE_INTEL_CURSOR_HOOK_CMD, matcher: "Shell" };
17370
+ const updated = { ...root, hooks: { ...existingHooks, preToolUse: [newEntry, ...existingPreToolUse] } };
17371
+ try {
17372
+ fs39.mkdirSync(cursorDir, { recursive: true });
17373
+ const tmp = `${hooksPath}.tmp.${Date.now()}`;
17374
+ fs39.writeFileSync(tmp, JSON.stringify(updated, null, 2) + "\n", "utf-8");
17375
+ fs39.renameSync(tmp, hooksPath);
17376
+ } catch (err) {
17377
+ return { result: "skipped", reason: `Could not write ~/.cursor/hooks.json: ${err instanceof Error ? err.message : String(err)}` };
17378
+ }
17379
+ return { result: "installed" };
17380
+ }
17381
+ function geminiHookAlreadyPresent(root) {
17382
+ const hooks = root?.hooks;
17383
+ const beforeTool = hooks?.BeforeTool;
17384
+ if (!Array.isArray(beforeTool)) return false;
17385
+ return beforeTool.some((entry) => {
17386
+ const entryHooks = entry?.hooks;
17387
+ if (!Array.isArray(entryHooks)) return false;
17388
+ return entryHooks.some((h) => {
17389
+ const cmd = h?.command;
17390
+ return typeof cmd === "string" && (cmd.includes("code-intel-hook") || cmd.includes("code-intel hook"));
17391
+ });
17392
+ });
17393
+ }
17394
+ function installGeminiHook() {
17395
+ const geminiDir = path39.join(os18.homedir(), ".gemini");
17396
+ const settingsPath = path39.join(geminiDir, "settings.json");
17397
+ if (!fs39.existsSync(geminiDir)) {
17398
+ return { result: "skipped", reason: "Gemini CLI not installed (~/.gemini directory not found)" };
17399
+ }
17400
+ let root = {};
17401
+ if (fs39.existsSync(settingsPath)) {
17402
+ try {
17403
+ const raw = fs39.readFileSync(settingsPath, "utf-8");
17404
+ if (raw.trim()) root = JSON.parse(raw);
17405
+ } catch (err) {
17406
+ return { result: "skipped", reason: `Could not parse ~/.gemini/settings.json: ${err instanceof Error ? err.message : String(err)}` };
17407
+ }
17408
+ }
17409
+ if (geminiHookAlreadyPresent(root)) return { result: "already-present" };
17410
+ if (fs39.existsSync(settingsPath)) {
17411
+ try {
17412
+ fs39.copyFileSync(settingsPath, `${settingsPath}.bak`);
17413
+ } catch {
17414
+ }
17415
+ }
17416
+ const existingHooks = root.hooks ?? {};
17417
+ const existingBeforeTool = Array.isArray(existingHooks.BeforeTool) ? existingHooks.BeforeTool : [];
17418
+ const newEntry = {
17419
+ matcher: "run_shell_command",
17420
+ hooks: [{ type: "command", command: CODE_INTEL_GEMINI_HOOK_CMD }]
17421
+ };
17422
+ const updated = { ...root, hooks: { ...existingHooks, BeforeTool: [newEntry, ...existingBeforeTool] } };
17423
+ try {
17424
+ const tmp = `${settingsPath}.tmp.${Date.now()}`;
17425
+ fs39.writeFileSync(tmp, JSON.stringify(updated, null, 2) + "\n", "utf-8");
17426
+ fs39.renameSync(tmp, settingsPath);
17427
+ } catch (err) {
17428
+ return { result: "skipped", reason: `Could not write ~/.gemini/settings.json: ${err instanceof Error ? err.message : String(err)}` };
17429
+ }
17430
+ return { result: "installed" };
17431
+ }
16709
17432
  program.command("setup").description("Configure MCP server for your editors (one-time setup)").option("--completion", "Auto-install shell completion for the detected shell").addHelpText("after", `
16710
17433
  Configure the code-intel MCP server for Claude Desktop, VS Code, or any
16711
17434
  editor that supports the Model Context Protocol.
@@ -16761,8 +17484,129 @@ program.command("setup").description("Configure MCP server for your editors (one
16761
17484
  console.log(" " + JSON.stringify({ servers: { "code-intel": { type: "stdio", command: "npx", args: ["code-intel", "mcp", "."] } } }, null, 2).split("\n").join("\n "));
16762
17485
  console.log('\n To verify in VS Code: open Command Palette \u2192 "MCP: List Servers" and confirm code-intel is Running.');
16763
17486
  console.log("\n Next: run `code-intel analyze` inside your project to build the knowledge graph.\n");
17487
+ const hookResult = installClaudeHook();
17488
+ if (hookResult.result === "installed") {
17489
+ const backupPath = path39.join(os18.homedir(), ".claude", "settings.json.bak");
17490
+ console.log("\n \u2705 Claude Code hook installed");
17491
+ console.log(" grep/rg/cat on source files \u2192 code-intel search/inspect");
17492
+ if (fs39.existsSync(backupPath)) {
17493
+ console.log(` Backup: ${backupPath}`);
17494
+ }
17495
+ console.log("\n \u21BA Restart Claude Code for the hook to take effect.");
17496
+ } else if (hookResult.result === "already-present") {
17497
+ console.log("\n \u2705 Claude Code hook already installed");
17498
+ } else {
17499
+ logger_default.warn(`
17500
+ \u26A0 Claude Code hook skipped: ${hookResult.reason}`);
17501
+ console.log("\n Add manually to ~/.claude/settings.json under hooks.PreToolUse[]:");
17502
+ console.log(" (insert as the FIRST entry so it runs before any existing hooks)\n");
17503
+ console.log(" " + JSON.stringify({
17504
+ matcher: "Bash",
17505
+ hooks: [{ type: "command", command: CODE_INTEL_HOOK_CMD }]
17506
+ }, null, 2).split("\n").join("\n "));
17507
+ }
17508
+ const cursorResult = installCursorHook();
17509
+ if (cursorResult.result === "installed") {
17510
+ console.log("\n \u2705 Cursor hook installed");
17511
+ console.log(" grep/rg/cat on source files \u2192 code-intel search/inspect");
17512
+ } else if (cursorResult.result === "already-present") {
17513
+ console.log("\n \u2705 Cursor hook already installed");
17514
+ } else {
17515
+ console.log(`
17516
+ \u2139 Cursor hook skipped: ${cursorResult.reason}`);
17517
+ }
17518
+ const geminiResult = installGeminiHook();
17519
+ if (geminiResult.result === "installed") {
17520
+ console.log("\n \u2705 Gemini CLI hook installed");
17521
+ console.log(" grep/rg/cat on source files \u2192 code-intel search/inspect");
17522
+ } else if (geminiResult.result === "already-present") {
17523
+ console.log("\n \u2705 Gemini CLI hook already installed");
17524
+ } else {
17525
+ console.log(`
17526
+ \u2139 Gemini CLI hook skipped: ${geminiResult.reason}`);
17527
+ }
17528
+ const copilotResult = installCopilotHook(process.cwd());
17529
+ if (copilotResult.result === "installed") {
17530
+ console.log("\n \u2705 GitHub Copilot hook installed (.github/hooks/code-intel-rewrite.json)");
17531
+ console.log(" grep/rg/cat on source files \u2192 code-intel search/inspect");
17532
+ } else if (copilotResult.result === "already-present") {
17533
+ console.log("\n \u2705 GitHub Copilot hook already installed");
17534
+ } else {
17535
+ console.log(`
17536
+ \u2139 GitHub Copilot hook skipped: ${copilotResult.reason}`);
17537
+ }
17538
+ const openCodeResult = installOpenCodePlugin();
17539
+ if (openCodeResult.result === "installed") {
17540
+ console.log("\n \u2705 OpenCode plugin installed (~/.config/opencode/plugins/code-intel.ts)");
17541
+ console.log(" grep/rg/cat on source files \u2192 code-intel search/inspect");
17542
+ } else if (openCodeResult.result === "already-present") {
17543
+ console.log("\n \u2705 OpenCode plugin already installed");
17544
+ } else {
17545
+ console.log(`
17546
+ \u2139 OpenCode plugin skipped: ${openCodeResult.reason}`);
17547
+ }
17548
+ const openClawResult = installOpenClawPlugin();
17549
+ if (openClawResult.result === "installed") {
17550
+ console.log("\n \u2705 OpenClaw plugin installed (~/.openclaw/extensions/code-intel/)");
17551
+ console.log(" grep/rg/cat on source files \u2192 code-intel search/inspect");
17552
+ } else if (openClawResult.result === "already-present") {
17553
+ console.log("\n \u2705 OpenClaw plugin already installed");
17554
+ } else {
17555
+ console.log(`
17556
+ \u2139 OpenClaw plugin skipped: ${openClawResult.reason}`);
17557
+ }
17558
+ console.log("\n \u2500\u2500\u2500 Prompt-level agents (rules files) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
17559
+ const cwd = process.cwd();
17560
+ const clineResult = installRulesFile(path39.join(cwd, ".clinerules"), "Cline");
17561
+ if (clineResult.result === "installed") {
17562
+ console.log(" \u2705 Cline/Roo Code rules installed (.clinerules)");
17563
+ } else if (clineResult.result === "already-present") {
17564
+ console.log(" \u2705 Cline/Roo Code rules already present (.clinerules)");
17565
+ } else {
17566
+ console.log(` \u2139 Cline/Roo Code rules skipped: ${clineResult.reason}`);
17567
+ }
17568
+ const windsurfResult = installRulesFile(path39.join(cwd, ".windsurfrules"), "Windsurf");
17569
+ if (windsurfResult.result === "installed") {
17570
+ console.log(" \u2705 Windsurf rules installed (.windsurfrules)");
17571
+ } else if (windsurfResult.result === "already-present") {
17572
+ console.log(" \u2705 Windsurf rules already present (.windsurfrules)");
17573
+ } else {
17574
+ console.log(` \u2139 Windsurf rules skipped: ${windsurfResult.reason}`);
17575
+ }
17576
+ const kiloResult = installRulesFile(
17577
+ path39.join(cwd, ".kilocode", "rules", "code-intel-rules.md"),
17578
+ "Kilo Code"
17579
+ );
17580
+ if (kiloResult.result === "installed") {
17581
+ console.log(" \u2705 Kilo Code rules installed (.kilocode/rules/code-intel-rules.md)");
17582
+ } else if (kiloResult.result === "already-present") {
17583
+ console.log(" \u2705 Kilo Code rules already present");
17584
+ } else {
17585
+ console.log(` \u2139 Kilo Code rules skipped: ${kiloResult.reason}`);
17586
+ }
17587
+ const antigravityResult = installRulesFile(
17588
+ path39.join(cwd, ".agents", "rules", "code-intel-rules.md"),
17589
+ "Antigravity"
17590
+ );
17591
+ if (antigravityResult.result === "installed") {
17592
+ console.log(" \u2705 Google Antigravity rules installed (.agents/rules/code-intel-rules.md)");
17593
+ } else if (antigravityResult.result === "already-present") {
17594
+ console.log(" \u2705 Google Antigravity rules already present");
17595
+ } else {
17596
+ console.log(` \u2139 Google Antigravity rules skipped: ${antigravityResult.reason}`);
17597
+ }
17598
+ const codexResult = installRulesFile(path39.join(cwd, "AGENTS.md"), "Codex");
17599
+ if (codexResult.result === "installed") {
17600
+ console.log(" \u2705 Codex CLI rules appended (AGENTS.md)");
17601
+ } else if (codexResult.result === "already-present") {
17602
+ console.log(" \u2705 Codex CLI rules already present (AGENTS.md)");
17603
+ } else {
17604
+ console.log(` \u2139 Codex CLI rules skipped: ${codexResult.reason}`);
17605
+ }
17606
+ console.log("\n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
17607
+ console.log("\n \u25C8 Setup complete. Run `code-intel analyze` to build the knowledge graph.\n");
16764
17608
  });
16765
- program.command("analyze").description("Index a repository and build the knowledge graph").argument("[path]", "Path to the repository (default: current directory)", ".").option("--force", "Force full re-index, ignoring cached data").option("--incremental", "Only re-parse files changed since last analysis (git diff or mtime)").option("--parallel", "Use worker threads for parse + resolve phases (faster on multi-core)").option("--skills", "Generate .claude/skills/ SKILL.md files from detected clusters").option("--embeddings", "Build vector embeddings for semantic search (slower, recommended)").option("--skip-embeddings", "Skip embedding generation (faster, text-search only)").option("--skip-agents-md", "Preserve any custom edits inside AGENTS.md / CLAUDE.md").option("--skip-git", "Allow indexing directories that are not Git repositories").option("--verbose", "Log every file skipped due to missing parser support").option("--summarize", "Generate AI summaries for function/class/method/interface nodes (opt-in)").option("--llm-provider <provider>", "LLM provider for --summarize: openai | anthropic | ollama (default: ollama)").option("--llm-model <model>", "LLM model name (e.g. gpt-4o-mini, claude-haiku-4-5, llama3)").option("--llm-batch-size <n>", "Concurrent LLM calls per batch (default: 20)", "20").option("--llm-max-nodes <n>", "Max nodes to summarize per run (cost guard)").option("--no-group-sync", "Skip automatic group sync after analysis").option("--dry-run", "Preview files that would be scanned + estimated time; no DB write").option("--max-memory <MB>", "Limit graph memory (MB); spill node content to free RAM when exceeded").option("--profile", "Write per-phase profiling data to .code-intel/profile.json").addHelpText("after", `
17609
+ program.command("analyze").description("Index a repository and build the knowledge graph").argument("[path]", "Path to the repository (default: current directory)", ".").option("--force", "Force full re-index, ignoring cached data").option("--incremental", "Only re-parse files changed since last analysis (git diff or mtime)").option("--parallel", "Use worker threads for parse + resolve phases (faster on multi-core)").option("--skills", "Generate .claude/skills/ SKILL.md files from detected clusters").option("--embeddings", "Build vector embeddings for semantic search (slower, recommended)").option("--skip-embeddings", "Skip embedding generation (faster, text-search only)").option("--skip-agents-md", "Preserve any custom edits inside AGENTS.md / CLAUDE.md").option("--skip-git", "Allow indexing directories that are not Git repositories").option("--verbose", "Log every file skipped due to missing parser support").option("--summarize", "Generate AI summaries for function/class/method/interface nodes (opt-in)").option("--llm-provider <provider>", "LLM provider for --summarize: openai | anthropic | ollama | custom (default: ollama)").option("--llm-model <model>", "LLM model name (e.g. gpt-4o-mini, claude-haiku-4-5, llama3, mistral)").option("--llm-base-url <url>", "Base URL for custom OpenAI-compatible API (e.g. http://localhost:1234/v1)").option("--llm-api-key <key>", "API key/token for the LLM provider (custom or override)").option("--llm-batch-size <n>", "Concurrent LLM calls per batch (default: 20)", "20").option("--llm-max-nodes <n>", "Max nodes to summarize per run (cost guard)").option("--no-group-sync", "Skip automatic group sync after analysis").option("--dry-run", "Preview files that would be scanned + estimated time; no DB write").option("--max-memory <MB>", "Limit graph memory (MB); spill node content to free RAM when exceeded").option("--profile", "Write per-phase profiling data to .code-intel/profile.json").addHelpText("after", `
16766
17610
  Parses your source code with tree-sitter, builds a Knowledge Graph of
16767
17611
  symbols and their relationships, persists it to .code-intel/graph.db,
16768
17612
  and auto-generates AGENTS.md + CLAUDE.md context blocks.
@@ -16782,6 +17626,7 @@ program.command("analyze").description("Index a repository and build the knowled
16782
17626
  $ code-intel analyze --summarize Generate AI summaries (uses Ollama by default)
16783
17627
  $ code-intel analyze --summarize --llm-provider openai --llm-model gpt-4o-mini
16784
17628
  $ code-intel analyze --summarize --llm-provider anthropic --llm-max-nodes 500
17629
+ $ code-intel analyze --summarize --llm-provider custom --llm-base-url http://localhost:1234/v1 --llm-model mistral --llm-api-key mytoken
16785
17630
  $ code-intel analyze --dry-run Preview files that would be scanned
16786
17631
  `).action(async (targetPath, opts) => {
16787
17632
  if (opts.dryRun) {
@@ -16828,6 +17673,8 @@ program.command("analyze").description("Index a repository and build the knowled
16828
17673
  const v = parseInt(opts.llmMaxNodes ?? "", 10);
16829
17674
  return Number.isFinite(v) && v >= 1 ? v : void 0;
16830
17675
  })(),
17676
+ llmBaseUrl: opts.llmBaseUrl,
17677
+ llmApiKey: opts.llmApiKey,
16831
17678
  maxMemoryMB: (() => {
16832
17679
  const v = parseInt(opts.maxMemory ?? "", 10);
16833
17680
  return Number.isFinite(v) && v >= 1 ? v : void 0;
@@ -16867,11 +17714,18 @@ program.command("mcp").description("Start MCP server over stdio \u2014 exposes a
16867
17714
  await startMcpStdio(graph, name, root);
16868
17715
  }
16869
17716
  });
16870
- program.command("serve").description("Start the local HTTP server + web UI for graph exploration").argument("[path]", "Path to analyze (default: current directory)", ".").option("-p, --port <port>", "Port to listen on", "4747").option("--force", "Force re-analysis even if an index already exists").addHelpText("after", `
17717
+ program.command("serve").description("Start the local HTTP server + web UI for graph exploration").argument("[path]", "Path to analyze (default: current directory)", ".").option("-p, --port <port>", "Port to listen on", "4747").option("--force", "Force re-analysis even if an index already exists").option("-d, --detach", "Run server in background (detached); PID written to .code-intel/serve.pid").addHelpText("after", `
16871
17718
  If a .code-intel/graph.db index already exists for the path, the server
16872
17719
  loads the persisted graph directly and starts immediately \u2014 no re-analysis.
16873
17720
  Use --force to discard the existing index and re-analyze from scratch.
16874
17721
 
17722
+ Background mode:
17723
+ $ code-intel serve --detach # start in background
17724
+ $ code-intel serve --detach --port 8080
17725
+ $ code-intel stop # stop the background server
17726
+
17727
+ PID file: <path>/.code-intel/serve.pid
17728
+
16875
17729
  The web UI offers:
16876
17730
  \xB7 Force-directed Knowledge Graph with color-coded node types
16877
17731
  \xB7 BM25 text search + optional semantic (vector) search
@@ -16884,6 +17738,7 @@ program.command("serve").description("Start the local HTTP server + web UI for g
16884
17738
  $ code-intel serve ./my-project
16885
17739
  $ code-intel serve --port 8080
16886
17740
  $ code-intel serve --force
17741
+ $ code-intel serve --detach
16887
17742
  `).action(async (targetPath, options) => {
16888
17743
  const workspaceRoot = path39.resolve(targetPath);
16889
17744
  const repoName = path39.basename(workspaceRoot);
@@ -16892,6 +17747,44 @@ program.command("serve").description("Start the local HTTP server + web UI for g
16892
17747
  console.error(` \u2717 Path does not exist: ${workspaceRoot}`);
16893
17748
  process.exit(1);
16894
17749
  }
17750
+ if (options.detach) {
17751
+ const { spawn } = await import('child_process');
17752
+ const pidDir = path39.join(workspaceRoot, ".code-intel");
17753
+ const pidFile = path39.join(pidDir, "serve.pid");
17754
+ if (fs39.existsSync(pidFile)) {
17755
+ const existingPid = parseInt(fs39.readFileSync(pidFile, "utf-8").trim(), 10);
17756
+ try {
17757
+ process.kill(existingPid, 0);
17758
+ console.log(` \u26A0 Server already running (PID ${existingPid}). Stop it first with: code-intel stop`);
17759
+ process.exit(0);
17760
+ } catch {
17761
+ fs39.unlinkSync(pidFile);
17762
+ }
17763
+ }
17764
+ const selfArgs = ["serve", targetPath, "--port", options.port];
17765
+ if (options.force) selfArgs.push("--force");
17766
+ const logDir = path39.join(os18.homedir(), ".code-intel", "logs");
17767
+ fs39.mkdirSync(logDir, { recursive: true });
17768
+ const logFile = path39.join(logDir, `serve-${Date.now()}.log`);
17769
+ const logFd = fs39.openSync(logFile, "a");
17770
+ const child = spawn(process.execPath, [process.argv[1], ...selfArgs], {
17771
+ detached: true,
17772
+ stdio: ["ignore", logFd, logFd],
17773
+ env: process.env
17774
+ });
17775
+ child.unref();
17776
+ fs39.mkdirSync(pidDir, { recursive: true });
17777
+ fs39.writeFileSync(pidFile, String(child.pid), "utf-8");
17778
+ console.log(` \u2705 Server started in background`);
17779
+ console.log(` PID : ${child.pid}`);
17780
+ console.log(` Port : ${options.port}`);
17781
+ console.log(` URL : http://localhost:${options.port}`);
17782
+ console.log(` Log : ${logFile}`);
17783
+ console.log(` PID file : ${pidFile}`);
17784
+ console.log(`
17785
+ Stop with: code-intel stop`);
17786
+ process.exit(0);
17787
+ }
16895
17788
  const existingIndex = !options.force && fs39.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
16896
17789
  if (existingIndex) {
16897
17790
  const meta = loadMetadata(workspaceRoot);
@@ -16911,10 +17804,81 @@ program.command("serve").description("Start the local HTTP server + web UI for g
16911
17804
  await startHttpServer(lazyGraph, repoName, parseInt(options.port, 10), workspaceRoot);
16912
17805
  }
16913
17806
  } else {
16914
- logger_default.warn(` [serve] No index found for: ${workspaceRoot}`);
16915
- logger_default.warn(` [serve] Run \`code-intel analyze\` first, then re-run \`code-intel serve\`.`);
17807
+ console.log(` \u2139 No index found for: ${workspaceRoot}`);
17808
+ const registry = loadRegistry();
17809
+ const available = registry.filter((r) => fs39.existsSync(getDbPath(r.path)) && loadMetadata(r.path) !== null).sort((a, b) => new Date(b.indexedAt).getTime() - new Date(a.indexedAt).getTime());
17810
+ if (available.length > 0) {
17811
+ const fallback = available[0];
17812
+ console.log(` \u25C8 Falling back to most recently indexed repo: ${fallback.path}`);
17813
+ console.log(` (${fallback.stats.nodes} nodes \xB7 ${fallback.stats.edges} edges \xB7 indexed ${fallback.indexedAt})`);
17814
+ console.log(` \u2139 To index this folder run: code-intel analyze
17815
+ `);
17816
+ const fallbackMeta = loadMetadata(fallback.path);
17817
+ const fallbackDbPath = getDbPath(fallback.path);
17818
+ if (fallbackMeta.parser === "regex" || fallbackMeta.parser === void 0) {
17819
+ console.log(` \u26A0 Fallback index was built with regex parser. Starting with empty graph.`);
17820
+ const emptyGraph = createKnowledgeGraph();
17821
+ await startHttpServer(emptyGraph, fallback.name, parseInt(options.port, 10), fallback.path);
17822
+ } else {
17823
+ const lazyGraph = new LazyKnowledgeGraph();
17824
+ const db = new DbManager(fallbackDbPath, true);
17825
+ await db.init();
17826
+ await lazyGraph.init(db, fallbackMeta.stats.nodes, fallbackMeta.stats.edges);
17827
+ setImmediate(() => lazyGraph.warmTopNodes(500).catch(() => {
17828
+ }));
17829
+ await startHttpServer(lazyGraph, fallback.name, parseInt(options.port, 10), fallback.path);
17830
+ }
17831
+ if (available.length > 1) {
17832
+ console.log(`
17833
+ Other indexed repos available (switch via web UI or re-run serve with path):`);
17834
+ for (const r of available.slice(1)) {
17835
+ console.log(` code-intel serve ${r.path}`);
17836
+ }
17837
+ }
17838
+ } else {
17839
+ console.log(` \u2139 No indexed repositories found. Starting server with empty graph.`);
17840
+ console.log(` Run \`code-intel analyze\` to index a repository, then reload the UI.
17841
+ `);
17842
+ const emptyGraph = createKnowledgeGraph();
17843
+ await startHttpServer(emptyGraph, repoName, parseInt(options.port, 10), workspaceRoot);
17844
+ }
17845
+ }
17846
+ });
17847
+ program.command("stop").description("Stop a background server started with `code-intel serve --detach`").argument("[path]", "Project path whose server to stop (default: current directory)", ".").addHelpText("after", `
17848
+ Reads the PID from <path>/.code-intel/serve.pid and sends SIGTERM.
17849
+ The PID file is removed automatically after a successful stop.
17850
+
17851
+ Examples:
17852
+ $ code-intel stop
17853
+ $ code-intel stop ./my-project
17854
+ `).action((targetPath) => {
17855
+ const workspaceRoot = path39.resolve(targetPath);
17856
+ const pidFile = path39.join(workspaceRoot, ".code-intel", "serve.pid");
17857
+ if (!fs39.existsSync(pidFile)) {
17858
+ console.log(` \u2139 No background server found for: ${workspaceRoot}`);
17859
+ console.log(` (PID file not present: ${pidFile})`);
17860
+ process.exit(0);
17861
+ }
17862
+ const pid = parseInt(fs39.readFileSync(pidFile, "utf-8").trim(), 10);
17863
+ if (isNaN(pid)) {
17864
+ console.error(` \u2717 Invalid PID file: ${pidFile}`);
17865
+ fs39.unlinkSync(pidFile);
16916
17866
  process.exit(1);
16917
17867
  }
17868
+ try {
17869
+ process.kill(pid, "SIGTERM");
17870
+ fs39.unlinkSync(pidFile);
17871
+ console.log(` \u2705 Stopped server (PID ${pid})`);
17872
+ } catch (err) {
17873
+ const code = err.code;
17874
+ if (code === "ESRCH") {
17875
+ fs39.unlinkSync(pidFile);
17876
+ console.log(` \u2139 Server (PID ${pid}) was not running. Stale PID file removed.`);
17877
+ } else {
17878
+ console.error(` \u2717 Failed to stop server (PID ${pid}): ${err.message}`);
17879
+ process.exit(1);
17880
+ }
17881
+ }
16918
17882
  });
16919
17883
  program.command("watch").description("Start HTTP server + file watcher (auto-reindex on file changes)").argument("[path]", "Path to watch (default: current directory)", ".").option("-p, --port <port>", "Port to listen on", "4747").option("--force", "Force re-analysis even if an index already exists").addHelpText("after", `
16920
17884
  Starts the HTTP server and automatically re-indexes changed files.
@@ -18079,7 +19043,7 @@ program.command("migrate").description("Manage database schema migrations").opti
18079
19043
  $ code-intel migrate
18080
19044
  $ code-intel migrate --rollback
18081
19045
  `).action((opts) => {
18082
- const dbPath = opts.db ?? path39.join(os13.homedir(), ".code-intel", "users.db");
19046
+ const dbPath = opts.db ?? path39.join(os18.homedir(), ".code-intel", "users.db");
18083
19047
  if (!fs39.existsSync(dbPath)) {
18084
19048
  console.error(`
18085
19049
  \u2717 Database not found: ${dbPath}
@@ -18195,7 +19159,7 @@ authCmd.command("login").description("Authenticate via OIDC device flow \u2014 o
18195
19159
  }
18196
19160
  try {
18197
19161
  const tokens = await pollDeviceFlow3(config, deviceResponse);
18198
- const tokenPath = path39.join(os13.homedir(), ".code-intel", "oidc-token.json");
19162
+ const tokenPath = path39.join(os18.homedir(), ".code-intel", "oidc-token.json");
18199
19163
  const tokenData = {
18200
19164
  accessToken: tokens.accessToken,
18201
19165
  refreshToken: tokens.refreshToken,
@@ -18216,7 +19180,7 @@ authCmd.command("login").description("Authenticate via OIDC device flow \u2014 o
18216
19180
  }
18217
19181
  });
18218
19182
  authCmd.command("status").description("Show the current OIDC authentication status").action(() => {
18219
- const tokenPath = path39.join(os13.homedir(), ".code-intel", "oidc-token.json");
19183
+ const tokenPath = path39.join(os18.homedir(), ".code-intel", "oidc-token.json");
18220
19184
  if (!fs39.existsSync(tokenPath)) {
18221
19185
  console.log("\n Not authenticated via OIDC. Run: code-intel auth login\n");
18222
19186
  return;
@@ -18234,7 +19198,7 @@ authCmd.command("status").description("Show the current OIDC authentication stat
18234
19198
  }
18235
19199
  });
18236
19200
  authCmd.command("logout").description("Remove locally stored OIDC token").action(() => {
18237
- const tokenPath = path39.join(os13.homedir(), ".code-intel", "oidc-token.json");
19201
+ const tokenPath = path39.join(os18.homedir(), ".code-intel", "oidc-token.json");
18238
19202
  if (fs39.existsSync(tokenPath)) {
18239
19203
  fs39.unlinkSync(tokenPath);
18240
19204
  console.log("\n \u2705 OIDC token removed. You are now logged out.\n");
@@ -18390,7 +19354,7 @@ ${err instanceof Error ? err.message : err}
18390
19354
  });
18391
19355
  (function ensurePermissions() {
18392
19356
  try {
18393
- const dir = path39.join(os13.homedir(), ".code-intel");
19357
+ const dir = path39.join(os18.homedir(), ".code-intel");
18394
19358
  secureMkdir(dir);
18395
19359
  tightenDbFiles(dir);
18396
19360
  } catch {
@@ -19195,6 +20159,35 @@ program.command("context").description("Build a token-efficient context document
19195
20159
  console.log("");
19196
20160
  }
19197
20161
  });
20162
+ {
20163
+ const rewriteCmd = new Command("rewrite").description("Rewrite a shell command to its code-intel equivalent (used by hooks)").argument("<cmd>", "Raw shell command to check").addHelpText("after", `
20164
+ Exit codes:
20165
+ 0 Rewrite found \u2014 rewritten command printed to stdout
20166
+ 1 No code-intel equivalent \u2014 pass through unchanged
20167
+
20168
+ Examples:
20169
+ $ code-intel rewrite 'grep "AuthService" src/'
20170
+ code-intel search "AuthService"
20171
+
20172
+ $ code-intel rewrite 'git status'
20173
+ (no output, exit 1)
20174
+ `).action((cmd) => {
20175
+ runRewrite(cmd);
20176
+ });
20177
+ program.addCommand(rewriteCmd, { hidden: true });
20178
+ }
20179
+ {
20180
+ const hookCmd = new Command("hook").description("Run as a PreToolUse hook for an AI agent (reads/writes JSON on stdin/stdout)").argument("<agent>", "Agent type: claude").action((agent) => {
20181
+ if (agent === "claude") {
20182
+ runClaudeHook();
20183
+ } else {
20184
+ process.stderr.write(`[code-intel] Unknown hook agent: ${agent}
20185
+ `);
20186
+ process.exit(0);
20187
+ }
20188
+ });
20189
+ program.addCommand(hookCmd, { hidden: true });
20190
+ }
19198
20191
  program.parse();
19199
20192
  //# sourceMappingURL=main.js.map
19200
20193
  //# sourceMappingURL=main.js.map