@n1creator/openacp-cli 2026.712.4 → 2026.712.5

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/index.js CHANGED
@@ -328,22 +328,22 @@ __export(config_registry_exports, {
328
328
  resolveOptions: () => resolveOptions,
329
329
  setFieldValueAsync: () => setFieldValueAsync
330
330
  });
331
- function getFieldDef(path35) {
332
- return CONFIG_REGISTRY.find((f) => f.path === path35);
331
+ function getFieldDef(path36) {
332
+ return CONFIG_REGISTRY.find((f) => f.path === path36);
333
333
  }
334
334
  function getSafeFields() {
335
335
  return CONFIG_REGISTRY.filter((f) => f.scope === "safe");
336
336
  }
337
- function isHotReloadable(path35) {
338
- const def = getFieldDef(path35);
337
+ function isHotReloadable(path36) {
338
+ const def = getFieldDef(path36);
339
339
  return def?.hotReload ?? false;
340
340
  }
341
341
  function resolveOptions(def, config) {
342
342
  if (!def.options) return void 0;
343
343
  return typeof def.options === "function" ? def.options(config) : def.options;
344
344
  }
345
- function getConfigValue(config, path35) {
346
- const parts = path35.split(".");
345
+ function getConfigValue(config, path36) {
346
+ const parts = path36.split(".");
347
347
  let current = config;
348
348
  for (const part of parts) {
349
349
  if (current && typeof current === "object" && part in current) {
@@ -674,6 +674,14 @@ var init_config = __esm({
674
674
  { envVar: "OPENACP_API_PORT", pluginName: "@openacp/api-server", key: "port", transform: (v) => Number(v) },
675
675
  { envVar: "OPENACP_SPEECH_STT_PROVIDER", pluginName: "@openacp/speech", key: "sttProvider" },
676
676
  { envVar: "OPENACP_SPEECH_GROQ_API_KEY", pluginName: "@openacp/speech", key: "groqApiKey" },
677
+ { envVar: "OPENACP_SPEECH_LOCAL_WHISPER_LANGUAGE", pluginName: "@openacp/speech", key: "localWhisperLanguage" },
678
+ { envVar: "OPENACP_SPEECH_LOCAL_WHISPER_MODEL", pluginName: "@openacp/speech", key: "localWhisperModel" },
679
+ { envVar: "OPENACP_SPEECH_LOCAL_WHISPER_BEAM_SIZE", pluginName: "@openacp/speech", key: "localWhisperBeamSize", transform: (v) => Number(v) },
680
+ { envVar: "OPENACP_SPEECH_LOCAL_WHISPER_VAD_FILTER", pluginName: "@openacp/speech", key: "localWhisperVadFilter", transform: (v) => v === "true" },
681
+ { envVar: "OPENACP_SPEECH_LOCAL_WHISPER_DEVICE", pluginName: "@openacp/speech", key: "localWhisperDevice" },
682
+ { envVar: "OPENACP_SPEECH_LOCAL_WHISPER_COMPUTE_TYPE", pluginName: "@openacp/speech", key: "localWhisperComputeType" },
683
+ { envVar: "OPENACP_SPEECH_LOCAL_WHISPER_TIMEOUT_MS", pluginName: "@openacp/speech", key: "localWhisperTimeoutMs", transform: (v) => Number(v) },
684
+ { envVar: "OPENACP_SPEECH_LOCAL_WHISPER_SCRIPT_PATH", pluginName: "@openacp/speech", key: "localWhisperScriptPath" },
677
685
  { envVar: "OPENACP_TELEGRAM_BOT_TOKEN", pluginName: "@openacp/telegram", key: "botToken" },
678
686
  { envVar: "OPENACP_TELEGRAM_CHAT_ID", pluginName: "@openacp/telegram", key: "chatId", transform: (v) => Number(v) },
679
687
  // Future adapters — no-ops if plugin settings don't exist
@@ -1525,6 +1533,166 @@ var init_agent_installer = __esm({
1525
1533
  }
1526
1534
  });
1527
1535
 
1536
+ // src/plugins/speech/providers/groq.ts
1537
+ function mimeToExt(mimeType) {
1538
+ const map = {
1539
+ "audio/ogg": ".ogg",
1540
+ "audio/wav": ".wav",
1541
+ "audio/mpeg": ".mp3",
1542
+ "audio/mp4": ".m4a",
1543
+ "audio/webm": ".webm",
1544
+ "audio/flac": ".flac"
1545
+ };
1546
+ return map[mimeType] || ".bin";
1547
+ }
1548
+ var GROQ_API_URL, GroqSTT;
1549
+ var init_groq = __esm({
1550
+ "src/plugins/speech/providers/groq.ts"() {
1551
+ "use strict";
1552
+ GROQ_API_URL = "https://api.groq.com/openai/v1/audio/transcriptions";
1553
+ GroqSTT = class {
1554
+ constructor(apiKey, defaultModel = "whisper-large-v3-turbo") {
1555
+ this.apiKey = apiKey;
1556
+ this.defaultModel = defaultModel;
1557
+ }
1558
+ apiKey;
1559
+ defaultModel;
1560
+ name = "groq";
1561
+ /**
1562
+ * Transcribes audio using the Groq Whisper API.
1563
+ *
1564
+ * `verbose_json` response format is requested so the API returns language
1565
+ * detection and duration metadata alongside the transcript text.
1566
+ */
1567
+ async transcribe(audioBuffer, mimeType, options) {
1568
+ const ext = mimeToExt(mimeType);
1569
+ const form = new FormData();
1570
+ form.append("file", new Blob([new Uint8Array(audioBuffer)], { type: mimeType }), `audio${ext}`);
1571
+ form.append("model", options?.model || this.defaultModel);
1572
+ form.append("response_format", "verbose_json");
1573
+ if (options?.language) {
1574
+ form.append("language", options.language);
1575
+ }
1576
+ const resp = await fetch(GROQ_API_URL, {
1577
+ method: "POST",
1578
+ headers: { Authorization: `Bearer ${this.apiKey}` },
1579
+ body: form
1580
+ });
1581
+ if (!resp.ok) {
1582
+ const body = await resp.text();
1583
+ if (resp.status === 401) {
1584
+ throw new Error("Invalid Groq API key. Check your key at console.groq.com.");
1585
+ }
1586
+ if (resp.status === 413) {
1587
+ throw new Error("Audio file too large for Groq API (max 25MB).");
1588
+ }
1589
+ if (resp.status === 429) {
1590
+ throw new Error("Groq rate limit exceeded. Free tier: 28,800 seconds/day. Try again later.");
1591
+ }
1592
+ throw new Error(`Groq STT error (${resp.status}): ${body}`);
1593
+ }
1594
+ const data = await resp.json();
1595
+ return {
1596
+ text: data.text,
1597
+ language: data.language,
1598
+ duration: data.duration
1599
+ };
1600
+ }
1601
+ };
1602
+ }
1603
+ });
1604
+
1605
+ // src/plugins/speech/providers/local-whisper.ts
1606
+ import { execFile } from "child_process";
1607
+ import { existsSync as existsSync6 } from "fs";
1608
+ import { mkdtemp, rm, writeFile } from "fs/promises";
1609
+ import { tmpdir } from "os";
1610
+ import path14 from "path";
1611
+ import { fileURLToPath } from "url";
1612
+ import { promisify } from "util";
1613
+ function resolveLocalWhisperScriptPath() {
1614
+ const moduleDir = path14.dirname(fileURLToPath(import.meta.url));
1615
+ const candidates = [
1616
+ path14.resolve(moduleDir, "../scripts/transcribe_audio.sh"),
1617
+ path14.resolve(moduleDir, "speech/transcribe_audio.sh")
1618
+ ];
1619
+ return candidates.find(existsSync6) ?? candidates[0];
1620
+ }
1621
+ var execFileAsync, LOCAL_WHISPER_PROVIDER, LOCAL_WHISPER_DEFAULTS;
1622
+ var init_local_whisper = __esm({
1623
+ "src/plugins/speech/providers/local-whisper.ts"() {
1624
+ "use strict";
1625
+ execFileAsync = promisify(execFile);
1626
+ LOCAL_WHISPER_PROVIDER = "local-whisper";
1627
+ LOCAL_WHISPER_DEFAULTS = {
1628
+ language: "ru",
1629
+ model: "base",
1630
+ beamSize: 5,
1631
+ vadFilter: false,
1632
+ device: "cpu",
1633
+ computeType: "int8",
1634
+ timeoutMs: 12e4
1635
+ };
1636
+ }
1637
+ });
1638
+
1639
+ // src/plugins/speech/native-stt.ts
1640
+ function readLocalWhisperSettings(raw) {
1641
+ return {
1642
+ scriptPath: readString(raw.localWhisperScriptPath, resolveLocalWhisperScriptPath()),
1643
+ language: readString(raw.localWhisperLanguage, LOCAL_WHISPER_DEFAULTS.language),
1644
+ model: readString(raw.localWhisperModel, LOCAL_WHISPER_DEFAULTS.model),
1645
+ beamSize: readNumber(raw.localWhisperBeamSize, LOCAL_WHISPER_DEFAULTS.beamSize),
1646
+ vadFilter: readBoolean(raw.localWhisperVadFilter, LOCAL_WHISPER_DEFAULTS.vadFilter),
1647
+ device: readString(raw.localWhisperDevice, LOCAL_WHISPER_DEFAULTS.device),
1648
+ computeType: readString(raw.localWhisperComputeType, LOCAL_WHISPER_DEFAULTS.computeType),
1649
+ timeoutMs: readNumber(raw.localWhisperTimeoutMs, LOCAL_WHISPER_DEFAULTS.timeoutMs)
1650
+ };
1651
+ }
1652
+ function buildSpeechServiceConfig(raw) {
1653
+ const groqApiKey = readOptionalString(raw.groqApiKey);
1654
+ const requestedProvider = readOptionalString(raw.sttProvider);
1655
+ const provider = requestedProvider === LOCAL_WHISPER_PROVIDER ? LOCAL_WHISPER_PROVIDER : groqApiKey ? "groq" : null;
1656
+ const providers = {};
1657
+ if (groqApiKey) providers.groq = { apiKey: groqApiKey };
1658
+ if (provider === LOCAL_WHISPER_PROVIDER) {
1659
+ providers[LOCAL_WHISPER_PROVIDER] = {
1660
+ apiKey: "local",
1661
+ ...readLocalWhisperSettings(raw)
1662
+ };
1663
+ }
1664
+ return {
1665
+ stt: { provider, providers },
1666
+ tts: {
1667
+ provider: readOptionalString(raw.ttsProvider) ?? "edge-tts",
1668
+ providers: {}
1669
+ }
1670
+ };
1671
+ }
1672
+ function readString(value, fallback) {
1673
+ return readOptionalString(value) ?? fallback;
1674
+ }
1675
+ function readOptionalString(value) {
1676
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
1677
+ }
1678
+ function readNumber(value, fallback) {
1679
+ return readOptionalNumber(value) ?? fallback;
1680
+ }
1681
+ function readOptionalNumber(value) {
1682
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1683
+ }
1684
+ function readBoolean(value, fallback) {
1685
+ return typeof value === "boolean" ? value : fallback;
1686
+ }
1687
+ var init_native_stt = __esm({
1688
+ "src/plugins/speech/native-stt.ts"() {
1689
+ "use strict";
1690
+ init_groq();
1691
+ init_local_whisper();
1692
+ init_local_whisper();
1693
+ }
1694
+ });
1695
+
1528
1696
  // src/core/doctor/checks/config.ts
1529
1697
  import * as fs15 from "fs";
1530
1698
  var configCheck;
@@ -1589,7 +1757,7 @@ var init_config2 = __esm({
1589
1757
  // src/core/doctor/checks/agents.ts
1590
1758
  import { execFileSync as execFileSync3 } from "child_process";
1591
1759
  import * as fs16 from "fs";
1592
- import * as path15 from "path";
1760
+ import * as path16 from "path";
1593
1761
  function commandExists2(cmd) {
1594
1762
  try {
1595
1763
  execFileSync3("which", [cmd], { stdio: "pipe" });
@@ -1598,9 +1766,9 @@ function commandExists2(cmd) {
1598
1766
  }
1599
1767
  let dir = process.cwd();
1600
1768
  while (true) {
1601
- const binPath = path15.join(dir, "node_modules", ".bin", cmd);
1769
+ const binPath = path16.join(dir, "node_modules", ".bin", cmd);
1602
1770
  if (fs16.existsSync(binPath)) return true;
1603
- const parent = path15.dirname(dir);
1771
+ const parent = path16.dirname(dir);
1604
1772
  if (parent === dir) break;
1605
1773
  dir = parent;
1606
1774
  }
@@ -1622,7 +1790,7 @@ var init_agents = __esm({
1622
1790
  const defaultAgent = ctx.config.defaultAgent;
1623
1791
  let agents = {};
1624
1792
  try {
1625
- const agentsPath = path15.join(ctx.dataDir, "agents.json");
1793
+ const agentsPath = path16.join(ctx.dataDir, "agents.json");
1626
1794
  if (fs16.existsSync(agentsPath)) {
1627
1795
  const data = JSON.parse(fs16.readFileSync(agentsPath, "utf-8"));
1628
1796
  agents = data.installed ?? {};
@@ -1663,7 +1831,7 @@ __export(settings_manager_exports, {
1663
1831
  SettingsManager: () => SettingsManager
1664
1832
  });
1665
1833
  import fs17 from "fs";
1666
- import path16 from "path";
1834
+ import path17 from "path";
1667
1835
  var SettingsManager, SettingsAPIImpl;
1668
1836
  var init_settings_manager = __esm({
1669
1837
  "src/core/plugin/settings-manager.ts"() {
@@ -1706,7 +1874,7 @@ var init_settings_manager = __esm({
1706
1874
  }
1707
1875
  /** Resolve the absolute path to a plugin's settings.json file. */
1708
1876
  getSettingsPath(pluginName) {
1709
- return path16.join(this.basePath, pluginName, "settings.json");
1877
+ return path17.join(this.basePath, pluginName, "settings.json");
1710
1878
  }
1711
1879
  async getPluginSettings(pluginName) {
1712
1880
  return this.loadSettings(pluginName);
@@ -1736,7 +1904,7 @@ var init_settings_manager = __esm({
1736
1904
  }
1737
1905
  }
1738
1906
  writeFile(data) {
1739
- const dir = path16.dirname(this.settingsPath);
1907
+ const dir = path17.dirname(this.settingsPath);
1740
1908
  fs17.mkdirSync(dir, { recursive: true });
1741
1909
  fs17.writeFileSync(this.settingsPath, JSON.stringify(data, null, 2));
1742
1910
  this.cache = data;
@@ -1773,7 +1941,7 @@ var init_settings_manager = __esm({
1773
1941
  });
1774
1942
 
1775
1943
  // src/core/doctor/checks/telegram.ts
1776
- import * as path17 from "path";
1944
+ import * as path18 from "path";
1777
1945
  var BOT_TOKEN_REGEX, telegramCheck;
1778
1946
  var init_telegram = __esm({
1779
1947
  "src/core/doctor/checks/telegram.ts"() {
@@ -1789,7 +1957,7 @@ var init_telegram = __esm({
1789
1957
  return results;
1790
1958
  }
1791
1959
  const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
1792
- const sm = new SettingsManager2(path17.join(ctx.pluginsDir, "data"));
1960
+ const sm = new SettingsManager2(path18.join(ctx.pluginsDir, "data"));
1793
1961
  const ps = await sm.loadSettings("@openacp/telegram");
1794
1962
  const botToken = ps.botToken;
1795
1963
  const chatId = ps.chatId;
@@ -1961,7 +2129,7 @@ var init_storage = __esm({
1961
2129
 
1962
2130
  // src/core/doctor/checks/workspace.ts
1963
2131
  import * as fs19 from "fs";
1964
- import * as path18 from "path";
2132
+ import * as path19 from "path";
1965
2133
  var workspaceCheck;
1966
2134
  var init_workspace = __esm({
1967
2135
  "src/core/doctor/checks/workspace.ts"() {
@@ -1971,7 +2139,7 @@ var init_workspace = __esm({
1971
2139
  order: 5,
1972
2140
  async run(ctx) {
1973
2141
  const results = [];
1974
- const workspace = path18.dirname(ctx.dataDir);
2142
+ const workspace = path19.dirname(ctx.dataDir);
1975
2143
  if (!fs19.existsSync(workspace)) {
1976
2144
  results.push({
1977
2145
  status: "warn",
@@ -1999,7 +2167,7 @@ var init_workspace = __esm({
1999
2167
 
2000
2168
  // src/core/doctor/checks/plugins.ts
2001
2169
  import * as fs20 from "fs";
2002
- import * as path19 from "path";
2170
+ import * as path20 from "path";
2003
2171
  var pluginsCheck;
2004
2172
  var init_plugins = __esm({
2005
2173
  "src/core/doctor/checks/plugins.ts"() {
@@ -2018,7 +2186,7 @@ var init_plugins = __esm({
2018
2186
  fix: async () => {
2019
2187
  fs20.mkdirSync(ctx.pluginsDir, { recursive: true });
2020
2188
  fs20.writeFileSync(
2021
- path19.join(ctx.pluginsDir, "package.json"),
2189
+ path20.join(ctx.pluginsDir, "package.json"),
2022
2190
  JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
2023
2191
  );
2024
2192
  return { success: true, message: "initialized plugins directory" };
@@ -2027,7 +2195,7 @@ var init_plugins = __esm({
2027
2195
  return results;
2028
2196
  }
2029
2197
  results.push({ status: "pass", message: "Plugins directory exists" });
2030
- const pkgPath = path19.join(ctx.pluginsDir, "package.json");
2198
+ const pkgPath = path20.join(ctx.pluginsDir, "package.json");
2031
2199
  if (!fs20.existsSync(pkgPath)) {
2032
2200
  results.push({
2033
2201
  status: "warn",
@@ -2174,7 +2342,7 @@ var init_daemon = __esm({
2174
2342
 
2175
2343
  // src/core/utils/install-binary.ts
2176
2344
  import fs22 from "fs";
2177
- import path20 from "path";
2345
+ import path21 from "path";
2178
2346
  import https from "https";
2179
2347
  import os5 from "os";
2180
2348
  import { execFileSync as execFileSync4 } from "child_process";
@@ -2249,7 +2417,7 @@ function getDownloadUrl(spec) {
2249
2417
  async function ensureBinary(spec, binDir) {
2250
2418
  const resolvedBinDir = binDir ?? DEFAULT_BIN_DIR;
2251
2419
  const binName = IS_WINDOWS ? `${spec.name}.exe` : spec.name;
2252
- const binPath = path20.join(resolvedBinDir, binName);
2420
+ const binPath = path21.join(resolvedBinDir, binName);
2253
2421
  if (commandExists(spec.name)) {
2254
2422
  log18.debug({ name: spec.name }, "Found in PATH");
2255
2423
  return spec.name;
@@ -2263,7 +2431,7 @@ async function ensureBinary(spec, binDir) {
2263
2431
  fs22.mkdirSync(resolvedBinDir, { recursive: true });
2264
2432
  const url = getDownloadUrl(spec);
2265
2433
  const isArchive = spec.isArchive?.(url) ?? false;
2266
- const downloadDest = isArchive ? path20.join(resolvedBinDir, `${spec.name}.tgz`) : binPath;
2434
+ const downloadDest = isArchive ? path21.join(resolvedBinDir, `${spec.name}.tgz`) : binPath;
2267
2435
  await downloadFile(url, downloadDest);
2268
2436
  if (isArchive) {
2269
2437
  const listing = execFileSync4("tar", ["-tf", downloadDest], { stdio: "pipe" }).toString().trim().split("\n").filter(Boolean);
@@ -2291,7 +2459,7 @@ var init_install_binary = __esm({
2291
2459
  init_agent_dependencies();
2292
2460
  init_agent_installer();
2293
2461
  log18 = createChildLogger({ module: "binary-installer" });
2294
- DEFAULT_BIN_DIR = path20.join(os5.homedir(), ".openacp", "bin");
2462
+ DEFAULT_BIN_DIR = path21.join(os5.homedir(), ".openacp", "bin");
2295
2463
  IS_WINDOWS = os5.platform() === "win32";
2296
2464
  }
2297
2465
  });
@@ -2333,7 +2501,7 @@ var init_install_cloudflared = __esm({
2333
2501
 
2334
2502
  // src/core/doctor/checks/tunnel.ts
2335
2503
  import * as fs23 from "fs";
2336
- import * as path21 from "path";
2504
+ import * as path22 from "path";
2337
2505
  import * as os6 from "os";
2338
2506
  import { execFileSync as execFileSync5 } from "child_process";
2339
2507
  var tunnelCheck;
@@ -2350,7 +2518,7 @@ var init_tunnel = __esm({
2350
2518
  return results;
2351
2519
  }
2352
2520
  const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
2353
- const sm = new SettingsManager2(path21.join(ctx.pluginsDir, "data"));
2521
+ const sm = new SettingsManager2(path22.join(ctx.pluginsDir, "data"));
2354
2522
  const tunnelSettings = await sm.loadSettings("@openacp/tunnel");
2355
2523
  const tunnelEnabled = tunnelSettings.enabled ?? false;
2356
2524
  const provider = tunnelSettings.provider ?? "cloudflare";
@@ -2362,7 +2530,7 @@ var init_tunnel = __esm({
2362
2530
  results.push({ status: "pass", message: `Tunnel provider: ${provider}` });
2363
2531
  if (provider === "cloudflare") {
2364
2532
  const binName = os6.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
2365
- const binPath = path21.join(ctx.dataDir, "bin", binName);
2533
+ const binPath = path22.join(ctx.dataDir, "bin", binName);
2366
2534
  let found = false;
2367
2535
  if (fs23.existsSync(binPath)) {
2368
2536
  found = true;
@@ -2406,7 +2574,7 @@ var init_tunnel = __esm({
2406
2574
 
2407
2575
  // src/core/doctor/index.ts
2408
2576
  import * as fs24 from "fs";
2409
- import * as path22 from "path";
2577
+ import * as path23 from "path";
2410
2578
  var ALL_CHECKS, CHECK_TIMEOUT_MS, DoctorEngine;
2411
2579
  var init_doctor = __esm({
2412
2580
  "src/core/doctor/index.ts"() {
@@ -2494,7 +2662,7 @@ var init_doctor = __esm({
2494
2662
  /** Constructs the shared context used by all checks — loads config if available. */
2495
2663
  async buildContext() {
2496
2664
  const dataDir = this.dataDir;
2497
- const configPath = process.env.OPENACP_CONFIG_PATH || path22.join(dataDir, "config.json");
2665
+ const configPath = process.env.OPENACP_CONFIG_PATH || path23.join(dataDir, "config.json");
2498
2666
  let config = null;
2499
2667
  let rawConfig = null;
2500
2668
  try {
@@ -2505,16 +2673,16 @@ var init_doctor = __esm({
2505
2673
  config = cm.get();
2506
2674
  } catch {
2507
2675
  }
2508
- const logsDir = config ? expandHome2(config.logging.logDir) : path22.join(dataDir, "logs");
2676
+ const logsDir = config ? expandHome2(config.logging.logDir) : path23.join(dataDir, "logs");
2509
2677
  return {
2510
2678
  config,
2511
2679
  rawConfig,
2512
2680
  configPath,
2513
2681
  dataDir,
2514
- sessionsPath: path22.join(dataDir, "sessions.json"),
2515
- pidPath: path22.join(dataDir, "openacp.pid"),
2516
- portFilePath: path22.join(dataDir, "api.port"),
2517
- pluginsDir: path22.join(dataDir, "plugins"),
2682
+ sessionsPath: path23.join(dataDir, "sessions.json"),
2683
+ pidPath: path23.join(dataDir, "openacp.pid"),
2684
+ portFilePath: path23.join(dataDir, "api.port"),
2685
+ pluginsDir: path23.join(dataDir, "plugins"),
2518
2686
  logsDir
2519
2687
  };
2520
2688
  }
@@ -2523,11 +2691,11 @@ var init_doctor = __esm({
2523
2691
  });
2524
2692
 
2525
2693
  // src/core/instance/instance-context.ts
2526
- import path24 from "path";
2694
+ import path25 from "path";
2527
2695
  import fs26 from "fs";
2528
2696
  import os8 from "os";
2529
2697
  function getGlobalRoot() {
2530
- return path24.join(os8.homedir(), ".openacp");
2698
+ return path25.join(os8.homedir(), ".openacp");
2531
2699
  }
2532
2700
  var init_instance_context = __esm({
2533
2701
  "src/core/instance/instance-context.ts"() {
@@ -2537,11 +2705,11 @@ var init_instance_context = __esm({
2537
2705
 
2538
2706
  // src/core/instance/instance-registry.ts
2539
2707
  import fs27 from "fs";
2540
- import path25 from "path";
2708
+ import path26 from "path";
2541
2709
  import { randomUUID as randomUUID2 } from "crypto";
2542
2710
  function readIdFromConfig(instanceRoot) {
2543
2711
  try {
2544
- const raw = JSON.parse(fs27.readFileSync(path25.join(instanceRoot, "config.json"), "utf-8"));
2712
+ const raw = JSON.parse(fs27.readFileSync(path26.join(instanceRoot, "config.json"), "utf-8"));
2545
2713
  return typeof raw.id === "string" && raw.id ? raw.id : null;
2546
2714
  } catch {
2547
2715
  return null;
@@ -2587,7 +2755,7 @@ var init_instance_registry = __esm({
2587
2755
  }
2588
2756
  /** Persist the registry to disk, creating parent directories if needed. */
2589
2757
  save() {
2590
- const dir = path25.dirname(this.registryPath);
2758
+ const dir = path26.dirname(this.registryPath);
2591
2759
  fs27.mkdirSync(dir, { recursive: true });
2592
2760
  fs27.writeFileSync(this.registryPath, JSON.stringify(this.data, null, 2));
2593
2761
  }
@@ -2654,14 +2822,14 @@ __export(plugin_installer_exports, {
2654
2822
  importFromDir: () => importFromDir,
2655
2823
  installNpmPlugin: () => installNpmPlugin
2656
2824
  });
2657
- import { execFile } from "child_process";
2658
- import { promisify } from "util";
2825
+ import { execFile as execFile2 } from "child_process";
2826
+ import { promisify as promisify2 } from "util";
2659
2827
  import * as fs29 from "fs/promises";
2660
- import * as path27 from "path";
2828
+ import * as path28 from "path";
2661
2829
  import { pathToFileURL } from "url";
2662
2830
  async function importFromDir(packageName, dir) {
2663
- const pkgDir = path27.join(dir, "node_modules", ...packageName.split("/"));
2664
- const pkgJsonPath = path27.join(pkgDir, "package.json");
2831
+ const pkgDir = path28.join(dir, "node_modules", ...packageName.split("/"));
2832
+ const pkgJsonPath = path28.join(pkgDir, "package.json");
2665
2833
  let pkgJson;
2666
2834
  try {
2667
2835
  pkgJson = JSON.parse(await fs29.readFile(pkgJsonPath, "utf-8"));
@@ -2677,7 +2845,7 @@ async function importFromDir(packageName, dir) {
2677
2845
  } else {
2678
2846
  entry = pkgJson.main ?? "index.js";
2679
2847
  }
2680
- const entryPath = path27.join(pkgDir, entry);
2848
+ const entryPath = path28.join(pkgDir, entry);
2681
2849
  try {
2682
2850
  await fs29.access(entryPath);
2683
2851
  } catch {
@@ -2694,16 +2862,16 @@ async function installNpmPlugin(packageName, pluginsDir) {
2694
2862
  return await importFromDir(packageName, dir);
2695
2863
  } catch {
2696
2864
  }
2697
- await execFileAsync("npm", ["install", packageName, "--prefix", dir, "--save", "--ignore-scripts"], {
2865
+ await execFileAsync2("npm", ["install", packageName, "--prefix", dir, "--save", "--ignore-scripts"], {
2698
2866
  timeout: 6e4
2699
2867
  });
2700
2868
  return await importFromDir(packageName, dir);
2701
2869
  }
2702
- var execFileAsync, VALID_NPM_NAME;
2870
+ var execFileAsync2, VALID_NPM_NAME;
2703
2871
  var init_plugin_installer = __esm({
2704
2872
  "src/core/plugin/plugin-installer.ts"() {
2705
2873
  "use strict";
2706
- execFileAsync = promisify(execFile);
2874
+ execFileAsync2 = promisify2(execFile2);
2707
2875
  VALID_NPM_NAME = /^(@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*(@[\w.^~>=<|-]+)?$/i;
2708
2876
  }
2709
2877
  });
@@ -2771,10 +2939,10 @@ var install_context_exports = {};
2771
2939
  __export(install_context_exports, {
2772
2940
  createInstallContext: () => createInstallContext
2773
2941
  });
2774
- import path28 from "path";
2942
+ import path29 from "path";
2775
2943
  function createInstallContext(opts) {
2776
2944
  const { pluginName, settingsManager, basePath, instanceRoot } = opts;
2777
- const dataDir = path28.join(basePath, pluginName, "data");
2945
+ const dataDir = path29.join(basePath, pluginName, "data");
2778
2946
  return {
2779
2947
  pluginName,
2780
2948
  terminal: createTerminalIO(),
@@ -2802,13 +2970,13 @@ __export(api_client_exports, {
2802
2970
  waitForPortFile: () => waitForPortFile
2803
2971
  });
2804
2972
  import * as fs30 from "fs";
2805
- import * as path29 from "path";
2973
+ import * as path30 from "path";
2806
2974
  import { setTimeout as sleep } from "timers/promises";
2807
2975
  function defaultPortFile(root) {
2808
- return path29.join(root, "api.port");
2976
+ return path30.join(root, "api.port");
2809
2977
  }
2810
2978
  function defaultSecretFile(root) {
2811
- return path29.join(root, "api-secret");
2979
+ return path30.join(root, "api-secret");
2812
2980
  }
2813
2981
  async function waitForPortFile(portFilePath, timeoutMs = 5e3, intervalMs = 100) {
2814
2982
  const deadline = Date.now() + timeoutMs;
@@ -2979,7 +3147,7 @@ var init_notification = __esm({
2979
3147
 
2980
3148
  // src/plugins/file-service/file-service.ts
2981
3149
  import fs32 from "fs";
2982
- import path32 from "path";
3150
+ import path33 from "path";
2983
3151
  import { OggOpusDecoder } from "ogg-opus-decoder";
2984
3152
  import wav from "node-wav";
2985
3153
  function classifyMime(mimeType) {
@@ -3039,7 +3207,7 @@ var init_file_service = __esm({
3039
3207
  const entries = await fs32.promises.readdir(this.baseDir, { withFileTypes: true });
3040
3208
  for (const entry of entries) {
3041
3209
  if (!entry.isDirectory()) continue;
3042
- const dirPath = path32.join(this.baseDir, entry.name);
3210
+ const dirPath = path33.join(this.baseDir, entry.name);
3043
3211
  try {
3044
3212
  const stat = await fs32.promises.stat(dirPath);
3045
3213
  if (stat.mtimeMs < cutoff) {
@@ -3062,10 +3230,10 @@ var init_file_service = __esm({
3062
3230
  * so the user-facing name is not lost.
3063
3231
  */
3064
3232
  async saveFile(sessionId, fileName, data, mimeType) {
3065
- const sessionDir = path32.join(this.baseDir, sessionId);
3233
+ const sessionDir = path33.join(this.baseDir, sessionId);
3066
3234
  await fs32.promises.mkdir(sessionDir, { recursive: true });
3067
3235
  const safeName = `${Date.now()}-${fileName.replace(/[^a-zA-Z0-9._-]/g, "_")}`;
3068
- const filePath = path32.join(sessionDir, safeName);
3236
+ const filePath = path33.join(sessionDir, safeName);
3069
3237
  await fs32.promises.writeFile(filePath, data);
3070
3238
  return {
3071
3239
  type: classifyMime(mimeType),
@@ -3083,12 +3251,12 @@ var init_file_service = __esm({
3083
3251
  try {
3084
3252
  const stat = await fs32.promises.stat(filePath);
3085
3253
  if (!stat.isFile()) return null;
3086
- const ext = path32.extname(filePath).toLowerCase();
3254
+ const ext = path33.extname(filePath).toLowerCase();
3087
3255
  const mimeType = EXT_TO_MIME[ext] || "application/octet-stream";
3088
3256
  return {
3089
3257
  type: classifyMime(mimeType),
3090
3258
  filePath,
3091
- fileName: path32.basename(filePath),
3259
+ fileName: path33.basename(filePath),
3092
3260
  mimeType,
3093
3261
  size: stat.size
3094
3262
  };
@@ -3720,8 +3888,8 @@ data: ${JSON.stringify(data)}
3720
3888
 
3721
3889
  // src/plugins/api-server/static-server.ts
3722
3890
  import * as fs33 from "fs";
3723
- import * as path33 from "path";
3724
- import { fileURLToPath } from "url";
3891
+ import * as path34 from "path";
3892
+ import { fileURLToPath as fileURLToPath2 } from "url";
3725
3893
  var MIME_TYPES, StaticServer;
3726
3894
  var init_static_server = __esm({
3727
3895
  "src/plugins/api-server/static-server.ts"() {
@@ -3743,17 +3911,17 @@ var init_static_server = __esm({
3743
3911
  constructor(uiDir) {
3744
3912
  this.uiDir = uiDir;
3745
3913
  if (!this.uiDir) {
3746
- const __filename = fileURLToPath(import.meta.url);
3747
- const candidate = path33.resolve(path33.dirname(__filename), "../../ui/dist");
3748
- if (fs33.existsSync(path33.join(candidate, "index.html"))) {
3914
+ const __filename = fileURLToPath2(import.meta.url);
3915
+ const candidate = path34.resolve(path34.dirname(__filename), "../../ui/dist");
3916
+ if (fs33.existsSync(path34.join(candidate, "index.html"))) {
3749
3917
  this.uiDir = candidate;
3750
3918
  }
3751
3919
  if (!this.uiDir) {
3752
- const publishCandidate = path33.resolve(
3753
- path33.dirname(__filename),
3920
+ const publishCandidate = path34.resolve(
3921
+ path34.dirname(__filename),
3754
3922
  "../ui"
3755
3923
  );
3756
- if (fs33.existsSync(path33.join(publishCandidate, "index.html"))) {
3924
+ if (fs33.existsSync(path34.join(publishCandidate, "index.html"))) {
3757
3925
  this.uiDir = publishCandidate;
3758
3926
  }
3759
3927
  }
@@ -3771,9 +3939,9 @@ var init_static_server = __esm({
3771
3939
  serve(req, res) {
3772
3940
  if (!this.uiDir) return false;
3773
3941
  const urlPath = (req.url || "/").split("?")[0];
3774
- const safePath = path33.normalize(urlPath);
3775
- const filePath = path33.join(this.uiDir, safePath);
3776
- if (!filePath.startsWith(this.uiDir + path33.sep) && filePath !== this.uiDir)
3942
+ const safePath = path34.normalize(urlPath);
3943
+ const filePath = path34.join(this.uiDir, safePath);
3944
+ if (!filePath.startsWith(this.uiDir + path34.sep) && filePath !== this.uiDir)
3777
3945
  return false;
3778
3946
  let realFilePath;
3779
3947
  try {
@@ -3783,11 +3951,11 @@ var init_static_server = __esm({
3783
3951
  }
3784
3952
  if (realFilePath !== null) {
3785
3953
  const realUiDir = fs33.realpathSync(this.uiDir);
3786
- if (!realFilePath.startsWith(realUiDir + path33.sep) && realFilePath !== realUiDir)
3954
+ if (!realFilePath.startsWith(realUiDir + path34.sep) && realFilePath !== realUiDir)
3787
3955
  return false;
3788
3956
  }
3789
3957
  if (realFilePath !== null && fs33.existsSync(realFilePath) && fs33.statSync(realFilePath).isFile()) {
3790
- const ext = path33.extname(filePath);
3958
+ const ext = path34.extname(filePath);
3791
3959
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
3792
3960
  const isHashed = /\.[a-zA-Z0-9]{8,}\.(js|css)$/.test(filePath);
3793
3961
  const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "no-cache";
@@ -3798,7 +3966,7 @@ var init_static_server = __esm({
3798
3966
  fs33.createReadStream(realFilePath).pipe(res);
3799
3967
  return true;
3800
3968
  }
3801
- const indexPath = path33.join(this.uiDir, "index.html");
3969
+ const indexPath = path34.join(this.uiDir, "index.html");
3802
3970
  if (fs33.existsSync(indexPath)) {
3803
3971
  res.writeHead(200, {
3804
3972
  "Content-Type": "text/html; charset=utf-8",
@@ -3916,87 +4084,19 @@ var init_speech_service = __esm({
3916
4084
  }
3917
4085
  });
3918
4086
 
3919
- // src/plugins/speech/providers/groq.ts
3920
- function mimeToExt(mimeType) {
3921
- const map = {
3922
- "audio/ogg": ".ogg",
3923
- "audio/wav": ".wav",
3924
- "audio/mpeg": ".mp3",
3925
- "audio/mp4": ".m4a",
3926
- "audio/webm": ".webm",
3927
- "audio/flac": ".flac"
3928
- };
3929
- return map[mimeType] || ".bin";
3930
- }
3931
- var GROQ_API_URL, GroqSTT;
3932
- var init_groq = __esm({
3933
- "src/plugins/speech/providers/groq.ts"() {
3934
- "use strict";
3935
- GROQ_API_URL = "https://api.groq.com/openai/v1/audio/transcriptions";
3936
- GroqSTT = class {
3937
- constructor(apiKey, defaultModel = "whisper-large-v3-turbo") {
3938
- this.apiKey = apiKey;
3939
- this.defaultModel = defaultModel;
3940
- }
3941
- apiKey;
3942
- defaultModel;
3943
- name = "groq";
3944
- /**
3945
- * Transcribes audio using the Groq Whisper API.
3946
- *
3947
- * `verbose_json` response format is requested so the API returns language
3948
- * detection and duration metadata alongside the transcript text.
3949
- */
3950
- async transcribe(audioBuffer, mimeType, options) {
3951
- const ext = mimeToExt(mimeType);
3952
- const form = new FormData();
3953
- form.append("file", new Blob([new Uint8Array(audioBuffer)], { type: mimeType }), `audio${ext}`);
3954
- form.append("model", options?.model || this.defaultModel);
3955
- form.append("response_format", "verbose_json");
3956
- if (options?.language) {
3957
- form.append("language", options.language);
3958
- }
3959
- const resp = await fetch(GROQ_API_URL, {
3960
- method: "POST",
3961
- headers: { Authorization: `Bearer ${this.apiKey}` },
3962
- body: form
3963
- });
3964
- if (!resp.ok) {
3965
- const body = await resp.text();
3966
- if (resp.status === 401) {
3967
- throw new Error("Invalid Groq API key. Check your key at console.groq.com.");
3968
- }
3969
- if (resp.status === 413) {
3970
- throw new Error("Audio file too large for Groq API (max 25MB).");
3971
- }
3972
- if (resp.status === 429) {
3973
- throw new Error("Groq rate limit exceeded. Free tier: 28,800 seconds/day. Try again later.");
3974
- }
3975
- throw new Error(`Groq STT error (${resp.status}): ${body}`);
3976
- }
3977
- const data = await resp.json();
3978
- return {
3979
- text: data.text,
3980
- language: data.language,
3981
- duration: data.duration
3982
- };
3983
- }
3984
- };
3985
- }
3986
- });
3987
-
3988
4087
  // src/plugins/speech/exports.ts
3989
4088
  var init_exports = __esm({
3990
4089
  "src/plugins/speech/exports.ts"() {
3991
4090
  "use strict";
3992
4091
  init_speech_service();
3993
4092
  init_groq();
4093
+ init_local_whisper();
3994
4094
  }
3995
4095
  });
3996
4096
 
3997
4097
  // src/plugins/context/context-cache.ts
3998
4098
  import * as fs34 from "fs";
3999
- import * as path34 from "path";
4099
+ import * as path35 from "path";
4000
4100
  import * as crypto2 from "crypto";
4001
4101
  var DEFAULT_TTL_MS, ContextCache;
4002
4102
  var init_context_cache = __esm({
@@ -4015,7 +4115,7 @@ var init_context_cache = __esm({
4015
4115
  return crypto2.createHash("sha256").update(`${repoPath}:${queryKey}`).digest("hex").slice(0, 16);
4016
4116
  }
4017
4117
  filePath(repoPath, queryKey) {
4018
- return path34.join(this.cacheDir, `${this.keyHash(repoPath, queryKey)}.json`);
4118
+ return path35.join(this.cacheDir, `${this.keyHash(repoPath, queryKey)}.json`);
4019
4119
  }
4020
4120
  get(repoPath, queryKey) {
4021
4121
  const fp = this.filePath(repoPath, queryKey);
@@ -5005,8 +5105,8 @@ function formatToolSummary(name, rawInput, displaySummary) {
5005
5105
  }
5006
5106
  if (lowerName === "grep") {
5007
5107
  const pattern = args.pattern ?? "";
5008
- const path35 = args.path ?? "";
5009
- return pattern ? `\u{1F50D} Grep "${pattern}"${path35 ? ` in ${path35}` : ""}` : `\u{1F527} ${name}`;
5108
+ const path36 = args.path ?? "";
5109
+ return pattern ? `\u{1F50D} Grep "${pattern}"${path36 ? ` in ${path36}` : ""}` : `\u{1F527} ${name}`;
5010
5110
  }
5011
5111
  if (lowerName === "glob") {
5012
5112
  const pattern = args.pattern ?? "";
@@ -5042,8 +5142,8 @@ function formatToolTitle(name, rawInput, displayTitle) {
5042
5142
  }
5043
5143
  if (lowerName === "grep") {
5044
5144
  const pattern = args.pattern ?? "";
5045
- const path35 = args.path ?? "";
5046
- return pattern ? `"${pattern}"${path35 ? ` in ${path35}` : ""}` : name;
5145
+ const path36 = args.path ?? "";
5146
+ return pattern ? `"${pattern}"${path36 ? ` in ${path36}` : ""}` : name;
5047
5147
  }
5048
5148
  if (lowerName === "glob") {
5049
5149
  return String(args.pattern ?? name);
@@ -6356,14 +6456,14 @@ __export(version_exports, {
6356
6456
  getLatestVersion: () => getLatestVersion,
6357
6457
  runUpdate: () => runUpdate
6358
6458
  });
6359
- import { fileURLToPath as fileURLToPath2 } from "url";
6459
+ import { fileURLToPath as fileURLToPath3 } from "url";
6360
6460
  import { dirname as dirname12, join as join16, resolve as resolve6 } from "path";
6361
- import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
6461
+ import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
6362
6462
  function findPackageJson() {
6363
- let dir = dirname12(fileURLToPath2(import.meta.url));
6463
+ let dir = dirname12(fileURLToPath3(import.meta.url));
6364
6464
  for (let i = 0; i < 5; i++) {
6365
6465
  const candidate = join16(dir, "package.json");
6366
- if (existsSync16(candidate)) return candidate;
6466
+ if (existsSync17(candidate)) return candidate;
6367
6467
  const parent = resolve6(dir, "..");
6368
6468
  if (parent === dir) break;
6369
6469
  dir = parent;
@@ -7241,7 +7341,7 @@ __export(integrate_exports, {
7241
7341
  listIntegrations: () => listIntegrations,
7242
7342
  uninstallIntegration: () => uninstallIntegration
7243
7343
  });
7244
- import { existsSync as existsSync17, mkdirSync as mkdirSync11, readFileSync as readFileSync15, writeFileSync as writeFileSync11, unlinkSync as unlinkSync7, chmodSync, rmdirSync } from "fs";
7344
+ import { existsSync as existsSync18, mkdirSync as mkdirSync11, readFileSync as readFileSync15, writeFileSync as writeFileSync11, unlinkSync as unlinkSync7, chmodSync, rmdirSync } from "fs";
7245
7345
  import { join as join17, dirname as dirname13 } from "path";
7246
7346
  import { homedir as homedir4 } from "os";
7247
7347
  function isHooksIntegrationSpec(spec) {
@@ -7403,7 +7503,7 @@ function generateOpencodePlugin(spec) {
7403
7503
  function mergeSettingsJson(settingsPath, hookEvent, hookScriptPath) {
7404
7504
  const fullPath = expandPath(settingsPath);
7405
7505
  let settings = {};
7406
- if (existsSync17(fullPath)) {
7506
+ if (existsSync18(fullPath)) {
7407
7507
  const raw = readFileSync15(fullPath, "utf-8");
7408
7508
  writeFileSync11(`${fullPath}.bak`, raw);
7409
7509
  settings = JSON.parse(raw);
@@ -7426,7 +7526,7 @@ function mergeSettingsJson(settingsPath, hookEvent, hookScriptPath) {
7426
7526
  function mergeHooksJson(settingsPath, hookEvent, hookScriptPath) {
7427
7527
  const fullPath = expandPath(settingsPath);
7428
7528
  let config = { version: 1 };
7429
- if (existsSync17(fullPath)) {
7529
+ if (existsSync18(fullPath)) {
7430
7530
  const raw = readFileSync15(fullPath, "utf-8");
7431
7531
  writeFileSync11(`${fullPath}.bak`, raw);
7432
7532
  config = JSON.parse(raw);
@@ -7444,7 +7544,7 @@ function mergeHooksJson(settingsPath, hookEvent, hookScriptPath) {
7444
7544
  }
7445
7545
  function removeFromSettingsJson(settingsPath, hookEvent) {
7446
7546
  const fullPath = expandPath(settingsPath);
7447
- if (!existsSync17(fullPath)) return;
7547
+ if (!existsSync18(fullPath)) return;
7448
7548
  const raw = readFileSync15(fullPath, "utf-8");
7449
7549
  const settings = JSON.parse(raw);
7450
7550
  const hooks = settings.hooks;
@@ -7459,7 +7559,7 @@ function removeFromSettingsJson(settingsPath, hookEvent) {
7459
7559
  }
7460
7560
  function removeFromHooksJson(settingsPath, hookEvent) {
7461
7561
  const fullPath = expandPath(settingsPath);
7462
- if (!existsSync17(fullPath)) return;
7562
+ if (!existsSync18(fullPath)) return;
7463
7563
  const raw = readFileSync15(fullPath, "utf-8");
7464
7564
  const config = JSON.parse(raw);
7465
7565
  const hooks = config.hooks;
@@ -7525,7 +7625,7 @@ async function uninstallHooksIntegration(agentKey, spec) {
7525
7625
  const hooksDir = expandPath(spec.hooksDirPath);
7526
7626
  for (const filename of ["openacp-inject-session.sh", "openacp-handoff.sh"]) {
7527
7627
  const filePath = join17(hooksDir, filename);
7528
- if (existsSync17(filePath)) {
7628
+ if (existsSync18(filePath)) {
7529
7629
  unlinkSync7(filePath);
7530
7630
  logs.push(`Removed ${filePath}`);
7531
7631
  }
@@ -7534,7 +7634,7 @@ async function uninstallHooksIntegration(agentKey, spec) {
7534
7634
  if (spec.commandFormat === "skill") {
7535
7635
  const skillDir = expandPath(join17(spec.commandsPath, spec.handoffCommandName));
7536
7636
  const skillPath = join17(skillDir, "SKILL.md");
7537
- if (existsSync17(skillPath)) {
7637
+ if (existsSync18(skillPath)) {
7538
7638
  unlinkSync7(skillPath);
7539
7639
  try {
7540
7640
  rmdirSync(skillDir);
@@ -7544,7 +7644,7 @@ async function uninstallHooksIntegration(agentKey, spec) {
7544
7644
  }
7545
7645
  } else {
7546
7646
  const cmdPath = expandPath(join17(spec.commandsPath, `${spec.handoffCommandName}.md`));
7547
- if (existsSync17(cmdPath)) {
7647
+ if (existsSync18(cmdPath)) {
7548
7648
  unlinkSync7(cmdPath);
7549
7649
  logs.push(`Removed ${cmdPath}`);
7550
7650
  }
@@ -7571,11 +7671,11 @@ async function installPluginIntegration(_agentKey, spec) {
7571
7671
  const pluginsDir = expandPath(spec.pluginsPath);
7572
7672
  mkdirSync11(pluginsDir, { recursive: true });
7573
7673
  const pluginPath = join17(pluginsDir, spec.pluginFileName);
7574
- if (existsSync17(commandPath) && existsSync17(pluginPath)) {
7674
+ if (existsSync18(commandPath) && existsSync18(pluginPath)) {
7575
7675
  logs.push("Already installed, skipping.");
7576
7676
  return { success: true, logs };
7577
7677
  }
7578
- if (existsSync17(commandPath) || existsSync17(pluginPath)) {
7678
+ if (existsSync18(commandPath) || existsSync18(pluginPath)) {
7579
7679
  logs.push("Overwriting existing files.");
7580
7680
  }
7581
7681
  writeFileSync11(commandPath, generateOpencodeHandoffCommand(spec));
@@ -7593,13 +7693,13 @@ async function uninstallPluginIntegration(_agentKey, spec) {
7593
7693
  try {
7594
7694
  const commandPath = join17(expandPath(spec.commandsPath), spec.handoffCommandFile);
7595
7695
  let removedCount = 0;
7596
- if (existsSync17(commandPath)) {
7696
+ if (existsSync18(commandPath)) {
7597
7697
  unlinkSync7(commandPath);
7598
7698
  logs.push(`Removed ${commandPath}`);
7599
7699
  removedCount += 1;
7600
7700
  }
7601
7701
  const pluginPath = join17(expandPath(spec.pluginsPath), spec.pluginFileName);
7602
- if (existsSync17(pluginPath)) {
7702
+ if (existsSync18(pluginPath)) {
7603
7703
  unlinkSync7(pluginPath);
7604
7704
  logs.push(`Removed ${pluginPath}`);
7605
7705
  removedCount += 1;
@@ -7633,11 +7733,11 @@ function buildHandoffItem(agentKey, spec) {
7633
7733
  isInstalled() {
7634
7734
  if (isHooksIntegrationSpec(spec)) {
7635
7735
  const hooksDir = expandPath(spec.hooksDirPath);
7636
- return existsSync17(join17(hooksDir, "openacp-inject-session.sh")) && existsSync17(join17(hooksDir, "openacp-handoff.sh"));
7736
+ return existsSync18(join17(hooksDir, "openacp-inject-session.sh")) && existsSync18(join17(hooksDir, "openacp-handoff.sh"));
7637
7737
  }
7638
7738
  const commandPath = join17(expandPath(spec.commandsPath), spec.handoffCommandFile);
7639
7739
  const pluginPath = join17(expandPath(spec.pluginsPath), spec.pluginFileName);
7640
- return existsSync17(commandPath) && existsSync17(pluginPath);
7740
+ return existsSync18(commandPath) && existsSync18(pluginPath);
7641
7741
  },
7642
7742
  install: () => installIntegration(agentKey, spec),
7643
7743
  uninstall: () => uninstallIntegration(agentKey, spec)
@@ -7659,7 +7759,7 @@ function buildTunnelItem(spec) {
7659
7759
  name: "Tunnel",
7660
7760
  description: "Expose local ports to the internet via OpenACP tunnel",
7661
7761
  isInstalled() {
7662
- return existsSync17(getTunnelPath());
7762
+ return existsSync18(getTunnelPath());
7663
7763
  },
7664
7764
  async install() {
7665
7765
  const logs = [];
@@ -7678,7 +7778,7 @@ function buildTunnelItem(spec) {
7678
7778
  const logs = [];
7679
7779
  try {
7680
7780
  const skillPath = getTunnelPath();
7681
- if (existsSync17(skillPath)) {
7781
+ if (existsSync18(skillPath)) {
7682
7782
  unlinkSync7(skillPath);
7683
7783
  try {
7684
7784
  rmdirSync(dirname13(skillPath));
@@ -16124,7 +16224,7 @@ var SessionFactory = class {
16124
16224
  };
16125
16225
 
16126
16226
  // src/core/core.ts
16127
- import path14 from "path";
16227
+ import path15 from "path";
16128
16228
  import { nanoid as nanoid3 } from "nanoid";
16129
16229
 
16130
16230
  // src/core/sessions/session-store.ts
@@ -18238,6 +18338,7 @@ function registerCoreMenuItems(registry) {
18238
18338
 
18239
18339
  // src/core/core.ts
18240
18340
  init_log();
18341
+ init_native_stt();
18241
18342
  init_events();
18242
18343
  var log17 = createChildLogger({ module: "core" });
18243
18344
  var OpenACPCore = class {
@@ -18381,21 +18482,7 @@ var OpenACPCore = class {
18381
18482
  const settingsMgr = this.settingsManager;
18382
18483
  if (settingsMgr) {
18383
18484
  const pluginCfg = await settingsMgr.loadSettings("@openacp/speech");
18384
- const groqApiKey = pluginCfg.groqApiKey;
18385
- const sttProviders = {};
18386
- if (groqApiKey) {
18387
- sttProviders.groq = { apiKey: groqApiKey };
18388
- }
18389
- const newSpeechConfig = {
18390
- stt: {
18391
- provider: groqApiKey ? "groq" : null,
18392
- providers: sttProviders
18393
- },
18394
- tts: {
18395
- provider: pluginCfg.ttsProvider ?? null,
18396
- providers: {}
18397
- }
18398
- };
18485
+ const newSpeechConfig = buildSpeechServiceConfig(pluginCfg);
18399
18486
  speechSvc.refreshProviders(newSpeechConfig);
18400
18487
  log17.info("Speech service config updated at runtime (from plugin settings)");
18401
18488
  }
@@ -18404,7 +18491,7 @@ var OpenACPCore = class {
18404
18491
  }
18405
18492
  );
18406
18493
  registerCoreMenuItems(this.menuRegistry);
18407
- this.assistantRegistry.setInstanceRoot(path14.dirname(ctx.root));
18494
+ this.assistantRegistry.setInstanceRoot(path15.dirname(ctx.root));
18408
18495
  this.assistantRegistry.register(createSessionsSection(this));
18409
18496
  this.assistantRegistry.register(createAgentsSection(this));
18410
18497
  this.assistantRegistry.register(createConfigSection(this));
@@ -18838,8 +18925,8 @@ ${text3}`;
18838
18925
  message: `Agent '${agentName}' not found`
18839
18926
  };
18840
18927
  }
18841
- const { existsSync: existsSync18 } = await import("fs");
18842
- if (!existsSync18(cwd)) {
18928
+ const { existsSync: existsSync19 } = await import("fs");
18929
+ if (!existsSync19(cwd)) {
18843
18930
  return {
18844
18931
  ok: false,
18845
18932
  error: "invalid_cwd",
@@ -19224,29 +19311,29 @@ init_doctor();
19224
19311
  init_config_registry();
19225
19312
 
19226
19313
  // src/core/config/config-editor.ts
19227
- import * as path30 from "path";
19314
+ import * as path31 from "path";
19228
19315
  import * as clack2 from "@clack/prompts";
19229
19316
 
19230
19317
  // src/cli/autostart.ts
19231
19318
  init_log();
19232
19319
  import { execFileSync as execFileSync6 } from "child_process";
19233
19320
  import * as fs25 from "fs";
19234
- import * as path23 from "path";
19321
+ import * as path24 from "path";
19235
19322
  import * as os7 from "os";
19236
19323
  var log19 = createChildLogger({ module: "autostart" });
19237
- var LEGACY_LAUNCHD_PLIST_PATH = path23.join(os7.homedir(), "Library", "LaunchAgents", "com.openacp.daemon.plist");
19238
- var LEGACY_SYSTEMD_SERVICE_PATH = path23.join(os7.homedir(), ".config", "systemd", "user", "openacp.service");
19324
+ var LEGACY_LAUNCHD_PLIST_PATH = path24.join(os7.homedir(), "Library", "LaunchAgents", "com.openacp.daemon.plist");
19325
+ var LEGACY_SYSTEMD_SERVICE_PATH = path24.join(os7.homedir(), ".config", "systemd", "user", "openacp.service");
19239
19326
  function getLaunchdLabel(instanceId) {
19240
19327
  return `com.openacp.daemon.${instanceId}`;
19241
19328
  }
19242
19329
  function getLaunchdPlistPath(instanceId) {
19243
- return path23.join(os7.homedir(), "Library", "LaunchAgents", `${getLaunchdLabel(instanceId)}.plist`);
19330
+ return path24.join(os7.homedir(), "Library", "LaunchAgents", `${getLaunchdLabel(instanceId)}.plist`);
19244
19331
  }
19245
19332
  function getSystemdServiceName(instanceId) {
19246
19333
  return `openacp-${instanceId}`;
19247
19334
  }
19248
19335
  function getSystemdServicePath(instanceId) {
19249
- return path23.join(os7.homedir(), ".config", "systemd", "user", `${getSystemdServiceName(instanceId)}.service`);
19336
+ return path24.join(os7.homedir(), ".config", "systemd", "user", `${getSystemdServiceName(instanceId)}.service`);
19250
19337
  }
19251
19338
  function isAutoStartSupported() {
19252
19339
  return process.platform === "darwin" || process.platform === "linux";
@@ -19260,7 +19347,7 @@ function escapeSystemdValue(str) {
19260
19347
  }
19261
19348
  function generateLaunchdPlist(nodePath, cliPath, logDir2, instanceRoot, instanceId) {
19262
19349
  const label = getLaunchdLabel(instanceId);
19263
- const logFile = path23.join(logDir2, "openacp.log");
19350
+ const logFile = path24.join(logDir2, "openacp.log");
19264
19351
  return `<?xml version="1.0" encoding="UTF-8"?>
19265
19352
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
19266
19353
  <plist version="1.0">
@@ -19342,14 +19429,14 @@ function installAutoStart(logDir2, instanceRoot, instanceId) {
19342
19429
  return { success: false, error: "Auto-start not supported on this platform" };
19343
19430
  }
19344
19431
  const nodePath = process.execPath;
19345
- const cliPath = path23.resolve(process.argv[1]);
19346
- const resolvedLogDir = logDir2.startsWith("~") ? path23.join(os7.homedir(), logDir2.slice(1)) : logDir2;
19432
+ const cliPath = path24.resolve(process.argv[1]);
19433
+ const resolvedLogDir = logDir2.startsWith("~") ? path24.join(os7.homedir(), logDir2.slice(1)) : logDir2;
19347
19434
  try {
19348
19435
  migrateLegacy();
19349
19436
  if (process.platform === "darwin") {
19350
19437
  const plistPath = getLaunchdPlistPath(instanceId);
19351
19438
  const plist = generateLaunchdPlist(nodePath, cliPath, resolvedLogDir, instanceRoot, instanceId);
19352
- const dir = path23.dirname(plistPath);
19439
+ const dir = path24.dirname(plistPath);
19353
19440
  fs25.mkdirSync(dir, { recursive: true });
19354
19441
  fs25.writeFileSync(plistPath, plist);
19355
19442
  const uid = process.getuid();
@@ -19366,7 +19453,7 @@ function installAutoStart(logDir2, instanceRoot, instanceId) {
19366
19453
  const servicePath = getSystemdServicePath(instanceId);
19367
19454
  const serviceName = getSystemdServiceName(instanceId);
19368
19455
  const unit = generateSystemdUnit(nodePath, cliPath, instanceRoot, instanceId);
19369
- const dir = path23.dirname(servicePath);
19456
+ const dir = path24.dirname(servicePath);
19370
19457
  fs25.mkdirSync(dir, { recursive: true });
19371
19458
  fs25.writeFileSync(servicePath, unit);
19372
19459
  execFileSync6("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
@@ -19435,24 +19522,24 @@ init_instance_context();
19435
19522
  init_instance_registry();
19436
19523
  init_log();
19437
19524
  import fs28 from "fs";
19438
- import path26 from "path";
19525
+ import path27 from "path";
19439
19526
  var log20 = createChildLogger({ module: "resolve-instance-id" });
19440
19527
  function resolveInstanceId(instanceRoot) {
19441
19528
  try {
19442
- const configPath = path26.join(instanceRoot, "config.json");
19529
+ const configPath = path27.join(instanceRoot, "config.json");
19443
19530
  const raw = JSON.parse(fs28.readFileSync(configPath, "utf-8"));
19444
19531
  if (raw.id && typeof raw.id === "string") return raw.id;
19445
19532
  } catch {
19446
19533
  }
19447
19534
  try {
19448
- const reg = new InstanceRegistry(path26.join(getGlobalRoot(), "instances.json"));
19535
+ const reg = new InstanceRegistry(path27.join(getGlobalRoot(), "instances.json"));
19449
19536
  reg.load();
19450
19537
  const entry = reg.getByRoot(instanceRoot);
19451
19538
  if (entry?.id) return entry.id;
19452
19539
  } catch (err) {
19453
19540
  log20.debug({ err: err.message, instanceRoot }, "Could not read instance registry, using fallback id");
19454
19541
  }
19455
- return path26.basename(path26.dirname(instanceRoot)).replace(/[^a-zA-Z0-9-]/g, "-") || "default";
19542
+ return path27.basename(path27.dirname(instanceRoot)).replace(/[^a-zA-Z0-9-]/g, "-") || "default";
19456
19543
  }
19457
19544
 
19458
19545
  // src/core/config/config-editor.ts
@@ -20024,7 +20111,7 @@ async function runConfigEditor(configManager, mode = "file", apiPort, settingsMa
20024
20111
  await configManager.load();
20025
20112
  const config = configManager.get();
20026
20113
  const updates = {};
20027
- const instanceRoot = path30.dirname(configManager.getConfigPath());
20114
+ const instanceRoot = path31.dirname(configManager.getConfigPath());
20028
20115
  console.log(`
20029
20116
  ${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
20030
20117
  console.log(dim(`Config: ${configManager.getConfigPath()}`));
@@ -20081,17 +20168,17 @@ ${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
20081
20168
  async function sendConfigViaApi(port, updates) {
20082
20169
  const { apiCall: call } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
20083
20170
  const paths = flattenToPaths(updates);
20084
- for (const { path: path35, value } of paths) {
20171
+ for (const { path: path36, value } of paths) {
20085
20172
  const res = await call(port, "/api/config", {
20086
20173
  method: "PATCH",
20087
20174
  headers: { "Content-Type": "application/json" },
20088
- body: JSON.stringify({ path: path35, value })
20175
+ body: JSON.stringify({ path: path36, value })
20089
20176
  });
20090
20177
  const data = await res.json();
20091
20178
  if (!res.ok) {
20092
- console.log(warn(`Failed to update ${path35}: ${data.error}`));
20179
+ console.log(warn(`Failed to update ${path36}: ${data.error}`));
20093
20180
  } else if (data.needsRestart) {
20094
- console.log(warn(`${path35} updated \u2014 restart required`));
20181
+ console.log(warn(`${path36} updated \u2014 restart required`));
20095
20182
  }
20096
20183
  }
20097
20184
  }
@@ -20112,18 +20199,18 @@ function flattenToPaths(obj, prefix = "") {
20112
20199
  init_config();
20113
20200
  import { spawn as spawn3 } from "child_process";
20114
20201
  import * as fs31 from "fs";
20115
- import * as path31 from "path";
20202
+ import * as path32 from "path";
20116
20203
  function getPidPath(root) {
20117
- return path31.join(root, "openacp.pid");
20204
+ return path32.join(root, "openacp.pid");
20118
20205
  }
20119
20206
  function getLogDir(root) {
20120
- return path31.join(root, "logs");
20207
+ return path32.join(root, "logs");
20121
20208
  }
20122
20209
  function getRunningMarker(root) {
20123
- return path31.join(root, "running");
20210
+ return path32.join(root, "running");
20124
20211
  }
20125
20212
  function writePidFile(pidPath, pid) {
20126
- const dir = path31.dirname(pidPath);
20213
+ const dir = path32.dirname(pidPath);
20127
20214
  fs31.mkdirSync(dir, { recursive: true });
20128
20215
  fs31.writeFileSync(pidPath, String(pid));
20129
20216
  }
@@ -20172,8 +20259,8 @@ function startDaemon(pidPath, logDir2, instanceRoot) {
20172
20259
  }
20173
20260
  const resolvedLogDir = logDir2 ? expandHome2(logDir2) : getLogDir(instanceRoot);
20174
20261
  fs31.mkdirSync(resolvedLogDir, { recursive: true });
20175
- const logFile = path31.join(resolvedLogDir, "openacp.log");
20176
- const cliPath = path31.resolve(process.argv[1]);
20262
+ const logFile = path32.join(resolvedLogDir, "openacp.log");
20263
+ const cliPath = path32.resolve(process.argv[1]);
20177
20264
  const nodePath = process.execPath;
20178
20265
  const out = fs31.openSync(logFile, "a");
20179
20266
  const err = fs31.openSync(logFile, "a");
@@ -20257,7 +20344,7 @@ async function stopDaemon(pidPath, instanceRoot) {
20257
20344
  }
20258
20345
  function markRunning(root) {
20259
20346
  const marker = getRunningMarker(root);
20260
- fs31.mkdirSync(path31.dirname(marker), { recursive: true });
20347
+ fs31.mkdirSync(path32.dirname(marker), { recursive: true });
20261
20348
  fs31.writeFileSync(marker, "");
20262
20349
  }
20263
20350
  function clearRunning(root) {