@todoforai/edge 0.13.13 → 0.13.15

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 +313 -173
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -47695,7 +47695,7 @@ var require_ignore = __commonJS((exports, module) => {
47695
47695
  makeArray(isString(pattern) ? splitPattern(pattern) : pattern).forEach(this._add, this);
47696
47696
  return this._added;
47697
47697
  }
47698
- test(path7, checkUnignored, mode) {
47698
+ test(path8, checkUnignored, mode) {
47699
47699
  let ignored = false;
47700
47700
  let unignored = false;
47701
47701
  let matchedRule;
@@ -47704,7 +47704,7 @@ var require_ignore = __commonJS((exports, module) => {
47704
47704
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
47705
47705
  return;
47706
47706
  }
47707
- const matched = rule[mode].test(path7);
47707
+ const matched = rule[mode].test(path8);
47708
47708
  if (!matched) {
47709
47709
  return;
47710
47710
  }
@@ -47725,20 +47725,20 @@ var require_ignore = __commonJS((exports, module) => {
47725
47725
  var throwError = (message, Ctor) => {
47726
47726
  throw new Ctor(message);
47727
47727
  };
47728
- var checkPath = (path7, originalPath, doThrow) => {
47729
- if (!isString(path7)) {
47728
+ var checkPath = (path8, originalPath, doThrow) => {
47729
+ if (!isString(path8)) {
47730
47730
  return doThrow(`path must be a string, but got \`${originalPath}\``, TypeError);
47731
47731
  }
47732
- if (!path7) {
47732
+ if (!path8) {
47733
47733
  return doThrow(`path must not be empty`, TypeError);
47734
47734
  }
47735
- if (checkPath.isNotRelative(path7)) {
47735
+ if (checkPath.isNotRelative(path8)) {
47736
47736
  const r = "`path.relative()`d";
47737
47737
  return doThrow(`path should be a ${r} string, but got "${originalPath}"`, RangeError);
47738
47738
  }
47739
47739
  return true;
47740
47740
  };
47741
- var isNotRelative = (path7) => REGEX_TEST_INVALID_PATH.test(path7);
47741
+ var isNotRelative = (path8) => REGEX_TEST_INVALID_PATH.test(path8);
47742
47742
  checkPath.isNotRelative = isNotRelative;
47743
47743
  checkPath.convert = (p10) => p10;
47744
47744
 
@@ -47767,15 +47767,15 @@ var require_ignore = __commonJS((exports, module) => {
47767
47767
  return this.add(pattern);
47768
47768
  }
47769
47769
  _test(originalPath, cache, checkUnignored, slices) {
47770
- const path7 = originalPath && checkPath.convert(originalPath);
47771
- checkPath(path7, originalPath, this._strictPathCheck ? throwError : RETURN_FALSE);
47772
- return this._t(path7, cache, checkUnignored, slices);
47770
+ const path8 = originalPath && checkPath.convert(originalPath);
47771
+ checkPath(path8, originalPath, this._strictPathCheck ? throwError : RETURN_FALSE);
47772
+ return this._t(path8, cache, checkUnignored, slices);
47773
47773
  }
47774
- checkIgnore(path7) {
47775
- if (!REGEX_TEST_TRAILING_SLASH.test(path7)) {
47776
- return this.test(path7);
47774
+ checkIgnore(path8) {
47775
+ if (!REGEX_TEST_TRAILING_SLASH.test(path8)) {
47776
+ return this.test(path8);
47777
47777
  }
47778
- const slices = path7.split(SLASH).filter(Boolean);
47778
+ const slices = path8.split(SLASH).filter(Boolean);
47779
47779
  slices.pop();
47780
47780
  if (slices.length) {
47781
47781
  const parent = this._t(slices.join(SLASH) + SLASH, this._testCache, true, slices);
@@ -47783,42 +47783,42 @@ var require_ignore = __commonJS((exports, module) => {
47783
47783
  return parent;
47784
47784
  }
47785
47785
  }
47786
- return this._rules.test(path7, false, MODE_CHECK_IGNORE);
47786
+ return this._rules.test(path8, false, MODE_CHECK_IGNORE);
47787
47787
  }
47788
- _t(path7, cache, checkUnignored, slices) {
47789
- if (path7 in cache) {
47790
- return cache[path7];
47788
+ _t(path8, cache, checkUnignored, slices) {
47789
+ if (path8 in cache) {
47790
+ return cache[path8];
47791
47791
  }
47792
47792
  if (!slices) {
47793
- slices = path7.split(SLASH).filter(Boolean);
47793
+ slices = path8.split(SLASH).filter(Boolean);
47794
47794
  }
47795
47795
  slices.pop();
47796
47796
  if (!slices.length) {
47797
- return cache[path7] = this._rules.test(path7, checkUnignored, MODE_IGNORE);
47797
+ return cache[path8] = this._rules.test(path8, checkUnignored, MODE_IGNORE);
47798
47798
  }
47799
47799
  const parent = this._t(slices.join(SLASH) + SLASH, cache, checkUnignored, slices);
47800
- return cache[path7] = parent.ignored ? parent : this._rules.test(path7, checkUnignored, MODE_IGNORE);
47800
+ return cache[path8] = parent.ignored ? parent : this._rules.test(path8, checkUnignored, MODE_IGNORE);
47801
47801
  }
47802
- ignores(path7) {
47803
- return this._test(path7, this._ignoreCache, false).ignored;
47802
+ ignores(path8) {
47803
+ return this._test(path8, this._ignoreCache, false).ignored;
47804
47804
  }
47805
47805
  createFilter() {
47806
- return (path7) => !this.ignores(path7);
47806
+ return (path8) => !this.ignores(path8);
47807
47807
  }
47808
47808
  filter(paths) {
47809
47809
  return makeArray(paths).filter(this.createFilter());
47810
47810
  }
47811
- test(path7) {
47812
- return this._test(path7, this._testCache, true);
47811
+ test(path8) {
47812
+ return this._test(path8, this._testCache, true);
47813
47813
  }
47814
47814
  }
47815
47815
  var factory = (options) => new Ignore(options);
47816
- var isPathValid = (path7) => checkPath(path7 && checkPath.convert(path7), path7, RETURN_FALSE);
47816
+ var isPathValid = (path8) => checkPath(path8 && checkPath.convert(path8), path8, RETURN_FALSE);
47817
47817
  var setupWindows = () => {
47818
47818
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
47819
47819
  checkPath.convert = makePosix;
47820
47820
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
47821
- checkPath.isNotRelative = (path7) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path7) || isNotRelative(path7);
47821
+ checkPath.isNotRelative = (path8) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path8) || isNotRelative(path8);
47822
47822
  };
47823
47823
  if (typeof process !== "undefined" && process.platform === "win32") {
47824
47824
  setupWindows();
@@ -48645,9 +48645,9 @@ class BrowserExtensionBridge {
48645
48645
  }
48646
48646
 
48647
48647
  // src/handlers.ts
48648
- import fs11 from "fs";
48648
+ import fs12 from "fs";
48649
48649
  import { mkdir, rm as rm2, writeFile } from "fs/promises";
48650
- import path8 from "path";
48650
+ import path9 from "path";
48651
48651
 
48652
48652
  // src/path-utils.ts
48653
48653
  import path2 from "path";
@@ -48790,7 +48790,6 @@ var tool_catalog_default = {
48790
48790
  pkg: "tiktok-uploader",
48791
48791
  installer: "pip",
48792
48792
  label: "TikTok",
48793
- statusCmd: "python3 -c 'import tiktok_uploader' 2>&1",
48794
48793
  loginCmd: "npx @todoforai/tiktok-cookie-helper",
48795
48794
  credentialPaths: [
48796
48795
  "~/.tiktok/cookies.txt"
@@ -48806,7 +48805,6 @@ var tool_catalog_default = {
48806
48805
  label: "Instagram",
48807
48806
  capabilities: "Upload photos & reels, post stories, send DMs, like & comment, manage followers",
48808
48807
  description: 'Use for Instagram automation: upload photos/reels, post stories, DMs, like/comment, follower management. Python library; call via `python3 -c "from instagrapi import Client; ..."`.',
48809
- statusCmd: 'python3 -c "import instagrapi" 2>/dev/null',
48810
48808
  versionCmd: "pip show instagrapi 2>/dev/null | grep -oP 'Version: \\K.*'"
48811
48809
  },
48812
48810
  mudslide: {
@@ -48828,7 +48826,6 @@ var tool_catalog_default = {
48828
48826
  pkg: "telegram-send",
48829
48827
  installer: "pip",
48830
48828
  label: "Telegram",
48831
- statusCmd: "test -f ~/.config/telegram-send/telegram-send.conf && echo 'configured'",
48832
48829
  loginCmd: "telegram-send --configure",
48833
48830
  credentialPaths: [
48834
48831
  "~/.config/telegram-send/telegram-send.conf"
@@ -49052,7 +49049,6 @@ var tool_catalog_default = {
49052
49049
  pkg: "@shopify/cli",
49053
49050
  installer: "npm",
49054
49051
  label: "Shopify",
49055
- statusCmd: "test -f ~/.config/shopify/config.json && echo 'authenticated'",
49056
49052
  loginCmd: "shopify auth login",
49057
49053
  credentialPaths: [
49058
49054
  "~/.config/shopify/config.json"
@@ -49091,7 +49087,6 @@ var tool_catalog_default = {
49091
49087
  label: "TODOforAI",
49092
49088
  capabilities: "Create & manage TODOs, run workflows, API access",
49093
49089
  description: "Use to programmatically create/list/update TODOs in TODOforAI, kick off workflows, call the platform API.",
49094
- statusCmd: "todoai whoami >/dev/null 2>&1",
49095
49090
  installCmd: "bun add -g @todoforai/cli",
49096
49091
  versionCmd: "todoai --version 2>/dev/null | head -1",
49097
49092
  internal: true
@@ -49153,7 +49148,7 @@ var tool_catalog_default = {
49153
49148
  "~/.config/rclone/rclone.conf"
49154
49149
  ],
49155
49150
  capabilities: "Access Google Drive, OneDrive, Dropbox, S3 and 40+ cloud providers. List, copy, sync, move, mount files as virtual filesystem (FUSE). Use 'rclone config create <name> <provider>' to connect (opens browser for OAuth).",
49156
- description: "Use to access cloud storage (Drive/OneDrive/Dropbox/S3/40+). Connect: `rclone config create <name> <provider>` (OAuth via browser). Core ops: `rclone listremotes`, `rclone ls <remote>:<path>`, `rclone copy|sync <src> <dst>`. Mount as FUSE (lazy fetch): `rclone mount <remote>: ~/.todoforai/mnt/<remote> --vfs-cache-mode full --daemon`; unmount with `fusermount -u <path>`. See `subProviders` for per-provider connect commands.",
49151
+ description: "Use to access cloud storage (Drive/OneDrive/Dropbox/S3/40+). Connect: `rclone config create <name> <provider>` (OAuth via browser). Core ops: `rclone listremotes`, `rclone lsf <remote>:<path>`, `rclone cat <remote>:<file>`, `rclone copy|sync <src> <dst>`. Mount as FUSE (lazy fetch): `rclone mount <remote>: ~/.todoforai/mnt/<remote> --vfs-cache-mode full --daemon`; unmount with `fusermount -u <path>`. Note: FUSE `mount` needs /dev/fuse + CAP_SYS_ADMIN — works on VM sandboxes and bridge devices, but NOT in lite sandboxes; there use `lsf`/`cat`/`copy`/`sync` (HTTPS-only) instead. See `subProviders` for per-provider connect commands.",
49157
49152
  subProviders: {
49158
49153
  gdrive: {
49159
49154
  label: "Google Drive",
@@ -49199,7 +49194,7 @@ var tool_catalog_default = {
49199
49194
  installer: "pip",
49200
49195
  label: "PDF",
49201
49196
  capabilities: "PDF text editing, extraction, merge, split",
49202
- description: "Use to programmatically read/edit/merge/split PDFs (text + positions). Call from `python3 -c`. Extract text: `pymupdf.open(p)[i].get_text()`; with bbox/font: `.get_text('dict')`. Replace text in place: `page.add_redact_annot(page.search_for('old')[0], text='new'); page.apply_redactions(); doc.save(out)`. For PDF → markdown prefer `firecrawl parse`.",
49197
+ description: "Use to programmatically read/edit/merge/split PDFs (text + positions). Call from `python3 -c`. Extract text: `pymupdf.open(p)[i].get_text()`; with bbox/font: `.get_text('dict')`. Replace text in place: `page.add_redact_annot(page.search_for('old')[0], text='new'); page.apply_redactions(); doc.save(out)`.",
49203
49198
  versionCmd: "python3 -c 'import pymupdf; print(pymupdf.__version__)' 2>/dev/null",
49204
49199
  preinstallCloud: true
49205
49200
  },
@@ -49210,23 +49205,9 @@ var tool_catalog_default = {
49210
49205
  preinstallCloud: true,
49211
49206
  label: "Browser",
49212
49207
  capabilities: "Headless browser automation, web scraping, accessibility tree snapshots, screenshots, PDF generation",
49213
- description: "Headless browser for JS-rendered pages, automated flows, screenshots, PDF export. **Default browser choice** — use this unless the user's own session/cookies are required (then use `todoforai-browser`). Prefer `curl` for plain HTTP, `firecrawl` for bulk scrape/crawl. Commands: open <url>, click/type/fill <selector> <text>, snapshot (accessibility tree with @refs — use these for clicking), screenshot [path], pdf <path>, eval <js>, wait <selector|ms>.",
49208
+ description: "Headless browser for JS-rendered pages, automated flows, screenshots, PDF export. **Default browser choice** — use this unless the user's own session/cookies are required (then use `todoforai-browser`). Prefer `curl` for plain HTTP. Commands: open <url>, click/type/fill <selector> <text>, snapshot (accessibility tree with @refs — use these for clicking), screenshot [path], pdf <path>, eval <js>, wait <selector|ms>.",
49214
49209
  versionCmd: "agent-browser --version 2>/dev/null | head -1"
49215
49210
  },
49216
- firecrawl: {
49217
- category: "development",
49218
- pkg: "firecrawl-cli",
49219
- installer: "npm",
49220
- label: "Firecrawl",
49221
- statusCmd: "firecrawl view-config 2>&1 | grep -q 'Authenticated' && echo authenticated",
49222
- loginCmd: "firecrawl login",
49223
- credentialPaths: [
49224
- "~/.config/firecrawl-cli"
49225
- ],
49226
- capabilities: "Web scraping, crawling, search, map URLs, parse local HTML/PDF/DOCX to markdown, AI agent extraction",
49227
- description: "Use for web → markdown at scale: single pages (`firecrawl scrape <url>`), whole sites (`firecrawl crawl <url>`), URL discovery (`firecrawl map <url>`), web search (`firecrawl search <query>`), local doc conversion (`firecrawl parse <file.pdf|docx|html|xlsx>`), AI-guided extraction (`firecrawl agent <prompt>`). Prefer over `curl` when you want markdown not HTML. Auth: `firecrawl login` or FIRECRAWL_API_KEY.",
49228
- versionCmd: "firecrawl --version 2>/dev/null | head -1"
49229
- },
49230
49211
  "todoforai-browser": {
49231
49212
  category: "development",
49232
49213
  pkg: "@todoforai/browser",
@@ -49267,7 +49248,6 @@ var tool_catalog_default = {
49267
49248
  capabilities: "Explore a codebase as a real TODO: read-only agent maps structure, surfaces relevant files, streams findings to terminal",
49268
49249
  description: 'Codebase exploration as a real TODO with patched permissions (read/grep/bash only) and live streaming. Usage: `tfa-explore [--repo <path>] "<question>"`. Creates a TODO visible in the UI and streams block events to stdout until DONE.',
49269
49250
  versionCmd: "tfa-explore --version 2>/dev/null | head -1",
49270
- statusCmd: "tfa-explore whoami",
49271
49251
  installCmd: "bun add -g @todoforai/tfa-explore",
49272
49252
  preinstall: true,
49273
49253
  preinstallCloud: true,
@@ -49281,7 +49261,6 @@ var tool_catalog_default = {
49281
49261
  capabilities: "Review a git diff as a real TODO: read-only agent assesses goal, finds issues, suggests simpler approaches",
49282
49262
  description: 'Capture `git diff` and ask a read-only sub-agent to review it as a real TODO. Usage: `tfa-review [--repo <path>] [--against <ref>] "<goal>"`. Default diffs uncommitted changes vs HEAD. Streams block events to stdout until DONE.',
49283
49263
  versionCmd: "tfa-review --version 2>/dev/null | head -1",
49284
- statusCmd: "tfa-review whoami",
49285
49264
  installCmd: "bun add -g @todoforai/tfa-review",
49286
49265
  preinstall: true,
49287
49266
  preinstallCloud: true,
@@ -49295,7 +49274,6 @@ var tool_catalog_default = {
49295
49274
  capabilities: "Summarize files or piped input as a real TODO with no-tools sub-agent",
49296
49275
  description: 'Summarize file contents or stdin as a real TODO. Usage: `tfa-summary [-f <file>]... [<files...>] [<focus>]` or `cat x | tfa-summary "focus"`. No tools (content is inlined); streams block events to stdout until DONE.',
49297
49276
  versionCmd: "tfa-summary --version 2>/dev/null | head -1",
49298
- statusCmd: "tfa-summary whoami",
49299
49277
  installCmd: "bun add -g @todoforai/tfa-summary",
49300
49278
  internal: true
49301
49279
  },
@@ -51390,12 +51368,7 @@ import { spawn as nodeSpawn } from "child_process";
51390
51368
  // src/shell-pause-detector.ts
51391
51369
  import os5 from "os";
51392
51370
  import { readFile, readlink } from "fs/promises";
51393
-
51394
- class NullDetector {
51395
- watch() {
51396
- return { cancel: () => {}, reset: () => {} };
51397
- }
51398
- }
51371
+ var ECHO = 8;
51399
51372
  var READ_SYSCALL_NR = {
51400
51373
  x64: 0,
51401
51374
  arm64: 63,
@@ -51405,23 +51378,29 @@ var READ_SYSCALL_NR = {
51405
51378
  var READ_NR = READ_SYSCALL_NR[process.arch] ?? -1;
51406
51379
  var POLL_MS = 250;
51407
51380
  var GRACE_TICKS = 2;
51381
+ var IS_LINUX2 = os5.platform() === "linux" && READ_NR >= 0;
51408
51382
 
51409
- class LinuxSyscallDetector {
51410
- watch(pid, onPaused) {
51383
+ class PtyPauseDetector {
51384
+ watch(pid, onPaused, terminal) {
51411
51385
  let pausedTicks = 0;
51412
51386
  let signalled = false;
51413
51387
  let cancelled = false;
51414
51388
  let rootStdin = null;
51415
- readFdTarget(pid, 0).then((t) => {
51416
- rootStdin = t;
51417
- });
51389
+ if (IS_LINUX2)
51390
+ readFdTarget(pid, 0).then((t) => {
51391
+ rootStdin = t;
51392
+ });
51418
51393
  const tick = async () => {
51419
51394
  if (cancelled)
51420
51395
  return;
51421
- const fgPid = await getForegroundPid(pid);
51422
- const sc2 = fgPid != null ? await readSyscall(fgPid) : null;
51423
- const leafTarget = sc2 && sc2.nr === READ_NR && sc2.fd >= 0 && fgPid != null ? await readFdTarget(fgPid, sc2.fd) : null;
51424
- if (isTerminalReadPause(sc2, leafTarget, rootStdin)) {
51396
+ let blocked = terminal != null && !(terminal.localFlags & ECHO);
51397
+ if (!blocked && IS_LINUX2) {
51398
+ const fgPid = await getForegroundPid(pid);
51399
+ const sc2 = fgPid != null ? await readSyscall(fgPid) : null;
51400
+ const leafTarget = sc2 && sc2.nr === READ_NR && sc2.fd >= 0 && fgPid != null ? await readFdTarget(fgPid, sc2.fd) : null;
51401
+ blocked = isTerminalReadPause(sc2, leafTarget, rootStdin);
51402
+ }
51403
+ if (blocked) {
51425
51404
  if (!signalled && ++pausedTicks >= GRACE_TICKS) {
51426
51405
  signalled = true;
51427
51406
  onPaused();
@@ -51492,7 +51471,7 @@ async function readFdTarget(pid, fd3) {
51492
51471
  return null;
51493
51472
  }
51494
51473
  }
51495
- var pauseDetector = os5.platform() === "linux" && READ_NR >= 0 ? new LinuxSyscallDetector : new NullDetector;
51474
+ var pauseDetector = new PtyPauseDetector;
51496
51475
 
51497
51476
  // src/shell.ts
51498
51477
  var IS_WIN = os6.platform() === "win32";
@@ -51652,13 +51631,13 @@ async function executeBlock(blockId, content, send, todoId, messageId, timeout,
51652
51631
  }
51653
51632
  }, timeout * 1000);
51654
51633
  let cancelPauseWatch = null;
51655
- const startPauseWatch = (pid) => {
51634
+ const startPauseWatch = (pid, terminal) => {
51656
51635
  if (!keepAliveOnTimeout)
51657
51636
  return;
51658
51637
  const watcher = pauseDetector.watch(pid, () => {
51659
51638
  if (processes.has(blockId))
51660
51639
  resolveAlive();
51661
- });
51640
+ }, terminal);
51662
51641
  cancelPauseWatch = watcher.cancel;
51663
51642
  const h = processes.get(blockId);
51664
51643
  if (h)
@@ -51708,7 +51687,7 @@ async function executeBlock(blockId, content, send, todoId, messageId, timeout,
51708
51687
  const handle = { terminal, proc, pid: proc.pid };
51709
51688
  processes.set(blockId, handle);
51710
51689
  const timer = startTimeout();
51711
- startPauseWatch(proc.pid);
51690
+ startPauseWatch(proc.pid, terminal);
51712
51691
  proc.exited.then((code) => {
51713
51692
  terminal.close();
51714
51693
  onExit(code ?? -1, timer);
@@ -51889,9 +51868,9 @@ function consumeExitedOutput(pid) {
51889
51868
  }
51890
51869
 
51891
51870
  // src/functions.ts
51892
- import fs10 from "fs";
51893
- import path7 from "path";
51894
- import os8 from "os";
51871
+ import fs11 from "fs";
51872
+ import path8 from "path";
51873
+ import os9 from "os";
51895
51874
 
51896
51875
  // src/skills.ts
51897
51876
  import fs9 from "fs";
@@ -52068,6 +52047,62 @@ function sanitize(s) {
52068
52047
  return s.split(/\s+/).filter(Boolean).join(" ");
52069
52048
  }
52070
52049
 
52050
+ // src/agent-md.ts
52051
+ import fs10 from "fs";
52052
+ import path7 from "path";
52053
+ import os8 from "os";
52054
+ var MAX_BYTES = 64 * 1024;
52055
+ var FILENAMES = ["AGENT.md", "AGENTS.md"];
52056
+ async function discoverAgentMd(rootPaths, opts = {}) {
52057
+ const includeUserScope = opts.includeUserScope ?? true;
52058
+ const maxBytes = opts.maxBytes ?? MAX_BYTES;
52059
+ const dirs = [
52060
+ ...rootPaths.map((p10) => ({ dir: p10, scope: "repo" }))
52061
+ ];
52062
+ if (includeUserScope) {
52063
+ dirs.push({ dir: path7.join(os8.homedir(), ".agents"), scope: "user" });
52064
+ }
52065
+ const files = [];
52066
+ const errors = [];
52067
+ const seen = new Set;
52068
+ for (const { dir, scope } of dirs) {
52069
+ for (const name of FILENAMES) {
52070
+ const full = path7.join(dir, name);
52071
+ if (seen.has(full))
52072
+ continue;
52073
+ let stat;
52074
+ try {
52075
+ stat = fs10.statSync(full);
52076
+ } catch {
52077
+ continue;
52078
+ }
52079
+ if (!stat.isFile())
52080
+ continue;
52081
+ seen.add(full);
52082
+ try {
52083
+ const fd3 = fs10.openSync(full, "r");
52084
+ const buf = Buffer.alloc(maxBytes);
52085
+ let bytes;
52086
+ try {
52087
+ bytes = fs10.readSync(fd3, buf, 0, maxBytes, 0);
52088
+ } finally {
52089
+ fs10.closeSync(fd3);
52090
+ }
52091
+ files.push({
52092
+ path: full,
52093
+ scope,
52094
+ content: buf.subarray(0, bytes).toString("utf-8"),
52095
+ bytes: stat.size,
52096
+ truncated: stat.size > maxBytes
52097
+ });
52098
+ } catch (e) {
52099
+ errors.push({ path: full, message: `read failed: ${e?.message ?? e}` });
52100
+ }
52101
+ }
52102
+ }
52103
+ return { files, errors };
52104
+ }
52105
+
52071
52106
  // src/functions.ts
52072
52107
  var FUNCTION_REGISTRY = new Map;
52073
52108
  function register(name, fn2) {
@@ -52083,12 +52118,12 @@ register("get_environment_variable", async (args) => ({
52083
52118
  value: process.env[args.var_name] ?? null
52084
52119
  }));
52085
52120
  register("get_system_info", async () => {
52086
- let system2 = os8.platform();
52121
+ let system2 = os9.platform();
52087
52122
  if (system2 === "darwin")
52088
52123
  system2 = "macOS";
52089
52124
  else if (system2 === "linux") {
52090
52125
  try {
52091
- const release = fs10.readFileSync("/etc/os-release", "utf-8");
52126
+ const release = fs11.readFileSync("/etc/os-release", "utf-8");
52092
52127
  const m = release.match(/PRETTY_NAME="(.+?)"/);
52093
52128
  if (m)
52094
52129
  system2 = m[1];
@@ -52098,10 +52133,10 @@ register("get_system_info", async () => {
52098
52133
  system2 = "Linux";
52099
52134
  }
52100
52135
  } else if (system2 === "win32") {
52101
- system2 = `Windows ${os8.release()}`;
52136
+ system2 = `Windows ${os9.release()}`;
52102
52137
  }
52103
- const shell = process.env.SHELL ? path7.basename(process.env.SHELL) : "unknown";
52104
- const mount_path = path7.join(os8.homedir(), ".todoforai", "mnt", "todoforai");
52138
+ const shell = process.env.SHELL ? path8.basename(process.env.SHELL) : "unknown";
52139
+ const mount_path = path8.join(os9.homedir(), ".todoforai", "mnt", "todoforai");
52105
52140
  return { system: system2, shell, mount_path };
52106
52141
  });
52107
52142
  register("get_available_tools", async () => {
@@ -52145,11 +52180,11 @@ register("uninstall_tool", async (args) => {
52145
52180
  });
52146
52181
  register("get_workspace_tree", async (args) => {
52147
52182
  const { path: p10, max_depth = 2 } = args;
52148
- const root = path7.resolve(p10.replace(/^~/, process.env.HOME || "~"));
52149
- if (!fs10.existsSync(root) || !fs10.statSync(root).isDirectory()) {
52183
+ const root = path8.resolve(p10.replace(/^~/, process.env.HOME || "~"));
52184
+ if (!fs11.existsSync(root) || !fs11.statSync(root).isDirectory()) {
52150
52185
  return { tree: "", is_git: false };
52151
52186
  }
52152
- const isGit = fs10.existsSync(path7.join(root, ".git"));
52187
+ const isGit = fs11.existsSync(path8.join(root, ".git"));
52153
52188
  if (process.platform !== "win32") {
52154
52189
  try {
52155
52190
  const { execSync: execSync2 } = await import("child_process");
@@ -52172,12 +52207,12 @@ register("get_workspace_tree", async (args) => {
52172
52207
  const ig2 = ignore();
52173
52208
  if (isGit) {
52174
52209
  let scanGitignores = function(dir) {
52175
- const giPath = path7.join(dir, ".gitignore");
52176
- if (fs10.existsSync(giPath)) {
52210
+ const giPath = path8.join(dir, ".gitignore");
52211
+ if (fs11.existsSync(giPath)) {
52177
52212
  try {
52178
- const relDir = path7.relative(root, dir).replace(/\\/g, "/");
52213
+ const relDir = path8.relative(root, dir).replace(/\\/g, "/");
52179
52214
  const prefix = relDir === "" || relDir === "." ? "" : relDir + "/";
52180
- for (let line of fs10.readFileSync(giPath, "utf-8").split(`
52215
+ for (let line of fs11.readFileSync(giPath, "utf-8").split(`
52181
52216
  `)) {
52182
52217
  line = line.trim();
52183
52218
  if (!line || line.startsWith("#"))
@@ -52191,21 +52226,21 @@ register("get_workspace_tree", async (args) => {
52191
52226
  } catch {}
52192
52227
  }
52193
52228
  try {
52194
- for (const e of fs10.readdirSync(dir, { withFileTypes: true })) {
52229
+ for (const e of fs11.readdirSync(dir, { withFileTypes: true })) {
52195
52230
  if (e.isDirectory() && e.name !== ".git")
52196
- scanGitignores(path7.join(dir, e.name));
52231
+ scanGitignores(path8.join(dir, e.name));
52197
52232
  }
52198
52233
  } catch {}
52199
52234
  };
52200
52235
  scanGitignores(root);
52201
52236
  }
52202
- const lines = [path7.basename(root) + "/"];
52237
+ const lines = [path8.basename(root) + "/"];
52203
52238
  function walk(dirPath, prefix, depth) {
52204
52239
  if (depth > max_depth)
52205
52240
  return;
52206
52241
  let entries;
52207
52242
  try {
52208
- entries = fs10.readdirSync(dirPath, { withFileTypes: true });
52243
+ entries = fs11.readdirSync(dirPath, { withFileTypes: true });
52209
52244
  } catch {
52210
52245
  return;
52211
52246
  }
@@ -52213,7 +52248,7 @@ register("get_workspace_tree", async (args) => {
52213
52248
  if (e.name === ".git")
52214
52249
  return false;
52215
52250
  if (isGit) {
52216
- let rel = path7.relative(root, path7.join(dirPath, e.name)).replace(/\\/g, "/");
52251
+ let rel = path8.relative(root, path8.join(dirPath, e.name)).replace(/\\/g, "/");
52217
52252
  if (e.isDirectory())
52218
52253
  rel += "/";
52219
52254
  if (ig2.ignores(rel))
@@ -52235,7 +52270,7 @@ register("get_workspace_tree", async (args) => {
52235
52270
  lines.push(`${prefix}${connector}${entry.name}${suffix}`);
52236
52271
  if (entry.isDirectory()) {
52237
52272
  const extension2 = isLast ? " " : "│ ";
52238
- walk(path7.join(dirPath, entry.name), prefix + extension2, depth + 1);
52273
+ walk(path8.join(dirPath, entry.name), prefix + extension2, depth + 1);
52239
52274
  }
52240
52275
  }
52241
52276
  }
@@ -52248,25 +52283,30 @@ register("get_skills", async (args) => {
52248
52283
  const includeUserScope = args?.includeUserScope ?? true;
52249
52284
  return await discoverSkills(paths, { includeUserScope });
52250
52285
  });
52286
+ register("get_agent_md", async (args) => {
52287
+ const paths = Array.isArray(args?.paths) ? args.paths : [];
52288
+ const includeUserScope = args?.includeUserScope ?? true;
52289
+ return await discoverAgentMd(paths, { includeUserScope });
52290
+ });
52251
52291
  register("get_os_aware_default_path", async () => {
52252
52292
  let p10 = getPlatformDefaultDirectory();
52253
- if (!p10.endsWith(path7.sep))
52254
- p10 += path7.sep;
52293
+ if (!p10.endsWith(path8.sep))
52294
+ p10 += path8.sep;
52255
52295
  return { path: p10 };
52256
52296
  });
52257
52297
  register("create_directory", async (args) => {
52258
52298
  const { name } = args;
52259
52299
  if (!name?.trim())
52260
52300
  throw new Error("Folder name cannot be empty");
52261
- const baseDir = path7.resolve(getPathOrDefault(args.path).replace(/^~/, process.env.HOME || "~"));
52262
- let target = path7.resolve(name.replace(/^~/, process.env.HOME || "~"));
52263
- if (!path7.isAbsolute(name))
52264
- target = path7.join(baseDir, name.trim());
52265
- const existed = fs10.existsSync(target);
52266
- fs10.mkdirSync(target, { recursive: true });
52301
+ const baseDir = path8.resolve(getPathOrDefault(args.path).replace(/^~/, process.env.HOME || "~"));
52302
+ let target = path8.resolve(name.replace(/^~/, process.env.HOME || "~"));
52303
+ if (!path8.isAbsolute(name))
52304
+ target = path8.join(baseDir, name.trim());
52305
+ const existed = fs11.existsSync(target);
52306
+ fs11.mkdirSync(target, { recursive: true });
52267
52307
  let full = target;
52268
- if (!full.endsWith(path7.sep))
52269
- full += path7.sep;
52308
+ if (!full.endsWith(path8.sep))
52309
+ full += path8.sep;
52270
52310
  return { path: full, created: !existed, exists: true };
52271
52311
  });
52272
52312
  FUNCTION_REGISTRY.set("getOSAwareDefaultPath", FUNCTION_REGISTRY.get("get_os_aware_default_path"));
@@ -52300,7 +52340,7 @@ register("execute_shell_command", async (args, client) => {
52300
52340
  if (!canStream) {
52301
52341
  const { exec } = await import("child_process");
52302
52342
  const result = await new Promise((resolve) => {
52303
- exec(cmd, { cwd: cwd || os8.tmpdir(), encoding: "utf-8", timeout: timeout * 1000, maxBuffer: 10485760, env: { ...buildEnvWithTools(), ...getConnectionEnv(), TODOFORAI_TODO_ID: todoId, TODOFORAI_MESSAGE_ID: messageId, TODOFORAI_BLOCK_ID: blockId, TODOFORAI_AGENT_SETTINGS_ID: agentSettingsId } }, (_err, stdout, stderr) => {
52343
+ exec(cmd, { cwd: cwd || os9.tmpdir(), encoding: "utf-8", timeout: timeout * 1000, maxBuffer: 10485760, env: { ...buildEnvWithTools(), ...getConnectionEnv(), TODOFORAI_TODO_ID: todoId, TODOFORAI_MESSAGE_ID: messageId, TODOFORAI_BLOCK_ID: blockId, TODOFORAI_AGENT_SETTINGS_ID: agentSettingsId } }, (_err, stdout, stderr) => {
52304
52344
  resolve((stdout || "") + (stderr || ""));
52305
52345
  });
52306
52346
  });
@@ -52365,17 +52405,17 @@ var LIST_DIR_MAX_ENTRIES = 1e4;
52365
52405
  register("list_dir", async (args) => {
52366
52406
  const { path: p10, rootPath = "", fallbackRootPaths = [] } = args;
52367
52407
  const fullPath = resolveFilePath(p10, rootPath, fallbackRootPaths);
52368
- const st2 = fs10.statSync(fullPath);
52408
+ const st2 = fs11.statSync(fullPath);
52369
52409
  if (!st2.isDirectory())
52370
52410
  throw new Error(`Not a directory: ${fullPath}`);
52371
- const dirents = fs10.readdirSync(fullPath, { withFileTypes: true });
52411
+ const dirents = fs11.readdirSync(fullPath, { withFileTypes: true });
52372
52412
  if (dirents.length > LIST_DIR_MAX_ENTRIES) {
52373
52413
  throw new Error(`Directory too large: ${dirents.length} entries (max ${LIST_DIR_MAX_ENTRIES})`);
52374
52414
  }
52375
52415
  const entries = dirents.map((d) => {
52376
52416
  let size = 0, mtime = 0, mode = 0, is_dir = d.isDirectory();
52377
52417
  try {
52378
- const s = fs10.lstatSync(path7.join(fullPath, d.name));
52418
+ const s = fs11.lstatSync(path8.join(fullPath, d.name));
52379
52419
  size = Number(s.size);
52380
52420
  mtime = s.mtimeMs / 1000;
52381
52421
  mode = s.mode & 511;
@@ -52388,23 +52428,123 @@ register("list_dir", async (args) => {
52388
52428
  register("create_file", async (args) => {
52389
52429
  const { path: p10, content, rootPath = "", fallbackRootPaths = [] } = args;
52390
52430
  const fullPath = resolveFilePath(p10, rootPath, fallbackRootPaths);
52391
- const dir = path7.dirname(fullPath);
52431
+ const dir = path8.dirname(fullPath);
52392
52432
  if (dir)
52393
- fs10.mkdirSync(dir, { recursive: true });
52394
- fs10.writeFileSync(fullPath, content, "utf-8");
52433
+ fs11.mkdirSync(dir, { recursive: true });
52434
+ fs11.writeFileSync(fullPath, content, "utf-8");
52395
52435
  return { path: fullPath, bytes: Buffer.byteLength(content, "utf-8") };
52396
52436
  });
52397
52437
  register("read_file_base64", async (args) => {
52398
52438
  const { path: p10, rootPath = "", fallbackRootPaths = [] } = args;
52399
52439
  const fullPath = resolveFilePath(p10, rootPath, fallbackRootPaths);
52400
- if (!fs10.existsSync(fullPath))
52440
+ if (!fs11.existsSync(fullPath))
52401
52441
  throw new Error(`File not found: ${fullPath}`);
52402
- const stat = fs10.statSync(fullPath);
52442
+ const stat = fs11.statSync(fullPath);
52403
52443
  if (stat.size > 50000000)
52404
52444
  throw new Error(`File too large: ${stat.size.toLocaleString()} bytes (max 50MB)`);
52405
- const data = fs10.readFileSync(fullPath);
52445
+ const data = fs11.readFileSync(fullPath);
52406
52446
  return { path: fullPath, base64: data.toString("base64"), bytes: data.length };
52407
52447
  });
52448
+ register("search_files", async (args) => {
52449
+ const { pattern, path: p10 = ".", cwd = args.root_path ?? "", head = 100, max_count = 5, glob: globPattern = "", ignore_case = true } = args;
52450
+ const { execSync: execWhich } = await import("child_process");
52451
+ const whichCmd = process.platform === "win32" ? "where" : "which";
52452
+ const which = (bin) => {
52453
+ try {
52454
+ return execWhich(`${whichCmd} ${bin}`, { encoding: "utf-8" }).trim().split(`
52455
+ `)[0].trim();
52456
+ } catch {
52457
+ return null;
52458
+ }
52459
+ };
52460
+ let rgPath = which("rg");
52461
+ if (!rgPath) {
52462
+ await ensureTool("rg");
52463
+ rgPath = which("rg");
52464
+ }
52465
+ let searchPath = p10.replace(/^~/, process.env.HOME || "~");
52466
+ if (!path8.isAbsolute(searchPath) && cwd)
52467
+ searchPath = path8.join(cwd, searchPath);
52468
+ searchPath = path8.resolve(searchPath);
52469
+ if (!fs11.existsSync(searchPath))
52470
+ throw new Error(`Search path does not exist: ${searchPath}`);
52471
+ let cmd;
52472
+ if (rgPath) {
52473
+ cmd = [rgPath, "--no-heading", "--line-number", "--color=never"];
52474
+ if (ignore_case)
52475
+ cmd.push("--ignore-case");
52476
+ if (max_count > 0)
52477
+ cmd.push(`--max-count=${max_count}`);
52478
+ if (globPattern)
52479
+ cmd.push("--glob", globPattern);
52480
+ cmd.push(pattern, searchPath);
52481
+ } else {
52482
+ console.warn("[search_files] ripgrep (rg) not found, falling back to grep");
52483
+ const grepPath = which("grep") || "grep";
52484
+ cmd = [grepPath, "-rn", "--color=never"];
52485
+ if (ignore_case)
52486
+ cmd.push("-i");
52487
+ if (max_count > 0)
52488
+ cmd.push(`--max-count=${max_count}`);
52489
+ if (globPattern) {
52490
+ cmd.push(`--include=${globPattern}`);
52491
+ }
52492
+ cmd.push(pattern, searchPath);
52493
+ }
52494
+ const { spawn: spawnChild } = await import("child_process");
52495
+ const { stdout, stderr, code } = await new Promise((resolve) => {
52496
+ const child = spawnChild(cmd[0], cmd.slice(1));
52497
+ let out = "", err2 = "";
52498
+ child.stdout?.on("data", (d) => {
52499
+ out += d.toString();
52500
+ });
52501
+ child.stderr?.on("data", (d) => {
52502
+ err2 += d.toString();
52503
+ });
52504
+ child.on("close", (exitCode) => resolve({ stdout: out, stderr: err2, code: exitCode ?? 1 }));
52505
+ });
52506
+ if (code === 0) {
52507
+ let output = stdout;
52508
+ const lines = output.split(`
52509
+ `).filter((l) => l.trim());
52510
+ if (lines.length > head) {
52511
+ output = lines.slice(0, head).join(`
52512
+ `) + `
52513
+ ... (${lines.length - head} more matches truncated)`;
52514
+ }
52515
+ if ((cwd || searchPath) && output) {
52516
+ const searchBase = searchPath && fs11.existsSync(searchPath) && fs11.statSync(searchPath).isDirectory() ? searchPath : path8.dirname(searchPath);
52517
+ const bases = Array.from(new Set([cwd, searchBase].filter(Boolean)));
52518
+ const lines2 = output.split(`
52519
+ `).map((line) => {
52520
+ if (line.includes(":")) {
52521
+ const colonIdx = line.indexOf(":");
52522
+ let filePart = line.slice(0, colonIdx);
52523
+ const rest = line.slice(colonIdx);
52524
+ try {
52525
+ const candidates = [filePart, ...bases.map((b) => path8.relative(b, filePart))].filter((p11) => (p11.match(/\.\.\//g) || []).length <= 2);
52526
+ filePart = candidates.reduce((a, b) => a.length <= b.length ? a : b, filePart);
52527
+ } catch {}
52528
+ let fullLine = filePart + rest;
52529
+ if (fullLine.length > 300) {
52530
+ fullLine = fullLine.slice(0, 300) + "...";
52531
+ }
52532
+ return fullLine;
52533
+ }
52534
+ return line;
52535
+ });
52536
+ output = lines2.join(`
52537
+ `);
52538
+ }
52539
+ if (output.length > 1e5)
52540
+ output = output.slice(0, 1e5) + `
52541
+ ... (output truncated)`;
52542
+ return { result: output };
52543
+ }
52544
+ if (code === 1)
52545
+ return { result: "No matches found." };
52546
+ throw new Error(`search error (exit ${code}): ${stderr}`);
52547
+ });
52408
52548
  register("download_attachment", async (args, client) => {
52409
52549
  if (!client)
52410
52550
  throw new Error("Client instance required");
@@ -52419,13 +52559,13 @@ register("download_attachment", async (args, client) => {
52419
52559
  throw new Error(`Backend responded with ${res.status}`);
52420
52560
  const base = getPathOrDefault(rootPath);
52421
52561
  let target = p10.replace(/^~/, process.env.HOME || "~");
52422
- if (!path7.isAbsolute(target))
52423
- target = path7.join(base, target);
52424
- target = path7.resolve(target);
52425
- fs10.mkdirSync(path7.dirname(target), { recursive: true });
52562
+ if (!path8.isAbsolute(target))
52563
+ target = path8.join(base, target);
52564
+ target = path8.resolve(target);
52565
+ fs11.mkdirSync(path8.dirname(target), { recursive: true });
52426
52566
  try {
52427
52567
  const data = Buffer.from(await res.arrayBuffer());
52428
- fs10.writeFileSync(target, data);
52568
+ fs11.writeFileSync(target, data);
52429
52569
  return { path: target, bytes: data.length };
52430
52570
  } catch (e) {
52431
52571
  throw new Error(`Download failed: ${e.message}`);
@@ -52453,13 +52593,13 @@ register("register_attachment", async (args, client) => {
52453
52593
  const { filePath, userId = "test-user", isPublic = false, agentSettingsId = "", todoId = "", rootPath = "" } = args;
52454
52594
  const base = getPathOrDefault(rootPath);
52455
52595
  let target = filePath.replace(/^~/, process.env.HOME || "~");
52456
- if (!path7.isAbsolute(target))
52457
- target = path7.join(base, target);
52458
- target = path7.resolve(target);
52459
- if (!fs10.existsSync(target))
52596
+ if (!path8.isAbsolute(target))
52597
+ target = path8.join(base, target);
52598
+ target = path8.resolve(target);
52599
+ if (!fs11.existsSync(target))
52460
52600
  throw new Error(`File not found: ${target}`);
52461
52601
  const form = new FormData;
52462
- form.append("file", new Blob([fs10.readFileSync(target)]), path7.basename(target));
52602
+ form.append("file", new Blob([fs11.readFileSync(target)]), path8.basename(target));
52463
52603
  if (userId)
52464
52604
  form.append("userId", userId);
52465
52605
  if (agentSettingsId)
@@ -52503,9 +52643,9 @@ async function handleBlockSave(payload, send) {
52503
52643
  const { blockId, todoId, filepath, rootPath, fallbackRootPaths = [], content, requestId } = payload;
52504
52644
  try {
52505
52645
  const resolved = resolveFilePath(filepath, rootPath, fallbackRootPaths);
52506
- const ext = path8.extname(resolved).toLowerCase();
52646
+ const ext = path9.extname(resolved).toLowerCase();
52507
52647
  if (ext === ".docx" || ext === ".xlsx") {
52508
- if (!fs11.existsSync(resolved)) {
52648
+ if (!fs12.existsSync(resolved)) {
52509
52649
  throw new Error(`Cannot create new ${ext} file from XML — file must already exist: ${filepath}`);
52510
52650
  }
52511
52651
  if (ext === ".docx")
@@ -52513,10 +52653,10 @@ async function handleBlockSave(payload, send) {
52513
52653
  else
52514
52654
  saveXlsxContent(resolved, content);
52515
52655
  } else {
52516
- const dir = path8.dirname(resolved);
52656
+ const dir = path9.dirname(resolved);
52517
52657
  if (dir)
52518
- fs11.mkdirSync(dir, { recursive: true });
52519
- fs11.writeFileSync(resolved, content, "utf-8");
52658
+ fs12.mkdirSync(dir, { recursive: true });
52659
+ fs12.writeFileSync(resolved, content, "utf-8");
52520
52660
  }
52521
52661
  await send(msg.blockSaveResult(blockId, todoId, "SUCCESS", requestId));
52522
52662
  } catch (e) {
@@ -52527,22 +52667,22 @@ async function handleGetFolders(payload, send) {
52527
52667
  const { requestId, edgeId } = payload;
52528
52668
  const rawPath = getPathOrDefault(payload.path);
52529
52669
  try {
52530
- const expandedPath = path8.resolve(rawPath.replace(/^~/, process.env.HOME || "~"));
52670
+ const expandedPath = path9.resolve(rawPath.replace(/^~/, process.env.HOME || "~"));
52531
52671
  let targetPath;
52532
- if (fs11.existsSync(expandedPath) && fs11.statSync(expandedPath).isDirectory()) {
52672
+ if (fs12.existsSync(expandedPath) && fs12.statSync(expandedPath).isDirectory()) {
52533
52673
  targetPath = expandedPath;
52534
52674
  } else {
52535
- targetPath = path8.dirname(expandedPath);
52675
+ targetPath = path9.dirname(expandedPath);
52536
52676
  }
52537
- if (!fs11.existsSync(targetPath) || !fs11.statSync(targetPath).isDirectory()) {
52677
+ if (!fs12.existsSync(targetPath) || !fs12.statSync(targetPath).isDirectory()) {
52538
52678
  throw new Error(`No existing ancestor for path: ${rawPath}`);
52539
52679
  }
52540
52680
  const folders = [];
52541
52681
  const files = [];
52542
- for (const item of fs11.readdirSync(targetPath)) {
52543
- const full = path8.join(targetPath, item);
52682
+ for (const item of fs12.readdirSync(targetPath)) {
52683
+ const full = path9.join(targetPath, item);
52544
52684
  try {
52545
- if (fs11.statSync(full).isDirectory())
52685
+ if (fs12.statSync(full).isDirectory())
52546
52686
  folders.push(full);
52547
52687
  else
52548
52688
  files.push(full);
@@ -52576,10 +52716,10 @@ async function handleDeletePath(payload, send) {
52576
52716
  async function handleWriteFile(payload, send, pendingBinaries) {
52577
52717
  const { requestId, edgeId, path: dirPath, fileName, binaryId, dataBase64 } = payload;
52578
52718
  try {
52579
- const filePath = path8.join(dirPath, fileName);
52580
- const dir = path8.dirname(filePath);
52719
+ const filePath = path9.join(dirPath, fileName);
52720
+ const dir = path9.dirname(filePath);
52581
52721
  if (dir)
52582
- fs11.mkdirSync(dir, { recursive: true });
52722
+ fs12.mkdirSync(dir, { recursive: true });
52583
52723
  let buffer;
52584
52724
  if (binaryId && pendingBinaries?.has(binaryId)) {
52585
52725
  buffer = pendingBinaries.get(binaryId);
@@ -52600,8 +52740,8 @@ async function handleCd(payload, send, edgeConfig, onConfigChange) {
52600
52740
  const { requestId, edgeId } = payload;
52601
52741
  const rawPath = getPathOrDefault(payload.path);
52602
52742
  try {
52603
- const resolved = path8.resolve(rawPath.replace(/^~/, process.env.HOME || "~"));
52604
- if (!fs11.existsSync(resolved) || !fs11.statSync(resolved).isDirectory()) {
52743
+ const resolved = path9.resolve(rawPath.replace(/^~/, process.env.HOME || "~"));
52744
+ if (!fs12.existsSync(resolved) || !fs12.statSync(resolved).isDirectory()) {
52605
52745
  throw new Error(`Path does not exist or is not a directory: ${rawPath}`);
52606
52746
  }
52607
52747
  const normalized = resolved.replace(/\/+$/, "");
@@ -52656,12 +52796,12 @@ async function handleFunctionCall(payload, send, client) {
52656
52796
 
52657
52797
  // src/edge.ts
52658
52798
  function generateFingerprint() {
52659
- const os9 = __require("os");
52660
- const fs12 = __require("fs");
52799
+ const os10 = __require("os");
52800
+ const fs13 = __require("fs");
52661
52801
  const identifiers = {};
52662
52802
  if (process.platform === "linux") {
52663
52803
  try {
52664
- const mid = fs12.readFileSync("/etc/machine-id", "utf-8").trim();
52804
+ const mid = fs13.readFileSync("/etc/machine-id", "utf-8").trim();
52665
52805
  if (mid)
52666
52806
  identifiers.machine_id = mid;
52667
52807
  } catch {}
@@ -52676,8 +52816,8 @@ function generateFingerprint() {
52676
52816
  }
52677
52817
  if (Object.keys(identifiers).length === 0) {
52678
52818
  identifiers.platform = process.platform;
52679
- identifiers.machine = os9.machine?.() || os9.arch();
52680
- identifiers.node = os9.hostname();
52819
+ identifiers.machine = os10.machine?.() || os10.arch();
52820
+ identifiers.node = os10.hostname();
52681
52821
  }
52682
52822
  const keys = Object.keys(identifiers).sort();
52683
52823
  const json = "{" + keys.map((k) => JSON.stringify(k) + ": " + JSON.stringify(identifiers[k])).join(", ") + "}";
@@ -52863,8 +53003,8 @@ class TODOforAIEdge {
52863
53003
  if (edgeId && edgeId !== this.edgeId)
52864
53004
  return;
52865
53005
  if (payload.workspacepaths) {
52866
- const path9 = __require("path");
52867
- payload.workspacepaths = payload.workspacepaths.filter((p10) => !FORBIDDEN_PATHS2.has(path9.normalize(p10).replace(/\/+$/, "")));
53006
+ const path10 = __require("path");
53007
+ payload.workspacepaths = payload.workspacepaths.filter((p10) => !FORBIDDEN_PATHS2.has(path10.normalize(p10).replace(/\/+$/, "")));
52868
53008
  }
52869
53009
  Object.assign(this.edgeConfig, payload);
52870
53010
  if (this.addWorkspacePath) {
@@ -53146,18 +53286,18 @@ class ServerError extends Error {
53146
53286
  }
53147
53287
 
53148
53288
  // node_modules/@todoforai/update-notifier/src/index.ts
53149
- import fs12 from "node:fs";
53150
- import path9 from "node:path";
53151
- import os9 from "node:os";
53289
+ import fs13 from "node:fs";
53290
+ import path10 from "node:path";
53291
+ import os10 from "node:os";
53152
53292
  function isLinkedInstall() {
53153
53293
  try {
53154
- return !fs12.realpathSync(process.argv[1] || "").includes(`${path9.sep}node_modules${path9.sep}`);
53294
+ return !fs13.realpathSync(process.argv[1] || "").includes(`${path10.sep}node_modules${path10.sep}`);
53155
53295
  } catch {
53156
53296
  return false;
53157
53297
  }
53158
53298
  }
53159
53299
  var TTL_MS = 24 * 60 * 60 * 1000;
53160
- var CACHE_DIR = path9.join(os9.homedir(), ".config", "todoforai");
53300
+ var CACHE_DIR = path10.join(os10.homedir(), ".config", "todoforai");
53161
53301
  function cmpVer(a, b) {
53162
53302
  const pa2 = a.split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
53163
53303
  const pb2 = b.split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
@@ -53171,10 +53311,10 @@ function cmpVer(a, b) {
53171
53311
  function checkForUpdates(pkg) {
53172
53312
  if (!process.stderr.isTTY || process.env.CI || process.env.NO_UPDATE_NOTIFIER || isLinkedInstall())
53173
53313
  return;
53174
- const cacheFile = path9.join(CACHE_DIR, `notifier-${encodeURIComponent(pkg.name)}.json`);
53314
+ const cacheFile = path10.join(CACHE_DIR, `notifier-${encodeURIComponent(pkg.name)}.json`);
53175
53315
  let cache = {};
53176
53316
  try {
53177
- cache = JSON.parse(fs12.readFileSync(cacheFile, "utf8"));
53317
+ cache = JSON.parse(fs13.readFileSync(cacheFile, "utf8"));
53178
53318
  } catch {}
53179
53319
  if (cache.latest && cmpVer(cache.latest, pkg.version) > 0) {
53180
53320
  process.stderr.write(`
@@ -53185,42 +53325,42 @@ function checkForUpdates(pkg) {
53185
53325
  }
53186
53326
  if (Date.now() - (cache.ts ?? 0) > TTL_MS) {
53187
53327
  try {
53188
- fs12.mkdirSync(CACHE_DIR, { recursive: true });
53189
- fs12.writeFileSync(cacheFile, JSON.stringify({ ...cache, ts: Date.now() }));
53328
+ fs13.mkdirSync(CACHE_DIR, { recursive: true });
53329
+ fs13.writeFileSync(cacheFile, JSON.stringify({ ...cache, ts: Date.now() }));
53190
53330
  } catch {}
53191
53331
  fetch(`https://registry.npmjs.org/${pkg.name}/latest`, { signal: AbortSignal.timeout(3000) }).then((r) => r.ok ? r.json() : null).then((j) => {
53192
53332
  if (!j?.version)
53193
53333
  return;
53194
- fs12.writeFileSync(cacheFile, JSON.stringify({ ts: Date.now(), latest: j.version }));
53334
+ fs13.writeFileSync(cacheFile, JSON.stringify({ ts: Date.now(), latest: j.version }));
53195
53335
  }).catch(() => {});
53196
53336
  }
53197
53337
  }
53198
53338
 
53199
53339
  // src/index.ts
53200
53340
  import { fileURLToPath as fileURLToPath2 } from "url";
53201
- import fs13 from "fs";
53202
- import path10 from "path";
53203
- import os10 from "os";
53341
+ import fs14 from "fs";
53342
+ import path11 from "path";
53343
+ import os11 from "os";
53204
53344
  import crypto3 from "crypto";
53205
53345
  function readOwnPackage() {
53206
53346
  try {
53207
- const pkgPath = path10.resolve(fileURLToPath2(import.meta.url), "../../package.json");
53208
- return JSON.parse(fs13.readFileSync(pkgPath, "utf-8"));
53347
+ const pkgPath = path11.resolve(fileURLToPath2(import.meta.url), "../../package.json");
53348
+ return JSON.parse(fs14.readFileSync(pkgPath, "utf-8"));
53209
53349
  } catch {
53210
53350
  return null;
53211
53351
  }
53212
53352
  }
53213
53353
  function lockPath(apiUrl, userId) {
53214
- const dir = path10.join(os10.homedir(), ".todoforai");
53215
- fs13.mkdirSync(dir, { recursive: true });
53354
+ const dir = path11.join(os11.homedir(), ".todoforai");
53355
+ fs14.mkdirSync(dir, { recursive: true });
53216
53356
  const hash = crypto3.createHash("sha256").update(`${apiUrl}
53217
53357
  ${userId}`).digest("hex").slice(0, 12);
53218
- return path10.join(dir, `edge-${hash}.lock`);
53358
+ return path11.join(dir, `edge-${hash}.lock`);
53219
53359
  }
53220
53360
  function isEdgeProcess(pid) {
53221
53361
  try {
53222
53362
  process.kill(pid, 0);
53223
- const cmdline = fs13.readFileSync(`/proc/${pid}/cmdline`, "utf-8");
53363
+ const cmdline = fs14.readFileSync(`/proc/${pid}/cmdline`, "utf-8");
53224
53364
  return cmdline.includes("index.ts") || cmdline.includes("todoforai-edge");
53225
53365
  } catch {
53226
53366
  return false;
@@ -53228,7 +53368,7 @@ function isEdgeProcess(pid) {
53228
53368
  }
53229
53369
  function killExistingEdge(lp2) {
53230
53370
  try {
53231
- const pid = parseInt(fs13.readFileSync(lp2, "utf-8").trim(), 10);
53371
+ const pid = parseInt(fs14.readFileSync(lp2, "utf-8").trim(), 10);
53232
53372
  if (!isNaN(pid) && isEdgeProcess(pid)) {
53233
53373
  console.log(`\x1B[33mKilling existing edge process (pid ${pid})...\x1B[0m`);
53234
53374
  process.kill(pid, "SIGTERM");
@@ -53251,7 +53391,7 @@ function killExistingEdge(lp2) {
53251
53391
  }
53252
53392
  function acquireLock(lp2, kill = false) {
53253
53393
  try {
53254
- const pid = parseInt(fs13.readFileSync(lp2, "utf-8").trim(), 10);
53394
+ const pid = parseInt(fs14.readFileSync(lp2, "utf-8").trim(), 10);
53255
53395
  if (!isNaN(pid) && isEdgeProcess(pid)) {
53256
53396
  if (kill) {
53257
53397
  killExistingEdge(lp2);
@@ -53259,13 +53399,13 @@ function acquireLock(lp2, kill = false) {
53259
53399
  return false;
53260
53400
  }
53261
53401
  } catch {}
53262
- fs13.writeFileSync(lp2, String(process.pid));
53402
+ fs14.writeFileSync(lp2, String(process.pid));
53263
53403
  return true;
53264
53404
  }
53265
53405
  function releaseLock(lp2) {
53266
53406
  try {
53267
- if (fs13.readFileSync(lp2, "utf-8").trim() === String(process.pid))
53268
- fs13.unlinkSync(lp2);
53407
+ if (fs14.readFileSync(lp2, "utf-8").trim() === String(process.pid))
53408
+ fs14.unlinkSync(lp2);
53269
53409
  } catch {}
53270
53410
  }
53271
53411
  var MIN_BUN_VERSION = "1.3.14";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@todoforai/edge",
3
- "version": "0.13.13",
3
+ "version": "0.13.15",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "todoforai-edge": "dist/index.js"