open-agents-ai 0.13.5 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1128 -223
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1170,7 +1170,7 @@ var init_shell = __esm({
1170
1170
  const timeout = args["timeout"] ?? this.defaultTimeout;
1171
1171
  const stdinInput = args["stdin"];
1172
1172
  const start = performance.now();
1173
- return new Promise((resolve15) => {
1173
+ return new Promise((resolve16) => {
1174
1174
  const child = spawn("bash", ["-c", command], {
1175
1175
  cwd: this.workingDir,
1176
1176
  env: {
@@ -1223,7 +1223,7 @@ var init_shell = __esm({
1223
1223
  const combined = stdout + stderr;
1224
1224
  const looksInteractive = /\? .+[›>]|y\/n|yes\/no|\(Y\/n\)|\[y\/N\]/i.test(combined);
1225
1225
  const hint = looksInteractive ? " The command appears to be waiting for interactive input. Use non-interactive flags (e.g., --yes, --no-input) or provide input via the stdin parameter." : "";
1226
- resolve15({
1226
+ resolve16({
1227
1227
  success: false,
1228
1228
  output: stdout,
1229
1229
  error: `Command timed out after ${timeout}ms.${hint}`,
@@ -1232,7 +1232,7 @@ var init_shell = __esm({
1232
1232
  return;
1233
1233
  }
1234
1234
  const success = code === 0;
1235
- resolve15({
1235
+ resolve16({
1236
1236
  success,
1237
1237
  output: stdout + (stderr && success ? `
1238
1238
  STDERR:
@@ -1243,7 +1243,7 @@ ${stderr}` : ""),
1243
1243
  });
1244
1244
  child.on("error", (err) => {
1245
1245
  clearTimeout(timer);
1246
- resolve15({
1246
+ resolve16({
1247
1247
  success: false,
1248
1248
  output: stdout,
1249
1249
  error: err.message,
@@ -4031,8 +4031,8 @@ function deleteCustomToolDefinition(name, scope, repoRoot) {
4031
4031
  const dir = scope === "project" && repoRoot ? projectToolsDir(repoRoot) : globalToolsDir();
4032
4032
  const filePath = join11(dir, `${name}.json`);
4033
4033
  if (existsSync8(filePath)) {
4034
- const { unlinkSync: unlinkSync3 } = __require("node:fs");
4035
- unlinkSync3(filePath);
4034
+ const { unlinkSync: unlinkSync4 } = __require("node:fs");
4035
+ unlinkSync4(filePath);
4036
4036
  return true;
4037
4037
  }
4038
4038
  return false;
@@ -4150,7 +4150,7 @@ var init_custom_tool = __esm({
4150
4150
  }
4151
4151
  /** Execute a single shell command and return output */
4152
4152
  runCommand(command) {
4153
- return new Promise((resolve15) => {
4153
+ return new Promise((resolve16) => {
4154
4154
  const child = spawn3("bash", ["-c", command], {
4155
4155
  cwd: this.workingDir,
4156
4156
  env: { ...process.env, CI: "true", NO_COLOR: "1" },
@@ -4175,11 +4175,11 @@ var init_custom_tool = __esm({
4175
4175
  child.kill("SIGTERM");
4176
4176
  } catch {
4177
4177
  }
4178
- resolve15({ success: false, output: stdout, error: "Command timed out after 60s" });
4178
+ resolve16({ success: false, output: stdout, error: "Command timed out after 60s" });
4179
4179
  }, 6e4);
4180
4180
  child.on("close", (code) => {
4181
4181
  clearTimeout(timer);
4182
- resolve15({
4182
+ resolve16({
4183
4183
  success: code === 0,
4184
4184
  output: stdout + (stderr && code === 0 ? `
4185
4185
  STDERR:
@@ -4189,7 +4189,7 @@ ${stderr}` : ""),
4189
4189
  });
4190
4190
  child.on("error", (err) => {
4191
4191
  clearTimeout(timer);
4192
- resolve15({ success: false, output: stdout, error: err.message });
4192
+ resolve16({ success: false, output: stdout, error: err.message });
4193
4193
  });
4194
4194
  });
4195
4195
  }
@@ -4840,6 +4840,300 @@ ${content}`,
4840
4840
  }
4841
4841
  });
4842
4842
 
4843
+ // packages/execution/dist/tools/transcribe-tool.js
4844
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, readFileSync as readFileSync9, unlinkSync } from "node:fs";
4845
+ import { join as join13, basename as basename3, extname as extname3, resolve as resolve12 } from "node:path";
4846
+ import { homedir as homedir5 } from "node:os";
4847
+ import { execSync as execSync9, spawn as spawn4 } from "node:child_process";
4848
+ function isTranscribable(path) {
4849
+ const ext = extname3(path).toLowerCase();
4850
+ return AUDIO_EXTS.has(ext) || VIDEO_EXTS.has(ext);
4851
+ }
4852
+ async function loadTranscribeCli() {
4853
+ if (_tcChecked)
4854
+ return _tcModule;
4855
+ _tcChecked = true;
4856
+ try {
4857
+ const globalRoot = execSync9("npm root -g", {
4858
+ encoding: "utf-8",
4859
+ timeout: 5e3,
4860
+ stdio: ["pipe", "pipe", "pipe"]
4861
+ }).trim();
4862
+ const tcPath = join13(globalRoot, "transcribe-cli");
4863
+ if (existsSync10(join13(tcPath, "dist", "index.js"))) {
4864
+ const { createRequire: createRequire4 } = await import("node:module");
4865
+ const req = createRequire4(import.meta.url);
4866
+ _tcModule = req(join13(tcPath, "dist", "index.js"));
4867
+ return _tcModule;
4868
+ }
4869
+ } catch {
4870
+ }
4871
+ const nvmBase = join13(homedir5(), ".nvm", "versions", "node");
4872
+ if (existsSync10(nvmBase)) {
4873
+ try {
4874
+ const { readdirSync: readdirSync9 } = await import("node:fs");
4875
+ for (const ver of readdirSync9(nvmBase)) {
4876
+ const tcPath = join13(nvmBase, ver, "lib", "node_modules", "transcribe-cli");
4877
+ if (existsSync10(join13(tcPath, "dist", "index.js"))) {
4878
+ const { createRequire: createRequire4 } = await import("node:module");
4879
+ const req = createRequire4(import.meta.url);
4880
+ _tcModule = req(join13(tcPath, "dist", "index.js"));
4881
+ return _tcModule;
4882
+ }
4883
+ }
4884
+ } catch {
4885
+ }
4886
+ }
4887
+ return null;
4888
+ }
4889
+ function formatTime(seconds) {
4890
+ const m = Math.floor(seconds / 60);
4891
+ const s = Math.floor(seconds % 60);
4892
+ return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
4893
+ }
4894
+ var AUDIO_EXTS, VIDEO_EXTS, _tcModule, _tcChecked, TranscribeFileTool, TranscribeUrlTool;
4895
+ var init_transcribe_tool = __esm({
4896
+ "packages/execution/dist/tools/transcribe-tool.js"() {
4897
+ "use strict";
4898
+ AUDIO_EXTS = /* @__PURE__ */ new Set([
4899
+ ".mp3",
4900
+ ".wav",
4901
+ ".flac",
4902
+ ".aac",
4903
+ ".m4a",
4904
+ ".ogg",
4905
+ ".wma",
4906
+ ".opus"
4907
+ ]);
4908
+ VIDEO_EXTS = /* @__PURE__ */ new Set([
4909
+ ".mp4",
4910
+ ".mkv",
4911
+ ".avi",
4912
+ ".mov",
4913
+ ".webm",
4914
+ ".flv",
4915
+ ".wmv",
4916
+ ".m4v",
4917
+ ".ts"
4918
+ ]);
4919
+ _tcModule = null;
4920
+ _tcChecked = false;
4921
+ TranscribeFileTool = class {
4922
+ name = "transcribe_file";
4923
+ description = "Transcribe a local audio or video file to text using Whisper (faster-whisper). Supports MP3, WAV, FLAC, AAC, M4A, OGG, MP4, MKV, AVI, MOV, WebM. Returns the full transcription text with optional speaker diarization. Transcription is 100% local \u2014 no API keys needed.";
4924
+ parameters = {
4925
+ type: "object",
4926
+ properties: {
4927
+ path: {
4928
+ type: "string",
4929
+ description: "Path to the audio or video file to transcribe"
4930
+ },
4931
+ model: {
4932
+ type: "string",
4933
+ description: "Whisper model size: tiny, base, small, medium, large-v3 (default: base)",
4934
+ enum: ["tiny", "base", "small", "medium", "large-v3"]
4935
+ },
4936
+ diarize: {
4937
+ type: "boolean",
4938
+ description: "Enable speaker diarization (identify who said what). Default: false"
4939
+ }
4940
+ },
4941
+ required: ["path"]
4942
+ };
4943
+ workingDir;
4944
+ constructor(workingDir) {
4945
+ this.workingDir = workingDir;
4946
+ }
4947
+ async execute(args) {
4948
+ const start = performance.now();
4949
+ const filePath = resolve12(this.workingDir, String(args["path"] ?? ""));
4950
+ const model = String(args["model"] ?? "base");
4951
+ const diarize = Boolean(args["diarize"] ?? false);
4952
+ if (!existsSync10(filePath)) {
4953
+ return {
4954
+ success: false,
4955
+ output: "",
4956
+ error: `File not found: ${filePath}`,
4957
+ durationMs: performance.now() - start
4958
+ };
4959
+ }
4960
+ if (!isTranscribable(filePath)) {
4961
+ return {
4962
+ success: false,
4963
+ output: "",
4964
+ error: `Unsupported file type: ${extname3(filePath)}. Supported: ${[...AUDIO_EXTS, ...VIDEO_EXTS].join(", ")}`,
4965
+ durationMs: performance.now() - start
4966
+ };
4967
+ }
4968
+ const tc = await loadTranscribeCli();
4969
+ if (!tc) {
4970
+ return this.execViaCli(filePath, model, diarize, start);
4971
+ }
4972
+ try {
4973
+ const result = await tc.transcribe(filePath, {
4974
+ model,
4975
+ format: "json",
4976
+ diarize,
4977
+ wordTimestamps: false
4978
+ });
4979
+ const transcriptDir = join13(this.workingDir, ".oa", "transcripts");
4980
+ mkdirSync4(transcriptDir, { recursive: true });
4981
+ const outFile = join13(transcriptDir, `${basename3(filePath)}.txt`);
4982
+ writeFileSync4(outFile, result.text, "utf-8");
4983
+ const lines = [
4984
+ `Transcription of: ${basename3(filePath)}`,
4985
+ `Model: ${model} | Language: ${result.language} | Duration: ${result.duration ? `${result.duration.toFixed(1)}s` : "unknown"}`,
4986
+ `Words: ${result.wordCount} | Saved to: ${outFile}`,
4987
+ ""
4988
+ ];
4989
+ if (result.speakers.length > 1) {
4990
+ lines.push(`Speakers: ${result.speakers.join(", ")}`);
4991
+ lines.push("");
4992
+ }
4993
+ if (diarize && result.segments.length > 0) {
4994
+ for (const seg of result.segments) {
4995
+ const ts = `[${formatTime(seg.start)} \u2192 ${formatTime(seg.end)}]`;
4996
+ const speaker = seg.speaker ? `${seg.speaker}: ` : "";
4997
+ lines.push(`${ts} ${speaker}${seg.text}`);
4998
+ }
4999
+ } else {
5000
+ lines.push(result.text);
5001
+ }
5002
+ return {
5003
+ success: true,
5004
+ output: lines.join("\n"),
5005
+ durationMs: performance.now() - start
5006
+ };
5007
+ } catch (err) {
5008
+ return {
5009
+ success: false,
5010
+ output: "",
5011
+ error: `Transcription failed: ${err instanceof Error ? err.message : String(err)}`,
5012
+ durationMs: performance.now() - start
5013
+ };
5014
+ }
5015
+ }
5016
+ /** Fallback: invoke transcribe-cli as a subprocess. */
5017
+ async execViaCli(filePath, model, diarize, start) {
5018
+ try {
5019
+ const args = [filePath, "-m", model, "-f", "txt"];
5020
+ if (diarize)
5021
+ args.push("--diarize");
5022
+ const output = execSync9(`transcribe-cli ${args.join(" ")}`, {
5023
+ encoding: "utf-8",
5024
+ timeout: 3e5,
5025
+ // 5 min max
5026
+ cwd: this.workingDir,
5027
+ stdio: ["pipe", "pipe", "pipe"]
5028
+ });
5029
+ return {
5030
+ success: true,
5031
+ output: output.trim() || "(empty transcription)",
5032
+ durationMs: performance.now() - start
5033
+ };
5034
+ } catch (err) {
5035
+ const stderr = err.stderr || "";
5036
+ return {
5037
+ success: false,
5038
+ output: err.stdout || "",
5039
+ error: `transcribe-cli failed: ${stderr || err.message}`,
5040
+ durationMs: performance.now() - start
5041
+ };
5042
+ }
5043
+ }
5044
+ };
5045
+ TranscribeUrlTool = class {
5046
+ name = "transcribe_url";
5047
+ description = "Download an audio or video file from a URL and transcribe it to text. Supports direct links to MP3, WAV, MP4, and other media files. The file is downloaded to a temp location, transcribed locally, then cleaned up.";
5048
+ parameters = {
5049
+ type: "object",
5050
+ properties: {
5051
+ url: {
5052
+ type: "string",
5053
+ description: "URL of the audio or video file to download and transcribe"
5054
+ },
5055
+ model: {
5056
+ type: "string",
5057
+ description: "Whisper model size: tiny, base, small, medium, large-v3 (default: base)",
5058
+ enum: ["tiny", "base", "small", "medium", "large-v3"]
5059
+ }
5060
+ },
5061
+ required: ["url"]
5062
+ };
5063
+ workingDir;
5064
+ constructor(workingDir) {
5065
+ this.workingDir = workingDir;
5066
+ }
5067
+ async execute(args) {
5068
+ const start = performance.now();
5069
+ const url = String(args["url"] ?? "");
5070
+ const model = String(args["model"] ?? "base");
5071
+ if (!url) {
5072
+ return {
5073
+ success: false,
5074
+ output: "",
5075
+ error: "URL is required.",
5076
+ durationMs: performance.now() - start
5077
+ };
5078
+ }
5079
+ const tmpDir = join13(this.workingDir, ".oa", "tmp");
5080
+ mkdirSync4(tmpDir, { recursive: true });
5081
+ const urlPath = new URL(url).pathname;
5082
+ let ext = extname3(urlPath).toLowerCase();
5083
+ if (!ext || !AUDIO_EXTS.has(ext) && !VIDEO_EXTS.has(ext)) {
5084
+ ext = ".mp3";
5085
+ }
5086
+ const tmpFile = join13(tmpDir, `download-${Date.now()}${ext}`);
5087
+ try {
5088
+ try {
5089
+ execSync9(`curl -sL -o "${tmpFile}" "${url}"`, {
5090
+ timeout: 12e4,
5091
+ stdio: ["pipe", "pipe", "pipe"]
5092
+ });
5093
+ } catch {
5094
+ execSync9(`wget -q -O "${tmpFile}" "${url}"`, {
5095
+ timeout: 12e4,
5096
+ stdio: ["pipe", "pipe", "pipe"]
5097
+ });
5098
+ }
5099
+ if (!existsSync10(tmpFile)) {
5100
+ return {
5101
+ success: false,
5102
+ output: "",
5103
+ error: "Download failed \u2014 file not created.",
5104
+ durationMs: performance.now() - start
5105
+ };
5106
+ }
5107
+ const fileTool = new TranscribeFileTool(this.workingDir);
5108
+ const result = await fileTool.execute({ path: tmpFile, model });
5109
+ try {
5110
+ unlinkSync(tmpFile);
5111
+ } catch {
5112
+ }
5113
+ return {
5114
+ success: result.success,
5115
+ output: `Source: ${url}
5116
+ ${result.output}`,
5117
+ error: result.error,
5118
+ durationMs: performance.now() - start
5119
+ };
5120
+ } catch (err) {
5121
+ try {
5122
+ unlinkSync(tmpFile);
5123
+ } catch {
5124
+ }
5125
+ return {
5126
+ success: false,
5127
+ output: "",
5128
+ error: `Failed to download/transcribe: ${err instanceof Error ? err.message : String(err)}`,
5129
+ durationMs: performance.now() - start
5130
+ };
5131
+ }
5132
+ }
5133
+ };
5134
+ }
5135
+ });
5136
+
4843
5137
  // packages/execution/dist/shellRunner.js
4844
5138
  var init_shellRunner = __esm({
4845
5139
  "packages/execution/dist/shellRunner.js"() {
@@ -4943,6 +5237,7 @@ var init_dist2 = __esm({
4943
5237
  init_custom_tool();
4944
5238
  init_tool_creator();
4945
5239
  init_skill_tools();
5240
+ init_transcribe_tool();
4946
5241
  init_shellRunner();
4947
5242
  init_gitWorktree();
4948
5243
  init_patchApplier();
@@ -6074,7 +6369,7 @@ var init_code_retriever = __esm({
6074
6369
  import { execFile as execFile4 } from "node:child_process";
6075
6370
  import { promisify as promisify4 } from "node:util";
6076
6371
  import { readFile as readFile7, readdir, stat } from "node:fs/promises";
6077
- import { join as join13, extname as extname3 } from "node:path";
6372
+ import { join as join14, extname as extname4 } from "node:path";
6078
6373
  async function searchByPath(pathPattern, options) {
6079
6374
  const allFiles = await collectFiles(options.rootDir, options.includeGlobs ?? DEFAULT_INCLUDE_GLOBS, options.excludeGlobs ?? DEFAULT_EXCLUDE_GLOBS);
6080
6375
  const pattern = options.caseInsensitive ? pathPattern.toLowerCase() : pathPattern;
@@ -6216,11 +6511,11 @@ async function walkForFiles(rootDir, dir, excludeGlobs, results) {
6216
6511
  continue;
6217
6512
  if (excludeGlobs.some((g) => entry.name === g || matchesGlob(entry.name, g)))
6218
6513
  continue;
6219
- const absPath = join13(dir, entry.name);
6514
+ const absPath = join14(dir, entry.name);
6220
6515
  if (entry.isDirectory()) {
6221
6516
  await walkForFiles(rootDir, absPath, excludeGlobs, results);
6222
6517
  } else if (entry.isFile()) {
6223
- const ext = extname3(entry.name);
6518
+ const ext = extname4(entry.name);
6224
6519
  if (!ALL_CODE_EXTS.has(ext))
6225
6520
  continue;
6226
6521
  const relativePath = absPath.startsWith(rootDir) ? absPath.slice(rootDir.length).replace(/^\//, "") : absPath;
@@ -6391,7 +6686,7 @@ var init_graphExpand = __esm({
6391
6686
 
6392
6687
  // packages/retrieval/dist/snippetPacker.js
6393
6688
  import { readFile as readFile8 } from "node:fs/promises";
6394
- import { join as join14 } from "node:path";
6689
+ import { join as join15 } from "node:path";
6395
6690
  async function packSnippets(requests, opts = {}) {
6396
6691
  const maxTokens = opts.maxTokens ?? DEFAULT_MAX_TOKENS;
6397
6692
  const contextLines = opts.contextLines ?? DEFAULT_CONTEXT_LINES;
@@ -6417,7 +6712,7 @@ async function packSnippets(requests, opts = {}) {
6417
6712
  return { packed, dropped, totalTokens };
6418
6713
  }
6419
6714
  async function extractSnippet(req, repoRoot, contextLines = DEFAULT_CONTEXT_LINES) {
6420
- const absPath = req.filePath.startsWith("/") ? req.filePath : join14(repoRoot, req.filePath);
6715
+ const absPath = req.filePath.startsWith("/") ? req.filePath : join15(repoRoot, req.filePath);
6421
6716
  let content;
6422
6717
  try {
6423
6718
  content = await readFile8(absPath, "utf-8");
@@ -8726,6 +9021,408 @@ var init_dist5 = __esm({
8726
9021
  }
8727
9022
  });
8728
9023
 
9024
+ // packages/cli/dist/tui/listen.js
9025
+ import { spawn as spawn5, execSync as execSync10 } from "node:child_process";
9026
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
9027
+ import { join as join16 } from "node:path";
9028
+ import { homedir as homedir6 } from "node:os";
9029
+ import { EventEmitter } from "node:events";
9030
+ function isAudioPath(path) {
9031
+ const ext = path.toLowerCase().split(".").pop();
9032
+ return ext ? AUDIO_EXTENSIONS.has(`.${ext}`) : false;
9033
+ }
9034
+ function isVideoPath(path) {
9035
+ const ext = path.toLowerCase().split(".").pop();
9036
+ return ext ? VIDEO_EXTENSIONS.has(`.${ext}`) : false;
9037
+ }
9038
+ function isTranscribablePath(path) {
9039
+ return isAudioPath(path) || isVideoPath(path);
9040
+ }
9041
+ function findMicCaptureCommand() {
9042
+ const platform3 = process.platform;
9043
+ if (platform3 === "linux") {
9044
+ try {
9045
+ execSync10("which arecord", { stdio: "pipe" });
9046
+ return {
9047
+ cmd: "arecord",
9048
+ args: ["-f", "S16_LE", "-r", "16000", "-c", "1", "-t", "raw", "-q", "-"]
9049
+ };
9050
+ } catch {
9051
+ }
9052
+ }
9053
+ if (platform3 === "darwin") {
9054
+ try {
9055
+ execSync10("which sox", { stdio: "pipe" });
9056
+ return {
9057
+ cmd: "sox",
9058
+ args: ["-d", "-t", "raw", "-r", "16000", "-c", "1", "-b", "16", "-e", "signed-integer", "-"]
9059
+ };
9060
+ } catch {
9061
+ }
9062
+ }
9063
+ try {
9064
+ execSync10("which ffmpeg", { stdio: "pipe" });
9065
+ if (platform3 === "linux") {
9066
+ return {
9067
+ cmd: "ffmpeg",
9068
+ args: [
9069
+ "-f",
9070
+ "pulse",
9071
+ "-i",
9072
+ "default",
9073
+ "-ar",
9074
+ "16000",
9075
+ "-ac",
9076
+ "1",
9077
+ "-f",
9078
+ "s16le",
9079
+ "-loglevel",
9080
+ "quiet",
9081
+ "pipe:1"
9082
+ ]
9083
+ };
9084
+ } else if (platform3 === "darwin") {
9085
+ return {
9086
+ cmd: "ffmpeg",
9087
+ args: [
9088
+ "-f",
9089
+ "avfoundation",
9090
+ "-i",
9091
+ ":0",
9092
+ "-ar",
9093
+ "16000",
9094
+ "-ac",
9095
+ "1",
9096
+ "-f",
9097
+ "s16le",
9098
+ "-loglevel",
9099
+ "quiet",
9100
+ "pipe:1"
9101
+ ]
9102
+ };
9103
+ }
9104
+ } catch {
9105
+ }
9106
+ return null;
9107
+ }
9108
+ function getListenEngine(config) {
9109
+ if (!_engine) {
9110
+ _engine = new ListenEngine(config);
9111
+ }
9112
+ return _engine;
9113
+ }
9114
+ var AUDIO_EXTENSIONS, VIDEO_EXTENSIONS, ListenEngine, _engine;
9115
+ var init_listen = __esm({
9116
+ "packages/cli/dist/tui/listen.js"() {
9117
+ "use strict";
9118
+ AUDIO_EXTENSIONS = /* @__PURE__ */ new Set([
9119
+ ".mp3",
9120
+ ".wav",
9121
+ ".flac",
9122
+ ".aac",
9123
+ ".m4a",
9124
+ ".ogg",
9125
+ ".wma",
9126
+ ".opus"
9127
+ ]);
9128
+ VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([
9129
+ ".mp4",
9130
+ ".mkv",
9131
+ ".avi",
9132
+ ".mov",
9133
+ ".webm",
9134
+ ".flv",
9135
+ ".wmv",
9136
+ ".m4v",
9137
+ ".ts"
9138
+ ]);
9139
+ ListenEngine = class extends EventEmitter {
9140
+ config;
9141
+ micProcess = null;
9142
+ liveTranscriber = null;
9143
+ // TranscribeLive from transcribe-cli
9144
+ active = false;
9145
+ silenceTimer = null;
9146
+ countdownInterval = null;
9147
+ lastTranscriptTime = 0;
9148
+ pendingText = "";
9149
+ blinkTimer = null;
9150
+ blinkState = false;
9151
+ transcribeCliAvailable = null;
9152
+ constructor(config) {
9153
+ super();
9154
+ this.config = {
9155
+ model: config?.model ?? "base",
9156
+ mode: config?.mode ?? "confirm",
9157
+ silenceTimeoutMs: config?.silenceTimeoutMs ?? 3e3
9158
+ };
9159
+ }
9160
+ get isActive() {
9161
+ return this.active;
9162
+ }
9163
+ get isBlinking() {
9164
+ return this.active && this.blinkState;
9165
+ }
9166
+ get currentModel() {
9167
+ return this.config.model;
9168
+ }
9169
+ get currentMode() {
9170
+ return this.config.mode;
9171
+ }
9172
+ get pendingTranscript() {
9173
+ return this.pendingText;
9174
+ }
9175
+ setModel(model) {
9176
+ this.config.model = model;
9177
+ }
9178
+ setMode(mode) {
9179
+ this.config.mode = mode;
9180
+ }
9181
+ /** Check if transcribe-cli is available. */
9182
+ async isAvailable() {
9183
+ if (this.transcribeCliAvailable !== null)
9184
+ return this.transcribeCliAvailable;
9185
+ try {
9186
+ const mod = await this.loadTranscribeCli();
9187
+ this.transcribeCliAvailable = mod !== null;
9188
+ } catch {
9189
+ this.transcribeCliAvailable = false;
9190
+ }
9191
+ if (!this.transcribeCliAvailable) {
9192
+ try {
9193
+ execSync10("which transcribe-cli", { stdio: "pipe" });
9194
+ this.transcribeCliAvailable = true;
9195
+ } catch {
9196
+ this.transcribeCliAvailable = false;
9197
+ }
9198
+ }
9199
+ return this.transcribeCliAvailable;
9200
+ }
9201
+ /** Load transcribe-cli dynamically (it may not be installed). */
9202
+ async loadTranscribeCli() {
9203
+ try {
9204
+ const globalRoot = execSync10("npm root -g", {
9205
+ encoding: "utf-8",
9206
+ timeout: 5e3,
9207
+ stdio: ["pipe", "pipe", "pipe"]
9208
+ }).trim();
9209
+ const tcPath = join16(globalRoot, "transcribe-cli");
9210
+ if (existsSync11(join16(tcPath, "dist", "index.js"))) {
9211
+ const { createRequire: createRequire4 } = await import("node:module");
9212
+ const req = createRequire4(import.meta.url);
9213
+ return req(join16(tcPath, "dist", "index.js"));
9214
+ }
9215
+ } catch {
9216
+ }
9217
+ const nvmBase = join16(homedir6(), ".nvm", "versions", "node");
9218
+ if (existsSync11(nvmBase)) {
9219
+ try {
9220
+ const { readdirSync: readdirSync9 } = await import("node:fs");
9221
+ for (const ver of readdirSync9(nvmBase)) {
9222
+ const tcPath = join16(nvmBase, ver, "lib", "node_modules", "transcribe-cli");
9223
+ if (existsSync11(join16(tcPath, "dist", "index.js"))) {
9224
+ const { createRequire: createRequire4 } = await import("node:module");
9225
+ const req = createRequire4(import.meta.url);
9226
+ return req(join16(tcPath, "dist", "index.js"));
9227
+ }
9228
+ }
9229
+ } catch {
9230
+ }
9231
+ }
9232
+ return null;
9233
+ }
9234
+ /**
9235
+ * Start listening — captures microphone audio and feeds to TranscribeLive.
9236
+ */
9237
+ async start() {
9238
+ if (this.active)
9239
+ return "Already listening.";
9240
+ const micCmd = findMicCaptureCommand();
9241
+ if (!micCmd) {
9242
+ return "No microphone capture tool found. Install arecord (Linux), sox (macOS), or ffmpeg.";
9243
+ }
9244
+ const tc = await this.loadTranscribeCli();
9245
+ if (!tc) {
9246
+ return "transcribe-cli not installed. Run: npm i -g transcribe-cli";
9247
+ }
9248
+ const TranscribeLive = tc.TranscribeLive;
9249
+ this.liveTranscriber = new TranscribeLive({
9250
+ model: this.config.model,
9251
+ sampleRate: 16e3,
9252
+ channels: 1,
9253
+ sampleWidth: 2,
9254
+ chunkDuration: 3
9255
+ // 3s chunks for responsive transcription
9256
+ });
9257
+ this.liveTranscriber.on("transcript", (evt) => {
9258
+ if (!evt.text.trim())
9259
+ return;
9260
+ this.lastTranscriptTime = Date.now();
9261
+ this.pendingText = evt.text.trim();
9262
+ this.emit("transcript", this.pendingText, evt.isFinal);
9263
+ if (this.config.mode === "auto") {
9264
+ this.resetSilenceTimer();
9265
+ }
9266
+ });
9267
+ this.liveTranscriber.on("error", (err) => {
9268
+ this.emit("error", err);
9269
+ });
9270
+ await new Promise((resolve16, reject) => {
9271
+ const timeout = setTimeout(() => reject(new Error("Model load timeout (60s)")), 6e4);
9272
+ this.liveTranscriber.on("ready", () => {
9273
+ clearTimeout(timeout);
9274
+ resolve16();
9275
+ });
9276
+ this.liveTranscriber.on("error", (err) => {
9277
+ clearTimeout(timeout);
9278
+ reject(err);
9279
+ });
9280
+ });
9281
+ this.micProcess = spawn5(micCmd.cmd, micCmd.args, {
9282
+ stdio: ["pipe", "pipe", "pipe"],
9283
+ env: { ...process.env }
9284
+ });
9285
+ this.micProcess.stdout?.on("data", (chunk) => {
9286
+ if (this.active && this.liveTranscriber) {
9287
+ this.liveTranscriber.write(chunk);
9288
+ }
9289
+ });
9290
+ this.micProcess.stderr?.on("data", () => {
9291
+ });
9292
+ this.micProcess.on("error", (err) => {
9293
+ this.emit("error", new Error(`Mic capture failed: ${err.message}`));
9294
+ this.stop();
9295
+ });
9296
+ this.micProcess.on("close", () => {
9297
+ if (this.active) {
9298
+ this.stop();
9299
+ }
9300
+ });
9301
+ this.active = true;
9302
+ this.blinkState = true;
9303
+ this.blinkTimer = setInterval(() => {
9304
+ this.blinkState = !this.blinkState;
9305
+ this.emit("recording", this.blinkState);
9306
+ }, 500);
9307
+ this.lastTranscriptTime = Date.now();
9308
+ this.emit("started");
9309
+ this.emit("recording", true);
9310
+ const modeDesc = this.config.mode === "auto" ? `auto (${this.config.silenceTimeoutMs / 1e3}s timeout)` : "confirm (press Enter to submit)";
9311
+ return `Listening with ${this.config.model} model (${modeDesc})`;
9312
+ }
9313
+ /**
9314
+ * Stop listening — cleanup mic, transcriber, timers.
9315
+ */
9316
+ async stop() {
9317
+ if (!this.active)
9318
+ return "Not listening.";
9319
+ this.active = false;
9320
+ this.blinkState = false;
9321
+ if (this.blinkTimer) {
9322
+ clearInterval(this.blinkTimer);
9323
+ this.blinkTimer = null;
9324
+ }
9325
+ if (this.silenceTimer) {
9326
+ clearTimeout(this.silenceTimer);
9327
+ this.silenceTimer = null;
9328
+ }
9329
+ if (this.countdownInterval) {
9330
+ clearInterval(this.countdownInterval);
9331
+ this.countdownInterval = null;
9332
+ }
9333
+ if (this.micProcess) {
9334
+ try {
9335
+ this.micProcess.kill("SIGTERM");
9336
+ } catch {
9337
+ }
9338
+ this.micProcess = null;
9339
+ }
9340
+ if (this.liveTranscriber) {
9341
+ try {
9342
+ this.liveTranscriber.stop();
9343
+ } catch {
9344
+ }
9345
+ this.liveTranscriber = null;
9346
+ }
9347
+ this.emit("recording", false);
9348
+ this.emit("stopped");
9349
+ const result = this.pendingText;
9350
+ this.pendingText = "";
9351
+ return result || "Stopped listening.";
9352
+ }
9353
+ /**
9354
+ * Accept the current pending transcript (for confirm mode).
9355
+ * Returns the text to inject into the input line.
9356
+ */
9357
+ acceptTranscript() {
9358
+ const text = this.pendingText;
9359
+ this.pendingText = "";
9360
+ return text;
9361
+ }
9362
+ /**
9363
+ * Transcribe a file (audio or video) using transcribe-cli.
9364
+ * Returns the transcription result.
9365
+ */
9366
+ async transcribeFile(filePath, outputDir) {
9367
+ const tc = await this.loadTranscribeCli();
9368
+ if (!tc)
9369
+ return null;
9370
+ try {
9371
+ const result = await tc.transcribe(filePath, {
9372
+ model: this.config.model,
9373
+ format: "json",
9374
+ diarize: false,
9375
+ wordTimestamps: false
9376
+ });
9377
+ if (outputDir) {
9378
+ const { basename: basename8 } = await import("node:path");
9379
+ const transcriptDir = join16(outputDir, ".oa", "transcripts");
9380
+ mkdirSync5(transcriptDir, { recursive: true });
9381
+ const outFile = join16(transcriptDir, `${basename8(filePath)}.txt`);
9382
+ writeFileSync5(outFile, result.text, "utf-8");
9383
+ }
9384
+ return {
9385
+ text: result.text,
9386
+ duration: result.duration,
9387
+ speakers: result.speakers,
9388
+ segments: result.segments
9389
+ };
9390
+ } catch (err) {
9391
+ this.emit("error", err instanceof Error ? err : new Error(String(err)));
9392
+ return null;
9393
+ }
9394
+ }
9395
+ // -------------------------------------------------------------------------
9396
+ // Auto-mode silence detection
9397
+ // -------------------------------------------------------------------------
9398
+ resetSilenceTimer() {
9399
+ if (this.silenceTimer)
9400
+ clearTimeout(this.silenceTimer);
9401
+ if (this.countdownInterval)
9402
+ clearInterval(this.countdownInterval);
9403
+ const timeoutMs = this.config.silenceTimeoutMs;
9404
+ const startTime = Date.now();
9405
+ this.countdownInterval = setInterval(() => {
9406
+ const elapsed = Date.now() - startTime;
9407
+ const remaining = Math.max(0, Math.ceil((timeoutMs - elapsed) / 1e3));
9408
+ this.emit("countdown", remaining);
9409
+ if (remaining <= 0 && this.countdownInterval) {
9410
+ clearInterval(this.countdownInterval);
9411
+ this.countdownInterval = null;
9412
+ }
9413
+ }, 1e3);
9414
+ this.silenceTimer = setTimeout(() => {
9415
+ if (this.active && this.pendingText) {
9416
+ this.emit("countdown", 0);
9417
+ this.emit("transcript", this.pendingText, true);
9418
+ }
9419
+ }, timeoutMs);
9420
+ }
9421
+ };
9422
+ _engine = null;
9423
+ }
9424
+ });
9425
+
8729
9426
  // packages/cli/dist/tui/model-picker.js
8730
9427
  async function fetchOllamaModels(baseUrl) {
8731
9428
  const url = `${baseUrl.replace(/\/$/, "")}/api/tags`;
@@ -9016,10 +9713,16 @@ function renderSlashHelp() {
9016
9713
  ["/dream deep", "Deep dream \u2014 multi-cycle exploration with sleep architecture"],
9017
9714
  ["/dream lucid", "Lucid dream \u2014 implements proposals, tests, evaluates in cycles"],
9018
9715
  ["/dream stop", "Wake up \u2014 stop dreaming"],
9716
+ ["/listen", "Toggle live microphone transcription (Whisper)"],
9717
+ ["/listen <model>", "Set model: tiny, base, small, medium, large"],
9718
+ ["/listen confirm", "Require Enter to submit transcription"],
9719
+ ["/listen auto", "Auto-submit after 3s silence (blinking \u25CF indicator)"],
9720
+ ["/listen stop", "Stop listening"],
9019
9721
  ["/bruteforce", "Toggle brute-force mode (auto re-engage on turn limit)"],
9020
9722
  ["/tools", "List agent-created custom tools"],
9021
9723
  ["/skills", "List available AIWG skills"],
9022
9724
  ["/skills <keyword>", "Filter skills by name or trigger"],
9725
+ ["/<skill-name> [args]", "Invoke an AIWG skill directly"],
9023
9726
  ["/verbose", "Toggle verbose mode"],
9024
9727
  ["/clear", "Clear the screen"],
9025
9728
  ["/help", "Show this help"],
@@ -9149,6 +9852,10 @@ function formatToolArgs(toolName, args) {
9149
9852
  return String(args["region"] ?? "full screen");
9150
9853
  case "ocr":
9151
9854
  return `${args["path"] ?? ""}${args["region"] ? ` (${args["region"]})` : ""}`;
9855
+ case "transcribe_file":
9856
+ return `${args["path"] ?? ""}${args["model"] ? ` (${args["model"]})` : ""}`;
9857
+ case "transcribe_url":
9858
+ return truncStr(String(args["url"] ?? ""), 60);
9152
9859
  default:
9153
9860
  return Object.entries(args).map(([k, v]) => `${k}=${truncStr(String(v), 30)}`).join(", ");
9154
9861
  }
@@ -9214,7 +9921,10 @@ var init_render = __esm({
9214
9921
  // Image tools
9215
9922
  image_read: "\u{1F5BC}\uFE0F",
9216
9923
  screenshot: "\u{1F4F8}",
9217
- ocr: "\u{1F441}\uFE0F"
9924
+ ocr: "\u{1F441}\uFE0F",
9925
+ // Transcription tools
9926
+ transcribe_file: "\u{1F399}\uFE0F",
9927
+ transcribe_url: "\u{1F399}\uFE0F"
9218
9928
  };
9219
9929
  TOOL_LABELS = {
9220
9930
  file_read: "Read",
@@ -9245,7 +9955,10 @@ var init_render = __esm({
9245
9955
  // Image tools
9246
9956
  image_read: "Image read",
9247
9957
  screenshot: "Screenshot",
9248
- ocr: "OCR"
9958
+ ocr: "OCR",
9959
+ // Transcription tools
9960
+ transcribe_file: "Transcribe",
9961
+ transcribe_url: "Transcribe URL"
9249
9962
  };
9250
9963
  _contentWriteHook = null;
9251
9964
  HINTS = [
@@ -9287,6 +10000,8 @@ var init_render = __esm({
9287
10000
  "image_read",
9288
10001
  "screenshot",
9289
10002
  "ocr",
10003
+ "transcribe_file",
10004
+ "transcribe_url",
9290
10005
  "aiwg_setup",
9291
10006
  "aiwg_health",
9292
10007
  "aiwg_workflow",
@@ -9301,11 +10016,13 @@ var init_render = __esm({
9301
10016
  "/config",
9302
10017
  "/update",
9303
10018
  "/voice",
10019
+ "/listen",
9304
10020
  "/stream",
9305
10021
  "/verbose",
9306
10022
  "/dream",
9307
10023
  "/bruteforce",
9308
10024
  "/tools",
10025
+ "/skills",
9309
10026
  "/clear",
9310
10027
  "/help",
9311
10028
  "/quit"
@@ -9314,52 +10031,52 @@ var init_render = __esm({
9314
10031
  });
9315
10032
 
9316
10033
  // packages/cli/dist/tui/oa-directory.js
9317
- import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync4, readdirSync as readdirSync6, statSync as statSync5, unlinkSync } from "node:fs";
9318
- import { join as join15, relative as relative2, basename as basename3, extname as extname4 } from "node:path";
9319
- import { homedir as homedir5 } from "node:os";
10034
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync6, readdirSync as readdirSync6, statSync as statSync5, unlinkSync as unlinkSync2 } from "node:fs";
10035
+ import { join as join17, relative as relative2, basename as basename4, extname as extname5 } from "node:path";
10036
+ import { homedir as homedir7 } from "node:os";
9320
10037
  function initOaDirectory(repoRoot) {
9321
- const oaPath = join15(repoRoot, OA_DIR);
10038
+ const oaPath = join17(repoRoot, OA_DIR);
9322
10039
  for (const sub of SUBDIRS) {
9323
- mkdirSync4(join15(oaPath, sub), { recursive: true });
10040
+ mkdirSync6(join17(oaPath, sub), { recursive: true });
9324
10041
  }
9325
10042
  return oaPath;
9326
10043
  }
9327
10044
  function hasOaDirectory(repoRoot) {
9328
- return existsSync10(join15(repoRoot, OA_DIR, "index"));
10045
+ return existsSync12(join17(repoRoot, OA_DIR, "index"));
9329
10046
  }
9330
10047
  function loadProjectSettings(repoRoot) {
9331
- const settingsPath = join15(repoRoot, OA_DIR, "settings.json");
10048
+ const settingsPath = join17(repoRoot, OA_DIR, "settings.json");
9332
10049
  try {
9333
- if (existsSync10(settingsPath)) {
9334
- return JSON.parse(readFileSync9(settingsPath, "utf-8"));
10050
+ if (existsSync12(settingsPath)) {
10051
+ return JSON.parse(readFileSync10(settingsPath, "utf-8"));
9335
10052
  }
9336
10053
  } catch {
9337
10054
  }
9338
10055
  return {};
9339
10056
  }
9340
10057
  function saveProjectSettings(repoRoot, settings) {
9341
- const oaPath = join15(repoRoot, OA_DIR);
9342
- mkdirSync4(oaPath, { recursive: true });
10058
+ const oaPath = join17(repoRoot, OA_DIR);
10059
+ mkdirSync6(oaPath, { recursive: true });
9343
10060
  const existing = loadProjectSettings(repoRoot);
9344
10061
  const merged = { ...existing, ...settings };
9345
- writeFileSync4(join15(oaPath, "settings.json"), JSON.stringify(merged, null, 2) + "\n", "utf-8");
10062
+ writeFileSync6(join17(oaPath, "settings.json"), JSON.stringify(merged, null, 2) + "\n", "utf-8");
9346
10063
  }
9347
10064
  function loadGlobalSettings() {
9348
- const settingsPath = join15(homedir5(), ".open-agents", "settings.json");
10065
+ const settingsPath = join17(homedir7(), ".open-agents", "settings.json");
9349
10066
  try {
9350
- if (existsSync10(settingsPath)) {
9351
- return JSON.parse(readFileSync9(settingsPath, "utf-8"));
10067
+ if (existsSync12(settingsPath)) {
10068
+ return JSON.parse(readFileSync10(settingsPath, "utf-8"));
9352
10069
  }
9353
10070
  } catch {
9354
10071
  }
9355
10072
  return {};
9356
10073
  }
9357
10074
  function saveGlobalSettings(settings) {
9358
- const dir = join15(homedir5(), ".open-agents");
9359
- mkdirSync4(dir, { recursive: true });
10075
+ const dir = join17(homedir7(), ".open-agents");
10076
+ mkdirSync6(dir, { recursive: true });
9360
10077
  const existing = loadGlobalSettings();
9361
10078
  const merged = { ...existing, ...settings };
9362
- writeFileSync4(join15(dir, "settings.json"), JSON.stringify(merged, null, 2) + "\n", "utf-8");
10079
+ writeFileSync6(join17(dir, "settings.json"), JSON.stringify(merged, null, 2) + "\n", "utf-8");
9363
10080
  }
9364
10081
  function resolveSettings(repoRoot) {
9365
10082
  const global = loadGlobalSettings();
@@ -9374,12 +10091,12 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
9374
10091
  while (dir && !visited.has(dir)) {
9375
10092
  visited.add(dir);
9376
10093
  for (const name of CONTEXT_FILES) {
9377
- const filePath = join15(dir, name);
10094
+ const filePath = join17(dir, name);
9378
10095
  const normalizedName = name.toLowerCase();
9379
- if (existsSync10(filePath) && !seen.has(filePath)) {
10096
+ if (existsSync12(filePath) && !seen.has(filePath)) {
9380
10097
  seen.add(filePath);
9381
10098
  try {
9382
- let content = readFileSync9(filePath, "utf-8");
10099
+ let content = readFileSync10(filePath, "utf-8");
9383
10100
  if (content.length > maxContentLen) {
9384
10101
  content = content.slice(0, maxContentLen) + "\n\n...(truncated)";
9385
10102
  }
@@ -9393,11 +10110,11 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
9393
10110
  }
9394
10111
  }
9395
10112
  }
9396
- const projectMap = join15(dir, OA_DIR, "context", "project-map.md");
9397
- if (existsSync10(projectMap) && !seen.has(projectMap)) {
10113
+ const projectMap = join17(dir, OA_DIR, "context", "project-map.md");
10114
+ if (existsSync12(projectMap) && !seen.has(projectMap)) {
9398
10115
  seen.add(projectMap);
9399
10116
  try {
9400
- let content = readFileSync9(projectMap, "utf-8");
10117
+ let content = readFileSync10(projectMap, "utf-8");
9401
10118
  if (content.length > maxContentLen) {
9402
10119
  content = content.slice(0, maxContentLen) + "\n\n...(truncated)";
9403
10120
  }
@@ -9409,7 +10126,7 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
9409
10126
  } catch {
9410
10127
  }
9411
10128
  }
9412
- const parent = join15(dir, "..");
10129
+ const parent = join17(dir, "..");
9413
10130
  if (parent === dir)
9414
10131
  break;
9415
10132
  dir = parent;
@@ -9427,16 +10144,16 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
9427
10144
  return found;
9428
10145
  }
9429
10146
  function readIndexMeta(repoRoot) {
9430
- const metaPath = join15(repoRoot, OA_DIR, "index", "meta.json");
10147
+ const metaPath = join17(repoRoot, OA_DIR, "index", "meta.json");
9431
10148
  try {
9432
- return JSON.parse(readFileSync9(metaPath, "utf-8"));
10149
+ return JSON.parse(readFileSync10(metaPath, "utf-8"));
9433
10150
  } catch {
9434
10151
  return null;
9435
10152
  }
9436
10153
  }
9437
10154
  function generateProjectMap(repoRoot) {
9438
10155
  const sections = [];
9439
- const repoName2 = basename3(repoRoot);
10156
+ const repoName2 = basename4(repoRoot);
9440
10157
  sections.push(`# Project Map: ${repoName2}
9441
10158
  `);
9442
10159
  sections.push(`> Auto-generated by open-agents. Updated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
@@ -9480,28 +10197,28 @@ ${tree}\`\`\`
9480
10197
  sections.push("");
9481
10198
  }
9482
10199
  const content = sections.join("\n");
9483
- const contextDir = join15(repoRoot, OA_DIR, "context");
9484
- mkdirSync4(contextDir, { recursive: true });
9485
- writeFileSync4(join15(contextDir, "project-map.md"), content, "utf-8");
10200
+ const contextDir = join17(repoRoot, OA_DIR, "context");
10201
+ mkdirSync6(contextDir, { recursive: true });
10202
+ writeFileSync6(join17(contextDir, "project-map.md"), content, "utf-8");
9486
10203
  return content;
9487
10204
  }
9488
10205
  function saveSession(repoRoot, session) {
9489
- const historyDir = join15(repoRoot, OA_DIR, "history");
9490
- mkdirSync4(historyDir, { recursive: true });
9491
- writeFileSync4(join15(historyDir, `${session.id}.json`), JSON.stringify(session, null, 2), "utf-8");
10206
+ const historyDir = join17(repoRoot, OA_DIR, "history");
10207
+ mkdirSync6(historyDir, { recursive: true });
10208
+ writeFileSync6(join17(historyDir, `${session.id}.json`), JSON.stringify(session, null, 2), "utf-8");
9492
10209
  }
9493
10210
  function loadRecentSessions(repoRoot, limit = 5) {
9494
- const historyDir = join15(repoRoot, OA_DIR, "history");
9495
- if (!existsSync10(historyDir))
10211
+ const historyDir = join17(repoRoot, OA_DIR, "history");
10212
+ if (!existsSync12(historyDir))
9496
10213
  return [];
9497
10214
  try {
9498
10215
  const files = readdirSync6(historyDir).filter((f) => f.endsWith(".json")).map((f) => {
9499
- const stat3 = statSync5(join15(historyDir, f));
10216
+ const stat3 = statSync5(join17(historyDir, f));
9500
10217
  return { file: f, mtime: stat3.mtimeMs };
9501
10218
  }).sort((a, b) => b.mtime - a.mtime).slice(0, limit);
9502
10219
  return files.map((f) => {
9503
10220
  try {
9504
- return JSON.parse(readFileSync9(join15(historyDir, f.file), "utf-8"));
10221
+ return JSON.parse(readFileSync10(join17(historyDir, f.file), "utf-8"));
9505
10222
  } catch {
9506
10223
  return null;
9507
10224
  }
@@ -9511,18 +10228,18 @@ function loadRecentSessions(repoRoot, limit = 5) {
9511
10228
  }
9512
10229
  }
9513
10230
  function savePendingTask(repoRoot, task) {
9514
- const historyDir = join15(repoRoot, OA_DIR, "history");
9515
- mkdirSync4(historyDir, { recursive: true });
9516
- writeFileSync4(join15(historyDir, PENDING_TASK_FILE), JSON.stringify(task, null, 2) + "\n", "utf-8");
10231
+ const historyDir = join17(repoRoot, OA_DIR, "history");
10232
+ mkdirSync6(historyDir, { recursive: true });
10233
+ writeFileSync6(join17(historyDir, PENDING_TASK_FILE), JSON.stringify(task, null, 2) + "\n", "utf-8");
9517
10234
  }
9518
10235
  function loadPendingTask(repoRoot) {
9519
- const filePath = join15(repoRoot, OA_DIR, "history", PENDING_TASK_FILE);
10236
+ const filePath = join17(repoRoot, OA_DIR, "history", PENDING_TASK_FILE);
9520
10237
  try {
9521
- if (!existsSync10(filePath))
10238
+ if (!existsSync12(filePath))
9522
10239
  return null;
9523
- const data = JSON.parse(readFileSync9(filePath, "utf-8"));
10240
+ const data = JSON.parse(readFileSync10(filePath, "utf-8"));
9524
10241
  try {
9525
- unlinkSync(filePath);
10242
+ unlinkSync2(filePath);
9526
10243
  } catch {
9527
10244
  }
9528
10245
  return data;
@@ -9548,12 +10265,12 @@ function detectManifests(repoRoot) {
9548
10265
  { file: "docker-compose.yaml", type: "Docker Compose" }
9549
10266
  ];
9550
10267
  for (const check of checks) {
9551
- const filePath = join15(repoRoot, check.file);
9552
- if (existsSync10(filePath)) {
10268
+ const filePath = join17(repoRoot, check.file);
10269
+ if (existsSync12(filePath)) {
9553
10270
  let name;
9554
10271
  if (check.nameField) {
9555
10272
  try {
9556
- const data = JSON.parse(readFileSync9(filePath, "utf-8"));
10273
+ const data = JSON.parse(readFileSync10(filePath, "utf-8"));
9557
10274
  name = data[check.nameField];
9558
10275
  } catch {
9559
10276
  }
@@ -9582,7 +10299,7 @@ function findKeyFiles(repoRoot) {
9582
10299
  { pattern: "CLAUDE.md", description: "Claude Code context" }
9583
10300
  ];
9584
10301
  for (const check of checks) {
9585
- if (existsSync10(join15(repoRoot, check.pattern))) {
10302
+ if (existsSync12(join17(repoRoot, check.pattern))) {
9586
10303
  keyFiles.push({ path: check.pattern, description: check.description });
9587
10304
  }
9588
10305
  }
@@ -9608,12 +10325,12 @@ function buildDirTree(root, maxDepth, prefix = "", depth = 0) {
9608
10325
  if (entry.isDirectory()) {
9609
10326
  let fileCount = 0;
9610
10327
  try {
9611
- fileCount = readdirSync6(join15(root, entry.name)).filter((f) => !f.startsWith(".")).length;
10328
+ fileCount = readdirSync6(join17(root, entry.name)).filter((f) => !f.startsWith(".")).length;
9612
10329
  } catch {
9613
10330
  }
9614
10331
  result += `${prefix}${connector}${entry.name}/ (${fileCount})
9615
10332
  `;
9616
- result += buildDirTree(join15(root, entry.name), maxDepth, childPrefix, depth + 1);
10333
+ result += buildDirTree(join17(root, entry.name), maxDepth, childPrefix, depth + 1);
9617
10334
  } else if (depth < maxDepth) {
9618
10335
  result += `${prefix}${connector}${entry.name}
9619
10336
  `;
@@ -9808,7 +10525,7 @@ async function handleSlashCommand(input, ctx) {
9808
10525
  }
9809
10526
  }
9810
10527
  process.stdout.write("\n");
9811
- renderInfo("The agent can use these via the skill_execute tool.");
10528
+ renderInfo('Invoke directly: /<skill-name> [args] (e.g. /ralph "fix tests" --completion "npm test passes")');
9812
10529
  renderInfo("Filter with: /skills <keyword>");
9813
10530
  }
9814
10531
  return "handled";
@@ -9829,6 +10546,38 @@ async function handleSlashCommand(input, ctx) {
9829
10546
  }
9830
10547
  return "handled";
9831
10548
  }
10549
+ case "listen":
10550
+ case "mic": {
10551
+ if (!ctx.listenToggle) {
10552
+ renderWarning("Listen mode not available in this context.");
10553
+ return "handled";
10554
+ }
10555
+ if (arg === "stop" || arg === "off") {
10556
+ const msg2 = await (ctx.listenStop?.() ?? Promise.resolve("Not listening."));
10557
+ renderInfo(msg2);
10558
+ return "handled";
10559
+ }
10560
+ if (arg === "confirm") {
10561
+ const msg2 = ctx.listenSetMode?.("confirm") ?? "Confirm mode set.";
10562
+ renderInfo(msg2);
10563
+ return "handled";
10564
+ }
10565
+ if (arg === "auto") {
10566
+ const msg2 = ctx.listenSetMode?.("auto") ?? "Auto mode set.";
10567
+ renderInfo(msg2);
10568
+ return "handled";
10569
+ }
10570
+ const modelSizes = ["tiny", "base", "small", "medium", "large", "large-v3"];
10571
+ if (arg && modelSizes.includes(arg.toLowerCase())) {
10572
+ const model = arg.toLowerCase() === "large" ? "large-v3" : arg.toLowerCase();
10573
+ const msg2 = await (ctx.listenSetModel?.(model) ?? Promise.resolve(`Model set to ${model}.`));
10574
+ renderInfo(msg2);
10575
+ return "handled";
10576
+ }
10577
+ const msg = await ctx.listenToggle();
10578
+ renderInfo(msg);
10579
+ return "handled";
10580
+ }
9832
10581
  case "bruteforce":
9833
10582
  case "brute": {
9834
10583
  const isOn = ctx.bruteForceToggle();
@@ -9837,9 +10586,19 @@ async function handleSlashCommand(input, ctx) {
9837
10586
  renderInfo(`Brute-force mode: ${isOn ? "on" : "off"}${hasLocal ? " (project-local)" : ""}` + (isOn ? " \u2014 agent will auto re-engage when turn limit is hit, reassess and try creative strategies" : ""));
9838
10587
  return "handled";
9839
10588
  }
9840
- default:
10589
+ default: {
10590
+ const skills = discoverSkills(ctx.repoRoot);
10591
+ const skill = skills.find((s) => s.name === cmd || s.name === cmd.replace(/_/g, "-"));
10592
+ if (skill) {
10593
+ const content = loadSkillContent(skill.filePath);
10594
+ if (content) {
10595
+ renderInfo(`Loading skill: ${c2.bold(skill.name)} (${skill.source})`);
10596
+ return { type: "skill", name: skill.name, content, args: arg };
10597
+ }
10598
+ }
9841
10599
  renderWarning(`Unknown command: /${cmd}. Type /help for available commands.`);
9842
10600
  return "handled";
10601
+ }
9843
10602
  }
9844
10603
  }
9845
10604
  async function listModels(ctx) {
@@ -9971,17 +10730,17 @@ async function handleUpdate(subcommand, repoRoot, savePendingTaskState) {
9971
10730
  try {
9972
10731
  const { createRequire: createRequire4 } = await import("node:module");
9973
10732
  const { fileURLToPath: fileURLToPath3 } = await import("node:url");
9974
- const { dirname: dirname5, join: join26 } = await import("node:path");
9975
- const { existsSync: existsSync17 } = await import("node:fs");
10733
+ const { dirname: dirname5, join: join28 } = await import("node:path");
10734
+ const { existsSync: existsSync19 } = await import("node:fs");
9976
10735
  const req = createRequire4(import.meta.url);
9977
10736
  const thisDir = dirname5(fileURLToPath3(import.meta.url));
9978
10737
  const candidates = [
9979
- join26(thisDir, "..", "package.json"),
9980
- join26(thisDir, "..", "..", "package.json"),
9981
- join26(thisDir, "..", "..", "..", "package.json")
10738
+ join28(thisDir, "..", "package.json"),
10739
+ join28(thisDir, "..", "..", "package.json"),
10740
+ join28(thisDir, "..", "..", "..", "package.json")
9982
10741
  ];
9983
10742
  for (const pkgPath of candidates) {
9984
- if (existsSync17(pkgPath)) {
10743
+ if (existsSync19(pkgPath)) {
9985
10744
  const pkg = req(pkgPath);
9986
10745
  if (pkg.name === "open-agents-ai" || pkg.name === "@open-agents/cli") {
9987
10746
  currentVersion = pkg.version ?? "0.0.0";
@@ -10065,17 +10824,17 @@ var init_commands = __esm({
10065
10824
 
10066
10825
  // packages/cli/dist/tui/setup.js
10067
10826
  import * as readline from "node:readline";
10068
- import { execSync as execSync9 } from "node:child_process";
10069
- import { existsSync as existsSync11, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5 } from "node:fs";
10070
- import { join as join16 } from "node:path";
10071
- import { homedir as homedir6 } from "node:os";
10827
+ import { execSync as execSync11 } from "node:child_process";
10828
+ import { existsSync as existsSync13, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "node:fs";
10829
+ import { join as join18 } from "node:path";
10830
+ import { homedir as homedir8 } from "node:os";
10072
10831
  function detectSystemSpecs() {
10073
10832
  let totalRamGB = 0;
10074
10833
  let availableRamGB = 0;
10075
10834
  let gpuVramGB = 0;
10076
10835
  let gpuName = "";
10077
10836
  try {
10078
- const memInfo = execSync9("free -b 2>/dev/null || sysctl -n hw.memsize 2>/dev/null", {
10837
+ const memInfo = execSync11("free -b 2>/dev/null || sysctl -n hw.memsize 2>/dev/null", {
10079
10838
  encoding: "utf8",
10080
10839
  timeout: 5e3
10081
10840
  });
@@ -10095,7 +10854,7 @@ function detectSystemSpecs() {
10095
10854
  } catch {
10096
10855
  }
10097
10856
  try {
10098
- const nvidiaSmi = execSync9("nvidia-smi --query-gpu=memory.total,name --format=csv,noheader,nounits 2>/dev/null", { encoding: "utf8", timeout: 5e3 });
10857
+ const nvidiaSmi = execSync11("nvidia-smi --query-gpu=memory.total,name --format=csv,noheader,nounits 2>/dev/null", { encoding: "utf8", timeout: 5e3 });
10099
10858
  const lines = nvidiaSmi.trim().split("\n");
10100
10859
  if (lines.length > 0) {
10101
10860
  for (const line of lines) {
@@ -10151,13 +10910,13 @@ function modelSupportsToolCalling(modelName) {
10151
10910
  return false;
10152
10911
  }
10153
10912
  function ask(rl, question) {
10154
- return new Promise((resolve15) => {
10155
- rl.question(question, (answer) => resolve15(answer.trim()));
10913
+ return new Promise((resolve16) => {
10914
+ rl.question(question, (answer) => resolve16(answer.trim()));
10156
10915
  });
10157
10916
  }
10158
10917
  function pullModelWithAutoUpdate(tag) {
10159
10918
  try {
10160
- execSync9(`ollama pull ${tag}`, {
10919
+ execSync11(`ollama pull ${tag}`, {
10161
10920
  stdio: "inherit",
10162
10921
  timeout: 36e5
10163
10922
  // 1 hour max
@@ -10174,7 +10933,7 @@ function pullModelWithAutoUpdate(tag) {
10174
10933
 
10175
10934
  `);
10176
10935
  try {
10177
- execSync9("curl -fsSL https://ollama.com/install.sh | sh", {
10936
+ execSync11("curl -fsSL https://ollama.com/install.sh | sh", {
10178
10937
  stdio: "inherit",
10179
10938
  timeout: 3e5
10180
10939
  // 5 min max for install
@@ -10185,7 +10944,7 @@ function pullModelWithAutoUpdate(tag) {
10185
10944
  process.stdout.write(` ${c2.cyan("\u25CF")} Retrying pull of ${c2.bold(tag)}...
10186
10945
 
10187
10946
  `);
10188
- execSync9(`ollama pull ${tag}`, {
10947
+ execSync11(`ollama pull ${tag}`, {
10189
10948
  stdio: "inherit",
10190
10949
  timeout: 36e5
10191
10950
  });
@@ -10363,12 +11122,12 @@ async function doSetup(config, rl) {
10363
11122
  `PARAMETER num_predict 16384`,
10364
11123
  `PARAMETER stop "<|endoftext|>"`
10365
11124
  ].join("\n");
10366
- const modelDir2 = join16(homedir6(), ".open-agents", "models");
10367
- mkdirSync5(modelDir2, { recursive: true });
10368
- const modelfilePath = join16(modelDir2, `Modelfile.${customName}`);
10369
- writeFileSync5(modelfilePath, modelfileContent + "\n", "utf8");
11125
+ const modelDir2 = join18(homedir8(), ".open-agents", "models");
11126
+ mkdirSync7(modelDir2, { recursive: true });
11127
+ const modelfilePath = join18(modelDir2, `Modelfile.${customName}`);
11128
+ writeFileSync7(modelfilePath, modelfileContent + "\n", "utf8");
10370
11129
  process.stdout.write(` ${c2.dim("Creating model...")} `);
10371
- execSync9(`ollama create ${customName} -f ${modelfilePath}`, {
11130
+ execSync11(`ollama create ${customName} -f ${modelfilePath}`, {
10372
11131
  stdio: "pipe",
10373
11132
  timeout: 12e4
10374
11133
  });
@@ -10411,7 +11170,7 @@ async function isModelAvailable(config) {
10411
11170
  }
10412
11171
  function isFirstRun() {
10413
11172
  try {
10414
- return !existsSync11(join16(homedir6(), ".open-agents", "config.json"));
11173
+ return !existsSync13(join18(homedir8(), ".open-agents", "config.json"));
10415
11174
  } catch {
10416
11175
  return true;
10417
11176
  }
@@ -10451,10 +11210,10 @@ var init_setup = __esm({
10451
11210
  });
10452
11211
 
10453
11212
  // packages/cli/dist/tui/project-context.js
10454
- import { existsSync as existsSync12, readFileSync as readFileSync10, readdirSync as readdirSync7 } from "node:fs";
10455
- import { join as join17, basename as basename4 } from "node:path";
10456
- import { execSync as execSync10 } from "node:child_process";
10457
- import { homedir as homedir7, platform, release } from "node:os";
11213
+ import { existsSync as existsSync14, readFileSync as readFileSync11, readdirSync as readdirSync7 } from "node:fs";
11214
+ import { join as join19, basename as basename5 } from "node:path";
11215
+ import { execSync as execSync12 } from "node:child_process";
11216
+ import { homedir as homedir9, platform, release } from "node:os";
10458
11217
  function loadProjectFiles(repoRoot) {
10459
11218
  const discovered = discoverContextFiles(repoRoot);
10460
11219
  if (discovered.length === 0)
@@ -10472,10 +11231,10 @@ function loadProjectMap(repoRoot) {
10472
11231
  if (!hasOaDirectory(repoRoot)) {
10473
11232
  initOaDirectory(repoRoot);
10474
11233
  }
10475
- const mapPath = join17(repoRoot, OA_DIR, "context", "project-map.md");
10476
- if (existsSync12(mapPath)) {
11234
+ const mapPath = join19(repoRoot, OA_DIR, "context", "project-map.md");
11235
+ if (existsSync14(mapPath)) {
10477
11236
  try {
10478
- const content = readFileSync10(mapPath, "utf-8");
11237
+ const content = readFileSync11(mapPath, "utf-8");
10479
11238
  return content;
10480
11239
  } catch {
10481
11240
  }
@@ -10484,19 +11243,19 @@ function loadProjectMap(repoRoot) {
10484
11243
  }
10485
11244
  function getGitInfo(repoRoot) {
10486
11245
  try {
10487
- execSync10("git rev-parse --is-inside-work-tree", { cwd: repoRoot, stdio: "pipe" });
11246
+ execSync12("git rev-parse --is-inside-work-tree", { cwd: repoRoot, stdio: "pipe" });
10488
11247
  } catch {
10489
11248
  return "";
10490
11249
  }
10491
11250
  const lines = [];
10492
11251
  try {
10493
- const branch = execSync10("git branch --show-current", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
11252
+ const branch = execSync12("git branch --show-current", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
10494
11253
  if (branch)
10495
11254
  lines.push(`Branch: ${branch}`);
10496
11255
  } catch {
10497
11256
  }
10498
11257
  try {
10499
- const status = execSync10("git status --porcelain", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
11258
+ const status = execSync12("git status --porcelain", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
10500
11259
  if (status) {
10501
11260
  const changed = status.split("\n").length;
10502
11261
  lines.push(`Working tree: ${changed} changed file(s)`);
@@ -10506,7 +11265,7 @@ function getGitInfo(repoRoot) {
10506
11265
  } catch {
10507
11266
  }
10508
11267
  try {
10509
- const log = execSync10("git log --oneline -5 --no-decorate", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
11268
+ const log = execSync12("git log --oneline -5 --no-decorate", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
10510
11269
  if (log)
10511
11270
  lines.push(`Recent commits:
10512
11271
  ${log}`);
@@ -10516,33 +11275,33 @@ ${log}`);
10516
11275
  }
10517
11276
  function loadMemoryContext(repoRoot) {
10518
11277
  const sections = [];
10519
- const oaMemDir = join17(repoRoot, OA_DIR, "memory");
11278
+ const oaMemDir = join19(repoRoot, OA_DIR, "memory");
10520
11279
  const oaEntries = loadMemoryDir(oaMemDir, "project");
10521
11280
  if (oaEntries)
10522
11281
  sections.push(oaEntries);
10523
- const legacyMemDir = join17(repoRoot, ".open-agents", "memory");
10524
- if (legacyMemDir !== oaMemDir && existsSync12(legacyMemDir)) {
11282
+ const legacyMemDir = join19(repoRoot, ".open-agents", "memory");
11283
+ if (legacyMemDir !== oaMemDir && existsSync14(legacyMemDir)) {
10525
11284
  const legacyEntries = loadMemoryDir(legacyMemDir, "project/legacy");
10526
11285
  if (legacyEntries)
10527
11286
  sections.push(legacyEntries);
10528
11287
  }
10529
- const globalMemDir = join17(homedir7(), ".open-agents", "memory");
11288
+ const globalMemDir = join19(homedir9(), ".open-agents", "memory");
10530
11289
  const globalEntries = loadMemoryDir(globalMemDir, "global");
10531
11290
  if (globalEntries)
10532
11291
  sections.push(globalEntries);
10533
11292
  return sections.join("\n\n");
10534
11293
  }
10535
11294
  function loadMemoryDir(memDir, scope) {
10536
- if (!existsSync12(memDir))
11295
+ if (!existsSync14(memDir))
10537
11296
  return "";
10538
11297
  const lines = [];
10539
11298
  try {
10540
11299
  const files = readdirSync7(memDir).filter((f) => f.endsWith(".json"));
10541
11300
  for (const file of files.slice(0, 10)) {
10542
11301
  try {
10543
- const raw = readFileSync10(join17(memDir, file), "utf-8");
11302
+ const raw = readFileSync11(join19(memDir, file), "utf-8");
10544
11303
  const entries = JSON.parse(raw);
10545
- const topic = basename4(file, ".json");
11304
+ const topic = basename5(file, ".json");
10546
11305
  const keys = Object.keys(entries);
10547
11306
  if (keys.length === 0)
10548
11307
  continue;
@@ -11551,25 +12310,25 @@ var init_carousel = __esm({
11551
12310
  });
11552
12311
 
11553
12312
  // packages/cli/dist/tui/voice.js
11554
- import { existsSync as existsSync13, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6, readFileSync as readFileSync11, unlinkSync as unlinkSync2 } from "node:fs";
11555
- import { join as join18 } from "node:path";
11556
- import { homedir as homedir8, tmpdir as tmpdir2, platform as platform2 } from "node:os";
11557
- import { execSync as execSync11, spawn as nodeSpawn } from "node:child_process";
12313
+ import { existsSync as existsSync15, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8, readFileSync as readFileSync12, unlinkSync as unlinkSync3 } from "node:fs";
12314
+ import { join as join20 } from "node:path";
12315
+ import { homedir as homedir10, tmpdir as tmpdir2, platform as platform2 } from "node:os";
12316
+ import { execSync as execSync13, spawn as nodeSpawn } from "node:child_process";
11558
12317
  import { createRequire } from "node:module";
11559
12318
  function voiceDir() {
11560
- return join18(homedir8(), ".open-agents", "voice");
12319
+ return join20(homedir10(), ".open-agents", "voice");
11561
12320
  }
11562
12321
  function modelsDir() {
11563
- return join18(voiceDir(), "models");
12322
+ return join20(voiceDir(), "models");
11564
12323
  }
11565
12324
  function modelDir(id) {
11566
- return join18(modelsDir(), id);
12325
+ return join20(modelsDir(), id);
11567
12326
  }
11568
12327
  function modelOnnxPath(id) {
11569
- return join18(modelDir(id), "model.onnx");
12328
+ return join20(modelDir(id), "model.onnx");
11570
12329
  }
11571
12330
  function modelConfigPath(id) {
11572
- return join18(modelDir(id), "config.json");
12331
+ return join20(modelDir(id), "config.json");
11573
12332
  }
11574
12333
  function describeToolCall(toolName, args) {
11575
12334
  const path = args["path"];
@@ -11842,11 +12601,11 @@ var init_voice = __esm({
11842
12601
  const audioData = result["output"].data;
11843
12602
  if (audioData.length === 0)
11844
12603
  return;
11845
- const wavPath = join18(tmpdir2(), `oa-voice-${Date.now()}.wav`);
12604
+ const wavPath = join20(tmpdir2(), `oa-voice-${Date.now()}.wav`);
11846
12605
  this.writeWav(audioData, this.config.audio.sample_rate, wavPath);
11847
12606
  await this.playWav(wavPath);
11848
12607
  try {
11849
- unlinkSync2(wavPath);
12608
+ unlinkSync3(wavPath);
11850
12609
  } catch {
11851
12610
  }
11852
12611
  }
@@ -11922,7 +12681,7 @@ var init_voice = __esm({
11922
12681
  buffer.write("data", 36);
11923
12682
  buffer.writeUInt32LE(dataSize, 40);
11924
12683
  Buffer.from(int16.buffer, int16.byteOffset, int16.byteLength).copy(buffer, 44);
11925
- writeFileSync6(path, buffer);
12684
+ writeFileSync8(path, buffer);
11926
12685
  }
11927
12686
  // -------------------------------------------------------------------------
11928
12687
  // Audio playback (system default speakers)
@@ -11931,7 +12690,7 @@ var init_voice = __esm({
11931
12690
  const cmd = this.getPlayCommand(path);
11932
12691
  if (!cmd)
11933
12692
  return;
11934
- return new Promise((resolve15) => {
12693
+ return new Promise((resolve16) => {
11935
12694
  const child = nodeSpawn(cmd[0], cmd.slice(1), {
11936
12695
  stdio: "ignore",
11937
12696
  detached: false
@@ -11940,12 +12699,12 @@ var init_voice = __esm({
11940
12699
  child.on("close", () => {
11941
12700
  if (this.currentPlayback === child)
11942
12701
  this.currentPlayback = null;
11943
- resolve15();
12702
+ resolve16();
11944
12703
  });
11945
12704
  child.on("error", () => {
11946
12705
  if (this.currentPlayback === child)
11947
12706
  this.currentPlayback = null;
11948
- resolve15();
12707
+ resolve16();
11949
12708
  });
11950
12709
  setTimeout(() => {
11951
12710
  if (this.currentPlayback === child) {
@@ -11955,7 +12714,7 @@ var init_voice = __esm({
11955
12714
  }
11956
12715
  this.currentPlayback = null;
11957
12716
  }
11958
- resolve15();
12717
+ resolve16();
11959
12718
  }, 15e3);
11960
12719
  });
11961
12720
  }
@@ -11972,7 +12731,7 @@ var init_voice = __esm({
11972
12731
  }
11973
12732
  for (const player of ["paplay", "pw-play", "aplay"]) {
11974
12733
  try {
11975
- execSync11(`which ${player}`, { stdio: "pipe" });
12734
+ execSync13(`which ${player}`, { stdio: "pipe" });
11976
12735
  return [player, path];
11977
12736
  } catch {
11978
12737
  }
@@ -11994,36 +12753,36 @@ var init_voice = __esm({
11994
12753
  async ensureRuntime() {
11995
12754
  if (this.ort)
11996
12755
  return;
11997
- mkdirSync6(voiceDir(), { recursive: true });
11998
- const pkgPath = join18(voiceDir(), "package.json");
12756
+ mkdirSync8(voiceDir(), { recursive: true });
12757
+ const pkgPath = join20(voiceDir(), "package.json");
11999
12758
  const expectedDeps = {
12000
12759
  "onnxruntime-node": "^1.21.0",
12001
12760
  "phonemizer": "^1.2.1"
12002
12761
  };
12003
- if (existsSync13(pkgPath)) {
12762
+ if (existsSync15(pkgPath)) {
12004
12763
  try {
12005
- const existing = JSON.parse(readFileSync11(pkgPath, "utf8"));
12764
+ const existing = JSON.parse(readFileSync12(pkgPath, "utf8"));
12006
12765
  if (!existing.dependencies?.["phonemizer"]) {
12007
12766
  existing.dependencies = { ...existing.dependencies, ...expectedDeps };
12008
- writeFileSync6(pkgPath, JSON.stringify(existing, null, 2));
12767
+ writeFileSync8(pkgPath, JSON.stringify(existing, null, 2));
12009
12768
  }
12010
12769
  } catch {
12011
12770
  }
12012
12771
  }
12013
- if (!existsSync13(pkgPath)) {
12014
- writeFileSync6(pkgPath, JSON.stringify({
12772
+ if (!existsSync15(pkgPath)) {
12773
+ writeFileSync8(pkgPath, JSON.stringify({
12015
12774
  name: "open-agents-voice",
12016
12775
  private: true,
12017
12776
  dependencies: expectedDeps
12018
12777
  }, null, 2));
12019
12778
  }
12020
- const voiceRequire = createRequire(join18(voiceDir(), "index.js"));
12779
+ const voiceRequire = createRequire(join20(voiceDir(), "index.js"));
12021
12780
  try {
12022
12781
  this.ort = voiceRequire("onnxruntime-node");
12023
12782
  } catch {
12024
12783
  renderInfo("Installing ONNX runtime for voice synthesis...");
12025
12784
  try {
12026
- execSync11("npm install --no-audit --no-fund", {
12785
+ execSync13("npm install --no-audit --no-fund", {
12027
12786
  cwd: voiceDir(),
12028
12787
  stdio: "pipe",
12029
12788
  timeout: 12e4
@@ -12040,7 +12799,7 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
12040
12799
  } catch {
12041
12800
  renderInfo("Installing phonemizer for voice synthesis...");
12042
12801
  try {
12043
- execSync11("npm install --no-audit --no-fund", {
12802
+ execSync13("npm install --no-audit --no-fund", {
12044
12803
  cwd: voiceDir(),
12045
12804
  stdio: "pipe",
12046
12805
  timeout: 12e4
@@ -12063,18 +12822,18 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
12063
12822
  const dir = modelDir(id);
12064
12823
  const onnxPath = modelOnnxPath(id);
12065
12824
  const configPath = modelConfigPath(id);
12066
- if (existsSync13(onnxPath) && existsSync13(configPath))
12825
+ if (existsSync15(onnxPath) && existsSync15(configPath))
12067
12826
  return;
12068
- mkdirSync6(dir, { recursive: true });
12069
- if (!existsSync13(configPath)) {
12827
+ mkdirSync8(dir, { recursive: true });
12828
+ if (!existsSync15(configPath)) {
12070
12829
  renderInfo(`Downloading ${model.label} voice config...`);
12071
12830
  const configResp = await fetch(model.configUrl);
12072
12831
  if (!configResp.ok)
12073
12832
  throw new Error(`Failed to download config: HTTP ${configResp.status}`);
12074
12833
  const configText = await configResp.text();
12075
- writeFileSync6(configPath, configText);
12834
+ writeFileSync8(configPath, configText);
12076
12835
  }
12077
- if (!existsSync13(onnxPath)) {
12836
+ if (!existsSync15(onnxPath)) {
12078
12837
  renderInfo(`Downloading ${model.label} voice model (this may take a minute)...`);
12079
12838
  const onnxResp = await fetch(model.onnxUrl);
12080
12839
  if (!onnxResp.ok)
@@ -12098,7 +12857,7 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
12098
12857
  }
12099
12858
  process.stdout.write("\r" + " ".repeat(60) + "\r");
12100
12859
  const fullBuffer = Buffer.concat(chunks);
12101
- writeFileSync6(onnxPath, fullBuffer);
12860
+ writeFileSync8(onnxPath, fullBuffer);
12102
12861
  renderInfo(`${model.label} model downloaded (${formatBytes2(fullBuffer.length)}).`);
12103
12862
  }
12104
12863
  }
@@ -12110,10 +12869,10 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
12110
12869
  throw new Error("ONNX runtime not loaded");
12111
12870
  const onnxPath = modelOnnxPath(this.modelId);
12112
12871
  const configPath = modelConfigPath(this.modelId);
12113
- if (!existsSync13(onnxPath) || !existsSync13(configPath)) {
12872
+ if (!existsSync15(onnxPath) || !existsSync15(configPath)) {
12114
12873
  throw new Error(`Model files not found for ${this.modelId}`);
12115
12874
  }
12116
- this.config = JSON.parse(readFileSync11(configPath, "utf8"));
12875
+ this.config = JSON.parse(readFileSync12(configPath, "utf8"));
12117
12876
  renderInfo("Loading voice model...");
12118
12877
  this.session = await this.ort.InferenceSession.create(onnxPath, {
12119
12878
  executionProviders: ["cpu"],
@@ -12570,13 +13329,13 @@ var init_stream_renderer = __esm({
12570
13329
  });
12571
13330
 
12572
13331
  // packages/cli/dist/tui/edit-history.js
12573
- import { appendFileSync, mkdirSync as mkdirSync7 } from "node:fs";
12574
- import { join as join19 } from "node:path";
13332
+ import { appendFileSync, mkdirSync as mkdirSync9 } from "node:fs";
13333
+ import { join as join21 } from "node:path";
12575
13334
  function createEditHistoryLogger(repoRoot, sessionId) {
12576
- const historyDir = join19(repoRoot, ".oa", "history");
12577
- const logPath = join19(historyDir, "edits.jsonl");
13335
+ const historyDir = join21(repoRoot, ".oa", "history");
13336
+ const logPath = join21(historyDir, "edits.jsonl");
12578
13337
  try {
12579
- mkdirSync7(historyDir, { recursive: true });
13338
+ mkdirSync9(historyDir, { recursive: true });
12580
13339
  } catch {
12581
13340
  }
12582
13341
  function logToolCall(toolName, toolArgs, success) {
@@ -12685,9 +13444,9 @@ var init_edit_history = __esm({
12685
13444
  });
12686
13445
 
12687
13446
  // packages/cli/dist/tui/dream-engine.js
12688
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, readFileSync as readFileSync12, existsSync as existsSync14, cpSync, rmSync, readdirSync as readdirSync8 } from "node:fs";
12689
- import { join as join20, basename as basename5 } from "node:path";
12690
- import { execSync as execSync12 } from "node:child_process";
13447
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync9, readFileSync as readFileSync13, existsSync as existsSync16, cpSync, rmSync, readdirSync as readdirSync8 } from "node:fs";
13448
+ import { join as join22, basename as basename6 } from "node:path";
13449
+ import { execSync as execSync14 } from "node:child_process";
12691
13450
  function adaptTool(tool) {
12692
13451
  return {
12693
13452
  name: tool.name,
@@ -12861,14 +13620,14 @@ var init_dream_engine = __esm({
12861
13620
  const content = String(args["content"] ?? "");
12862
13621
  if (!rawPath)
12863
13622
  return { success: false, output: "", error: "path is required", durationMs: Date.now() - start };
12864
- const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/dreams") ? join20(this.dreamsDir, basename5(rawPath)) : join20(this.dreamsDir, rawPath);
13623
+ const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/dreams") ? join22(this.dreamsDir, basename6(rawPath)) : join22(this.dreamsDir, rawPath);
12865
13624
  if (!targetPath.startsWith(this.dreamsDir)) {
12866
13625
  return { success: false, output: "", error: "Dream mode: writes are confined to .oa/dreams/", durationMs: Date.now() - start };
12867
13626
  }
12868
13627
  try {
12869
- const dir = join20(targetPath, "..");
12870
- mkdirSync8(dir, { recursive: true });
12871
- writeFileSync7(targetPath, content, "utf-8");
13628
+ const dir = join22(targetPath, "..");
13629
+ mkdirSync10(dir, { recursive: true });
13630
+ writeFileSync9(targetPath, content, "utf-8");
12872
13631
  return { success: true, output: `Wrote ${content.length} bytes to ${rawPath}`, durationMs: Date.now() - start };
12873
13632
  } catch (err) {
12874
13633
  return { success: false, output: "", error: String(err), durationMs: Date.now() - start };
@@ -12896,20 +13655,20 @@ var init_dream_engine = __esm({
12896
13655
  const rawPath = String(args["path"] ?? "");
12897
13656
  const oldStr = String(args["old_string"] ?? "");
12898
13657
  const newStr = String(args["new_string"] ?? "");
12899
- const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/dreams") ? join20(this.dreamsDir, basename5(rawPath)) : join20(this.dreamsDir, rawPath);
13658
+ const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/dreams") ? join22(this.dreamsDir, basename6(rawPath)) : join22(this.dreamsDir, rawPath);
12900
13659
  if (!targetPath.startsWith(this.dreamsDir)) {
12901
13660
  return { success: false, output: "", error: "Dream mode: edits are confined to .oa/dreams/", durationMs: Date.now() - start };
12902
13661
  }
12903
13662
  try {
12904
- if (!existsSync14(targetPath)) {
13663
+ if (!existsSync16(targetPath)) {
12905
13664
  return { success: false, output: "", error: `File not found: ${rawPath}`, durationMs: Date.now() - start };
12906
13665
  }
12907
- let content = readFileSync12(targetPath, "utf-8");
13666
+ let content = readFileSync13(targetPath, "utf-8");
12908
13667
  if (!content.includes(oldStr)) {
12909
13668
  return { success: false, output: "", error: "old_string not found in file", durationMs: Date.now() - start };
12910
13669
  }
12911
13670
  content = content.replace(oldStr, newStr);
12912
- writeFileSync7(targetPath, content, "utf-8");
13671
+ writeFileSync9(targetPath, content, "utf-8");
12913
13672
  return { success: true, output: `Edited ${rawPath}`, durationMs: Date.now() - start };
12914
13673
  } catch (err) {
12915
13674
  return { success: false, output: "", error: String(err), durationMs: Date.now() - start };
@@ -12940,7 +13699,7 @@ var init_dream_engine = __esm({
12940
13699
  }
12941
13700
  }
12942
13701
  try {
12943
- const output = execSync12(cmd, {
13702
+ const output = execSync14(cmd, {
12944
13703
  cwd: this.repoRoot,
12945
13704
  timeout: 3e4,
12946
13705
  encoding: "utf-8",
@@ -12963,7 +13722,7 @@ var init_dream_engine = __esm({
12963
13722
  constructor(config, repoRoot) {
12964
13723
  this.config = config;
12965
13724
  this.repoRoot = repoRoot;
12966
- this.dreamsDir = join20(repoRoot, ".oa", "dreams");
13725
+ this.dreamsDir = join22(repoRoot, ".oa", "dreams");
12967
13726
  this.state = {
12968
13727
  mode: "default",
12969
13728
  active: false,
@@ -12994,7 +13753,7 @@ var init_dream_engine = __esm({
12994
13753
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
12995
13754
  results: []
12996
13755
  };
12997
- mkdirSync8(this.dreamsDir, { recursive: true });
13756
+ mkdirSync10(this.dreamsDir, { recursive: true });
12998
13757
  this.saveDreamState();
12999
13758
  try {
13000
13759
  for (let cycle = 1; cycle <= totalCycles; cycle++) {
@@ -13035,8 +13794,8 @@ ${result.summary}`;
13035
13794
  if (mode !== "default" || cycle === totalCycles) {
13036
13795
  renderDreamContraction(cycle);
13037
13796
  const cycleSummary = this.buildCycleSummary(cycle, previousFindings);
13038
- const summaryPath = join20(this.dreamsDir, `cycle-${cycle}-summary.md`);
13039
- writeFileSync7(summaryPath, cycleSummary, "utf-8");
13797
+ const summaryPath = join22(this.dreamsDir, `cycle-${cycle}-summary.md`);
13798
+ writeFileSync9(summaryPath, cycleSummary, "utf-8");
13040
13799
  }
13041
13800
  if (mode === "lucid" && !this.abortController.signal.aborted) {
13042
13801
  this.saveVersionCheckpoint(cycle);
@@ -13156,29 +13915,29 @@ Dreams directory: ${this.dreamsDir}`);
13156
13915
  }
13157
13916
  /** Save workspace backup for lucid mode */
13158
13917
  saveVersionCheckpoint(cycle) {
13159
- const checkpointDir = join20(this.dreamsDir, "checkpoints", `cycle-${cycle}`);
13918
+ const checkpointDir = join22(this.dreamsDir, "checkpoints", `cycle-${cycle}`);
13160
13919
  try {
13161
- mkdirSync8(checkpointDir, { recursive: true });
13920
+ mkdirSync10(checkpointDir, { recursive: true });
13162
13921
  try {
13163
- const gitStatus = execSync12("git status --porcelain", {
13922
+ const gitStatus = execSync14("git status --porcelain", {
13164
13923
  cwd: this.repoRoot,
13165
13924
  encoding: "utf-8",
13166
13925
  timeout: 1e4
13167
13926
  });
13168
- const gitDiff = execSync12("git diff", {
13927
+ const gitDiff = execSync14("git diff", {
13169
13928
  cwd: this.repoRoot,
13170
13929
  encoding: "utf-8",
13171
13930
  timeout: 1e4
13172
13931
  });
13173
- const gitHash = execSync12("git rev-parse HEAD 2>/dev/null || echo 'no-git'", {
13932
+ const gitHash = execSync14("git rev-parse HEAD 2>/dev/null || echo 'no-git'", {
13174
13933
  cwd: this.repoRoot,
13175
13934
  encoding: "utf-8",
13176
13935
  timeout: 5e3
13177
13936
  }).trim();
13178
- writeFileSync7(join20(checkpointDir, "git-status.txt"), gitStatus, "utf-8");
13179
- writeFileSync7(join20(checkpointDir, "git-diff.patch"), gitDiff, "utf-8");
13180
- writeFileSync7(join20(checkpointDir, "git-hash.txt"), gitHash, "utf-8");
13181
- writeFileSync7(join20(checkpointDir, "checkpoint.json"), JSON.stringify({
13937
+ writeFileSync9(join22(checkpointDir, "git-status.txt"), gitStatus, "utf-8");
13938
+ writeFileSync9(join22(checkpointDir, "git-diff.patch"), gitDiff, "utf-8");
13939
+ writeFileSync9(join22(checkpointDir, "git-hash.txt"), gitHash, "utf-8");
13940
+ writeFileSync9(join22(checkpointDir, "checkpoint.json"), JSON.stringify({
13182
13941
  cycle,
13183
13942
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
13184
13943
  gitHash,
@@ -13186,7 +13945,7 @@ Dreams directory: ${this.dreamsDir}`);
13186
13945
  }, null, 2), "utf-8");
13187
13946
  renderInfo(`Checkpoint saved: cycle ${cycle} (${gitHash.slice(0, 8)})`);
13188
13947
  } catch {
13189
- writeFileSync7(join20(checkpointDir, "checkpoint.json"), JSON.stringify({ cycle, timestamp: (/* @__PURE__ */ new Date()).toISOString(), mode: this.state.mode }, null, 2), "utf-8");
13948
+ writeFileSync9(join22(checkpointDir, "checkpoint.json"), JSON.stringify({ cycle, timestamp: (/* @__PURE__ */ new Date()).toISOString(), mode: this.state.mode }, null, 2), "utf-8");
13190
13949
  renderInfo(`Checkpoint saved: cycle ${cycle} (no git)`);
13191
13950
  }
13192
13951
  } catch (err) {
@@ -13244,14 +14003,14 @@ ${files.map((f) => `- [\`${f}\`](./${f})`).join("\n")}
13244
14003
  ---
13245
14004
  *Auto-generated by open-agents dream engine*
13246
14005
  `;
13247
- writeFileSync7(join20(this.dreamsDir, "PROPOSAL-INDEX.md"), index, "utf-8");
14006
+ writeFileSync9(join22(this.dreamsDir, "PROPOSAL-INDEX.md"), index, "utf-8");
13248
14007
  } catch {
13249
14008
  }
13250
14009
  }
13251
14010
  /** Save dream state for resume/inspection */
13252
14011
  saveDreamState() {
13253
14012
  try {
13254
- writeFileSync7(join20(this.dreamsDir, "dream-state.json"), JSON.stringify(this.state, null, 2) + "\n", "utf-8");
14013
+ writeFileSync9(join22(this.dreamsDir, "dream-state.json"), JSON.stringify(this.state, null, 2) + "\n", "utf-8");
13255
14014
  } catch {
13256
14015
  }
13257
14016
  }
@@ -13297,6 +14056,10 @@ var init_status_bar = __esm({
13297
14056
  * own output is suppressed.
13298
14057
  */
13299
14058
  inputStateProvider = null;
14059
+ /** Whether microphone is recording (blinking indicator) */
14060
+ _recording = false;
14061
+ /** Countdown seconds for auto-mode silence timeout (0 = not counting down) */
14062
+ _countdown = 0;
13300
14063
  /**
13301
14064
  * Provide a callback that returns readline's current input state.
13302
14065
  * StatusBar uses this to render typed text and position the cursor
@@ -13305,6 +14068,20 @@ var init_status_bar = __esm({
13305
14068
  setInputStateProvider(provider) {
13306
14069
  this.inputStateProvider = provider;
13307
14070
  }
14071
+ /** Set recording indicator state (blinking red ●) */
14072
+ setRecording(active) {
14073
+ this._recording = active;
14074
+ if (!active)
14075
+ this._countdown = 0;
14076
+ if (this.active)
14077
+ this.renderFooterPreserveCursor();
14078
+ }
14079
+ /** Set countdown seconds for auto-mode silence timeout */
14080
+ setCountdown(seconds) {
14081
+ this._countdown = seconds;
14082
+ if (this.active)
14083
+ this.renderFooterPreserveCursor();
14084
+ }
13308
14085
  /** Context window size to display. Can be updated if model changes. */
13309
14086
  setContextWindowSize(size) {
13310
14087
  this.metrics.contextWindowSize = size;
@@ -13453,7 +14230,13 @@ var init_status_bar = __esm({
13453
14230
  const ctxPct = ctxTotal > 0 ? Math.max(0, Math.min(100, Math.round((1 - ctxUsed / ctxTotal) * 100))) : 100;
13454
14231
  const ctxColor = ctxPct > 50 ? c2.green : ctxPct > 20 ? c2.yellow : c2.red;
13455
14232
  const ctxLabel = c2.blue("Ctx: ") + c2.bold(`${ctxUsed.toLocaleString()}/${ctxTotal.toLocaleString()}`) + ` ${ctxColor(`${ctxPct}%`)}`;
13456
- return ` ${tokInLabel}${pipe}${tokOutLabel}${pipe}${ctxLabel}`;
14233
+ let recordingLabel = "";
14234
+ if (this._recording) {
14235
+ const dot = c2.red("\u25CF");
14236
+ const countdown = this._countdown > 0 ? c2.dim(` ${this._countdown}s`) : "";
14237
+ recordingLabel = pipe + dot + c2.red(" REC") + countdown;
14238
+ }
14239
+ return ` ${tokInLabel}${pipe}${tokOutLabel}${pipe}${ctxLabel}${recordingLabel}`;
13457
14240
  }
13458
14241
  // -------------------------------------------------------------------------
13459
14242
  // Private
@@ -13580,23 +14363,22 @@ var init_status_bar = __esm({
13580
14363
  import * as readline2 from "node:readline";
13581
14364
  import { Writable } from "node:stream";
13582
14365
  import { cwd } from "node:process";
13583
- import { resolve as resolve12, join as join21, dirname as dirname3 } from "node:path";
14366
+ import { resolve as resolve13, join as join23, dirname as dirname3, extname as extname6 } from "node:path";
13584
14367
  import { createRequire as createRequire2 } from "node:module";
13585
14368
  import { fileURLToPath } from "node:url";
13586
- import { readFileSync as readFileSync13 } from "node:fs";
13587
- import { existsSync as existsSync15 } from "node:fs";
13588
- import { extname as extname5 } from "node:path";
14369
+ import { readFileSync as readFileSync14 } from "node:fs";
14370
+ import { existsSync as existsSync17 } from "node:fs";
13589
14371
  function getVersion() {
13590
14372
  try {
13591
14373
  const require2 = createRequire2(import.meta.url);
13592
14374
  const thisDir = dirname3(fileURLToPath(import.meta.url));
13593
14375
  const candidates = [
13594
- join21(thisDir, "..", "package.json"),
13595
- join21(thisDir, "..", "..", "package.json"),
13596
- join21(thisDir, "..", "..", "..", "package.json")
14376
+ join23(thisDir, "..", "package.json"),
14377
+ join23(thisDir, "..", "..", "package.json"),
14378
+ join23(thisDir, "..", "..", "..", "package.json")
13597
14379
  ];
13598
14380
  for (const pkgPath of candidates) {
13599
- if (existsSync15(pkgPath)) {
14381
+ if (existsSync17(pkgPath)) {
13600
14382
  const pkg = require2(pkgPath);
13601
14383
  if (pkg.name === "open-agents-ai" || pkg.name === "@open-agents/cli") {
13602
14384
  return pkg.version ?? "0.0.0";
@@ -13673,7 +14455,10 @@ function buildTools(repoRoot, config) {
13673
14455
  ...buildCustomTools(repoRoot),
13674
14456
  // Skill system (AIWG skills — discovery and execution)
13675
14457
  new SkillListTool(repoRoot),
13676
- new SkillExecuteTool(repoRoot)
14458
+ new SkillExecuteTool(repoRoot),
14459
+ // Transcription tools (transcribe-cli / faster-whisper)
14460
+ new TranscribeFileTool(repoRoot),
14461
+ new TranscribeUrlTool(repoRoot)
13677
14462
  ];
13678
14463
  return [
13679
14464
  ...executionTools.map(adaptTool2),
@@ -13926,7 +14711,7 @@ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce
13926
14711
  } };
13927
14712
  }
13928
14713
  async function startInteractive(config, repoPath) {
13929
- const repoRoot = resolve12(repoPath ?? cwd());
14714
+ const repoRoot = resolve13(repoPath ?? cwd());
13930
14715
  const isResumed = !!process.env.__OA_RESUMED;
13931
14716
  if (isResumed) {
13932
14717
  delete process.env.__OA_RESUMED;
@@ -14195,6 +14980,56 @@ async function startInteractive(config, repoPath) {
14195
14980
  },
14196
14981
  isDreaming() {
14197
14982
  return dreamEngine?.isActive ?? false;
14983
+ },
14984
+ // Listen mode (transcribe-cli integration)
14985
+ async listenToggle() {
14986
+ const engine = getListenEngine();
14987
+ if (engine.isActive) {
14988
+ const text = await engine.stop();
14989
+ statusBar.setRecording(false);
14990
+ return text ? `Stopped. Last transcript: "${text}"` : "Stopped listening.";
14991
+ }
14992
+ const available = await engine.isAvailable();
14993
+ if (!available) {
14994
+ return "transcribe-cli not installed. Run: npm i -g transcribe-cli";
14995
+ }
14996
+ engine.on("transcript", (text, isFinal) => {
14997
+ if (engine.currentMode === "confirm") {
14998
+ writeContent(() => renderInfo(`Heard: "${text}" ${c2.dim("(press Enter to submit)")}`));
14999
+ } else if (isFinal) {
15000
+ writeContent(() => renderInfo(`Auto-submitting: "${text}"`));
15001
+ rl.write(text + "\n");
15002
+ } else {
15003
+ writeContent(() => renderInfo(`Hearing: "${text}"`));
15004
+ }
15005
+ });
15006
+ engine.on("recording", (active) => {
15007
+ statusBar.setRecording(active);
15008
+ });
15009
+ engine.on("countdown", (seconds) => {
15010
+ statusBar.setCountdown(seconds);
15011
+ });
15012
+ engine.on("error", (err) => {
15013
+ writeContent(() => renderWarning(`Listen error: ${err.message}`));
15014
+ });
15015
+ const msg = await engine.start();
15016
+ return msg;
15017
+ },
15018
+ async listenSetModel(model) {
15019
+ const engine = getListenEngine();
15020
+ engine.setModel(model);
15021
+ return `Whisper model set to: ${model}`;
15022
+ },
15023
+ listenSetMode(mode) {
15024
+ const engine = getListenEngine();
15025
+ engine.setMode(mode);
15026
+ return mode === "auto" ? "Auto mode: transcriptions auto-submit after 3s silence" : "Confirm mode: press Enter to submit transcriptions";
15027
+ },
15028
+ async listenStop() {
15029
+ const engine = getListenEngine();
15030
+ const text = await engine.stop();
15031
+ statusBar.setRecording(false);
15032
+ return text ? `Stopped. Last transcript: "${text}"` : "Stopped listening.";
14198
15033
  }
14199
15034
  };
14200
15035
  showPrompt();
@@ -14242,16 +15077,56 @@ ${c2.dim("Goodbye!")}
14242
15077
  showPrompt();
14243
15078
  return;
14244
15079
  }
15080
+ if (typeof cmdResult === "object" && cmdResult.type === "skill") {
15081
+ const skillPrompt = cmdResult.args ? `<skill-context name="${cmdResult.name}">
15082
+ ${cmdResult.content}
15083
+ </skill-context>
15084
+
15085
+ ARGUMENTS: ${cmdResult.args}` : `<skill-context name="${cmdResult.name}">
15086
+ ${cmdResult.content}
15087
+ </skill-context>
15088
+
15089
+ Execute this skill now. Follow the behavioral guidance above.`;
15090
+ if (!carouselRetired && carousel.isRunning) {
15091
+ carousel.stop();
15092
+ carouselRetired = true;
15093
+ if (statusBar.isActive)
15094
+ statusBar.setScrollRegionTop(1);
15095
+ }
15096
+ writeContent(() => renderUserMessage(`/${cmdResult.name}${cmdResult.args ? " " + cmdResult.args : ""}`));
15097
+ lastSubmittedPrompt = skillPrompt;
15098
+ try {
15099
+ const task = startTask(skillPrompt, currentConfig, repoRoot, voiceEngine, {
15100
+ enabled: streamEnabled,
15101
+ renderer: streamRenderer
15102
+ }, {
15103
+ contextStores,
15104
+ taskMemoryStore: taskMemoryStore ?? void 0,
15105
+ failureStore: failureStore ?? void 0,
15106
+ toolPatternStore: toolPatternStore ?? void 0
15107
+ }, bruteForceEnabled, statusBar);
15108
+ activeTask = task;
15109
+ showPrompt();
15110
+ await task.promise;
15111
+ } catch (err) {
15112
+ const errMsg = err instanceof Error ? err.message : String(err);
15113
+ writeContent(() => renderError(errMsg));
15114
+ }
15115
+ activeTask = null;
15116
+ showPrompt();
15117
+ return;
15118
+ }
14245
15119
  }
14246
15120
  const cleanPath = input.replace(/^['"]|['"]$/g, "").trim();
14247
- const isImage = isImagePath(cleanPath) && existsSync15(resolve12(repoRoot, cleanPath));
15121
+ const isImage = isImagePath(cleanPath) && existsSync17(resolve13(repoRoot, cleanPath));
15122
+ const isMedia = !isImage && isTranscribablePath(cleanPath) && existsSync17(resolve13(repoRoot, cleanPath));
14248
15123
  if (activeTask) {
14249
15124
  if (isImage) {
14250
15125
  try {
14251
- const imgPath = resolve12(repoRoot, cleanPath);
14252
- const imgBuffer = readFileSync13(imgPath);
15126
+ const imgPath = resolve13(repoRoot, cleanPath);
15127
+ const imgBuffer = readFileSync14(imgPath);
14253
15128
  const base64 = imgBuffer.toString("base64");
14254
- const ext = extname5(cleanPath).toLowerCase();
15129
+ const ext = extname6(cleanPath).toLowerCase();
14255
15130
  const mime = ext === ".png" ? "image/png" : ext === ".gif" ? "image/gif" : ext === ".webp" ? "image/webp" : "image/jpeg";
14256
15131
  activeTask.runner.injectImage(base64, mime, `User shared image: ${cleanPath}`);
14257
15132
  writeContent(() => renderUserInterrupt(`[Image: ${cleanPath}]`));
@@ -14259,6 +15134,19 @@ ${c2.dim("Goodbye!")}
14259
15134
  activeTask.runner.injectUserMessage(input);
14260
15135
  writeContent(() => renderUserInterrupt(input));
14261
15136
  }
15137
+ } else if (isMedia) {
15138
+ writeContent(() => renderInfo(`Transcribing: ${cleanPath}...`));
15139
+ const engine = getListenEngine();
15140
+ const result = await engine.transcribeFile(resolve13(repoRoot, cleanPath), repoRoot);
15141
+ if (result) {
15142
+ const transcript = `[Transcription of ${cleanPath}]
15143
+ ${result.text}`;
15144
+ activeTask.runner.injectUserMessage(transcript);
15145
+ writeContent(() => renderUserInterrupt(`[Audio: ${cleanPath}] (${result.text.split(" ").length} words)`));
15146
+ } else {
15147
+ activeTask.runner.injectUserMessage(`User dropped audio/video file: ${cleanPath}. Use transcribe_file tool to transcribe it.`);
15148
+ writeContent(() => renderUserInterrupt(`[Media: ${cleanPath}]`));
15149
+ }
14262
15150
  } else {
14263
15151
  activeTask.runner.injectUserMessage(input);
14264
15152
  writeContent(() => renderUserInterrupt(input));
@@ -14274,6 +15162,22 @@ ${c2.dim("Goodbye!")}
14274
15162
  if (isImage && fullInput === input) {
14275
15163
  fullInput = `The user has provided an image file: ${cleanPath}. Read it with image_read and describe what you see. Extract any text with OCR if relevant.`;
14276
15164
  }
15165
+ if (isMedia && fullInput === input) {
15166
+ writeContent(() => renderInfo(`Transcribing: ${cleanPath}...`));
15167
+ const engine = getListenEngine();
15168
+ const result = await engine.transcribeFile(resolve13(repoRoot, cleanPath), repoRoot);
15169
+ if (result) {
15170
+ fullInput = `The user has provided an audio/video file: ${cleanPath}.
15171
+
15172
+ Transcription (${result.text.split(" ").length} words, ${result.duration ? result.duration.toFixed(1) + "s" : "unknown duration"}):
15173
+
15174
+ ${result.text}
15175
+
15176
+ Summarize or analyze this transcription as appropriate.`;
15177
+ } else {
15178
+ fullInput = `The user has provided an audio/video file: ${cleanPath}. Use the transcribe_file tool to transcribe it and then summarize the content.`;
15179
+ }
15180
+ }
14277
15181
  if (!carouselRetired && carousel.isRunning) {
14278
15182
  carousel.stop();
14279
15183
  carouselRetired = true;
@@ -14384,7 +15288,7 @@ ${c2.dim("(Use /quit to exit)")}
14384
15288
  });
14385
15289
  }
14386
15290
  async function runWithTUI(task, config, repoPath) {
14387
- const repoRoot = resolve12(repoPath ?? cwd());
15291
+ const repoRoot = resolve13(repoPath ?? cwd());
14388
15292
  const needsSetup = isFirstRun() || !await isModelAvailable(config);
14389
15293
  if (needsSetup && config.backendType === "ollama") {
14390
15294
  const setupModel = await runSetupWizard(config);
@@ -14421,6 +15325,7 @@ var init_interactive = __esm({
14421
15325
  "use strict";
14422
15326
  init_dist5();
14423
15327
  init_dist2();
15328
+ init_listen();
14424
15329
  init_updater();
14425
15330
  init_commands();
14426
15331
  init_setup();
@@ -14469,7 +15374,7 @@ import { glob } from "glob";
14469
15374
  import ignore from "ignore";
14470
15375
  import { readFile as readFile9, stat as stat2 } from "node:fs/promises";
14471
15376
  import { createHash } from "node:crypto";
14472
- import { join as join22, relative as relative3, extname as extname6, basename as basename6 } from "node:path";
15377
+ import { join as join24, relative as relative3, extname as extname7, basename as basename7 } from "node:path";
14473
15378
  var DEFAULT_EXCLUDE, LANGUAGE_MAP, CodebaseIndexer;
14474
15379
  var init_codebase_indexer = __esm({
14475
15380
  "packages/indexer/dist/codebase-indexer.js"() {
@@ -14513,7 +15418,7 @@ var init_codebase_indexer = __esm({
14513
15418
  const ig = ignore.default();
14514
15419
  if (this.config.respectGitignore) {
14515
15420
  try {
14516
- const gitignoreContent = await readFile9(join22(this.config.rootDir, ".gitignore"), "utf-8");
15421
+ const gitignoreContent = await readFile9(join24(this.config.rootDir, ".gitignore"), "utf-8");
14517
15422
  ig.add(gitignoreContent);
14518
15423
  } catch {
14519
15424
  }
@@ -14528,14 +15433,14 @@ var init_codebase_indexer = __esm({
14528
15433
  for (const relativePath of files) {
14529
15434
  if (ig.ignores(relativePath))
14530
15435
  continue;
14531
- const fullPath = join22(this.config.rootDir, relativePath);
15436
+ const fullPath = join24(this.config.rootDir, relativePath);
14532
15437
  try {
14533
15438
  const fileStat = await stat2(fullPath);
14534
15439
  if (fileStat.size > this.config.maxFileSize)
14535
15440
  continue;
14536
15441
  const content = await readFile9(fullPath);
14537
15442
  const hash = createHash("sha256").update(content).digest("hex");
14538
- const ext = extname6(relativePath);
15443
+ const ext = extname7(relativePath);
14539
15444
  indexed.push({
14540
15445
  path: fullPath,
14541
15446
  relativePath,
@@ -14551,7 +15456,7 @@ var init_codebase_indexer = __esm({
14551
15456
  }
14552
15457
  buildTree(files) {
14553
15458
  const root = {
14554
- name: basename6(this.config.rootDir),
15459
+ name: basename7(this.config.rootDir),
14555
15460
  path: this.config.rootDir,
14556
15461
  type: "directory",
14557
15462
  children: []
@@ -14574,7 +15479,7 @@ var init_codebase_indexer = __esm({
14574
15479
  if (!child) {
14575
15480
  child = {
14576
15481
  name: part,
14577
- path: join22(current.path, part),
15482
+ path: join24(current.path, part),
14578
15483
  type: "directory",
14579
15484
  children: []
14580
15485
  };
@@ -14648,14 +15553,14 @@ var index_repo_exports = {};
14648
15553
  __export(index_repo_exports, {
14649
15554
  indexRepoCommand: () => indexRepoCommand
14650
15555
  });
14651
- import { resolve as resolve13 } from "node:path";
14652
- import { existsSync as existsSync16, statSync as statSync6 } from "node:fs";
15556
+ import { resolve as resolve14 } from "node:path";
15557
+ import { existsSync as existsSync18, statSync as statSync6 } from "node:fs";
14653
15558
  import { cwd as cwd2 } from "node:process";
14654
15559
  async function indexRepoCommand(opts, _config) {
14655
- const repoRoot = resolve13(opts.repoPath ?? cwd2());
15560
+ const repoRoot = resolve14(opts.repoPath ?? cwd2());
14656
15561
  printHeader("Index Repository");
14657
15562
  printInfo(`Indexing: ${repoRoot}`);
14658
- if (!existsSync16(repoRoot)) {
15563
+ if (!existsSync18(repoRoot)) {
14659
15564
  printError(`Path does not exist: ${repoRoot}`);
14660
15565
  process.exit(1);
14661
15566
  }
@@ -14901,8 +15806,8 @@ var config_exports = {};
14901
15806
  __export(config_exports, {
14902
15807
  configCommand: () => configCommand
14903
15808
  });
14904
- import { join as join23, resolve as resolve14 } from "node:path";
14905
- import { homedir as homedir9 } from "node:os";
15809
+ import { join as join25, resolve as resolve15 } from "node:path";
15810
+ import { homedir as homedir11 } from "node:os";
14906
15811
  import { cwd as cwd3 } from "node:process";
14907
15812
  function coerceForSettings(key, value) {
14908
15813
  if (INT_KEYS.has(key))
@@ -14922,7 +15827,7 @@ async function configCommand(opts, config) {
14922
15827
  return handleShow(opts, config);
14923
15828
  }
14924
15829
  function handleShow(opts, config) {
14925
- const repoRoot = resolve14(opts.repoPath ?? cwd3());
15830
+ const repoRoot = resolve15(opts.repoPath ?? cwd3());
14926
15831
  printHeader("Configuration");
14927
15832
  printSection("Active Settings (merged)");
14928
15833
  printKeyValue("backendUrl", config.backendUrl, 2);
@@ -14954,7 +15859,7 @@ function handleShow(opts, config) {
14954
15859
  }
14955
15860
  }
14956
15861
  printSection("Config File");
14957
- printInfo(`~/.open-agents/config.json (${join23(homedir9(), ".open-agents", "config.json")})`);
15862
+ printInfo(`~/.open-agents/config.json (${join25(homedir11(), ".open-agents", "config.json")})`);
14958
15863
  printSection("Priority Chain");
14959
15864
  printInfo(" 1. CLI flags (--model, --backend-url, etc.)");
14960
15865
  printInfo(" 2. Project .oa/settings.json (--local)");
@@ -14987,13 +15892,13 @@ function handleSet(opts, _config) {
14987
15892
  process.exit(1);
14988
15893
  }
14989
15894
  if (opts.local) {
14990
- const repoRoot = resolve14(opts.repoPath ?? cwd3());
15895
+ const repoRoot = resolve15(opts.repoPath ?? cwd3());
14991
15896
  try {
14992
15897
  initOaDirectory(repoRoot);
14993
15898
  const coerced = coerceForSettings(key, value);
14994
15899
  saveProjectSettings(repoRoot, { [key]: coerced });
14995
15900
  printSuccess(`Project override set: ${key} = ${value}`);
14996
- printInfo(`Saved to ${join23(repoRoot, ".oa", "settings.json")}`);
15901
+ printInfo(`Saved to ${join25(repoRoot, ".oa", "settings.json")}`);
14997
15902
  printInfo("This override applies only when running in this workspace.");
14998
15903
  } catch (err) {
14999
15904
  printError(`Failed to save: ${err instanceof Error ? err.message : String(err)}`);
@@ -15053,7 +15958,7 @@ var serve_exports = {};
15053
15958
  __export(serve_exports, {
15054
15959
  serveCommand: () => serveCommand
15055
15960
  });
15056
- import { spawn as spawn4 } from "node:child_process";
15961
+ import { spawn as spawn6 } from "node:child_process";
15057
15962
  async function serveCommand(opts, config) {
15058
15963
  const backendType = config.backendType;
15059
15964
  if (backendType === "ollama") {
@@ -15145,8 +16050,8 @@ async function serveVllm(opts, config) {
15145
16050
  await runVllmServer(args, opts.verbose ?? false);
15146
16051
  }
15147
16052
  async function runVllmServer(args, verbose) {
15148
- return new Promise((resolve15, reject) => {
15149
- const child = spawn4("python", args, {
16053
+ return new Promise((resolve16, reject) => {
16054
+ const child = spawn6("python", args, {
15150
16055
  stdio: verbose ? "inherit" : ["ignore", "pipe", "pipe"],
15151
16056
  env: { ...process.env }
15152
16057
  });
@@ -15180,10 +16085,10 @@ async function runVllmServer(args, verbose) {
15180
16085
  child.once("exit", (code, signal) => {
15181
16086
  if (signal) {
15182
16087
  printInfo(`vLLM server stopped by signal ${signal}`);
15183
- resolve15();
16088
+ resolve16();
15184
16089
  } else if (code === 0) {
15185
16090
  printSuccess("vLLM server exited cleanly");
15186
- resolve15();
16091
+ resolve16();
15187
16092
  } else {
15188
16093
  printError(`vLLM server exited with code ${code}`);
15189
16094
  reject(new Error(`vLLM exited with code ${code}`));
@@ -15211,8 +16116,8 @@ __export(eval_exports, {
15211
16116
  evalCommand: () => evalCommand
15212
16117
  });
15213
16118
  import { tmpdir as tmpdir3 } from "node:os";
15214
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8 } from "node:fs";
15215
- import { join as join24 } from "node:path";
16119
+ import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync10 } from "node:fs";
16120
+ import { join as join26 } from "node:path";
15216
16121
  async function evalCommand(opts, config) {
15217
16122
  const suiteName = opts.suite ?? "basic";
15218
16123
  const suite = SUITES[suiteName];
@@ -15333,9 +16238,9 @@ async function evalCommand(opts, config) {
15333
16238
  process.exit(failed > 0 ? 1 : 0);
15334
16239
  }
15335
16240
  function createTempEvalRepo() {
15336
- const dir = join24(tmpdir3(), `open-agents-eval-${Date.now()}`);
15337
- mkdirSync9(dir, { recursive: true });
15338
- writeFileSync8(join24(dir, "package.json"), JSON.stringify({ name: "eval-repo", version: "0.0.0" }, null, 2) + "\n", "utf8");
16241
+ const dir = join26(tmpdir3(), `open-agents-eval-${Date.now()}`);
16242
+ mkdirSync11(dir, { recursive: true });
16243
+ writeFileSync10(join26(dir, "package.json"), JSON.stringify({ name: "eval-repo", version: "0.0.0" }, null, 2) + "\n", "utf8");
15339
16244
  return dir;
15340
16245
  }
15341
16246
  var BASIC_SUITE, FULL_SUITE, SUITES;
@@ -15395,7 +16300,7 @@ init_updater();
15395
16300
  import { parseArgs as nodeParseArgs2 } from "node:util";
15396
16301
  import { createRequire as createRequire3 } from "node:module";
15397
16302
  import { fileURLToPath as fileURLToPath2 } from "node:url";
15398
- import { dirname as dirname4, join as join25 } from "node:path";
16303
+ import { dirname as dirname4, join as join27 } from "node:path";
15399
16304
 
15400
16305
  // packages/cli/dist/cli.js
15401
16306
  import { createInterface } from "node:readline";
@@ -15502,7 +16407,7 @@ init_output();
15502
16407
  function getVersion2() {
15503
16408
  try {
15504
16409
  const require2 = createRequire3(import.meta.url);
15505
- const pkgPath = join25(dirname4(fileURLToPath2(import.meta.url)), "..", "package.json");
16410
+ const pkgPath = join27(dirname4(fileURLToPath2(import.meta.url)), "..", "package.json");
15506
16411
  const pkg = require2(pkgPath);
15507
16412
  return pkg.version;
15508
16413
  } catch {