@hasna/browser 0.3.2 → 0.3.4

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 (225) hide show
  1. package/dist/cli/index.js +999 -299
  2. package/dist/index.js +75 -22
  3. package/dist/mcp/index.js +1086 -338
  4. package/dist/server/index.js +90 -40
  5. package/package.json +1 -1
  6. package/dashboard/dist/assets/index-Cy4XUbL1.js +0 -40
  7. package/dashboard/dist/index.html +0 -16
  8. package/dist/cli/commands/browse.d.ts +0 -3
  9. package/dist/cli/commands/browse.d.ts.map +0 -1
  10. package/dist/cli/commands/script.d.ts +0 -3
  11. package/dist/cli/commands/script.d.ts.map +0 -1
  12. package/dist/cli/commands/session.d.ts +0 -3
  13. package/dist/cli/commands/session.d.ts.map +0 -1
  14. package/dist/cli/commands/tools.d.ts +0 -3
  15. package/dist/cli/commands/tools.d.ts.map +0 -1
  16. package/dist/cli/index.d.ts +0 -3
  17. package/dist/cli/index.d.ts.map +0 -1
  18. package/dist/cli/index.test.d.ts +0 -2
  19. package/dist/cli/index.test.d.ts.map +0 -1
  20. package/dist/db/agents.d.ts +0 -16
  21. package/dist/db/agents.d.ts.map +0 -1
  22. package/dist/db/agents.test.d.ts +0 -2
  23. package/dist/db/agents.test.d.ts.map +0 -1
  24. package/dist/db/console-log.d.ts +0 -6
  25. package/dist/db/console-log.d.ts.map +0 -1
  26. package/dist/db/crawl-results.d.ts +0 -6
  27. package/dist/db/crawl-results.d.ts.map +0 -1
  28. package/dist/db/gallery.d.ts +0 -26
  29. package/dist/db/gallery.d.ts.map +0 -1
  30. package/dist/db/gallery.test.d.ts +0 -2
  31. package/dist/db/gallery.test.d.ts.map +0 -1
  32. package/dist/db/heartbeats.d.ts +0 -6
  33. package/dist/db/heartbeats.d.ts.map +0 -1
  34. package/dist/db/network-log.d.ts +0 -7
  35. package/dist/db/network-log.d.ts.map +0 -1
  36. package/dist/db/projects.d.ts +0 -9
  37. package/dist/db/projects.d.ts.map +0 -1
  38. package/dist/db/projects.test.d.ts +0 -2
  39. package/dist/db/projects.test.d.ts.map +0 -1
  40. package/dist/db/recordings.d.ts +0 -11
  41. package/dist/db/recordings.d.ts.map +0 -1
  42. package/dist/db/recordings.test.d.ts +0 -2
  43. package/dist/db/recordings.test.d.ts.map +0 -1
  44. package/dist/db/schema.d.ts +0 -5
  45. package/dist/db/schema.d.ts.map +0 -1
  46. package/dist/db/schema.test.d.ts +0 -2
  47. package/dist/db/schema.test.d.ts.map +0 -1
  48. package/dist/db/scripts.d.ts +0 -84
  49. package/dist/db/scripts.d.ts.map +0 -1
  50. package/dist/db/sessions.d.ts +0 -35
  51. package/dist/db/sessions.d.ts.map +0 -1
  52. package/dist/db/sessions.test.d.ts +0 -2
  53. package/dist/db/sessions.test.d.ts.map +0 -1
  54. package/dist/db/snapshots.d.ts +0 -7
  55. package/dist/db/snapshots.d.ts.map +0 -1
  56. package/dist/db/timeline.d.ts +0 -11
  57. package/dist/db/timeline.d.ts.map +0 -1
  58. package/dist/engines/bun-webview.d.ts +0 -147
  59. package/dist/engines/bun-webview.d.ts.map +0 -1
  60. package/dist/engines/bun-webview.test.d.ts +0 -2
  61. package/dist/engines/bun-webview.test.d.ts.map +0 -1
  62. package/dist/engines/cdp.d.ts +0 -27
  63. package/dist/engines/cdp.d.ts.map +0 -1
  64. package/dist/engines/lightpanda.d.ts +0 -25
  65. package/dist/engines/lightpanda.d.ts.map +0 -1
  66. package/dist/engines/playwright.d.ts +0 -27
  67. package/dist/engines/playwright.d.ts.map +0 -1
  68. package/dist/engines/selector.d.ts +0 -17
  69. package/dist/engines/selector.d.ts.map +0 -1
  70. package/dist/engines/selector.test.d.ts +0 -2
  71. package/dist/engines/selector.test.d.ts.map +0 -1
  72. package/dist/index.d.ts +0 -28
  73. package/dist/index.d.ts.map +0 -1
  74. package/dist/lib/actions-ref.test.d.ts +0 -2
  75. package/dist/lib/actions-ref.test.d.ts.map +0 -1
  76. package/dist/lib/actions.d.ts +0 -91
  77. package/dist/lib/actions.d.ts.map +0 -1
  78. package/dist/lib/actions.test.d.ts +0 -2
  79. package/dist/lib/actions.test.d.ts.map +0 -1
  80. package/dist/lib/agents.d.ts +0 -9
  81. package/dist/lib/agents.d.ts.map +0 -1
  82. package/dist/lib/agents.test.d.ts +0 -2
  83. package/dist/lib/agents.test.d.ts.map +0 -1
  84. package/dist/lib/ai-inference.d.ts +0 -21
  85. package/dist/lib/ai-inference.d.ts.map +0 -1
  86. package/dist/lib/ai-task.d.ts +0 -23
  87. package/dist/lib/ai-task.d.ts.map +0 -1
  88. package/dist/lib/annotate.d.ts +0 -18
  89. package/dist/lib/annotate.d.ts.map +0 -1
  90. package/dist/lib/annotate.test.d.ts +0 -2
  91. package/dist/lib/annotate.test.d.ts.map +0 -1
  92. package/dist/lib/api-detector.d.ts +0 -17
  93. package/dist/lib/api-detector.d.ts.map +0 -1
  94. package/dist/lib/auth-flow.d.ts +0 -43
  95. package/dist/lib/auth-flow.d.ts.map +0 -1
  96. package/dist/lib/auth.d.ts +0 -28
  97. package/dist/lib/auth.d.ts.map +0 -1
  98. package/dist/lib/console.d.ts +0 -6
  99. package/dist/lib/console.d.ts.map +0 -1
  100. package/dist/lib/coordination.d.ts +0 -12
  101. package/dist/lib/coordination.d.ts.map +0 -1
  102. package/dist/lib/crawler.d.ts +0 -3
  103. package/dist/lib/crawler.d.ts.map +0 -1
  104. package/dist/lib/cron-manager.d.ts +0 -43
  105. package/dist/lib/cron-manager.d.ts.map +0 -1
  106. package/dist/lib/daemon-client.d.ts +0 -16
  107. package/dist/lib/daemon-client.d.ts.map +0 -1
  108. package/dist/lib/datasets.d.ts +0 -33
  109. package/dist/lib/datasets.d.ts.map +0 -1
  110. package/dist/lib/deep-performance.d.ts +0 -49
  111. package/dist/lib/deep-performance.d.ts.map +0 -1
  112. package/dist/lib/dialogs.d.ts +0 -15
  113. package/dist/lib/dialogs.d.ts.map +0 -1
  114. package/dist/lib/downloads.d.ts +0 -15
  115. package/dist/lib/downloads.d.ts.map +0 -1
  116. package/dist/lib/downloads.test.d.ts +0 -2
  117. package/dist/lib/downloads.test.d.ts.map +0 -1
  118. package/dist/lib/env-detector.d.ts +0 -12
  119. package/dist/lib/env-detector.d.ts.map +0 -1
  120. package/dist/lib/extractor.d.ts +0 -22
  121. package/dist/lib/extractor.d.ts.map +0 -1
  122. package/dist/lib/extractor.test.d.ts +0 -2
  123. package/dist/lib/extractor.test.d.ts.map +0 -1
  124. package/dist/lib/files-integration.d.ts +0 -13
  125. package/dist/lib/files-integration.d.ts.map +0 -1
  126. package/dist/lib/gallery-diff.d.ts +0 -3
  127. package/dist/lib/gallery-diff.d.ts.map +0 -1
  128. package/dist/lib/integrations.test.d.ts +0 -2
  129. package/dist/lib/integrations.test.d.ts.map +0 -1
  130. package/dist/lib/login-scripts.d.ts +0 -89
  131. package/dist/lib/login-scripts.d.ts.map +0 -1
  132. package/dist/lib/network.d.ts +0 -11
  133. package/dist/lib/network.d.ts.map +0 -1
  134. package/dist/lib/network.test.d.ts +0 -2
  135. package/dist/lib/network.test.d.ts.map +0 -1
  136. package/dist/lib/page-memory.d.ts +0 -14
  137. package/dist/lib/page-memory.d.ts.map +0 -1
  138. package/dist/lib/performance.d.ts +0 -13
  139. package/dist/lib/performance.d.ts.map +0 -1
  140. package/dist/lib/profiles.d.ts +0 -23
  141. package/dist/lib/profiles.d.ts.map +0 -1
  142. package/dist/lib/qol.test.d.ts +0 -2
  143. package/dist/lib/qol.test.d.ts.map +0 -1
  144. package/dist/lib/recorder.d.ts +0 -11
  145. package/dist/lib/recorder.d.ts.map +0 -1
  146. package/dist/lib/recorder.test.d.ts +0 -2
  147. package/dist/lib/recorder.test.d.ts.map +0 -1
  148. package/dist/lib/ref-cache.d.ts +0 -9
  149. package/dist/lib/ref-cache.d.ts.map +0 -1
  150. package/dist/lib/sanitize.d.ts +0 -21
  151. package/dist/lib/sanitize.d.ts.map +0 -1
  152. package/dist/lib/screenshot-v4.test.d.ts +0 -2
  153. package/dist/lib/screenshot-v4.test.d.ts.map +0 -1
  154. package/dist/lib/screenshot.d.ts +0 -11
  155. package/dist/lib/screenshot.d.ts.map +0 -1
  156. package/dist/lib/screenshot.test.d.ts +0 -2
  157. package/dist/lib/screenshot.test.d.ts.map +0 -1
  158. package/dist/lib/script-engine.d.ts +0 -28
  159. package/dist/lib/script-engine.d.ts.map +0 -1
  160. package/dist/lib/self-heal.d.ts +0 -18
  161. package/dist/lib/self-heal.d.ts.map +0 -1
  162. package/dist/lib/session-v3.test.d.ts +0 -2
  163. package/dist/lib/session-v3.test.d.ts.map +0 -1
  164. package/dist/lib/session.d.ts +0 -38
  165. package/dist/lib/session.d.ts.map +0 -1
  166. package/dist/lib/skills-runner.d.ts +0 -14
  167. package/dist/lib/skills-runner.d.ts.map +0 -1
  168. package/dist/lib/snapshot-diff.test.d.ts +0 -2
  169. package/dist/lib/snapshot-diff.test.d.ts.map +0 -1
  170. package/dist/lib/snapshot.d.ts +0 -34
  171. package/dist/lib/snapshot.d.ts.map +0 -1
  172. package/dist/lib/snapshot.test.d.ts +0 -2
  173. package/dist/lib/snapshot.test.d.ts.map +0 -1
  174. package/dist/lib/stealth.d.ts +0 -5
  175. package/dist/lib/stealth.d.ts.map +0 -1
  176. package/dist/lib/stealth.test.d.ts +0 -2
  177. package/dist/lib/stealth.test.d.ts.map +0 -1
  178. package/dist/lib/storage-state.d.ts +0 -15
  179. package/dist/lib/storage-state.d.ts.map +0 -1
  180. package/dist/lib/storage.d.ts +0 -19
  181. package/dist/lib/storage.d.ts.map +0 -1
  182. package/dist/lib/structured-extract.d.ts +0 -26
  183. package/dist/lib/structured-extract.d.ts.map +0 -1
  184. package/dist/lib/tabs.d.ts +0 -18
  185. package/dist/lib/tabs.d.ts.map +0 -1
  186. package/dist/lib/task-queue.d.ts +0 -21
  187. package/dist/lib/task-queue.d.ts.map +0 -1
  188. package/dist/lib/url-watcher.d.ts +0 -33
  189. package/dist/lib/url-watcher.d.ts.map +0 -1
  190. package/dist/lib/vision-fallback.d.ts +0 -29
  191. package/dist/lib/vision-fallback.d.ts.map +0 -1
  192. package/dist/lib/workflows.d.ts +0 -46
  193. package/dist/lib/workflows.d.ts.map +0 -1
  194. package/dist/mcp/actions.d.ts +0 -3
  195. package/dist/mcp/actions.d.ts.map +0 -1
  196. package/dist/mcp/capture.d.ts +0 -3
  197. package/dist/mcp/capture.d.ts.map +0 -1
  198. package/dist/mcp/data.d.ts +0 -3
  199. package/dist/mcp/data.d.ts.map +0 -1
  200. package/dist/mcp/gallery.test.d.ts +0 -2
  201. package/dist/mcp/gallery.test.d.ts.map +0 -1
  202. package/dist/mcp/helpers.d.ts +0 -55
  203. package/dist/mcp/helpers.d.ts.map +0 -1
  204. package/dist/mcp/index.d.ts +0 -3
  205. package/dist/mcp/index.d.ts.map +0 -1
  206. package/dist/mcp/index.test.d.ts +0 -2
  207. package/dist/mcp/index.test.d.ts.map +0 -1
  208. package/dist/mcp/meta.d.ts +0 -3
  209. package/dist/mcp/meta.d.ts.map +0 -1
  210. package/dist/mcp/network.d.ts +0 -3
  211. package/dist/mcp/network.d.ts.map +0 -1
  212. package/dist/mcp/recordings.d.ts +0 -3
  213. package/dist/mcp/recordings.d.ts.map +0 -1
  214. package/dist/mcp/scripts.d.ts +0 -3
  215. package/dist/mcp/scripts.d.ts.map +0 -1
  216. package/dist/mcp/sessions.d.ts +0 -3
  217. package/dist/mcp/sessions.d.ts.map +0 -1
  218. package/dist/mcp/v4.test.d.ts +0 -2
  219. package/dist/mcp/v4.test.d.ts.map +0 -1
  220. package/dist/server/index.d.ts +0 -2
  221. package/dist/server/index.d.ts.map +0 -1
  222. package/dist/server/index.test.d.ts +0 -2
  223. package/dist/server/index.test.d.ts.map +0 -1
  224. package/dist/types/index.d.ts +0 -425
  225. package/dist/types/index.d.ts.map +0 -1
package/dist/cli/index.js CHANGED
@@ -6,39 +6,60 @@ var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ function __accessProp(key) {
10
+ return this[key];
11
+ }
12
+ var __toESMCache_node;
13
+ var __toESMCache_esm;
9
14
  var __toESM = (mod, isNodeMode, target) => {
15
+ var canCache = mod != null && typeof mod === "object";
16
+ if (canCache) {
17
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
18
+ var cached = cache.get(mod);
19
+ if (cached)
20
+ return cached;
21
+ }
10
22
  target = mod != null ? __create(__getProtoOf(mod)) : {};
11
23
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
12
24
  for (let key of __getOwnPropNames(mod))
13
25
  if (!__hasOwnProp.call(to, key))
14
26
  __defProp(to, key, {
15
- get: () => mod[key],
27
+ get: __accessProp.bind(mod, key),
16
28
  enumerable: true
17
29
  });
30
+ if (canCache)
31
+ cache.set(mod, to);
18
32
  return to;
19
33
  };
20
- var __moduleCache = /* @__PURE__ */ new WeakMap;
21
34
  var __toCommonJS = (from) => {
22
- var entry = __moduleCache.get(from), desc;
35
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
23
36
  if (entry)
24
37
  return entry;
25
38
  entry = __defProp({}, "__esModule", { value: true });
26
- if (from && typeof from === "object" || typeof from === "function")
27
- __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
28
- get: () => from[key],
29
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
30
- }));
39
+ if (from && typeof from === "object" || typeof from === "function") {
40
+ for (var key of __getOwnPropNames(from))
41
+ if (!__hasOwnProp.call(entry, key))
42
+ __defProp(entry, key, {
43
+ get: __accessProp.bind(from, key),
44
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
45
+ });
46
+ }
31
47
  __moduleCache.set(from, entry);
32
48
  return entry;
33
49
  };
50
+ var __moduleCache;
34
51
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
52
+ var __returnValue = (v) => v;
53
+ function __exportSetter(name, newValue) {
54
+ this[name] = __returnValue.bind(null, newValue);
55
+ }
35
56
  var __export = (target, all) => {
36
57
  for (var name in all)
37
58
  __defProp(target, name, {
38
59
  get: all[name],
39
60
  enumerable: true,
40
61
  configurable: true,
41
- set: (newValue) => all[name] = () => newValue
62
+ set: __exportSetter.bind(all, name)
42
63
  });
43
64
  };
44
65
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -2144,10 +2165,30 @@ __export(exports_schema, {
2144
2165
  });
2145
2166
  import { Database } from "bun:sqlite";
2146
2167
  import { join } from "path";
2147
- import { mkdirSync } from "fs";
2168
+ import { mkdirSync, existsSync, readdirSync, copyFileSync, statSync } from "fs";
2148
2169
  import { homedir } from "os";
2149
2170
  function getDataDir() {
2150
- return process.env["BROWSER_DATA_DIR"] ?? join(homedir(), ".browser");
2171
+ if (process.env["BROWSER_DATA_DIR"])
2172
+ return process.env["BROWSER_DATA_DIR"];
2173
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
2174
+ const newDir = join(home, ".hasna", "browser");
2175
+ const oldDir = join(home, ".browser");
2176
+ if (existsSync(oldDir) && !existsSync(newDir)) {
2177
+ mkdirSync(newDir, { recursive: true });
2178
+ try {
2179
+ for (const file of readdirSync(oldDir)) {
2180
+ const oldPath = join(oldDir, file);
2181
+ const newPath = join(newDir, file);
2182
+ try {
2183
+ if (statSync(oldPath).isFile()) {
2184
+ copyFileSync(oldPath, newPath);
2185
+ }
2186
+ } catch {}
2187
+ }
2188
+ } catch {}
2189
+ }
2190
+ mkdirSync(newDir, { recursive: true });
2191
+ return newDir;
2151
2192
  }
2152
2193
  function getDatabase(path) {
2153
2194
  const resolvedPath = path ?? process.env["BROWSER_DB_PATH"] ?? join(getDataDir(), "browser.db");
@@ -2674,10 +2715,15 @@ class BrowserPool {
2674
2715
  this.pool.push({ browser, inUse: true, createdAt: Date.now() });
2675
2716
  return browser;
2676
2717
  }
2677
- return new Promise((resolve) => {
2718
+ return new Promise((resolve, reject) => {
2719
+ const timeout = setTimeout(() => {
2720
+ clearInterval(interval);
2721
+ reject(new BrowserError("Browser pool exhausted \u2014 no browser became available within 30s", "POOL_TIMEOUT", true));
2722
+ }, 30000);
2678
2723
  const interval = setInterval(() => {
2679
2724
  const free = this.pool.find((e) => !e.inUse);
2680
2725
  if (free) {
2726
+ clearTimeout(timeout);
2681
2727
  clearInterval(interval);
2682
2728
  free.inUse = true;
2683
2729
  resolve(free.browser);
@@ -2718,7 +2764,7 @@ function isLightpandaAvailable() {
2718
2764
  const paths = [
2719
2765
  "/usr/local/bin/lightpanda",
2720
2766
  "/usr/bin/lightpanda",
2721
- `${process.env["HOME"]}/.browser/bin/lightpanda`
2767
+ `${process.env["HOME"]}/.hasna/browser/bin/lightpanda`
2722
2768
  ];
2723
2769
  return paths.some((p) => {
2724
2770
  try {
@@ -2737,7 +2783,7 @@ function getLightpandaBinaryPath() {
2737
2783
  "lightpanda",
2738
2784
  "/usr/local/bin/lightpanda",
2739
2785
  "/usr/bin/lightpanda",
2740
- `${process.env["HOME"]}/.browser/bin/lightpanda`
2786
+ `${process.env["HOME"]}/.hasna/browser/bin/lightpanda`
2741
2787
  ];
2742
2788
  for (const p of paths) {
2743
2789
  try {
@@ -2811,18 +2857,18 @@ var init_lightpanda = __esm(() => {
2811
2857
  // src/engines/bun-webview.ts
2812
2858
  import { join as join2 } from "path";
2813
2859
  import { mkdirSync as mkdirSync2 } from "fs";
2814
- import { homedir as homedir2 } from "os";
2815
2860
  function isBunWebViewAvailable() {
2816
2861
  return typeof globalThis.Bun !== "undefined" && typeof globalThis.Bun.WebView !== "undefined";
2817
2862
  }
2818
2863
  function getProfileDir(profileName) {
2819
- const base = process.env["BROWSER_DATA_DIR"] ?? join2(homedir2(), ".browser");
2864
+ const base = getDataDir();
2820
2865
  const dir = join2(base, "profiles", profileName);
2821
2866
  mkdirSync2(dir, { recursive: true });
2822
2867
  return dir;
2823
2868
  }
2824
2869
  var BunWebViewSession;
2825
2870
  var init_bun_webview = __esm(() => {
2871
+ init_schema();
2826
2872
  BunWebViewSession = class BunWebViewSession {
2827
2873
  view;
2828
2874
  _sessionId;
@@ -3284,6 +3330,7 @@ function enableNetworkLogging(page, sessionId) {
3284
3330
  };
3285
3331
  const onResponse = (res) => {
3286
3332
  const start = requestStart.get(res.url()) ?? Date.now();
3333
+ requestStart.delete(res.url());
3287
3334
  const duration = Date.now() - start;
3288
3335
  const req = res.request();
3289
3336
  try {
@@ -3300,11 +3347,17 @@ function enableNetworkLogging(page, sessionId) {
3300
3347
  });
3301
3348
  } catch {}
3302
3349
  };
3350
+ const onRequestFailed = (req) => {
3351
+ requestStart.delete(req.url());
3352
+ };
3303
3353
  page.on("request", onRequest);
3304
3354
  page.on("response", onResponse);
3355
+ page.on("requestfailed", onRequestFailed);
3305
3356
  return () => {
3306
3357
  page.off("request", onRequest);
3307
3358
  page.off("response", onResponse);
3359
+ page.off("requestfailed", onRequestFailed);
3360
+ requestStart.clear();
3308
3361
  };
3309
3362
  }
3310
3363
  async function addInterceptRule(page, rule) {
@@ -3338,6 +3391,7 @@ function startHAR(page) {
3338
3391
  const start = requestStart.get(key);
3339
3392
  if (!start)
3340
3393
  return;
3394
+ requestStart.delete(key);
3341
3395
  const duration = Date.now() - start.time;
3342
3396
  const entry = {
3343
3397
  startedDateTime: new Date(start.time).toISOString(),
@@ -3360,15 +3414,20 @@ function startHAR(page) {
3360
3414
  timings: { send: 0, wait: duration, receive: 0 }
3361
3415
  };
3362
3416
  entries.push(entry);
3363
- requestStart.delete(key);
3417
+ };
3418
+ const onRequestFailed = (req) => {
3419
+ requestStart.delete(req.url() + req.method());
3364
3420
  };
3365
3421
  page.on("request", onRequest);
3366
3422
  page.on("response", onResponse);
3423
+ page.on("requestfailed", onRequestFailed);
3367
3424
  return {
3368
3425
  entries,
3369
3426
  stop: () => {
3370
3427
  page.off("request", onRequest);
3371
3428
  page.off("response", onResponse);
3429
+ page.off("requestfailed", onRequestFailed);
3430
+ requestStart.clear();
3372
3431
  return {
3373
3432
  log: {
3374
3433
  version: "1.2",
@@ -3734,9 +3793,8 @@ __export(exports_storage_state, {
3734
3793
  listStates: () => listStates,
3735
3794
  deleteState: () => deleteState
3736
3795
  });
3737
- import { mkdirSync as mkdirSync3, existsSync, readdirSync, unlinkSync } from "fs";
3796
+ import { mkdirSync as mkdirSync3, existsSync as existsSync2, readdirSync as readdirSync2, unlinkSync } from "fs";
3738
3797
  import { join as join3 } from "path";
3739
- import { homedir as homedir3 } from "os";
3740
3798
  function ensureDir() {
3741
3799
  mkdirSync3(STATES_DIR, { recursive: true });
3742
3800
  }
@@ -3754,11 +3812,11 @@ async function saveStateFromPage(page, name) {
3754
3812
  }
3755
3813
  function loadStatePath(name) {
3756
3814
  const path = statePath(name);
3757
- return existsSync(path) ? path : null;
3815
+ return existsSync2(path) ? path : null;
3758
3816
  }
3759
3817
  function listStates() {
3760
3818
  ensureDir();
3761
- return readdirSync(STATES_DIR).filter((f) => f.endsWith(".json")).map((f) => {
3819
+ return readdirSync2(STATES_DIR).filter((f) => f.endsWith(".json")).map((f) => {
3762
3820
  const path = join3(STATES_DIR, f);
3763
3821
  const stat = Bun.file(path);
3764
3822
  return {
@@ -3770,7 +3828,7 @@ function listStates() {
3770
3828
  }
3771
3829
  function deleteState(name) {
3772
3830
  const path = statePath(name);
3773
- if (existsSync(path)) {
3831
+ if (existsSync2(path)) {
3774
3832
  unlinkSync(path);
3775
3833
  return true;
3776
3834
  }
@@ -3778,7 +3836,8 @@ function deleteState(name) {
3778
3836
  }
3779
3837
  var STATES_DIR;
3780
3838
  var init_storage_state = __esm(() => {
3781
- STATES_DIR = join3(process.env["BROWSER_DATA_DIR"] ?? join3(homedir3(), ".browser"), "states");
3839
+ init_schema();
3840
+ STATES_DIR = join3(getDataDir(), "states");
3782
3841
  });
3783
3842
 
3784
3843
  // src/lib/session.ts
@@ -11599,12 +11658,8 @@ var init_gallery = __esm(() => {
11599
11658
  // src/lib/screenshot.ts
11600
11659
  import { join as join4 } from "path";
11601
11660
  import { mkdirSync as mkdirSync4 } from "fs";
11602
- import { homedir as homedir4 } from "os";
11603
- function getDataDir2() {
11604
- return process.env["BROWSER_DATA_DIR"] ?? join4(homedir4(), ".browser");
11605
- }
11606
11661
  function getScreenshotDir(projectId) {
11607
- const base = join4(getDataDir2(), "screenshots");
11662
+ const base = join4(getDataDir(), "screenshots");
11608
11663
  const date = new Date().toISOString().split("T")[0];
11609
11664
  const dir = projectId ? join4(base, projectId, date) : join4(base, date);
11610
11665
  mkdirSync4(dir, { recursive: true });
@@ -11739,7 +11794,7 @@ async function takeScreenshot(page, opts) {
11739
11794
  }
11740
11795
  async function generatePDF(page, opts) {
11741
11796
  try {
11742
- const base = join4(getDataDir2(), "pdfs");
11797
+ const base = join4(getDataDir(), "pdfs");
11743
11798
  const date = new Date().toISOString().split("T")[0];
11744
11799
  const dir = opts?.projectId ? join4(base, opts.projectId, date) : join4(base, date);
11745
11800
  mkdirSync4(dir, { recursive: true });
@@ -11765,6 +11820,7 @@ var import_sharp;
11765
11820
  var init_screenshot = __esm(() => {
11766
11821
  init_types();
11767
11822
  init_gallery();
11823
+ init_schema();
11768
11824
  import_sharp = __toESM(require_lib(), 1);
11769
11825
  });
11770
11826
 
@@ -12263,7 +12319,6 @@ __export(exports_gallery_diff, {
12263
12319
  });
12264
12320
  import { join as join5 } from "path";
12265
12321
  import { mkdirSync as mkdirSync5 } from "fs";
12266
- import { homedir as homedir5 } from "os";
12267
12322
  async function diffImages(path1, path2) {
12268
12323
  const img1 = import_sharp2.default(path1);
12269
12324
  const img2 = import_sharp2.default(path2);
@@ -12294,7 +12349,7 @@ async function diffImages(path1, path2) {
12294
12349
  diffBuffer[i + 2] = Math.round(raw1[i + 2] * 0.4);
12295
12350
  }
12296
12351
  }
12297
- const dataDir = process.env["BROWSER_DATA_DIR"] ?? join5(homedir5(), ".browser");
12352
+ const dataDir = getDataDir();
12298
12353
  const diffDir = join5(dataDir, "diffs");
12299
12354
  mkdirSync5(diffDir, { recursive: true });
12300
12355
  const diffPath = join5(diffDir, `diff-${Date.now()}.webp`);
@@ -12310,6 +12365,7 @@ async function diffImages(path1, path2) {
12310
12365
  }
12311
12366
  var import_sharp2;
12312
12367
  var init_gallery_diff = __esm(() => {
12368
+ init_schema();
12313
12369
  import_sharp2 = __toESM(require_lib(), 1);
12314
12370
  });
12315
12371
 
@@ -12425,13 +12481,13 @@ function listRuns(scriptId) {
12425
12481
  }));
12426
12482
  }
12427
12483
  function migrateJsonScripts() {
12428
- const { existsSync: existsSync3, readdirSync: readdirSync2, readFileSync } = __require("fs");
12484
+ const { existsSync: existsSync4, readdirSync: readdirSync3, readFileSync } = __require("fs");
12429
12485
  const { join: join6 } = __require("path");
12430
- const { getDataDir: getDataDir3 } = (init_schema(), __toCommonJS(exports_schema));
12431
- const dir = join6(getDataDir3(), "scripts");
12432
- if (!existsSync3(dir))
12486
+ const { getDataDir: getDataDir2 } = (init_schema(), __toCommonJS(exports_schema));
12487
+ const dir = join6(getDataDir2(), "scripts");
12488
+ if (!existsSync4(dir))
12433
12489
  return 0;
12434
- const files = readdirSync2(dir).filter((f) => f.endsWith(".json"));
12490
+ const files = readdirSync3(dir).filter((f) => f.endsWith(".json"));
12435
12491
  let migrated = 0;
12436
12492
  for (const file of files) {
12437
12493
  try {
@@ -12979,12 +13035,32 @@ var init_agents = __esm(() => {
12979
13035
  });
12980
13036
 
12981
13037
  // src/lib/agents.ts
13038
+ var exports_agents2 = {};
13039
+ __export(exports_agents2, {
13040
+ updateAgent: () => updateAgent,
13041
+ registerAgent: () => registerAgent2,
13042
+ listAgents: () => listAgents,
13043
+ isAgentStale: () => isAgentStale,
13044
+ heartbeat: () => heartbeat2,
13045
+ getAgentByName: () => getAgentByName,
13046
+ getAgent: () => getAgent,
13047
+ getActiveAgents: () => getActiveAgents,
13048
+ deleteAgent: () => deleteAgent,
13049
+ cleanStaleAgents: () => cleanStaleAgents
13050
+ });
12982
13051
  function registerAgent2(name, opts = {}) {
12983
13052
  return registerAgent(name, opts);
12984
13053
  }
12985
13054
  function heartbeat2(agentId) {
12986
13055
  heartbeat(agentId);
12987
13056
  }
13057
+ function isAgentStale(agent, thresholdMs = 5 * 60 * 1000) {
13058
+ const lastSeen = new Date(agent.last_seen).getTime();
13059
+ return Date.now() - lastSeen > thresholdMs;
13060
+ }
13061
+ function getActiveAgents(thresholdMs = 5 * 60 * 1000) {
13062
+ return listAgents().filter((a) => !isAgentStale(a, thresholdMs));
13063
+ }
12988
13064
  var init_agents2 = __esm(() => {
12989
13065
  init_agents();
12990
13066
  });
@@ -13272,11 +13348,10 @@ __export(exports_profiles, {
13272
13348
  deleteProfile: () => deleteProfile,
13273
13349
  applyProfile: () => applyProfile
13274
13350
  });
13275
- import { mkdirSync as mkdirSync6, existsSync as existsSync3, readdirSync as readdirSync2, rmSync, readFileSync, writeFileSync } from "fs";
13351
+ import { mkdirSync as mkdirSync6, existsSync as existsSync4, readdirSync as readdirSync3, rmSync, readFileSync, writeFileSync } from "fs";
13276
13352
  import { join as join6 } from "path";
13277
- import { homedir as homedir6 } from "os";
13278
13353
  function getProfilesDir() {
13279
- const dataDir = process.env["BROWSER_DATA_DIR"] ?? join6(homedir6(), ".browser");
13354
+ const dataDir = getDataDir();
13280
13355
  const dir = join6(dataDir, "profiles");
13281
13356
  mkdirSync6(dir, { recursive: true });
13282
13357
  return dir;
@@ -13315,17 +13390,17 @@ async function saveProfile(page, name) {
13315
13390
  }
13316
13391
  function loadProfile(name) {
13317
13392
  const dir = getProfileDir2(name);
13318
- if (!existsSync3(dir)) {
13393
+ if (!existsSync4(dir)) {
13319
13394
  throw new Error(`Profile not found: ${name}`);
13320
13395
  }
13321
13396
  const cookiesPath = join6(dir, "cookies.json");
13322
13397
  const storagePath = join6(dir, "storage.json");
13323
13398
  const metaPath = join6(dir, "meta.json");
13324
- const cookies = existsSync3(cookiesPath) ? JSON.parse(readFileSync(cookiesPath, "utf8")) : [];
13325
- const localStorage2 = existsSync3(storagePath) ? JSON.parse(readFileSync(storagePath, "utf8")) : {};
13399
+ const cookies = existsSync4(cookiesPath) ? JSON.parse(readFileSync(cookiesPath, "utf8")) : [];
13400
+ const localStorage2 = existsSync4(storagePath) ? JSON.parse(readFileSync(storagePath, "utf8")) : {};
13326
13401
  let savedAt = new Date().toISOString();
13327
13402
  let url;
13328
- if (existsSync3(metaPath)) {
13403
+ if (existsSync4(metaPath)) {
13329
13404
  const meta = JSON.parse(readFileSync(metaPath, "utf8"));
13330
13405
  savedAt = meta.saved_at ?? savedAt;
13331
13406
  url = meta.url;
@@ -13353,9 +13428,9 @@ async function applyProfile(page, profileData) {
13353
13428
  }
13354
13429
  function listProfiles() {
13355
13430
  const dir = getProfilesDir();
13356
- if (!existsSync3(dir))
13431
+ if (!existsSync4(dir))
13357
13432
  return [];
13358
- const entries = readdirSync2(dir, { withFileTypes: true });
13433
+ const entries = readdirSync3(dir, { withFileTypes: true });
13359
13434
  const profiles = [];
13360
13435
  for (const entry of entries) {
13361
13436
  if (!entry.isDirectory())
@@ -13368,18 +13443,18 @@ function listProfiles() {
13368
13443
  let storageKeyCount = 0;
13369
13444
  try {
13370
13445
  const metaPath = join6(profileDir, "meta.json");
13371
- if (existsSync3(metaPath)) {
13446
+ if (existsSync4(metaPath)) {
13372
13447
  const meta = JSON.parse(readFileSync(metaPath, "utf8"));
13373
13448
  savedAt = meta.saved_at ?? "";
13374
13449
  url = meta.url;
13375
13450
  }
13376
13451
  const cookiesPath = join6(profileDir, "cookies.json");
13377
- if (existsSync3(cookiesPath)) {
13452
+ if (existsSync4(cookiesPath)) {
13378
13453
  const cookies = JSON.parse(readFileSync(cookiesPath, "utf8"));
13379
13454
  cookieCount = Array.isArray(cookies) ? cookies.length : 0;
13380
13455
  }
13381
13456
  const storagePath = join6(profileDir, "storage.json");
13382
- if (existsSync3(storagePath)) {
13457
+ if (existsSync4(storagePath)) {
13383
13458
  const storage = JSON.parse(readFileSync(storagePath, "utf8"));
13384
13459
  storageKeyCount = Object.keys(storage).length;
13385
13460
  }
@@ -13396,7 +13471,7 @@ function listProfiles() {
13396
13471
  }
13397
13472
  function deleteProfile(name) {
13398
13473
  const dir = getProfileDir2(name);
13399
- if (!existsSync3(dir))
13474
+ if (!existsSync4(dir))
13400
13475
  return false;
13401
13476
  try {
13402
13477
  rmSync(dir, { recursive: true, force: true });
@@ -13405,7 +13480,9 @@ function deleteProfile(name) {
13405
13480
  return false;
13406
13481
  }
13407
13482
  }
13408
- var init_profiles = () => {};
13483
+ var init_profiles = __esm(() => {
13484
+ init_schema();
13485
+ });
13409
13486
 
13410
13487
  // src/lib/auth.ts
13411
13488
  var exports_auth = {};
@@ -13413,20 +13490,20 @@ __export(exports_auth, {
13413
13490
  loginWithCredentials: () => loginWithCredentials,
13414
13491
  getCredentials: () => getCredentials
13415
13492
  });
13416
- import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
13493
+ import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
13417
13494
  import { join as join7 } from "path";
13418
- import { homedir as homedir7 } from "os";
13495
+ import { homedir as homedir2 } from "os";
13419
13496
  async function getCredentials(service) {
13420
13497
  try {
13421
- const { getSecret } = await import(`${homedir7()}/Workspace/hasna/opensource/opensourcedev/open-secrets/src/store.js`);
13498
+ const { getSecret } = await import(`${homedir2()}/Workspace/hasna/opensource/opensourcedev/open-secrets/src/store.js`);
13422
13499
  const email = getSecret(`${service}_email`) ?? getSecret(`${service}_username`) ?? getSecret(`${service}_login`);
13423
13500
  const password = getSecret(`${service}_password`) ?? getSecret(`${service}_pass`);
13424
13501
  if (email?.value && password?.value) {
13425
13502
  return { email: email.value, password: password.value };
13426
13503
  }
13427
13504
  } catch {}
13428
- const secretsPath = join7(homedir7(), ".secrets");
13429
- if (existsSync4(secretsPath)) {
13505
+ const secretsPath = join7(homedir2(), ".secrets");
13506
+ if (existsSync5(secretsPath)) {
13430
13507
  const content = readFileSync2(secretsPath, "utf8");
13431
13508
  const lines = content.split(`
13432
13509
  `);
@@ -13511,13 +13588,9 @@ __export(exports_downloads, {
13511
13588
  });
13512
13589
  import { randomUUID as randomUUID11 } from "crypto";
13513
13590
  import { join as join8, basename, extname } from "path";
13514
- import { mkdirSync as mkdirSync7, existsSync as existsSync5, readdirSync as readdirSync3, statSync, unlinkSync as unlinkSync2, copyFileSync, writeFileSync as writeFileSync2, readFileSync as readFileSync3 } from "fs";
13515
- import { homedir as homedir8 } from "os";
13516
- function getDataDir3() {
13517
- return process.env["BROWSER_DATA_DIR"] ?? join8(homedir8(), ".browser");
13518
- }
13591
+ import { mkdirSync as mkdirSync7, existsSync as existsSync6, readdirSync as readdirSync4, statSync as statSync2, unlinkSync as unlinkSync2, copyFileSync as copyFileSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync3 } from "fs";
13519
13592
  function getDownloadsDir(sessionId) {
13520
- const base = join8(getDataDir3(), "downloads");
13593
+ const base = join8(getDataDir(), "downloads");
13521
13594
  const dir = sessionId ? join8(base, sessionId) : base;
13522
13595
  mkdirSync7(dir, { recursive: true });
13523
13596
  return dir;
@@ -13563,20 +13636,20 @@ function listDownloads(sessionId) {
13563
13636
  const dir = getDownloadsDir(sessionId);
13564
13637
  const results = [];
13565
13638
  function scanDir(d) {
13566
- if (!existsSync5(d))
13639
+ if (!existsSync6(d))
13567
13640
  return;
13568
- const entries = readdirSync3(d);
13641
+ const entries = readdirSync4(d);
13569
13642
  for (const entry of entries) {
13570
13643
  if (entry.endsWith(".meta.json"))
13571
13644
  continue;
13572
13645
  const full = join8(d, entry);
13573
- const stat = statSync(full);
13646
+ const stat = statSync2(full);
13574
13647
  if (stat.isDirectory()) {
13575
13648
  scanDir(full);
13576
13649
  continue;
13577
13650
  }
13578
13651
  const mpath = metaPath(full);
13579
- if (!existsSync5(mpath))
13652
+ if (!existsSync6(mpath))
13580
13653
  continue;
13581
13654
  try {
13582
13655
  const meta = JSON.parse(readFileSync3(mpath, "utf8"));
@@ -13607,7 +13680,7 @@ function deleteDownload(id, sessionId) {
13607
13680
  return false;
13608
13681
  try {
13609
13682
  unlinkSync2(file.path);
13610
- if (existsSync5(file.meta_path))
13683
+ if (existsSync6(file.meta_path))
13611
13684
  unlinkSync2(file.meta_path);
13612
13685
  return true;
13613
13686
  } catch {
@@ -13631,7 +13704,7 @@ function exportToPath(id, targetPath, sessionId) {
13631
13704
  const file = getDownload(id, sessionId);
13632
13705
  if (!file)
13633
13706
  throw new Error(`Download not found: ${id}`);
13634
- copyFileSync(file.path, targetPath);
13707
+ copyFileSync2(file.path, targetPath);
13635
13708
  return targetPath;
13636
13709
  }
13637
13710
  function detectType(filename) {
@@ -13651,7 +13724,9 @@ function detectType(filename) {
13651
13724
  };
13652
13725
  return map[ext] ?? "file";
13653
13726
  }
13654
- var init_downloads = () => {};
13727
+ var init_downloads = __esm(() => {
13728
+ init_schema();
13729
+ });
13655
13730
 
13656
13731
  // src/lib/daemon-client.ts
13657
13732
  var exports_daemon_client = {};
@@ -13662,9 +13737,8 @@ __export(exports_daemon_client, {
13662
13737
  getDaemonPidFile: () => getDaemonPidFile,
13663
13738
  getDaemonPid: () => getDaemonPid
13664
13739
  });
13665
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
13740
+ import { existsSync as existsSync7, readFileSync as readFileSync4 } from "fs";
13666
13741
  import { join as join9 } from "path";
13667
- import { homedir as homedir9 } from "os";
13668
13742
  function getDaemonPidFile() {
13669
13743
  return PID_FILE;
13670
13744
  }
@@ -13672,7 +13746,7 @@ function getDaemonPort() {
13672
13746
  return parseInt(process.env["BROWSER_DAEMON_PORT"] ?? String(DEFAULT_PORT), 10);
13673
13747
  }
13674
13748
  function isDaemonRunning() {
13675
- if (!existsSync6(PID_FILE))
13749
+ if (!existsSync7(PID_FILE))
13676
13750
  return false;
13677
13751
  try {
13678
13752
  const pid = parseInt(readFileSync4(PID_FILE, "utf8").trim(), 10);
@@ -13683,7 +13757,7 @@ function isDaemonRunning() {
13683
13757
  }
13684
13758
  }
13685
13759
  function getDaemonPid() {
13686
- if (!existsSync6(PID_FILE))
13760
+ if (!existsSync7(PID_FILE))
13687
13761
  return null;
13688
13762
  try {
13689
13763
  return parseInt(readFileSync4(PID_FILE, "utf8").trim(), 10);
@@ -13706,7 +13780,8 @@ async function getDaemonStatus() {
13706
13780
  }
13707
13781
  var PID_FILE, DEFAULT_PORT = 7030;
13708
13782
  var init_daemon_client = __esm(() => {
13709
- PID_FILE = join9(process.env["BROWSER_DATA_DIR"] ?? join9(homedir9(), ".browser"), "daemon.pid");
13783
+ init_schema();
13784
+ PID_FILE = join9(getDataDir(), "daemon.pid");
13710
13785
  });
13711
13786
 
13712
13787
  // node_modules/zod/v3/helpers/util.js
@@ -17769,8 +17844,7 @@ async function setSessionStorage(page, key, value) {
17769
17844
 
17770
17845
  // src/lib/files-integration.ts
17771
17846
  import { join as join10 } from "path";
17772
- import { mkdirSync as mkdirSync8, copyFileSync as copyFileSync2 } from "fs";
17773
- import { homedir as homedir10 } from "os";
17847
+ import { mkdirSync as mkdirSync8, copyFileSync as copyFileSync3 } from "fs";
17774
17848
  async function persistFile(localPath, opts) {
17775
17849
  try {
17776
17850
  const mod = await import("@hasna/files");
@@ -17779,13 +17853,13 @@ async function persistFile(localPath, opts) {
17779
17853
  return { id: ref.id, path: ref.path ?? localPath, permanent: true, provider: "open-files" };
17780
17854
  }
17781
17855
  } catch {}
17782
- const dataDir = process.env["BROWSER_DATA_DIR"] ?? join10(homedir10(), ".browser");
17856
+ const dataDir = getDataDir();
17783
17857
  const date = new Date().toISOString().split("T")[0];
17784
17858
  const dir = join10(dataDir, "persistent", date);
17785
17859
  mkdirSync8(dir, { recursive: true });
17786
17860
  const filename = localPath.split("/").pop() ?? "file";
17787
17861
  const targetPath = join10(dir, filename);
17788
- copyFileSync2(localPath, targetPath);
17862
+ copyFileSync3(localPath, targetPath);
17789
17863
  return {
17790
17864
  id: `local-${Date.now()}`,
17791
17865
  path: targetPath,
@@ -17793,7 +17867,9 @@ async function persistFile(localPath, opts) {
17793
17867
  provider: "local"
17794
17868
  };
17795
17869
  }
17796
- var init_files_integration = () => {};
17870
+ var init_files_integration = __esm(() => {
17871
+ init_schema();
17872
+ });
17797
17873
 
17798
17874
  // src/db/timeline.ts
17799
17875
  function logEvent(sessionId, eventType, details = {}) {
@@ -19201,16 +19277,22 @@ var init_capture = __esm(() => {
19201
19277
  init_helpers();
19202
19278
  });
19203
19279
 
19204
- // ../open-skills/dist/index.js
19280
+ // node_modules/@hasna/skills/dist/index.js
19205
19281
  var exports_dist = {};
19206
19282
  __export(exports_dist, {
19283
+ validateCron: () => validateCron,
19207
19284
  skillExists: () => skillExists,
19285
+ setScheduleEnabled: () => setScheduleEnabled,
19208
19286
  searchSkills: () => searchSkills,
19209
19287
  saveConfig: () => saveConfig,
19210
19288
  runSkill: () => runSkill,
19211
19289
  removeSkillForAgent: () => removeSkillForAgent,
19212
19290
  removeSkill: () => removeSkill,
19291
+ removeSchedule: () => removeSchedule,
19292
+ recordScheduleRun: () => recordScheduleRun,
19293
+ loadRegistry: () => loadRegistry,
19213
19294
  loadConfig: () => loadConfig,
19295
+ listSchedules: () => listSchedules,
19214
19296
  installSkills: () => installSkills,
19215
19297
  installSkillForAgent: () => installSkillForAgent,
19216
19298
  installSkill: () => installSkill,
@@ -19221,8 +19303,10 @@ __export(exports_dist, {
19221
19303
  getSkillDocs: () => getSkillDocs,
19222
19304
  getSkillBestDoc: () => getSkillBestDoc,
19223
19305
  getSkill: () => getSkill,
19306
+ getNextRun: () => getNextRun,
19224
19307
  getInstalledSkills: () => getInstalledSkills,
19225
19308
  getInstallMeta: () => getInstallMeta,
19309
+ getDueSchedules: () => getDueSchedules,
19226
19310
  getDisabledSkills: () => getDisabledSkills,
19227
19311
  getConfigPath: () => getConfigPath,
19228
19312
  getAllTags: () => getAllTags,
@@ -19230,23 +19314,111 @@ __export(exports_dist, {
19230
19314
  getAgentSkillPath: () => getAgentSkillPath,
19231
19315
  generateSkillMd: () => generateSkillMd,
19232
19316
  generateEnvExample: () => generateEnvExample,
19317
+ findSimilarSkills: () => findSimilarSkills,
19233
19318
  enableSkill: () => enableSkill,
19234
19319
  disableSkill: () => disableSkill,
19320
+ clearRegistryCache: () => clearRegistryCache,
19321
+ addSchedule: () => addSchedule,
19235
19322
  SKILLS: () => SKILLS,
19236
19323
  CATEGORIES: () => CATEGORIES,
19237
19324
  AGENT_TARGETS: () => AGENT_TARGETS
19238
19325
  });
19239
- import { existsSync as existsSync7, cpSync, mkdirSync as mkdirSync9, writeFileSync as writeFileSync3, rmSync as rmSync2, readdirSync as readdirSync4, statSync as statSync2, readFileSync as readFileSync5, accessSync, constants } from "fs";
19240
- import { join as join11, dirname } from "path";
19241
- import { homedir as homedir11 } from "os";
19242
- import { fileURLToPath } from "url";
19243
- import { existsSync as existsSync22, readFileSync as readFileSync22, readdirSync as readdirSync22 } from "fs";
19244
- import { join as join22 } from "path";
19245
- import { existsSync as existsSync32, readFileSync as readFileSync32, writeFileSync as writeFileSync22, mkdirSync as mkdirSync22 } from "fs";
19246
- import { join as join32, dirname as dirname2 } from "path";
19326
+ import { existsSync as existsSync8, readFileSync as readFileSync5, readdirSync as readdirSync5 } from "fs";
19327
+ import { join as join11 } from "path";
19328
+ import { homedir as homedir3 } from "os";
19329
+ import { existsSync as existsSync22, cpSync, mkdirSync as mkdirSync9, writeFileSync as writeFileSync3, rmSync as rmSync2, readdirSync as readdirSync22, statSync as statSync3, readFileSync as readFileSync22, accessSync, constants } from "fs";
19330
+ import { join as join22, dirname } from "path";
19247
19331
  import { homedir as homedir22 } from "os";
19332
+ import { fileURLToPath } from "url";
19333
+ import { existsSync as existsSync32, readFileSync as readFileSync32, readdirSync as readdirSync32 } from "fs";
19334
+ import { join as join32 } from "path";
19335
+ import { existsSync as existsSync42, readFileSync as readFileSync42, writeFileSync as writeFileSync22, mkdirSync as mkdirSync22 } from "fs";
19336
+ import { join as join42, dirname as dirname2 } from "path";
19337
+ import { homedir as homedir32 } from "os";
19338
+ import { existsSync as existsSync52, readFileSync as readFileSync52, writeFileSync as writeFileSync32, mkdirSync as mkdirSync32 } from "fs";
19339
+ import { join as join52 } from "path";
19340
+ function parseSkillMdFrontmatter(content) {
19341
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
19342
+ if (!match)
19343
+ return null;
19344
+ const result = {};
19345
+ for (const line of match[1].split(`
19346
+ `)) {
19347
+ const colon = line.indexOf(":");
19348
+ if (colon === -1)
19349
+ continue;
19350
+ const key = line.slice(0, colon).trim();
19351
+ const value = line.slice(colon + 1).trim();
19352
+ if (!key || !value)
19353
+ continue;
19354
+ if (key === "name")
19355
+ result.name = value;
19356
+ else if (key === "description")
19357
+ result.description = value;
19358
+ else if (key === "displayName" || key === "display_name")
19359
+ result.displayName = value;
19360
+ else if (key === "category")
19361
+ result.category = value;
19362
+ else if (key === "tags") {
19363
+ result.tags = value.replace(/[\[\]]/g, "").split(",").map((t) => t.trim()).filter(Boolean);
19364
+ }
19365
+ }
19366
+ return Object.keys(result).length > 0 ? result : null;
19367
+ }
19368
+ function discoverSkillsInDir(dir) {
19369
+ if (!existsSync8(dir))
19370
+ return [];
19371
+ const result = [];
19372
+ try {
19373
+ const entries = readdirSync5(dir, { withFileTypes: true });
19374
+ for (const entry of entries) {
19375
+ if (!entry.isDirectory())
19376
+ continue;
19377
+ const skillMdPath = join11(dir, entry.name, "SKILL.md");
19378
+ if (!existsSync8(skillMdPath))
19379
+ continue;
19380
+ let content;
19381
+ try {
19382
+ content = readFileSync5(skillMdPath, "utf-8");
19383
+ } catch {
19384
+ continue;
19385
+ }
19386
+ const fm = parseSkillMdFrontmatter(content);
19387
+ if (!fm?.name)
19388
+ continue;
19389
+ const name = fm.name.replace(/^skill-/, "");
19390
+ result.push({
19391
+ name,
19392
+ displayName: fm.displayName || name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
19393
+ description: fm.description || "",
19394
+ category: fm.category || "Development Tools",
19395
+ tags: fm.tags || [],
19396
+ source: "custom"
19397
+ });
19398
+ }
19399
+ } catch {}
19400
+ return result;
19401
+ }
19402
+ function loadRegistry(cwd) {
19403
+ const now = Date.now();
19404
+ if (_registryCache && now - _registryCacheTime < REGISTRY_CACHE_TTL) {
19405
+ return _registryCache;
19406
+ }
19407
+ const official = SKILLS.map((s) => ({ ...s, source: "official" }));
19408
+ const globalCustom = discoverSkillsInDir(join11(homedir3(), ".skills"));
19409
+ const projectCustom = discoverSkillsInDir(join11(cwd || process.cwd(), ".skills", "custom-skills"));
19410
+ const customNames = new Set([...globalCustom, ...projectCustom].map((s) => s.name));
19411
+ const filtered = official.filter((s) => !customNames.has(s.name));
19412
+ _registryCache = [...filtered, ...globalCustom, ...projectCustom];
19413
+ _registryCacheTime = now;
19414
+ return _registryCache;
19415
+ }
19416
+ function clearRegistryCache() {
19417
+ _registryCache = null;
19418
+ _registryCacheTime = 0;
19419
+ }
19248
19420
  function getSkillsByCategory(category) {
19249
- return SKILLS.filter((s) => s.category === category);
19421
+ return loadRegistry().filter((s) => s.category === category);
19250
19422
  }
19251
19423
  function editDistance(a, b) {
19252
19424
  if (a === b)
@@ -19292,7 +19464,7 @@ function searchSkills(query) {
19292
19464
  if (words.length === 0)
19293
19465
  return [];
19294
19466
  const scored = [];
19295
- for (const skill of SKILLS) {
19467
+ for (const skill of loadRegistry()) {
19296
19468
  const nameLower = skill.name.toLowerCase();
19297
19469
  const displayNameLower = skill.displayName.toLowerCase();
19298
19470
  const descriptionLower = skill.description.toLowerCase();
@@ -19328,56 +19500,71 @@ function searchSkills(query) {
19328
19500
  return scored.map((s) => s.skill);
19329
19501
  }
19330
19502
  function getSkill(name) {
19331
- return SKILLS.find((s) => s.name === name);
19503
+ return loadRegistry().find((s) => s.name === name);
19332
19504
  }
19333
19505
  function getSkillsByTag(tag) {
19334
19506
  const needle = tag.toLowerCase();
19335
- return SKILLS.filter((s) => s.tags.some((t) => t.toLowerCase().includes(needle)));
19507
+ return loadRegistry().filter((s) => s.tags.some((t) => t.toLowerCase().includes(needle)));
19336
19508
  }
19337
19509
  function getAllTags() {
19338
19510
  const tagSet = new Set;
19339
- for (const skill of SKILLS) {
19511
+ for (const skill of loadRegistry()) {
19340
19512
  for (const tag of skill.tags) {
19341
19513
  tagSet.add(tag.toLowerCase());
19342
19514
  }
19343
19515
  }
19344
19516
  return Array.from(tagSet).sort();
19345
19517
  }
19518
+ function levenshtein(a, b) {
19519
+ const m = a.length, n = b.length;
19520
+ const dp = Array.from({ length: m + 1 }, (_, i) => Array.from({ length: n + 1 }, (_2, j) => i === 0 ? j : j === 0 ? i : 0));
19521
+ for (let i = 1;i <= m; i++) {
19522
+ for (let j = 1;j <= n; j++) {
19523
+ dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
19524
+ }
19525
+ }
19526
+ return dp[m][n];
19527
+ }
19528
+ function findSimilarSkills(query, maxResults = 3) {
19529
+ const q = query.toLowerCase();
19530
+ const scored = loadRegistry().map((s) => ({ name: s.name, dist: levenshtein(q, s.name.toLowerCase()) })).filter((s) => s.dist <= Math.max(3, Math.floor(q.length / 2))).sort((a, b) => a.dist - b.dist);
19531
+ return scored.slice(0, maxResults).map((s) => s.name);
19532
+ }
19346
19533
  function normalizeSkillName(name) {
19347
19534
  return name.startsWith("skill-") ? name : `skill-${name}`;
19348
19535
  }
19349
19536
  function findSkillsDir() {
19350
19537
  let dir = __dirname2;
19351
19538
  for (let i = 0;i < 5; i++) {
19352
- const candidate = join11(dir, "skills");
19353
- if (existsSync7(candidate)) {
19539
+ const candidate = join22(dir, "skills");
19540
+ if (existsSync22(candidate) && !dir.includes(".skills")) {
19354
19541
  return candidate;
19355
19542
  }
19356
19543
  dir = dirname(dir);
19357
19544
  }
19358
- return join11(__dirname2, "..", "skills");
19545
+ return join22(__dirname2, "..", "skills");
19359
19546
  }
19360
19547
  function getSkillPath(name) {
19361
19548
  const skillName = normalizeSkillName(name);
19362
- return join11(SKILLS_DIR, skillName);
19549
+ return join22(SKILLS_DIR, skillName);
19363
19550
  }
19364
19551
  function skillExists(name) {
19365
- return existsSync7(getSkillPath(name));
19552
+ return existsSync22(getSkillPath(name));
19366
19553
  }
19367
19554
  function installSkill(name, options = {}) {
19368
19555
  const { targetDir = process.cwd(), overwrite = false } = options;
19369
19556
  const skillName = normalizeSkillName(name);
19370
19557
  const sourcePath = getSkillPath(name);
19371
- const destDir = join11(targetDir, ".skills");
19372
- const destPath = join11(destDir, skillName);
19373
- if (!existsSync7(sourcePath)) {
19558
+ const destDir = join22(targetDir, ".skills");
19559
+ const destPath = join22(destDir, skillName);
19560
+ if (!existsSync22(sourcePath)) {
19374
19561
  return {
19375
19562
  skill: name,
19376
19563
  success: false,
19377
19564
  error: `Skill '${name}' not found`
19378
19565
  };
19379
19566
  }
19380
- if (existsSync7(destPath) && !overwrite) {
19567
+ if (existsSync22(destPath) && !overwrite) {
19381
19568
  return {
19382
19569
  skill: name,
19383
19570
  success: false,
@@ -19386,10 +19573,10 @@ function installSkill(name, options = {}) {
19386
19573
  };
19387
19574
  }
19388
19575
  try {
19389
- if (!existsSync7(destDir)) {
19576
+ if (!existsSync22(destDir)) {
19390
19577
  mkdirSync9(destDir, { recursive: true });
19391
19578
  }
19392
- if (existsSync7(destPath) && overwrite) {
19579
+ if (existsSync22(destPath) && overwrite) {
19393
19580
  rmSync2(destPath, { recursive: true, force: true });
19394
19581
  }
19395
19582
  cpSync(sourcePath, destPath, {
@@ -19428,10 +19615,10 @@ function installSkills(names, options = {}) {
19428
19615
  return names.map((name) => installSkill(name, options));
19429
19616
  }
19430
19617
  function updateSkillsIndex(skillsDir) {
19431
- const indexPath = join11(skillsDir, "index.ts");
19618
+ const indexPath = join22(skillsDir, "index.ts");
19432
19619
  const meta = loadMeta(skillsDir);
19433
19620
  const disabledSet = new Set(meta.disabled || []);
19434
- const skills = readdirSync4(skillsDir).filter((f) => f.startsWith("skill-") && !f.includes(".") && !disabledSet.has(f.replace("skill-", "")));
19621
+ const skills = readdirSync22(skillsDir).filter((f) => f.startsWith("skill-") && !f.includes(".") && !disabledSet.has(f.replace("skill-", "")));
19435
19622
  const exports = skills.map((s) => {
19436
19623
  const name = s.replace("skill-", "").replace(/-/g, "_");
19437
19624
  return `export * as ${name} from './${s}/src/index.js';`;
@@ -19447,13 +19634,13 @@ ${exports}
19447
19634
  writeFileSync3(indexPath, content);
19448
19635
  }
19449
19636
  function getMetaPath(skillsDir) {
19450
- return join11(skillsDir, ".meta.json");
19637
+ return join22(skillsDir, ".meta.json");
19451
19638
  }
19452
19639
  function loadMeta(skillsDir) {
19453
19640
  const metaPath2 = getMetaPath(skillsDir);
19454
- if (existsSync7(metaPath2)) {
19641
+ if (existsSync22(metaPath2)) {
19455
19642
  try {
19456
- return JSON.parse(readFileSync5(metaPath2, "utf-8"));
19643
+ return JSON.parse(readFileSync22(metaPath2, "utf-8"));
19457
19644
  } catch {}
19458
19645
  }
19459
19646
  return { skills: {} };
@@ -19466,9 +19653,9 @@ function recordInstall(skillsDir, name) {
19466
19653
  const skillName = normalizeSkillName(name);
19467
19654
  let version = "unknown";
19468
19655
  try {
19469
- const pkgPath = join11(skillsDir, skillName, "package.json");
19470
- if (existsSync7(pkgPath)) {
19471
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
19656
+ const pkgPath = join22(skillsDir, skillName, "package.json");
19657
+ if (existsSync22(pkgPath)) {
19658
+ const pkg = JSON.parse(readFileSync22(pkgPath, "utf-8"));
19472
19659
  version = pkg.version || "unknown";
19473
19660
  }
19474
19661
  } catch {}
@@ -19481,12 +19668,12 @@ function recordRemove(skillsDir, name) {
19481
19668
  saveMeta(skillsDir, meta);
19482
19669
  }
19483
19670
  function getInstallMeta(targetDir = process.cwd()) {
19484
- return loadMeta(join11(targetDir, ".skills"));
19671
+ return loadMeta(join22(targetDir, ".skills"));
19485
19672
  }
19486
19673
  function disableSkill(name, targetDir = process.cwd()) {
19487
- const skillsDir = join11(targetDir, ".skills");
19674
+ const skillsDir = join22(targetDir, ".skills");
19488
19675
  const skillName = normalizeSkillName(name);
19489
- if (!existsSync7(join11(skillsDir, skillName)))
19676
+ if (!existsSync22(join22(skillsDir, skillName)))
19490
19677
  return false;
19491
19678
  const meta = loadMeta(skillsDir);
19492
19679
  const disabled = new Set(meta.disabled || []);
@@ -19499,7 +19686,7 @@ function disableSkill(name, targetDir = process.cwd()) {
19499
19686
  return true;
19500
19687
  }
19501
19688
  function enableSkill(name, targetDir = process.cwd()) {
19502
- const skillsDir = join11(targetDir, ".skills");
19689
+ const skillsDir = join22(targetDir, ".skills");
19503
19690
  const meta = loadMeta(skillsDir);
19504
19691
  const disabled = new Set(meta.disabled || []);
19505
19692
  if (!disabled.has(name))
@@ -19511,24 +19698,24 @@ function enableSkill(name, targetDir = process.cwd()) {
19511
19698
  return true;
19512
19699
  }
19513
19700
  function getDisabledSkills(targetDir = process.cwd()) {
19514
- const meta = loadMeta(join11(targetDir, ".skills"));
19701
+ const meta = loadMeta(join22(targetDir, ".skills"));
19515
19702
  return meta.disabled || [];
19516
19703
  }
19517
19704
  function getInstalledSkills(targetDir = process.cwd()) {
19518
- const skillsDir = join11(targetDir, ".skills");
19519
- if (!existsSync7(skillsDir)) {
19705
+ const skillsDir = join22(targetDir, ".skills");
19706
+ if (!existsSync22(skillsDir)) {
19520
19707
  return [];
19521
19708
  }
19522
- return readdirSync4(skillsDir).filter((f) => {
19523
- const fullPath = join11(skillsDir, f);
19524
- return f.startsWith("skill-") && statSync2(fullPath).isDirectory();
19709
+ return readdirSync22(skillsDir).filter((f) => {
19710
+ const fullPath = join22(skillsDir, f);
19711
+ return f.startsWith("skill-") && statSync3(fullPath).isDirectory();
19525
19712
  }).map((f) => f.replace("skill-", ""));
19526
19713
  }
19527
19714
  function removeSkill(name, targetDir = process.cwd()) {
19528
19715
  const skillName = normalizeSkillName(name);
19529
- const skillsDir = join11(targetDir, ".skills");
19530
- const skillPath = join11(skillsDir, skillName);
19531
- if (!existsSync7(skillPath)) {
19716
+ const skillsDir = join22(targetDir, ".skills");
19717
+ const skillPath = join22(skillsDir, skillName);
19718
+ if (!existsSync22(skillPath)) {
19532
19719
  return false;
19533
19720
  }
19534
19721
  rmSync2(skillPath, { recursive: true, force: true });
@@ -19537,27 +19724,31 @@ function removeSkill(name, targetDir = process.cwd()) {
19537
19724
  return true;
19538
19725
  }
19539
19726
  function getAgentSkillsDir(agent, scope = "global", projectDir) {
19540
- const agentDir = `.${agent}`;
19541
- if (scope === "project") {
19542
- return join11(projectDir || process.cwd(), agentDir, "skills");
19727
+ const base = projectDir || process.cwd();
19728
+ switch (agent) {
19729
+ case "pi":
19730
+ return scope === "project" ? join22(base, ".pi", "skills") : join22(homedir22(), ".pi", "agent", "skills");
19731
+ case "opencode":
19732
+ return scope === "project" ? join22(base, ".opencode", "skills") : join22(homedir22(), ".opencode", "skills");
19733
+ default:
19734
+ return scope === "project" ? join22(base, `.${agent}`, "skills") : join22(homedir22(), `.${agent}`, "skills");
19543
19735
  }
19544
- return join11(homedir11(), agentDir, "skills");
19545
19736
  }
19546
19737
  function getAgentSkillPath(name, agent, scope = "global", projectDir) {
19547
19738
  const skillName = normalizeSkillName(name);
19548
- return join11(getAgentSkillsDir(agent, scope, projectDir), skillName);
19739
+ return join22(getAgentSkillsDir(agent, scope, projectDir), skillName);
19549
19740
  }
19550
19741
  function installSkillForAgent(name, options, generateSkillMd) {
19551
19742
  const { agent, scope = "global", projectDir } = options;
19552
19743
  const skillName = normalizeSkillName(name);
19553
19744
  const sourcePath = getSkillPath(name);
19554
- if (!existsSync7(sourcePath)) {
19745
+ if (!existsSync22(sourcePath)) {
19555
19746
  return { skill: name, success: false, error: `Skill '${name}' not found` };
19556
19747
  }
19557
19748
  let skillMdContent = null;
19558
- const skillMdPath = join11(sourcePath, "SKILL.md");
19559
- if (existsSync7(skillMdPath)) {
19560
- skillMdContent = readFileSync5(skillMdPath, "utf-8");
19749
+ const skillMdPath = join22(sourcePath, "SKILL.md");
19750
+ if (existsSync22(skillMdPath)) {
19751
+ skillMdContent = readFileSync22(skillMdPath, "utf-8");
19561
19752
  } else if (generateSkillMd) {
19562
19753
  skillMdContent = generateSkillMd(name);
19563
19754
  }
@@ -19566,17 +19757,12 @@ function installSkillForAgent(name, options, generateSkillMd) {
19566
19757
  }
19567
19758
  const destDir = getAgentSkillPath(name, agent, scope, projectDir);
19568
19759
  if (scope === "global") {
19569
- const agentBaseDir = join11(homedir11(), `.${agent}`);
19570
- if (!existsSync7(agentBaseDir)) {
19571
- const agentLabels = {
19572
- claude: "Claude Code",
19573
- codex: "Codex CLI",
19574
- gemini: "Gemini CLI"
19575
- };
19760
+ const agentBaseDir = agent === "pi" ? join22(homedir22(), ".pi", "agent") : join22(homedir22(), `.${agent}`);
19761
+ if (!existsSync22(agentBaseDir)) {
19576
19762
  return {
19577
19763
  skill: name,
19578
19764
  success: false,
19579
- error: `Agent directory ${agentBaseDir} does not exist. Is ${agentLabels[agent]} installed?`
19765
+ error: `Agent directory ${agentBaseDir} does not exist. Is ${AGENT_LABELS[agent]} installed?`
19580
19766
  };
19581
19767
  }
19582
19768
  try {
@@ -19591,7 +19777,7 @@ function installSkillForAgent(name, options, generateSkillMd) {
19591
19777
  }
19592
19778
  try {
19593
19779
  mkdirSync9(destDir, { recursive: true });
19594
- writeFileSync3(join11(destDir, "SKILL.md"), skillMdContent);
19780
+ writeFileSync3(join22(destDir, "SKILL.md"), skillMdContent);
19595
19781
  return { skill: name, success: true, path: destDir };
19596
19782
  } catch (error) {
19597
19783
  return {
@@ -19604,7 +19790,7 @@ function installSkillForAgent(name, options, generateSkillMd) {
19604
19790
  function removeSkillForAgent(name, options) {
19605
19791
  const { agent, scope = "global", projectDir } = options;
19606
19792
  const destDir = getAgentSkillPath(name, agent, scope, projectDir);
19607
- if (!existsSync7(destDir)) {
19793
+ if (!existsSync22(destDir)) {
19608
19794
  return false;
19609
19795
  }
19610
19796
  rmSync2(destDir, { recursive: true, force: true });
@@ -19612,12 +19798,12 @@ function removeSkillForAgent(name, options) {
19612
19798
  }
19613
19799
  function getSkillDocs(name) {
19614
19800
  const skillPath = getSkillPath(name);
19615
- if (!existsSync22(skillPath))
19801
+ if (!existsSync32(skillPath))
19616
19802
  return null;
19617
19803
  return {
19618
- skillMd: readIfExists(join22(skillPath, "SKILL.md")),
19619
- readme: readIfExists(join22(skillPath, "README.md")),
19620
- claudeMd: readIfExists(join22(skillPath, "CLAUDE.md"))
19804
+ skillMd: readIfExists(join32(skillPath, "SKILL.md")),
19805
+ readme: readIfExists(join32(skillPath, "README.md")),
19806
+ claudeMd: readIfExists(join32(skillPath, "CLAUDE.md"))
19621
19807
  };
19622
19808
  }
19623
19809
  function getSkillBestDoc(name) {
@@ -19628,11 +19814,11 @@ function getSkillBestDoc(name) {
19628
19814
  }
19629
19815
  function getSkillRequirements(name) {
19630
19816
  const skillPath = getSkillPath(name);
19631
- if (!existsSync22(skillPath))
19817
+ if (!existsSync32(skillPath))
19632
19818
  return null;
19633
19819
  const texts = [];
19634
19820
  for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
19635
- const content = readIfExists(join22(skillPath, file));
19821
+ const content = readIfExists(join32(skillPath, file));
19636
19822
  if (content)
19637
19823
  texts.push(content);
19638
19824
  }
@@ -19659,10 +19845,10 @@ function getSkillRequirements(name) {
19659
19845
  }
19660
19846
  let cliCommand = null;
19661
19847
  let dependencies = {};
19662
- const pkgPath = join22(skillPath, "package.json");
19663
- if (existsSync22(pkgPath)) {
19848
+ const pkgPath = join32(skillPath, "package.json");
19849
+ if (existsSync32(pkgPath)) {
19664
19850
  try {
19665
- const pkg = JSON.parse(readFileSync22(pkgPath, "utf-8"));
19851
+ const pkg = JSON.parse(readFileSync32(pkgPath, "utf-8"));
19666
19852
  if (pkg.bin) {
19667
19853
  const binKeys = Object.keys(pkg.bin);
19668
19854
  if (binKeys.length > 0)
@@ -19682,25 +19868,25 @@ async function runSkill(name, args, options = {}) {
19682
19868
  const skillName = normalizeSkillName(name);
19683
19869
  let skillPath;
19684
19870
  if (options.installed) {
19685
- skillPath = join22(process.cwd(), ".skills", skillName);
19871
+ skillPath = join32(process.cwd(), ".skills", skillName);
19686
19872
  } else {
19687
- const installedPath = join22(process.cwd(), ".skills", skillName);
19688
- if (existsSync22(installedPath)) {
19873
+ const installedPath = join32(process.cwd(), ".skills", skillName);
19874
+ if (existsSync32(installedPath)) {
19689
19875
  skillPath = installedPath;
19690
19876
  } else {
19691
19877
  skillPath = getSkillPath(name);
19692
19878
  }
19693
19879
  }
19694
- if (!existsSync22(skillPath)) {
19880
+ if (!existsSync32(skillPath)) {
19695
19881
  return { exitCode: 1, error: `Skill '${name}' not found` };
19696
19882
  }
19697
- const pkgPath = join22(skillPath, "package.json");
19698
- if (!existsSync22(pkgPath)) {
19883
+ const pkgPath = join32(skillPath, "package.json");
19884
+ if (!existsSync32(pkgPath)) {
19699
19885
  return { exitCode: 1, error: `No package.json in skill '${name}'` };
19700
19886
  }
19701
19887
  let entryPoint;
19702
19888
  try {
19703
- const pkg = JSON.parse(readFileSync22(pkgPath, "utf-8"));
19889
+ const pkg = JSON.parse(readFileSync32(pkgPath, "utf-8"));
19704
19890
  if (pkg.bin) {
19705
19891
  const binValues = Object.values(pkg.bin);
19706
19892
  entryPoint = binValues[0];
@@ -19714,12 +19900,12 @@ async function runSkill(name, args, options = {}) {
19714
19900
  } catch {
19715
19901
  return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
19716
19902
  }
19717
- const entryPath = join22(skillPath, entryPoint);
19718
- if (!existsSync22(entryPath)) {
19903
+ const entryPath = join32(skillPath, entryPoint);
19904
+ if (!existsSync32(entryPath)) {
19719
19905
  return { exitCode: 1, error: `Entry point '${entryPoint}' not found in skill '${name}'` };
19720
19906
  }
19721
- const nodeModules = join22(skillPath, "node_modules");
19722
- if (!existsSync22(nodeModules)) {
19907
+ const nodeModules = join32(skillPath, "node_modules");
19908
+ if (!existsSync32(nodeModules)) {
19723
19909
  const install = Bun.spawn(["bun", "install", "--no-save"], {
19724
19910
  cwd: skillPath,
19725
19911
  stdout: "pipe",
@@ -19737,17 +19923,17 @@ async function runSkill(name, args, options = {}) {
19737
19923
  return { exitCode };
19738
19924
  }
19739
19925
  function generateEnvExample(targetDir = process.cwd()) {
19740
- const skillsDir = join22(targetDir, ".skills");
19741
- if (!existsSync22(skillsDir))
19926
+ const skillsDir = join32(targetDir, ".skills");
19927
+ if (!existsSync32(skillsDir))
19742
19928
  return "";
19743
- const dirs = readdirSync22(skillsDir).filter((f) => f.startsWith("skill-") && existsSync22(join22(skillsDir, f, "package.json")));
19929
+ const dirs = readdirSync32(skillsDir).filter((f) => f.startsWith("skill-") && existsSync32(join32(skillsDir, f, "package.json")));
19744
19930
  const envMap = new Map;
19745
19931
  for (const dir of dirs) {
19746
19932
  const skillName = dir.replace("skill-", "");
19747
- const skillPath = join22(skillsDir, dir);
19933
+ const skillPath = join32(skillsDir, dir);
19748
19934
  const texts = [];
19749
19935
  for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example"]) {
19750
- const content = readIfExists(join22(skillPath, file));
19936
+ const content = readIfExists(join32(skillPath, file));
19751
19937
  if (content)
19752
19938
  texts.push(content);
19753
19939
  }
@@ -19792,7 +19978,7 @@ function generateSkillMd(name) {
19792
19978
  if (!meta)
19793
19979
  return null;
19794
19980
  const skillPath = getSkillPath(name);
19795
- if (!existsSync22(skillPath))
19981
+ if (!existsSync32(skillPath))
19796
19982
  return null;
19797
19983
  const frontmatter = [
19798
19984
  "---",
@@ -19801,13 +19987,13 @@ function generateSkillMd(name) {
19801
19987
  "---"
19802
19988
  ].join(`
19803
19989
  `);
19804
- const readme = readIfExists(join22(skillPath, "README.md"));
19805
- const claudeMd = readIfExists(join22(skillPath, "CLAUDE.md"));
19990
+ const readme = readIfExists(join32(skillPath, "README.md"));
19991
+ const claudeMd = readIfExists(join32(skillPath, "CLAUDE.md"));
19806
19992
  let cliCommand = null;
19807
- const pkgPath = join22(skillPath, "package.json");
19808
- if (existsSync22(pkgPath)) {
19993
+ const pkgPath = join32(skillPath, "package.json");
19994
+ if (existsSync32(pkgPath)) {
19809
19995
  try {
19810
- const pkg = JSON.parse(readFileSync22(pkgPath, "utf-8"));
19996
+ const pkg = JSON.parse(readFileSync32(pkgPath, "utf-8"));
19811
19997
  if (pkg.bin) {
19812
19998
  const binKeys = Object.keys(pkg.bin);
19813
19999
  if (binKeys.length > 0)
@@ -19880,23 +20066,23 @@ function extractEnvVars(text) {
19880
20066
  }
19881
20067
  function readIfExists(path) {
19882
20068
  try {
19883
- if (existsSync22(path)) {
19884
- return readFileSync22(path, "utf-8");
20069
+ if (existsSync32(path)) {
20070
+ return readFileSync32(path, "utf-8");
19885
20071
  }
19886
20072
  } catch {}
19887
20073
  return null;
19888
20074
  }
19889
20075
  function getConfigPath(scope) {
19890
20076
  if (scope === "global") {
19891
- return join32(homedir22(), ".skillsrc");
20077
+ return join42(homedir32(), ".skillsrc");
19892
20078
  }
19893
- return join32(process.cwd(), "skills.config.json");
20079
+ return join42(process.cwd(), "skills.config.json");
19894
20080
  }
19895
20081
  function readConfigFile(path) {
19896
- if (!existsSync32(path))
20082
+ if (!existsSync42(path))
19897
20083
  return {};
19898
20084
  try {
19899
- const raw = readFileSync32(path, "utf-8");
20085
+ const raw = readFileSync42(path, "utf-8");
19900
20086
  const parsed = JSON.parse(raw);
19901
20087
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
19902
20088
  return {};
@@ -19927,9 +20113,9 @@ function saveConfig(key, value, scope = "project") {
19927
20113
  }
19928
20114
  const filePath = getConfigPath(scope);
19929
20115
  let existing = {};
19930
- if (existsSync32(filePath)) {
20116
+ if (existsSync42(filePath)) {
19931
20117
  try {
19932
- existing = JSON.parse(readFileSync32(filePath, "utf-8"));
20118
+ existing = JSON.parse(readFileSync42(filePath, "utf-8"));
19933
20119
  if (typeof existing !== "object" || existing === null || Array.isArray(existing)) {
19934
20120
  existing = {};
19935
20121
  }
@@ -19938,7 +20124,7 @@ function saveConfig(key, value, scope = "project") {
19938
20124
  }
19939
20125
  } else {
19940
20126
  const dir = dirname2(filePath);
19941
- if (!existsSync32(dir)) {
20127
+ if (!existsSync42(dir)) {
19942
20128
  mkdirSync22(dir, { recursive: true });
19943
20129
  }
19944
20130
  }
@@ -19946,7 +20132,158 @@ function saveConfig(key, value, scope = "project") {
19946
20132
  writeFileSync22(filePath, JSON.stringify(existing, null, 2) + `
19947
20133
  `);
19948
20134
  }
19949
- var CATEGORIES, SKILLS, __dirname2, SKILLS_DIR, AGENT_TARGETS, ENV_VAR_PATTERN, GENERIC_ENV_PATTERN, VALID_KEYS;
20135
+ function getSchedulesPath(targetDir = process.cwd()) {
20136
+ return join52(targetDir, ".skills", "schedules.json");
20137
+ }
20138
+ function loadSchedules(targetDir = process.cwd()) {
20139
+ const path = getSchedulesPath(targetDir);
20140
+ if (existsSync52(path)) {
20141
+ try {
20142
+ return JSON.parse(readFileSync52(path, "utf-8"));
20143
+ } catch {}
20144
+ }
20145
+ return { version: 1, schedules: [] };
20146
+ }
20147
+ function saveSchedules(data, targetDir = process.cwd()) {
20148
+ const path = getSchedulesPath(targetDir);
20149
+ const dir = join52(targetDir, ".skills");
20150
+ if (!existsSync52(dir))
20151
+ mkdirSync32(dir, { recursive: true });
20152
+ writeFileSync32(path, JSON.stringify(data, null, 2));
20153
+ }
20154
+ function validateCron(expr) {
20155
+ const fields = expr.trim().split(/\s+/);
20156
+ if (fields.length !== 5) {
20157
+ return { valid: false, error: `Expected 5 fields, got ${fields.length}. Format: "minute hour day-of-month month day-of-week"` };
20158
+ }
20159
+ return { valid: true };
20160
+ }
20161
+ function getNextRun(cron, from = new Date) {
20162
+ const { valid } = validateCron(cron);
20163
+ if (!valid)
20164
+ return null;
20165
+ const [minuteF, hourF, domF, monthF, dowF] = cron.trim().split(/\s+/);
20166
+ function parseField(f, min, max) {
20167
+ if (f === "*")
20168
+ return Array.from({ length: max - min + 1 }, (_, i) => i + min);
20169
+ if (f.startsWith("*/")) {
20170
+ const step = parseInt(f.slice(2));
20171
+ if (isNaN(step))
20172
+ return [];
20173
+ const vals = [];
20174
+ for (let i = min;i <= max; i += step)
20175
+ vals.push(i);
20176
+ return vals;
20177
+ }
20178
+ return f.split(",").flatMap((part) => {
20179
+ if (part.includes("-")) {
20180
+ const [lo, hi] = part.split("-").map(Number);
20181
+ return Array.from({ length: hi - lo + 1 }, (_, i) => i + lo);
20182
+ }
20183
+ const n = parseInt(part);
20184
+ return isNaN(n) ? [] : [n];
20185
+ });
20186
+ }
20187
+ const minutes = parseField(minuteF, 0, 59);
20188
+ const hours = parseField(hourF, 0, 23);
20189
+ const doms = parseField(domF, 1, 31);
20190
+ const months = parseField(monthF, 1, 12);
20191
+ const dows = parseField(dowF, 0, 6);
20192
+ const candidate = new Date(from);
20193
+ candidate.setSeconds(0, 0);
20194
+ candidate.setMinutes(candidate.getMinutes() + 1);
20195
+ const limit = new Date(from);
20196
+ limit.setFullYear(limit.getFullYear() + 1);
20197
+ while (candidate < limit) {
20198
+ const month = candidate.getMonth() + 1;
20199
+ const dom = candidate.getDate();
20200
+ const dow = candidate.getDay();
20201
+ const hour = candidate.getHours();
20202
+ const minute = candidate.getMinutes();
20203
+ if (!months.includes(month)) {
20204
+ candidate.setMonth(candidate.getMonth() + 1, 1);
20205
+ candidate.setHours(0, 0, 0, 0);
20206
+ continue;
20207
+ }
20208
+ if (!doms.includes(dom) || !dows.includes(dow)) {
20209
+ candidate.setDate(candidate.getDate() + 1);
20210
+ candidate.setHours(0, 0, 0, 0);
20211
+ continue;
20212
+ }
20213
+ if (!hours.includes(hour)) {
20214
+ candidate.setHours(candidate.getHours() + 1, 0, 0, 0);
20215
+ continue;
20216
+ }
20217
+ if (!minutes.includes(minute)) {
20218
+ candidate.setMinutes(candidate.getMinutes() + 1, 0, 0);
20219
+ continue;
20220
+ }
20221
+ return new Date(candidate);
20222
+ }
20223
+ return null;
20224
+ }
20225
+ function addSchedule(skill, cron, options = {}) {
20226
+ const { valid, error } = validateCron(cron);
20227
+ if (!valid)
20228
+ return { schedule: null, error };
20229
+ const data = loadSchedules(options.targetDir);
20230
+ const id = `${skill}-${Date.now()}`;
20231
+ const now = new Date;
20232
+ const nextRun = getNextRun(cron, now);
20233
+ const schedule = {
20234
+ id,
20235
+ name: options.name || `${skill} (${cron})`,
20236
+ skill,
20237
+ cron,
20238
+ args: options.args,
20239
+ enabled: true,
20240
+ createdAt: now.toISOString(),
20241
+ nextRun: nextRun?.toISOString()
20242
+ };
20243
+ data.schedules.push(schedule);
20244
+ saveSchedules(data, options.targetDir);
20245
+ return { schedule };
20246
+ }
20247
+ function listSchedules(targetDir) {
20248
+ return loadSchedules(targetDir).schedules;
20249
+ }
20250
+ function removeSchedule(idOrName, targetDir) {
20251
+ const data = loadSchedules(targetDir);
20252
+ const before = data.schedules.length;
20253
+ data.schedules = data.schedules.filter((s) => s.id !== idOrName && s.name !== idOrName);
20254
+ if (data.schedules.length === before)
20255
+ return false;
20256
+ saveSchedules(data, targetDir);
20257
+ return true;
20258
+ }
20259
+ function setScheduleEnabled(idOrName, enabled, targetDir) {
20260
+ const data = loadSchedules(targetDir);
20261
+ const schedule = data.schedules.find((s) => s.id === idOrName || s.name === idOrName);
20262
+ if (!schedule)
20263
+ return false;
20264
+ schedule.enabled = enabled;
20265
+ if (enabled) {
20266
+ schedule.nextRun = getNextRun(schedule.cron)?.toISOString();
20267
+ }
20268
+ saveSchedules(data, targetDir);
20269
+ return true;
20270
+ }
20271
+ function getDueSchedules(targetDir) {
20272
+ const now = new Date;
20273
+ return listSchedules(targetDir).filter((s) => s.enabled && s.nextRun && new Date(s.nextRun) <= now);
20274
+ }
20275
+ function recordScheduleRun(id, status, targetDir) {
20276
+ const data = loadSchedules(targetDir);
20277
+ const schedule = data.schedules.find((s) => s.id === id);
20278
+ if (!schedule)
20279
+ return;
20280
+ const now = new Date;
20281
+ schedule.lastRun = now.toISOString();
20282
+ schedule.lastRunStatus = status;
20283
+ schedule.nextRun = getNextRun(schedule.cron, now)?.toISOString();
20284
+ saveSchedules(data, targetDir);
20285
+ }
20286
+ var CATEGORIES, SKILLS, _registryCache = null, _registryCacheTime = 0, REGISTRY_CACHE_TTL = 5000, __dirname2, SKILLS_DIR, AGENT_TARGETS, AGENT_LABELS, ENV_VAR_PATTERN, GENERIC_ENV_PATTERN, VALID_KEYS;
19950
20287
  var init_dist = __esm(() => {
19951
20288
  CATEGORIES = [
19952
20289
  "Development Tools",
@@ -20984,6 +21321,20 @@ var init_dist = __esm(() => {
20984
21321
  category: "Design & Branding",
20985
21322
  tags: ["testimonials", "graphics", "social-proof", "marketing"]
20986
21323
  },
21324
+ {
21325
+ name: "colorextract",
21326
+ displayName: "Color Extract",
21327
+ description: "Extract complete color palettes from screenshots and images using Claude Vision. Outputs open-styles compatible profiles.",
21328
+ category: "Design & Branding",
21329
+ tags: ["colors", "palette", "design", "vision", "screenshot", "extract", "open-styles"]
21330
+ },
21331
+ {
21332
+ name: "siteanalyze",
21333
+ displayName: "Site Analyze",
21334
+ description: "Analyze any website's design system \u2014 detects shadcn/ui, Tailwind, extracts colors, typography, and components via Playwright + Claude Vision.",
21335
+ category: "Design & Branding",
21336
+ tags: ["design", "shadcn", "tailwind", "colors", "typography", "playwright", "analysis", "open-styles"]
21337
+ },
20987
21338
  {
20988
21339
  name: "browse",
20989
21340
  displayName: "Browse",
@@ -21386,7 +21737,14 @@ var init_dist = __esm(() => {
21386
21737
  ];
21387
21738
  __dirname2 = dirname(fileURLToPath(import.meta.url));
21388
21739
  SKILLS_DIR = findSkillsDir();
21389
- AGENT_TARGETS = ["claude", "codex", "gemini"];
21740
+ AGENT_TARGETS = ["claude", "codex", "gemini", "pi", "opencode"];
21741
+ AGENT_LABELS = {
21742
+ claude: "Claude Code",
21743
+ codex: "Codex CLI",
21744
+ gemini: "Gemini CLI",
21745
+ pi: "pi.dev",
21746
+ opencode: "OpenCode"
21747
+ };
21390
21748
  ENV_VAR_PATTERN = /\b([A-Z][A-Z0-9_]{2,}(?:_API_KEY|_KEY|_TOKEN|_SECRET|_URL|_ID|_PASSWORD|_ENDPOINT|_REGION|_BUCKET))\b/g;
21391
21749
  GENERIC_ENV_PATTERN = /\b((?:OPENAI|ANTHROPIC|GEMINI|XAI|ELEVENLABS|DEEPGRAM|REPLICATE|FAL|STABILITY|EXA|FIRECRAWL|TWILIO|SENDGRID|RESEND|SLACK|DISCORD|NOTION|LINEAR|GITHUB|AWS|GOOGLE|CLOUDFLARE|VERCEL|SUPABASE|STRIPE)_[A-Z_]+)\b/g;
21392
21750
  VALID_KEYS = {
@@ -21520,6 +21878,7 @@ var init_skills_runner = __esm(() => {
21520
21878
  var exports_cron_manager = {};
21521
21879
  __export(exports_cron_manager, {
21522
21880
  runCronJobNow: () => runCronJobNow,
21881
+ pruneCronEvents: () => pruneCronEvents,
21523
21882
  loadCronJobsOnStartup: () => loadCronJobsOnStartup,
21524
21883
  listCronJobs: () => listCronJobs,
21525
21884
  getCronJob: () => getCronJob,
@@ -21612,6 +21971,13 @@ function getCronEvents(jobId, limit = 10) {
21612
21971
  const rows = db.query("SELECT * FROM cron_events WHERE job_id = ? ORDER BY started_at DESC LIMIT ?").all(jobId, limit);
21613
21972
  return rows.map((r) => ({ ...r, success: r.success === 1, result: r.result ? JSON.parse(r.result) : undefined }));
21614
21973
  }
21974
+ function pruneCronEvents(retentionDays = 7) {
21975
+ ensureCronTable();
21976
+ const db = getDatabase();
21977
+ const cutoff = new Date(Date.now() - retentionDays * 86400000).toISOString();
21978
+ const result = db.prepare("DELETE FROM cron_events WHERE started_at < ?").run(cutoff);
21979
+ return result.changes;
21980
+ }
21615
21981
  async function executeCronJob(job) {
21616
21982
  ensureCronTable();
21617
21983
  const db = getDatabase();
@@ -21680,6 +22046,9 @@ function unregisterCronJob(id) {
21680
22046
  }
21681
22047
  function loadCronJobsOnStartup() {
21682
22048
  try {
22049
+ const pruned = pruneCronEvents();
22050
+ if (pruned > 0)
22051
+ console.error(`[browser] Pruned ${pruned} old cron event(s)`);
21683
22052
  const jobs = listCronJobs();
21684
22053
  for (const job of jobs) {
21685
22054
  if (job.enabled)
@@ -22263,8 +22632,8 @@ async function tryReplayAuth(page, domain) {
22263
22632
  return { replayed: false };
22264
22633
  if (flow.storage_state_path) {
22265
22634
  try {
22266
- const { existsSync: existsSync8, readFileSync: readFileSync6 } = await import("fs");
22267
- if (existsSync8(flow.storage_state_path)) {
22635
+ const { existsSync: existsSync9, readFileSync: readFileSync6 } = await import("fs");
22636
+ if (existsSync9(flow.storage_state_path)) {
22268
22637
  const state = JSON.parse(readFileSync6(flow.storage_state_path, "utf8"));
22269
22638
  if (state.cookies?.length) {
22270
22639
  await page.context().addCookies(state.cookies);
@@ -22504,7 +22873,6 @@ __export(exports_datasets, {
22504
22873
  import { randomUUID as randomUUID15 } from "crypto";
22505
22874
  import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync10 } from "fs";
22506
22875
  import { join as join12 } from "path";
22507
- import { homedir as homedir12 } from "os";
22508
22876
  function saveDataset(data) {
22509
22877
  const db = getDatabase();
22510
22878
  const id = randomUUID15();
@@ -22542,7 +22910,7 @@ function exportDataset(name, format) {
22542
22910
  const dataset = getDatasetByName(name);
22543
22911
  if (!dataset)
22544
22912
  throw new Error(`Dataset '${name}' not found`);
22545
- const dir = join12(process.env["BROWSER_DATA_DIR"] ?? join12(homedir12(), ".browser"), "exports");
22913
+ const dir = join12(getDataDir(), "exports");
22546
22914
  mkdirSync10(dir, { recursive: true });
22547
22915
  const filename = `${name}.${format}`;
22548
22916
  const path = join12(dir, filename);
@@ -22572,6 +22940,7 @@ function exportDataset(name, format) {
22572
22940
  }
22573
22941
  var init_datasets = __esm(() => {
22574
22942
  init_schema();
22943
+ init_schema();
22575
22944
  });
22576
22945
 
22577
22946
  // src/mcp/scripts.ts
@@ -22713,7 +23082,7 @@ var init_scripts2 = __esm(() => {
22713
23082
  init_helpers();
22714
23083
  });
22715
23084
 
22716
- // ../open-mementos/dist/index.js
23085
+ // node_modules/@hasna/mementos/dist/index.js
22717
23086
  var exports_dist2 = {};
22718
23087
  __export(exports_dist2, {
22719
23088
  withMemoryLock: () => withMemoryLock,
@@ -22823,17 +23192,20 @@ __export(exports_dist2, {
22823
23192
  DEFAULT_CONFIG: () => DEFAULT_CONFIG
22824
23193
  });
22825
23194
  import { Database as Database2 } from "bun:sqlite";
22826
- import { existsSync as existsSync8, mkdirSync as mkdirSync11 } from "fs";
23195
+ import { existsSync as existsSync9, mkdirSync as mkdirSync11 } from "fs";
22827
23196
  import { dirname as dirname3, join as join13, resolve as resolve2 } from "path";
22828
- import { existsSync as existsSync23, mkdirSync as mkdirSync23, readFileSync as readFileSync6, readdirSync as readdirSync5, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3 } from "fs";
22829
- import { homedir as homedir13 } from "os";
23197
+ import { existsSync as existsSync23, mkdirSync as mkdirSync23, readFileSync as readFileSync6, readdirSync as readdirSync6, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3 } from "fs";
23198
+ import { homedir as homedir4 } from "os";
22830
23199
  import { basename as basename2, dirname as dirname22, join as join23, resolve as resolve22 } from "path";
22831
- import { existsSync as existsSync33, mkdirSync as mkdirSync32, readFileSync as readFileSync23, writeFileSync as writeFileSync23 } from "fs";
23200
+ import { existsSync as existsSync33, mkdirSync as mkdirSync33, readFileSync as readFileSync23, writeFileSync as writeFileSync23 } from "fs";
22832
23201
  import { homedir as homedir23 } from "os";
22833
23202
  import { join as join33 } from "path";
22834
- import { existsSync as existsSync42, mkdirSync as mkdirSync42, readFileSync as readFileSync33, writeFileSync as writeFileSync32 } from "fs";
22835
- import { homedir as homedir32 } from "os";
22836
- import { join as join42 } from "path";
23203
+ import { existsSync as existsSync43, mkdirSync as mkdirSync42, readFileSync as readFileSync33, writeFileSync as writeFileSync33 } from "fs";
23204
+ import { homedir as homedir33 } from "os";
23205
+ import { join as join43 } from "path";
23206
+ function __exportSetter2(name, newValue) {
23207
+ this[name] = __returnValue2.bind(null, newValue);
23208
+ }
22837
23209
  function isInMemoryDb(path) {
22838
23210
  return path === ":memory:" || path.startsWith("file::memory:");
22839
23211
  }
@@ -22841,7 +23213,7 @@ function findNearestMementosDb(startDir) {
22841
23213
  let dir = resolve2(startDir);
22842
23214
  while (true) {
22843
23215
  const candidate = join13(dir, ".mementos", "mementos.db");
22844
- if (existsSync8(candidate))
23216
+ if (existsSync9(candidate))
22845
23217
  return candidate;
22846
23218
  const parent = dirname3(dir);
22847
23219
  if (parent === dir)
@@ -22853,7 +23225,7 @@ function findNearestMementosDb(startDir) {
22853
23225
  function findGitRoot(startDir) {
22854
23226
  let dir = resolve2(startDir);
22855
23227
  while (true) {
22856
- if (existsSync8(join13(dir, ".git")))
23228
+ if (existsSync9(join13(dir, ".git")))
22857
23229
  return dir;
22858
23230
  const parent = dirname3(dir);
22859
23231
  if (parent === dir)
@@ -22883,7 +23255,7 @@ function ensureDir2(filePath) {
22883
23255
  if (isInMemoryDb(filePath))
22884
23256
  return;
22885
23257
  const dir = dirname3(resolve2(filePath));
22886
- if (!existsSync8(dir)) {
23258
+ if (!existsSync9(dir)) {
22887
23259
  mkdirSync11(dir, { recursive: true });
22888
23260
  }
22889
23261
  }
@@ -24734,7 +25106,7 @@ function isValidCategory(value) {
24734
25106
  return VALID_CATEGORIES.includes(value);
24735
25107
  }
24736
25108
  function loadConfig2() {
24737
- const configPath = join23(homedir13(), ".mementos", "config.json");
25109
+ const configPath = join23(homedir4(), ".mementos", "config.json");
24738
25110
  let fileConfig = {};
24739
25111
  if (existsSync23(configPath)) {
24740
25112
  try {
@@ -24761,10 +25133,10 @@ function loadConfig2() {
24761
25133
  return merged;
24762
25134
  }
24763
25135
  function profilesDir() {
24764
- return join23(homedir13(), ".mementos", "profiles");
25136
+ return join23(homedir4(), ".mementos", "profiles");
24765
25137
  }
24766
25138
  function globalConfigPath() {
24767
- return join23(homedir13(), ".mementos", "config.json");
25139
+ return join23(homedir4(), ".mementos", "config.json");
24768
25140
  }
24769
25141
  function readGlobalConfig() {
24770
25142
  const p = globalConfigPath();
@@ -24801,7 +25173,7 @@ function listProfiles2() {
24801
25173
  const dir = profilesDir();
24802
25174
  if (!existsSync23(dir))
24803
25175
  return [];
24804
- return readdirSync5(dir).filter((f) => f.endsWith(".db")).map((f) => basename2(f, ".db")).sort();
25176
+ return readdirSync6(dir).filter((f) => f.endsWith(".db")).map((f) => basename2(f, ".db")).sort();
24805
25177
  }
24806
25178
  function deleteProfile2(name) {
24807
25179
  const dbPath = join23(profilesDir(), `${name}.db`);
@@ -25163,7 +25535,7 @@ function runCleanup(config, db) {
25163
25535
  function getAgentSyncDir(agentName) {
25164
25536
  const dir = join33(homedir23(), ".mementos", "agents", agentName);
25165
25537
  if (!existsSync33(dir)) {
25166
- mkdirSync32(dir, { recursive: true });
25538
+ mkdirSync33(dir, { recursive: true });
25167
25539
  }
25168
25540
  return dir;
25169
25541
  }
@@ -25916,7 +26288,7 @@ ${matched.map((m) => `- ${m.key}: ${m.value.slice(0, 120)}${m.value.length > 120
25916
26288
  };
25917
26289
  }
25918
26290
  function readConfig() {
25919
- if (!existsSync42(CONFIG_PATH))
26291
+ if (!existsSync43(CONFIG_PATH))
25920
26292
  return {};
25921
26293
  try {
25922
26294
  const raw = readFileSync33(CONFIG_PATH, "utf-8");
@@ -25926,10 +26298,10 @@ function readConfig() {
25926
26298
  }
25927
26299
  }
25928
26300
  function writeConfig(config) {
25929
- if (!existsSync42(CONFIG_DIR)) {
26301
+ if (!existsSync43(CONFIG_DIR)) {
25930
26302
  mkdirSync42(CONFIG_DIR, { recursive: true });
25931
26303
  }
25932
- writeFileSync32(CONFIG_PATH, JSON.stringify(config, null, 2) + `
26304
+ writeFileSync33(CONFIG_PATH, JSON.stringify(config, null, 2) + `
25933
26305
  `, "utf-8");
25934
26306
  }
25935
26307
  function getActiveModel() {
@@ -25946,13 +26318,13 @@ function clearActiveModel() {
25946
26318
  delete config.activeModel;
25947
26319
  writeConfig(config);
25948
26320
  }
25949
- var __defProp2, __export2 = (target, all) => {
26321
+ var __defProp2, __returnValue2 = (v) => v, __export2 = (target, all) => {
25950
26322
  for (var name in all)
25951
26323
  __defProp2(target, name, {
25952
26324
  get: all[name],
25953
26325
  enumerable: true,
25954
26326
  configurable: true,
25955
- set: (newValue) => all[name] = () => newValue
26327
+ set: __exportSetter2.bind(all, name)
25956
26328
  });
25957
26329
  }, __esm2 = (fn, res) => () => (fn && (res = fn(fn = 0)), res), exports_database, MIGRATIONS, _db2 = null, init_database, AgentConflictError, EntityNotFoundError, MemoryNotFoundError, DuplicateMemoryError, MemoryExpiredError, InvalidScopeError, VersionConflictError, MemoryConflictError, OPENAI_EMBED_URL = "https://api.openai.com/v1/embeddings", EMBED_MODEL = "text-embedding-3-small", EMBED_DIMENSIONS = 1536, REDACTED = "[REDACTED]", SECRET_PATTERNS, _idCounter = 0, hookRegistry, INSTRUCTION_PATTERNS, PROMOTIONAL_PATTERNS, RECALL_PROMOTE_THRESHOLD = 3, CONFLICT_WINDOW_MS, MEMORY_WRITE_TTL = 30, MemoryLockConflictError, sessionFocus, STOP_WORDS, DEFAULT_CONFIG, VALID_SCOPES, VALID_CATEGORIES, defaultSyncAgents, DEFAULT_AUTO_MEMORY_CONFIG, MEMORY_EXTRACTION_SYSTEM_PROMPT = `You are a precise memory extraction engine for an AI agent.
25958
26330
  Given text, extract facts worth remembering as structured JSON.
@@ -27288,8 +27660,8 @@ Return only a number 0-10.`);
27288
27660
  keepLonger: true
27289
27661
  };
27290
27662
  _stats = { checked: 0, skipped: 0, updated: 0 };
27291
- CONFIG_DIR = join42(homedir32(), ".mementos");
27292
- CONFIG_PATH = join42(CONFIG_DIR, "config.json");
27663
+ CONFIG_DIR = join43(homedir33(), ".mementos");
27664
+ CONFIG_PATH = join43(CONFIG_DIR, "config.json");
27293
27665
  });
27294
27666
 
27295
27667
  // src/lib/page-memory.ts
@@ -27367,7 +27739,7 @@ var init_page_memory = __esm(() => {
27367
27739
  inMemoryCache = new Map;
27368
27740
  });
27369
27741
 
27370
- // ../open-conversations/dist/index.js
27742
+ // node_modules/@hasna/conversations/dist/index.js
27371
27743
  var exports_dist3 = {};
27372
27744
  __export(exports_dist3, {
27373
27745
  useSpaceMessages: () => useSpaceMessages,
@@ -27376,6 +27748,7 @@ __export(exports_dist3, {
27376
27748
  updateProject: () => updateProject,
27377
27749
  unpinMessage: () => unpinMessage,
27378
27750
  unarchiveSpace: () => unarchiveSpace,
27751
+ tryBulkAcquireLock: () => tryBulkAcquireLock,
27379
27752
  startPolling: () => startPolling,
27380
27753
  sendMessage: () => sendMessage,
27381
27754
  searchMessages: () => searchMessages,
@@ -27384,6 +27757,7 @@ __export(exports_dist3, {
27384
27757
  renameAgent: () => renameAgent,
27385
27758
  removeReaction: () => removeReaction,
27386
27759
  removePresence: () => removePresence,
27760
+ releaseStaleAgentLocks: () => releaseStaleAgentLocks,
27387
27761
  releaseLock: () => releaseLock2,
27388
27762
  registerAgent: () => registerAgent4,
27389
27763
  readMessages: () => readMessages,
@@ -27395,6 +27769,7 @@ __export(exports_dist3, {
27395
27769
  listSpaces: () => listSpaces,
27396
27770
  listSessions: () => listSessions3,
27397
27771
  listProjects: () => listProjects3,
27772
+ listLocksEnriched: () => listLocksEnriched,
27398
27773
  listLocks: () => listLocks,
27399
27774
  listHotSessions: () => listHotSessions,
27400
27775
  listAgents: () => listAgents3,
@@ -27446,22 +27821,28 @@ __export(exports_dist3, {
27446
27821
  import { Database as Database3 } from "bun:sqlite";
27447
27822
  import { mkdirSync as mkdirSync12 } from "fs";
27448
27823
  import { join as join14, dirname as dirname5 } from "path";
27449
- import { homedir as homedir14 } from "os";
27824
+ import { homedir as homedir5 } from "os";
27450
27825
  import { randomUUID as randomUUID16 } from "crypto";
27451
- import { mkdirSync as mkdirSync24, copyFileSync as copyFileSync3, statSync as statSync3 } from "fs";
27826
+ import { mkdirSync as mkdirSync24, copyFileSync as copyFileSync4, statSync as statSync4 } from "fs";
27452
27827
  import { join as join34 } from "path";
27453
- import { homedir as homedir33 } from "os";
27828
+ import { homedir as homedir34 } from "os";
27454
27829
  import { readFileSync as readFileSync7 } from "fs";
27455
27830
  import { join as join24 } from "path";
27456
27831
  import { homedir as homedir24 } from "os";
27457
27832
  import { randomUUID as randomUUID22 } from "crypto";
27458
- import { readFileSync as readFileSync24, writeFileSync as writeFileSync6, mkdirSync as mkdirSync33 } from "fs";
27459
- import { join as join43, dirname as dirname23 } from "path";
27833
+ import { readFileSync as readFileSync24, writeFileSync as writeFileSync6, mkdirSync as mkdirSync34 } from "fs";
27834
+ import { join as join44, dirname as dirname23 } from "path";
27460
27835
  import { homedir as homedir42 } from "os";
27836
+ function __accessProp2(key) {
27837
+ return this[key];
27838
+ }
27839
+ function __exportSetter3(name, newValue) {
27840
+ this[name] = __returnValue3.bind(null, newValue);
27841
+ }
27461
27842
  function getDbPath2() {
27462
27843
  if (process.env.CONVERSATIONS_DB_PATH)
27463
27844
  return process.env.CONVERSATIONS_DB_PATH;
27464
- return join14(homedir14(), ".conversations", "messages.db");
27845
+ return join14(homedir5(), ".conversations", "messages.db");
27465
27846
  }
27466
27847
  function getDb() {
27467
27848
  if (db)
@@ -27613,6 +27994,9 @@ function getDb() {
27613
27994
  if (!spaceColNames.includes("archived_at")) {
27614
27995
  db.exec("ALTER TABLE spaces ADD COLUMN archived_at TEXT");
27615
27996
  }
27997
+ if (!spaceColNames.includes("topic")) {
27998
+ db.exec("ALTER TABLE spaces ADD COLUMN topic TEXT");
27999
+ }
27616
28000
  const msgCols2 = db.prepare("PRAGMA table_info(messages)").all();
27617
28001
  const colNames2 = msgCols2.map((c) => c.name);
27618
28002
  if (!colNames2.includes("edited_at")) {
@@ -27660,6 +28044,30 @@ function getDb() {
27660
28044
  if (!presenceColNames.includes("project_id")) {
27661
28045
  db.exec("ALTER TABLE agent_presence ADD COLUMN project_id TEXT");
27662
28046
  }
28047
+ db.exec(`
28048
+ CREATE TABLE IF NOT EXISTS message_read_receipts (
28049
+ message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
28050
+ agent TEXT NOT NULL,
28051
+ read_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
28052
+ PRIMARY KEY (message_id, agent)
28053
+ )
28054
+ `);
28055
+ db.exec("CREATE INDEX IF NOT EXISTS idx_read_receipts_message ON message_read_receipts(message_id)");
28056
+ db.exec("CREATE INDEX IF NOT EXISTS idx_read_receipts_agent ON message_read_receipts(agent)");
28057
+ db.exec(`
28058
+ CREATE TABLE IF NOT EXISTS message_mentions (
28059
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
28060
+ message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
28061
+ mentioned_agent TEXT NOT NULL,
28062
+ from_agent TEXT NOT NULL,
28063
+ space TEXT,
28064
+ notified_at TEXT,
28065
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now'))
28066
+ )
28067
+ `);
28068
+ db.exec("CREATE INDEX IF NOT EXISTS idx_mentions_agent ON message_mentions(mentioned_agent)");
28069
+ db.exec("CREATE INDEX IF NOT EXISTS idx_mentions_message ON message_mentions(message_id)");
28070
+ db.exec("CREATE INDEX IF NOT EXISTS idx_mentions_notified ON message_mentions(notified_at)");
27663
28071
  const ftsExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='messages_fts'").get();
27664
28072
  if (!ftsExists) {
27665
28073
  db.exec(`
@@ -27795,7 +28203,7 @@ function parseMessage(row) {
27795
28203
  function getAttachmentsDir() {
27796
28204
  if (process.env.CONVERSATIONS_ATTACHMENTS_DIR)
27797
28205
  return process.env.CONVERSATIONS_ATTACHMENTS_DIR;
27798
- return join34(homedir33(), ".conversations", "attachments");
28206
+ return join34(homedir34(), ".conversations", "attachments");
27799
28207
  }
27800
28208
  function guessMimeType(name) {
27801
28209
  const ext = name.split(".").pop()?.toLowerCase();
@@ -27845,8 +28253,8 @@ function sendMessage(opts) {
27845
28253
  const attachmentInfos = [];
27846
28254
  for (const att of opts.attachments) {
27847
28255
  const destPath = join34(attachmentsDir, att.name);
27848
- copyFileSync3(att.source_path, destPath);
27849
- const stat = statSync3(destPath);
28256
+ copyFileSync4(att.source_path, destPath);
28257
+ const stat = statSync4(destPath);
27850
28258
  attachmentInfos.push({
27851
28259
  name: att.name,
27852
28260
  path: destPath,
@@ -27858,6 +28266,12 @@ function sendMessage(opts) {
27858
28266
  db2.prepare("UPDATE messages SET attachments = ? WHERE id = ?").run(attachmentsJson, message.id);
27859
28267
  message.attachments = attachmentInfos;
27860
28268
  }
28269
+ if (opts.space) {
28270
+ const mentions = parseMentions(opts.content);
28271
+ if (mentions.length > 0) {
28272
+ processMentions(message.id, opts.from, opts.space, mentions, db2);
28273
+ }
28274
+ }
27861
28275
  fireWebhooks(message);
27862
28276
  return message;
27863
28277
  }
@@ -27896,11 +28310,34 @@ function readMessages(opts = {}) {
27896
28310
  if (opts.unread_only) {
27897
28311
  conditions.push("read_at IS NULL");
27898
28312
  }
28313
+ if (opts.threads_only) {
28314
+ conditions.push("reply_to IS NULL");
28315
+ }
28316
+ if (opts.mentions_only) {
28317
+ conditions.push(`id IN (SELECT message_id FROM message_mentions WHERE mentioned_agent = ?)`);
28318
+ params.push(opts.mentions_only.toLowerCase());
28319
+ }
28320
+ const isLatest = opts.latest && opts.latest > 0;
27899
28321
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
27900
- const resolvedLimit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
27901
- const order = opts.order?.toLowerCase() === "desc" ? "DESC" : "ASC";
27902
- const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at ${order}, id ${order} LIMIT ${resolvedLimit}`).all(...params);
27903
- const messages = rows.map(parseMessage);
28322
+ const resolvedLimit = isLatest ? Math.floor(opts.latest) : Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
28323
+ const order = isLatest ? "DESC" : opts.order?.toLowerCase() === "desc" ? "DESC" : "ASC";
28324
+ const resolvedOffset = opts.offset && opts.offset > 0 ? Math.floor(opts.offset) : 0;
28325
+ const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at ${order}, id ${order} LIMIT ${resolvedLimit} OFFSET ${resolvedOffset}`).all(...params);
28326
+ let messages = rows.map(parseMessage);
28327
+ if (opts.include_reply_counts && messages.length > 0) {
28328
+ const db22 = getDb();
28329
+ const counts = db22.prepare(`SELECT reply_to, COUNT(*) as c FROM messages WHERE reply_to IN (${messages.map(() => "?").join(",")}) GROUP BY reply_to`).all(...messages.map((m) => m.id));
28330
+ const countMap = new Map(counts.map((r) => [r.reply_to, r.c]));
28331
+ messages = messages.map((m) => ({ ...m, reply_count: countMap.get(m.id) ?? 0 }));
28332
+ }
28333
+ if (opts.max_content_length && opts.max_content_length > 0) {
28334
+ messages = messages.map((m) => {
28335
+ if (m.content.length > opts.max_content_length) {
28336
+ return { ...m, content: m.content.slice(0, opts.max_content_length) + "\u2026", truncated: true };
28337
+ }
28338
+ return m;
28339
+ });
28340
+ }
27904
28341
  if (opts.compact)
27905
28342
  return messages.map(compactMessage);
27906
28343
  return messages;
@@ -28081,6 +28518,14 @@ function searchMessages(opts) {
28081
28518
  extraWhere += " AND m.to_agent = ?";
28082
28519
  ftsParams.push(opts.to);
28083
28520
  }
28521
+ if (opts.since) {
28522
+ extraWhere += " AND m.created_at >= ?";
28523
+ ftsParams.push(opts.since);
28524
+ }
28525
+ if (opts.until) {
28526
+ extraWhere += " AND m.created_at <= ?";
28527
+ ftsParams.push(opts.until);
28528
+ }
28084
28529
  const orderClause = sortByRelevance ? "ORDER BY rank" : "ORDER BY m.created_at DESC, m.id DESC";
28085
28530
  const rows2 = db2.prepare(`SELECT m.*, rank,
28086
28531
  snippet(messages_fts, 0, '**', '**', '...', 20) as snippet
@@ -28113,6 +28558,14 @@ function searchMessages(opts) {
28113
28558
  conditions.push("to_agent = ?");
28114
28559
  params.push(opts.to);
28115
28560
  }
28561
+ if (opts.since) {
28562
+ conditions.push("created_at >= ?");
28563
+ params.push(opts.since);
28564
+ }
28565
+ if (opts.until) {
28566
+ conditions.push("created_at <= ?");
28567
+ params.push(opts.until);
28568
+ }
28116
28569
  const where = `WHERE ${conditions.join(" AND ")}`;
28117
28570
  const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at DESC, id DESC LIMIT ${limit}`).all(...params);
28118
28571
  return rows.map((row) => {
@@ -28120,6 +28573,26 @@ function searchMessages(opts) {
28120
28573
  return { ...msg, snippet: null, relevance_score: 0 };
28121
28574
  });
28122
28575
  }
28576
+ function parseMentions(content) {
28577
+ const matches = content.match(/@([a-zA-Z0-9_-]+)/g) ?? [];
28578
+ return [...new Set(matches.map((m) => m.slice(1).toLowerCase()))];
28579
+ }
28580
+ async function processMentions(messageId, fromAgent, space, mentionedAgents, db2) {
28581
+ const stmt = db2.prepare("INSERT INTO message_mentions (message_id, mentioned_agent, from_agent, space) VALUES (?, ?, ?, ?)");
28582
+ for (const agent of mentionedAgents) {
28583
+ try {
28584
+ stmt.run(messageId, agent, fromAgent, space);
28585
+ if (agent !== fromAgent.toLowerCase()) {
28586
+ sendMessage({
28587
+ from: fromAgent,
28588
+ to: agent,
28589
+ content: `You were mentioned in #${space} by ${fromAgent} (message #${messageId})`,
28590
+ metadata: { type: "mention_notification", source_message_id: messageId, space }
28591
+ });
28592
+ }
28593
+ } catch {}
28594
+ }
28595
+ }
28123
28596
  function listSessions3(agent) {
28124
28597
  const db2 = getDb();
28125
28598
  const agentFilter = agent ? "WHERE from_agent = ? OR to_agent = ?" : "";
@@ -28654,7 +29127,7 @@ function getAutoName() {
28654
29127
  }
28655
29128
  cachedAutoName = name;
28656
29129
  try {
28657
- mkdirSync33(dirname23(AGENT_ID_FILE), { recursive: true });
29130
+ mkdirSync34(dirname23(AGENT_ID_FILE), { recursive: true });
28658
29131
  writeFileSync6(AGENT_ID_FILE, name + `
28659
29132
  `, "utf-8");
28660
29133
  } catch {}
@@ -28846,6 +29319,7 @@ function acquireLock2(resourceType, resourceId, agentId, lockType = "advisory",
28846
29319
  const db2 = getDb();
28847
29320
  return db2.transaction(() => {
28848
29321
  cleanExpiredLocks2();
29322
+ releaseStaleAgentLocks();
28849
29323
  const existing = db2.prepare(`
28850
29324
  SELECT * FROM resource_locks
28851
29325
  WHERE resource_type = ? AND resource_id = ? AND lock_type = ?
@@ -28872,6 +29346,55 @@ function acquireLock2(resourceType, resourceId, agentId, lockType = "advisory",
28872
29346
  return { acquired: true, lock };
28873
29347
  }).immediate();
28874
29348
  }
29349
+ function bulkAcquireLock(resources, agentId) {
29350
+ const db2 = getDb();
29351
+ return db2.transaction(() => {
29352
+ cleanExpiredLocks2();
29353
+ releaseStaleAgentLocks();
29354
+ const acquired = [];
29355
+ for (const { resource_type, resource_id, lock_type = "advisory", expiry_ms = DEFAULT_LOCK_EXPIRY_MS } of resources) {
29356
+ const existing = db2.prepare(`
29357
+ SELECT * FROM resource_locks
29358
+ WHERE resource_type = ? AND resource_id = ? AND lock_type = ?
29359
+ `).get(resource_type, resource_id, lock_type);
29360
+ if (existing && existing.agent_id !== agentId) {
29361
+ throw { _bulkConflict: true, resource_type, resource_id, held_by: existing.agent_id };
29362
+ }
29363
+ const expiresAt = new Date(Date.now() + expiry_ms).toISOString().slice(0, -1);
29364
+ if (existing) {
29365
+ db2.prepare(`
29366
+ UPDATE resource_locks SET expires_at = ?, locked_at = strftime('%Y-%m-%dT%H:%M:%f', 'now')
29367
+ WHERE resource_type = ? AND resource_id = ? AND lock_type = ?
29368
+ `).run(expiresAt, resource_type, resource_id, lock_type);
29369
+ } else {
29370
+ db2.prepare(`
29371
+ INSERT INTO resource_locks (resource_type, resource_id, agent_id, lock_type, locked_at, expires_at)
29372
+ VALUES (?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%f', 'now'), ?)
29373
+ `).run(resource_type, resource_id, agentId, lock_type, expiresAt);
29374
+ }
29375
+ const lock = db2.prepare(`
29376
+ SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND lock_type = ?
29377
+ `).get(resource_type, resource_id, lock_type);
29378
+ acquired.push(lock);
29379
+ }
29380
+ return { acquired: true, locks: acquired };
29381
+ }).immediate();
29382
+ }
29383
+ function tryBulkAcquireLock(resources, agentId) {
29384
+ try {
29385
+ return bulkAcquireLock(resources, agentId);
29386
+ } catch (err2) {
29387
+ const e = err2;
29388
+ if (e?._bulkConflict) {
29389
+ return {
29390
+ acquired: false,
29391
+ locks: [],
29392
+ blocked_by: { resource_type: e.resource_type, resource_id: e.resource_id, held_by: e.held_by }
29393
+ };
29394
+ }
29395
+ throw err2;
29396
+ }
29397
+ }
28875
29398
  function releaseLock2(resourceType, resourceId, agentId) {
28876
29399
  const db2 = getDb();
28877
29400
  const result = db2.prepare(`
@@ -28883,6 +29406,7 @@ function releaseLock2(resourceType, resourceId, agentId) {
28883
29406
  function checkLock2(resourceType, resourceId) {
28884
29407
  const db2 = getDb();
28885
29408
  cleanExpiredLocks2();
29409
+ releaseStaleAgentLocks();
28886
29410
  return db2.prepare(`
28887
29411
  SELECT * FROM resource_locks
28888
29412
  WHERE resource_type = ? AND resource_id = ?
@@ -28890,6 +29414,17 @@ function checkLock2(resourceType, resourceId) {
28890
29414
  LIMIT 1
28891
29415
  `).get(resourceType, resourceId);
28892
29416
  }
29417
+ function releaseStaleAgentLocks() {
29418
+ const db2 = getDb();
29419
+ const result = db2.prepare(`
29420
+ DELETE FROM resource_locks
29421
+ WHERE LOWER(agent_id) IN (
29422
+ SELECT LOWER(agent) FROM agent_presence
29423
+ WHERE last_seen_at < strftime('%Y-%m-%dT%H:%M:%f', 'now', '-${STALE_HEARTBEAT_SECONDS} seconds')
29424
+ )
29425
+ `).run();
29426
+ return result.changes;
29427
+ }
28893
29428
  function cleanExpiredLocks2() {
28894
29429
  const db2 = getDb();
28895
29430
  const result = db2.prepare(`
@@ -28900,6 +29435,7 @@ function cleanExpiredLocks2() {
28900
29435
  function listLocks(opts) {
28901
29436
  const db2 = getDb();
28902
29437
  cleanExpiredLocks2();
29438
+ releaseStaleAgentLocks();
28903
29439
  let query = "SELECT * FROM resource_locks WHERE 1=1";
28904
29440
  const params = [];
28905
29441
  if (opts?.resource_type) {
@@ -28913,6 +29449,31 @@ function listLocks(opts) {
28913
29449
  query += " ORDER BY locked_at ASC";
28914
29450
  return db2.prepare(query).all(...params);
28915
29451
  }
29452
+ function listLocksEnriched(opts) {
29453
+ const locks = listLocks(opts);
29454
+ const db2 = getDb();
29455
+ const nowMs = Date.now();
29456
+ return locks.map((lock) => {
29457
+ const lockedMs = new Date(lock.locked_at + "Z").getTime();
29458
+ const expiresMs = new Date(lock.expires_at + "Z").getTime();
29459
+ const presenceRow = db2.prepare(`
29460
+ SELECT role, status, last_seen_at, project_id FROM agent_presence WHERE LOWER(agent) = LOWER(?)
29461
+ `).get(lock.agent_id);
29462
+ const agent = presenceRow ? {
29463
+ role: presenceRow.role ?? null,
29464
+ status: presenceRow.status ?? null,
29465
+ online: presenceRow.last_seen_at ? nowMs - new Date(presenceRow.last_seen_at + "Z").getTime() < 60000 : false,
29466
+ last_seen_at: presenceRow.last_seen_at ?? null,
29467
+ project_id: presenceRow.project_id ?? null
29468
+ } : null;
29469
+ return {
29470
+ ...lock,
29471
+ locked_seconds_ago: Math.round((nowMs - lockedMs) / 1000),
29472
+ expires_in_seconds: Math.round((expiresMs - nowMs) / 1000),
29473
+ agent
29474
+ };
29475
+ });
29476
+ }
28916
29477
  function computeHotness(sessionId) {
28917
29478
  const db2 = getDb();
28918
29479
  const base = db2.prepare(`
@@ -29261,37 +29822,49 @@ function getGraphStats() {
29261
29822
  map[r.relation] = r.c;
29262
29823
  return { total_edges: total, by_relation: map };
29263
29824
  }
29264
- var __create2, __getProtoOf2, __defProp3, __getOwnPropNames2, __getOwnPropDesc2, __hasOwnProp2, __toESM2 = (mod, isNodeMode, target) => {
29825
+ var __create2, __getProtoOf2, __defProp3, __getOwnPropNames2, __getOwnPropDesc2, __hasOwnProp2, __toESMCache_node2, __toESMCache_esm2, __toESM2 = (mod, isNodeMode, target) => {
29826
+ var canCache = mod != null && typeof mod === "object";
29827
+ if (canCache) {
29828
+ var cache = isNodeMode ? __toESMCache_node2 ??= new WeakMap : __toESMCache_esm2 ??= new WeakMap;
29829
+ var cached = cache.get(mod);
29830
+ if (cached)
29831
+ return cached;
29832
+ }
29265
29833
  target = mod != null ? __create2(__getProtoOf2(mod)) : {};
29266
29834
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp3(target, "default", { value: mod, enumerable: true }) : target;
29267
29835
  for (let key of __getOwnPropNames2(mod))
29268
29836
  if (!__hasOwnProp2.call(to, key))
29269
29837
  __defProp3(to, key, {
29270
- get: () => mod[key],
29838
+ get: __accessProp2.bind(mod, key),
29271
29839
  enumerable: true
29272
29840
  });
29841
+ if (canCache)
29842
+ cache.set(mod, to);
29273
29843
  return to;
29274
- }, __moduleCache2, __toCommonJS2 = (from) => {
29275
- var entry = __moduleCache2.get(from), desc;
29844
+ }, __toCommonJS2 = (from) => {
29845
+ var entry = (__moduleCache2 ??= new WeakMap).get(from), desc;
29276
29846
  if (entry)
29277
29847
  return entry;
29278
29848
  entry = __defProp3({}, "__esModule", { value: true });
29279
- if (from && typeof from === "object" || typeof from === "function")
29280
- __getOwnPropNames2(from).map((key) => !__hasOwnProp2.call(entry, key) && __defProp3(entry, key, {
29281
- get: () => from[key],
29282
- enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable
29283
- }));
29849
+ if (from && typeof from === "object" || typeof from === "function") {
29850
+ for (var key of __getOwnPropNames2(from))
29851
+ if (!__hasOwnProp2.call(entry, key))
29852
+ __defProp3(entry, key, {
29853
+ get: __accessProp2.bind(from, key),
29854
+ enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable
29855
+ });
29856
+ }
29284
29857
  __moduleCache2.set(from, entry);
29285
29858
  return entry;
29286
- }, __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports), __export3 = (target, all) => {
29859
+ }, __moduleCache2, __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports), __returnValue3 = (v) => v, __export3 = (target, all) => {
29287
29860
  for (var name in all)
29288
29861
  __defProp3(target, name, {
29289
29862
  get: all[name],
29290
29863
  enumerable: true,
29291
29864
  configurable: true,
29292
- set: (newValue) => all[name] = () => newValue
29865
+ set: __exportSetter3.bind(all, name)
29293
29866
  });
29294
- }, exports_db, db = null, init_db = () => {}, require_react_development, require_react, cachedConfig = null, configLoadedAt = 0, CONFIG_CACHE_MS = 1e4, import_react, AGENT_NAMES, AGENT_ID_FILE, cachedAutoName = null, ONLINE_THRESHOLD_SECONDS = 60, CONFLICT_THRESHOLD_SECONDS, DEFAULT_LOCK_EXPIRY_MS, STOPWORDS;
29867
+ }, exports_db, db = null, init_db = () => {}, require_react_development, require_react, cachedConfig = null, configLoadedAt = 0, CONFIG_CACHE_MS = 1e4, import_react, AGENT_NAMES, AGENT_ID_FILE, cachedAutoName = null, ONLINE_THRESHOLD_SECONDS = 60, CONFLICT_THRESHOLD_SECONDS, DEFAULT_LOCK_EXPIRY_MS, STALE_HEARTBEAT_SECONDS, STOPWORDS;
29295
29868
  var init_dist3 = __esm(() => {
29296
29869
  __create2 = Object.create;
29297
29870
  __getProtoOf2 = Object.getPrototypeOf;
@@ -29299,7 +29872,6 @@ var init_dist3 = __esm(() => {
29299
29872
  __getOwnPropNames2 = Object.getOwnPropertyNames;
29300
29873
  __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
29301
29874
  __hasOwnProp2 = Object.prototype.hasOwnProperty;
29302
- __moduleCache2 = /* @__PURE__ */ new WeakMap;
29303
29875
  exports_db = {};
29304
29876
  __export3(exports_db, {
29305
29877
  getDbPath: () => getDbPath2,
@@ -31466,12 +32038,13 @@ Check the top-level render call using <` + parentName + ">.";
31466
32038
  "zinc-eagle",
31467
32039
  "zone-fox"
31468
32040
  ];
31469
- AGENT_ID_FILE = join43(homedir42(), ".conversations", "agent-id");
32041
+ AGENT_ID_FILE = join44(homedir42(), ".conversations", "agent-id");
31470
32042
  init_db();
31471
32043
  init_db();
31472
32044
  CONFLICT_THRESHOLD_SECONDS = 30 * 60;
31473
32045
  init_db();
31474
32046
  DEFAULT_LOCK_EXPIRY_MS = 5 * 60 * 1000;
32047
+ STALE_HEARTBEAT_SECONDS = 30 * 60;
31475
32048
  init_db();
31476
32049
  init_db();
31477
32050
  STOPWORDS = new Set([
@@ -31668,7 +32241,7 @@ var init_coordination = __esm(() => {
31668
32241
  activeNavigations = new Map;
31669
32242
  });
31670
32243
 
31671
- // ../open-todos/dist/index.js
32244
+ // node_modules/@hasna/todos/dist/index.js
31672
32245
  var exports_dist4 = {};
31673
32246
  __export(exports_dist4, {
31674
32247
  uuid: () => uuid2,
@@ -31695,6 +32268,7 @@ __export(exports_dist4, {
31695
32268
  setTaskStatus: () => setTaskStatus,
31696
32269
  setTaskPriority: () => setTaskPriority,
31697
32270
  setBudget: () => setBudget,
32271
+ setActiveModel: () => setActiveModel2,
31698
32272
  searchTasks: () => searchTasks,
31699
32273
  scoreTask: () => scoreTask,
31700
32274
  saveSnapshot: () => saveSnapshot,
@@ -31794,6 +32368,8 @@ __export(exports_dist4, {
31794
32368
  getAgentByName: () => getAgentByName2,
31795
32369
  getAgent: () => getAgent3,
31796
32370
  getActiveWork: () => getActiveWork,
32371
+ getActiveModel: () => getActiveModel2,
32372
+ gatherTrainingData: () => gatherTrainingData2,
31797
32373
  findTasksByFile: () => findTasksByFile,
31798
32374
  findRelatedTaskIds: () => findRelatedTaskIds,
31799
32375
  findPath: () => findPath2,
@@ -31831,6 +32407,7 @@ __export(exports_dist4, {
31831
32407
  closeDatabase: () => closeDatabase2,
31832
32408
  cloneTask: () => cloneTask,
31833
32409
  clearChecklist: () => clearChecklist,
32410
+ clearActiveModel: () => clearActiveModel2,
31834
32411
  cleanExpiredLocks: () => cleanExpiredLocks3,
31835
32412
  claimOrSteal: () => claimOrSteal,
31836
32413
  claimNextTask: () => claimNextTask,
@@ -31865,22 +32442,26 @@ __export(exports_dist4, {
31865
32442
  LockError: () => LockError,
31866
32443
  EXTRACT_TAGS: () => EXTRACT_TAGS,
31867
32444
  DependencyCycleError: () => DependencyCycleError,
32445
+ DEFAULT_MODEL: () => DEFAULT_MODEL3,
31868
32446
  CompletionGuardError: () => CompletionGuardError,
31869
32447
  AgentNotFoundError: () => AgentNotFoundError2
31870
32448
  });
31871
32449
  import { Database as Database4 } from "bun:sqlite";
31872
- import { existsSync as existsSync9, mkdirSync as mkdirSync13 } from "fs";
32450
+ import { existsSync as existsSync10, mkdirSync as mkdirSync13 } from "fs";
31873
32451
  import { dirname as dirname6, join as join15, resolve as resolve3 } from "path";
31874
32452
  import { existsSync as existsSync34 } from "fs";
31875
32453
  import { join as join35 } from "path";
31876
- import { existsSync as existsSync24, mkdirSync as mkdirSync25, readFileSync as readFileSync8, readdirSync as readdirSync6, statSync as statSync4, writeFileSync as writeFileSync7 } from "fs";
32454
+ import { existsSync as existsSync24, mkdirSync as mkdirSync25, readFileSync as readFileSync8, readdirSync as readdirSync7, statSync as statSync5, writeFileSync as writeFileSync7 } from "fs";
31877
32455
  import { join as join25 } from "path";
31878
- import { existsSync as existsSync43, readFileSync as readFileSync25, readdirSync as readdirSync23, writeFileSync as writeFileSync24 } from "fs";
31879
- import { join as join44 } from "path";
31880
- import { existsSync as existsSync52 } from "fs";
31881
- import { join as join52 } from "path";
31882
- import { readFileSync as readFileSync34, statSync as statSync22 } from "fs";
31883
- import { relative, resolve as resolve23, join as join62 } from "path";
32456
+ import { existsSync as existsSync44, mkdirSync as mkdirSync35, readFileSync as readFileSync25, writeFileSync as writeFileSync24 } from "fs";
32457
+ import { homedir as homedir6 } from "os";
32458
+ import { join as join45 } from "path";
32459
+ import { existsSync as existsSync53, readFileSync as readFileSync34, readdirSync as readdirSync23, writeFileSync as writeFileSync34 } from "fs";
32460
+ import { join as join53 } from "path";
32461
+ import { existsSync as existsSync62 } from "fs";
32462
+ import { join as join62 } from "path";
32463
+ import { readFileSync as readFileSync43, statSync as statSync22 } from "fs";
32464
+ import { relative, resolve as resolve23, join as join72 } from "path";
31884
32465
  import { execSync as execSync2 } from "child_process";
31885
32466
 
31886
32467
  class TodosClient {
@@ -32104,7 +32685,7 @@ function findNearestTodosDb(startDir) {
32104
32685
  let dir = resolve3(startDir);
32105
32686
  while (true) {
32106
32687
  const candidate = join15(dir, ".todos", "todos.db");
32107
- if (existsSync9(candidate))
32688
+ if (existsSync10(candidate))
32108
32689
  return candidate;
32109
32690
  const parent = dirname6(dir);
32110
32691
  if (parent === dir)
@@ -32116,7 +32697,7 @@ function findNearestTodosDb(startDir) {
32116
32697
  function findGitRoot2(startDir) {
32117
32698
  let dir = resolve3(startDir);
32118
32699
  while (true) {
32119
- if (existsSync9(join15(dir, ".git")))
32700
+ if (existsSync10(join15(dir, ".git")))
32120
32701
  return dir;
32121
32702
  const parent = dirname6(dir);
32122
32703
  if (parent === dir)
@@ -32146,7 +32727,7 @@ function ensureDir3(filePath) {
32146
32727
  if (isInMemoryDb2(filePath))
32147
32728
  return;
32148
32729
  const dir = dirname6(resolve3(filePath));
32149
- if (!existsSync9(dir)) {
32730
+ if (!existsSync10(dir)) {
32150
32731
  mkdirSync13(dir, { recursive: true });
32151
32732
  }
32152
32733
  }
@@ -32613,7 +33194,7 @@ function ensureDir23(dir) {
32613
33194
  function listJsonFiles(dir) {
32614
33195
  if (!existsSync24(dir))
32615
33196
  return [];
32616
- return readdirSync6(dir).filter((f) => f.endsWith(".json"));
33197
+ return readdirSync7(dir).filter((f) => f.endsWith(".json"));
32617
33198
  }
32618
33199
  function readJsonFile(path) {
32619
33200
  try {
@@ -32638,7 +33219,7 @@ function writeHighWaterMark(dir, value) {
32638
33219
  }
32639
33220
  function getFileMtimeMs(path) {
32640
33221
  try {
32641
- return statSync4(path).mtimeMs;
33222
+ return statSync5(path).mtimeMs;
32642
33223
  } catch {
32643
33224
  return null;
32644
33225
  }
@@ -34611,6 +35192,91 @@ function deleteSession2(id, db2) {
34611
35192
  const result = d.run("DELETE FROM sessions WHERE id = ?", [id]);
34612
35193
  return result.changes > 0;
34613
35194
  }
35195
+ function taskToCreateExample(task) {
35196
+ const userMsg = `Create a task: ${task.title}${task.description ? `
35197
+
35198
+ Description: ${task.description}` : ""}`;
35199
+ const taskDetails = {
35200
+ id: task.short_id ?? task.id,
35201
+ title: task.title,
35202
+ description: task.description ?? "",
35203
+ status: task.status,
35204
+ priority: task.priority,
35205
+ tags: task.tags,
35206
+ created_at: task.created_at
35207
+ };
35208
+ return {
35209
+ messages: [
35210
+ { role: "system", content: SYSTEM_PROMPT2 },
35211
+ { role: "user", content: userMsg },
35212
+ {
35213
+ role: "assistant",
35214
+ content: `Created task: ${JSON.stringify(taskDetails, null, 2)}`
35215
+ }
35216
+ ]
35217
+ };
35218
+ }
35219
+ function taskToStatusUpdateExample(task) {
35220
+ if (!task.completed_at && task.status === "pending")
35221
+ return null;
35222
+ const id = task.short_id ?? task.id;
35223
+ return {
35224
+ messages: [
35225
+ { role: "system", content: SYSTEM_PROMPT2 },
35226
+ { role: "user", content: `Mark task ${id} as ${task.status}` },
35227
+ {
35228
+ role: "assistant",
35229
+ content: `Task ${id} has been updated to status: ${task.status}. ${task.completed_at ? `Completed at: ${task.completed_at}` : ""}`.trim()
35230
+ }
35231
+ ]
35232
+ };
35233
+ }
35234
+ function taskToSearchExample(tasks, query) {
35235
+ const matched = tasks.filter((t) => t.title.toLowerCase().includes(query.toLowerCase())).slice(0, 5);
35236
+ return {
35237
+ messages: [
35238
+ { role: "system", content: SYSTEM_PROMPT2 },
35239
+ { role: "user", content: `Search tasks for: "${query}"` },
35240
+ {
35241
+ role: "assistant",
35242
+ content: matched.length > 0 ? `Found ${matched.length} task(s):
35243
+ ${matched.map((t) => `- [${t.short_id ?? t.id}] ${t.title} (${t.status})`).join(`
35244
+ `)}` : `No tasks found matching "${query}".`
35245
+ }
35246
+ ]
35247
+ };
35248
+ }
35249
+ function readConfig2() {
35250
+ if (!existsSync44(CONFIG_PATH2))
35251
+ return {};
35252
+ try {
35253
+ const raw = readFileSync25(CONFIG_PATH2, "utf-8");
35254
+ return JSON.parse(raw);
35255
+ } catch {
35256
+ return {};
35257
+ }
35258
+ }
35259
+ function writeConfig2(config) {
35260
+ if (!existsSync44(CONFIG_DIR2)) {
35261
+ mkdirSync35(CONFIG_DIR2, { recursive: true });
35262
+ }
35263
+ writeFileSync24(CONFIG_PATH2, JSON.stringify(config, null, 2) + `
35264
+ `, "utf-8");
35265
+ }
35266
+ function getActiveModel2() {
35267
+ const config = readConfig2();
35268
+ return config.activeModel ?? DEFAULT_MODEL3;
35269
+ }
35270
+ function setActiveModel2(modelId) {
35271
+ const config = readConfig2();
35272
+ config.activeModel = modelId;
35273
+ writeConfig2(config);
35274
+ }
35275
+ function clearActiveModel2() {
35276
+ const config = readConfig2();
35277
+ delete config.activeModel;
35278
+ writeConfig2(config);
35279
+ }
34614
35280
  function createHandoff(input, db2) {
34615
35281
  const d = db2 || getDatabase3();
34616
35282
  const id = uuid2();
@@ -35425,13 +36091,13 @@ function searchTasks(options, projectId, taskListId, db2) {
35425
36091
  return rows.map(rowToTask3);
35426
36092
  }
35427
36093
  function getTaskListDir(taskListId) {
35428
- return join44(HOME, ".claude", "tasks", taskListId);
36094
+ return join53(HOME, ".claude", "tasks", taskListId);
35429
36095
  }
35430
36096
  function readClaudeTask(dir, filename) {
35431
- return readJsonFile(join44(dir, filename));
36097
+ return readJsonFile(join53(dir, filename));
35432
36098
  }
35433
36099
  function writeClaudeTask(dir, task) {
35434
- writeJsonFile(join44(dir, `${task.id}.json`), task);
36100
+ writeJsonFile(join53(dir, `${task.id}.json`), task);
35435
36101
  }
35436
36102
  function toClaudeStatus(status) {
35437
36103
  if (status === "pending" || status === "in_progress" || status === "completed") {
@@ -35443,14 +36109,14 @@ function toSqliteStatus(status) {
35443
36109
  return status;
35444
36110
  }
35445
36111
  function readPrefixCounter(dir) {
35446
- const path = join44(dir, ".prefix-counter");
35447
- if (!existsSync43(path))
36112
+ const path = join53(dir, ".prefix-counter");
36113
+ if (!existsSync53(path))
35448
36114
  return 0;
35449
- const val = parseInt(readFileSync25(path, "utf-8").trim(), 10);
36115
+ const val = parseInt(readFileSync34(path, "utf-8").trim(), 10);
35450
36116
  return isNaN(val) ? 0 : val;
35451
36117
  }
35452
36118
  function writePrefixCounter(dir, value) {
35453
- writeFileSync24(join44(dir, ".prefix-counter"), String(value));
36119
+ writeFileSync34(join53(dir, ".prefix-counter"), String(value));
35454
36120
  }
35455
36121
  function formatPrefixedSubject(title, prefix, counter) {
35456
36122
  const padded = String(counter).padStart(5, "0");
@@ -35477,7 +36143,7 @@ function taskToClaudeTask(task, claudeTaskId, existingMeta) {
35477
36143
  }
35478
36144
  function pushToClaudeTaskList(taskListId, projectId, options = {}) {
35479
36145
  const dir = getTaskListDir(taskListId);
35480
- if (!existsSync43(dir))
36146
+ if (!existsSync53(dir))
35481
36147
  ensureDir23(dir);
35482
36148
  const filter = {};
35483
36149
  if (projectId)
@@ -35486,7 +36152,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
35486
36152
  const existingByTodosId = new Map;
35487
36153
  const files = listJsonFiles(dir);
35488
36154
  for (const f of files) {
35489
- const path = join44(dir, f);
36155
+ const path = join53(dir, f);
35490
36156
  const ct = readClaudeTask(dir, f);
35491
36157
  if (ct?.metadata?.["todos_id"]) {
35492
36158
  existingByTodosId.set(ct.metadata["todos_id"], { task: ct, mtimeMs: getFileMtimeMs(path) });
@@ -35573,7 +36239,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
35573
36239
  }
35574
36240
  function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
35575
36241
  const dir = getTaskListDir(taskListId);
35576
- if (!existsSync43(dir)) {
36242
+ if (!existsSync53(dir)) {
35577
36243
  return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
35578
36244
  }
35579
36245
  const files = readdirSync23(dir).filter((f) => f.endsWith(".json"));
@@ -35593,7 +36259,7 @@ function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
35593
36259
  }
35594
36260
  for (const f of files) {
35595
36261
  try {
35596
- const filePath = join44(dir, f);
36262
+ const filePath = join53(dir, f);
35597
36263
  const ct = readClaudeTask(dir, f);
35598
36264
  if (!ct)
35599
36265
  continue;
@@ -35661,16 +36327,16 @@ function syncClaudeTaskList(taskListId, projectId, options = {}) {
35661
36327
  }
35662
36328
  function agentBaseDir(agent) {
35663
36329
  const key = `TODOS_${agent.toUpperCase()}_TASKS_DIR`;
35664
- return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] || join52(HOME, ".todos", "agents");
36330
+ return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] || join62(HOME, ".todos", "agents");
35665
36331
  }
35666
36332
  function getTaskListDir2(agent, taskListId) {
35667
- return join52(agentBaseDir(agent), agent, taskListId);
36333
+ return join62(agentBaseDir(agent), agent, taskListId);
35668
36334
  }
35669
36335
  function readAgentTask(dir, filename) {
35670
- return readJsonFile(join52(dir, filename));
36336
+ return readJsonFile(join62(dir, filename));
35671
36337
  }
35672
36338
  function writeAgentTask(dir, task) {
35673
- writeJsonFile(join52(dir, `${task.id}.json`), task);
36339
+ writeJsonFile(join62(dir, `${task.id}.json`), task);
35674
36340
  }
35675
36341
  function taskToAgentTask(task, externalId, existingMeta) {
35676
36342
  return {
@@ -35695,7 +36361,7 @@ function metadataKey(agent) {
35695
36361
  }
35696
36362
  function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
35697
36363
  const dir = getTaskListDir2(agent, taskListId);
35698
- if (!existsSync52(dir))
36364
+ if (!existsSync62(dir))
35699
36365
  ensureDir23(dir);
35700
36366
  const filter = {};
35701
36367
  if (projectId)
@@ -35704,7 +36370,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
35704
36370
  const existingByTodosId = new Map;
35705
36371
  const files = listJsonFiles(dir);
35706
36372
  for (const f of files) {
35707
- const path = join52(dir, f);
36373
+ const path = join62(dir, f);
35708
36374
  const at = readAgentTask(dir, f);
35709
36375
  if (at?.metadata?.["todos_id"]) {
35710
36376
  existingByTodosId.set(at.metadata["todos_id"], { task: at, mtimeMs: getFileMtimeMs(path) });
@@ -35778,7 +36444,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
35778
36444
  }
35779
36445
  function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
35780
36446
  const dir = getTaskListDir2(agent, taskListId);
35781
- if (!existsSync52(dir)) {
36447
+ if (!existsSync62(dir)) {
35782
36448
  return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
35783
36449
  }
35784
36450
  const files = listJsonFiles(dir);
@@ -35797,7 +36463,7 @@ function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
35797
36463
  }
35798
36464
  for (const f of files) {
35799
36465
  try {
35800
- const filePath = join52(dir, f);
36466
+ const filePath = join62(dir, f);
35801
36467
  const at = readAgentTask(dir, f);
35802
36468
  if (!at)
35803
36469
  continue;
@@ -36002,9 +36668,9 @@ function extractTodos(options, db2) {
36002
36668
  const files = collectFiles(basePath, extensions);
36003
36669
  const allComments = [];
36004
36670
  for (const file of files) {
36005
- const fullPath = statSync22(basePath).isFile() ? basePath : join62(basePath, file);
36671
+ const fullPath = statSync22(basePath).isFile() ? basePath : join72(basePath, file);
36006
36672
  try {
36007
- const source = readFileSync34(fullPath, "utf-8");
36673
+ const source = readFileSync43(fullPath, "utf-8");
36008
36674
  const relPath = statSync22(basePath).isFile() ? relative(resolve23(basePath, ".."), fullPath) : file;
36009
36675
  const comments = extractFromSource(source, relPath, tags);
36010
36676
  allComments.push(...comments);
@@ -36364,7 +37030,29 @@ function checkBudget(agentId, db2) {
36364
37030
  }
36365
37031
  return { allowed: true, current_concurrent: concurrent, max_concurrent: budget.max_concurrent };
36366
37032
  }
36367
- var LOCK_EXPIRY_MINUTES = 30, MIGRATIONS2, _db3 = null, TASK_STATUSES, TASK_PRIORITIES, PLAN_STATUSES, VersionConflictError2, TaskNotFoundError, ProjectNotFoundError2, PlanNotFoundError, LockError, AgentNotFoundError2, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, HOME, cached = null, GUARD_DEFAULTS, DAY_NAMES, RELATIONSHIP_TYPES, EXTRACT_TAGS, DEFAULT_EXTENSIONS, SKIP_DIRS;
37033
+ var LOCK_EXPIRY_MINUTES = 30, MIGRATIONS2, _db3 = null, TASK_STATUSES, TASK_PRIORITIES, PLAN_STATUSES, VersionConflictError2, TaskNotFoundError, ProjectNotFoundError2, PlanNotFoundError, LockError, AgentNotFoundError2, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, HOME, cached = null, GUARD_DEFAULTS, DAY_NAMES, SYSTEM_PROMPT2 = "You are a task management assistant that creates, updates, and tracks tasks and projects.", gatherTrainingData2 = async (options = {}) => {
37034
+ const allTasks = listTasks({});
37035
+ const filtered = options.since ? allTasks.filter((t) => new Date(t.created_at) >= options.since) : allTasks;
37036
+ const sorted = filtered.slice().sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
37037
+ const fetchSet = options.limit ? sorted.slice(0, options.limit * 2) : sorted;
37038
+ const examples = [];
37039
+ for (const task of fetchSet) {
37040
+ examples.push(taskToCreateExample(task));
37041
+ const statusEx = taskToStatusUpdateExample(task);
37042
+ if (statusEx)
37043
+ examples.push(statusEx);
37044
+ }
37045
+ const searchTerms = ["urgent", "fix", "implement", "create", "update", "review"];
37046
+ for (const term of searchTerms) {
37047
+ examples.push(taskToSearchExample(sorted, term));
37048
+ }
37049
+ const finalExamples = options.limit ? examples.slice(0, options.limit) : examples;
37050
+ return {
37051
+ source: "todos",
37052
+ examples: finalExamples,
37053
+ count: finalExamples.length
37054
+ };
37055
+ }, DEFAULT_MODEL3 = "gpt-4o-mini", CONFIG_DIR2, CONFIG_PATH2, RELATIONSHIP_TYPES, EXTRACT_TAGS, DEFAULT_EXTENSIONS, SKIP_DIRS;
36368
37056
  var init_dist4 = __esm(() => {
36369
37057
  MIGRATIONS2 = [
36370
37058
  `
@@ -37041,6 +37729,8 @@ var init_dist4 = __esm(() => {
37041
37729
  saturday: 6,
37042
37730
  sat: 6
37043
37731
  };
37732
+ CONFIG_DIR2 = join45(homedir6(), ".todos");
37733
+ CONFIG_PATH2 = join45(CONFIG_DIR2, "config.json");
37044
37734
  RELATIONSHIP_TYPES = [
37045
37735
  "related_to",
37046
37736
  "conflicts_with",
@@ -37364,7 +38054,7 @@ ${snap.tree.slice(0, 2000)}`;
37364
38054
  const response = await client.messages.create({
37365
38055
  model,
37366
38056
  max_tokens: 512,
37367
- system: SYSTEM_PROMPT2,
38057
+ system: SYSTEM_PROMPT3,
37368
38058
  messages: [{
37369
38059
  role: "user",
37370
38060
  content: `Task: ${task}
@@ -37429,7 +38119,7 @@ What actions should I take next? Return JSON array.`
37429
38119
  }
37430
38120
  return { success: false, result: null, steps_taken: steps.length, steps, cost_estimate: totalTokens / 1000 * 0.00025, error: `Reached max steps (${maxSteps}) without completing task` };
37431
38121
  }
37432
- var SYSTEM_PROMPT2 = `You are a browser automation agent. Given a task and the current page state, decide which browser actions to take.
38122
+ var SYSTEM_PROMPT3 = `You are a browser automation agent. Given a task and the current page state, decide which browser actions to take.
37433
38123
 
37434
38124
  Return a JSON array of at most 3 actions to execute next:
37435
38125
  [{"tool": "navigate|click|type|scroll|evaluate|done", "args": {...}, "reason": "..."}]
@@ -37440,7 +38130,7 @@ var init_ai_task = () => {};
37440
38130
 
37441
38131
  // src/mcp/meta.ts
37442
38132
  function register10(server) {
37443
- server.tool("browser_register_agent", "Register an agent with the browser service", {
38133
+ server.tool("register_agent", "Register an agent session. Returns agent_id. Auto-triggers a heartbeat.", {
37444
38134
  name: exports_external.string(),
37445
38135
  description: exports_external.string().optional(),
37446
38136
  session_id: exports_external.string().optional().optional(),
@@ -37454,7 +38144,7 @@ function register10(server) {
37454
38144
  return err(e);
37455
38145
  }
37456
38146
  });
37457
- server.tool("browser_heartbeat", "Send a heartbeat for an agent", { agent_id: exports_external.string() }, async ({ agent_id }) => {
38147
+ server.tool("heartbeat", "Update last_seen_at to signal agent is active.", { agent_id: exports_external.string() }, async ({ agent_id }) => {
37458
38148
  try {
37459
38149
  heartbeat2(agent_id);
37460
38150
  return json({ ok: true, agent_id, timestamp: new Date().toISOString() });
@@ -37462,13 +38152,22 @@ function register10(server) {
37462
38152
  return err(e);
37463
38153
  }
37464
38154
  });
37465
- server.tool("browser_agent_list", "List registered agents", { project_id: exports_external.string().optional() }, async ({ project_id }) => {
38155
+ server.tool("list_agents", "List all registered agents.", { project_id: exports_external.string().optional() }, async ({ project_id }) => {
37466
38156
  try {
37467
38157
  return json({ agents: listAgents(project_id) });
37468
38158
  } catch (e) {
37469
38159
  return err(e);
37470
38160
  }
37471
38161
  });
38162
+ server.tool("set_focus", "Set active project context for this agent session.", { agent_id: exports_external.string(), project_id: exports_external.string().optional() }, async ({ agent_id, project_id }) => {
38163
+ try {
38164
+ const { updateAgent: update } = await Promise.resolve().then(() => (init_agents2(), exports_agents2));
38165
+ update(agent_id, { project_id: project_id ?? null });
38166
+ return json({ ok: true, agent_id, project_id });
38167
+ } catch (e) {
38168
+ return err(e);
38169
+ }
38170
+ });
37472
38171
  server.tool("browser_project_create", "Create or ensure a project exists", { name: exports_external.string(), path: exports_external.string(), description: exports_external.string().optional() }, async ({ name, path, description }) => {
37473
38172
  try {
37474
38173
  const project = ensureProject(name, path, description);
@@ -37768,9 +38467,10 @@ function register10(server) {
37768
38467
  { tool: "browser_crawl", description: "Crawl a URL recursively" }
37769
38468
  ],
37770
38469
  Agent: [
37771
- { tool: "browser_register_agent", description: "Register an agent" },
37772
- { tool: "browser_heartbeat", description: "Send agent heartbeat" },
37773
- { tool: "browser_agent_list", description: "List registered agents" }
38470
+ { tool: "register_agent", description: "Register an agent session" },
38471
+ { tool: "heartbeat", description: "Update agent last_seen_at" },
38472
+ { tool: "list_agents", description: "List registered agents" },
38473
+ { tool: "set_focus", description: "Set active project context" }
37774
38474
  ],
37775
38475
  Project: [
37776
38476
  { tool: "browser_project_create", description: "Create or ensure a project" },
@@ -37836,7 +38536,7 @@ function register10(server) {
37836
38536
  });
37837
38537
  server.tool("browser_version", "Get the running browser MCP version, tool count, and environment info. Use this to verify which binary is active.", {}, async () => {
37838
38538
  try {
37839
- const { getDataDir: getDataDir4 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
38539
+ const { getDataDir: getDataDir2 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
37840
38540
  const toolCount = Object.keys(server._registeredTools ?? {}).length;
37841
38541
  const { readFileSync: readFileSync9 } = await import("fs");
37842
38542
  const { join: join16 } = await import("path");
@@ -37845,7 +38545,7 @@ function register10(server) {
37845
38545
  version: _pkg.version,
37846
38546
  mcp_tools_count: toolCount,
37847
38547
  bun_version: Bun.version,
37848
- data_dir: getDataDir4(),
38548
+ data_dir: getDataDir2(),
37849
38549
  node_env: process.env["NODE_ENV"] ?? "production"
37850
38550
  });
37851
38551
  } catch (e) {
@@ -38294,7 +38994,7 @@ var init_snapshots = __esm(() => {
38294
38994
  // src/server/index.ts
38295
38995
  var exports_server = {};
38296
38996
  import { join as join17 } from "path";
38297
- import { existsSync as existsSync10 } from "fs";
38997
+ import { existsSync as existsSync11 } from "fs";
38298
38998
  function ok(data, status = 200) {
38299
38999
  return new Response(JSON.stringify(data), {
38300
39000
  status,
@@ -38553,14 +39253,14 @@ var init_server = __esm(() => {
38553
39253
  if (path.match(/^\/api\/gallery\/([^/]+)\/thumbnail$/) && method === "GET") {
38554
39254
  const id = path.split("/")[3];
38555
39255
  const entry = getEntry(id);
38556
- if (!entry?.thumbnail_path || !existsSync10(entry.thumbnail_path))
39256
+ if (!entry?.thumbnail_path || !existsSync11(entry.thumbnail_path))
38557
39257
  return notFound("Thumbnail not found");
38558
39258
  return new Response(Bun.file(entry.thumbnail_path), { headers: { ...CORS_HEADERS } });
38559
39259
  }
38560
39260
  if (path.match(/^\/api\/gallery\/([^/]+)\/image$/) && method === "GET") {
38561
39261
  const id = path.split("/")[3];
38562
39262
  const entry = getEntry(id);
38563
- if (!entry?.path || !existsSync10(entry.path))
39263
+ if (!entry?.path || !existsSync11(entry.path))
38564
39264
  return notFound("Image not found");
38565
39265
  return new Response(Bun.file(entry.path), { headers: { ...CORS_HEADERS } });
38566
39266
  }
@@ -38588,7 +39288,7 @@ var init_server = __esm(() => {
38588
39288
  if (path.match(/^\/api\/downloads\/([^/]+)\/raw$/) && method === "GET") {
38589
39289
  const id = path.split("/")[3];
38590
39290
  const file = getDownload(id);
38591
- if (!file || !existsSync10(file.path))
39291
+ if (!file || !existsSync11(file.path))
38592
39292
  return notFound("Download not found");
38593
39293
  return new Response(Bun.file(file.path), { headers: { ...CORS_HEADERS } });
38594
39294
  }
@@ -38597,9 +39297,9 @@ var init_server = __esm(() => {
38597
39297
  return ok({ deleted: deleteDownload(id) });
38598
39298
  }
38599
39299
  const dashboardDist = join17(import.meta.dir, "../../dashboard/dist");
38600
- if (existsSync10(dashboardDist)) {
39300
+ if (existsSync11(dashboardDist)) {
38601
39301
  const filePath = path === "/" ? join17(dashboardDist, "index.html") : join17(dashboardDist, path);
38602
- if (existsSync10(filePath)) {
39302
+ if (existsSync11(filePath)) {
38603
39303
  return new Response(Bun.file(filePath), { headers: CORS_HEADERS });
38604
39304
  }
38605
39305
  return new Response(Bun.file(join17(dashboardDist, "index.html")), { headers: CORS_HEADERS });
@@ -39005,8 +39705,8 @@ function register3(program2) {
39005
39705
  }
39006
39706
  });
39007
39707
  scriptCmd.command("import <file>").description("Import a script from a JSON file into SQLite").action(async (file) => {
39008
- const { readFileSync, existsSync: existsSync3 } = await import("fs");
39009
- if (!existsSync3(file)) {
39708
+ const { readFileSync, existsSync: existsSync4 } = await import("fs");
39709
+ if (!existsSync4(file)) {
39010
39710
  console.log(chalk3.red(`File not found: ${file}`));
39011
39711
  return;
39012
39712
  }
@@ -39277,8 +39977,8 @@ ${entries.length} entries`));
39277
39977
  }
39278
39978
  const result = await diffImages2(e1.path, e2.path);
39279
39979
  if (opts.output) {
39280
- const { copyFileSync: copyFileSync4 } = await import("fs");
39281
- copyFileSync4(result.diff_path, opts.output);
39980
+ const { copyFileSync: copyFileSync5 } = await import("fs");
39981
+ copyFileSync5(result.diff_path, opts.output);
39282
39982
  console.log(chalk4.green(`\u2713 Diff saved: ${opts.output}`));
39283
39983
  }
39284
39984
  console.log(chalk4.blue(`Changed pixels: ${result.changed_pixels} / ${result.total_pixels} (${result.changed_percent.toFixed(2)}%)`));
@@ -39294,11 +39994,11 @@ ${entries.length} entries`));
39294
39994
  });
39295
39995
  galleryCmd.command("clean").description("Delete gallery entries with missing files").action(async () => {
39296
39996
  const { listEntries: listEntries2, deleteEntry: deleteEntry2 } = await Promise.resolve().then(() => (init_gallery(), exports_gallery));
39297
- const { existsSync: existsSync11 } = await import("fs");
39997
+ const { existsSync: existsSync12 } = await import("fs");
39298
39998
  const entries = listEntries2({ limit: 9999 });
39299
39999
  let removed = 0;
39300
40000
  for (const e of entries) {
39301
- if (!existsSync11(e.path)) {
40001
+ if (!existsSync12(e.path)) {
39302
40002
  deleteEntry2(e.id);
39303
40003
  removed++;
39304
40004
  }