@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/mcp/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);
@@ -110,10 +131,30 @@ __export(exports_schema, {
110
131
  });
111
132
  import { Database } from "bun:sqlite";
112
133
  import { join } from "path";
113
- import { mkdirSync } from "fs";
134
+ import { mkdirSync, existsSync, readdirSync, copyFileSync, statSync } from "fs";
114
135
  import { homedir } from "os";
115
136
  function getDataDir() {
116
- return process.env["BROWSER_DATA_DIR"] ?? join(homedir(), ".browser");
137
+ if (process.env["BROWSER_DATA_DIR"])
138
+ return process.env["BROWSER_DATA_DIR"];
139
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
140
+ const newDir = join(home, ".hasna", "browser");
141
+ const oldDir = join(home, ".browser");
142
+ if (existsSync(oldDir) && !existsSync(newDir)) {
143
+ mkdirSync(newDir, { recursive: true });
144
+ try {
145
+ for (const file of readdirSync(oldDir)) {
146
+ const oldPath = join(oldDir, file);
147
+ const newPath = join(newDir, file);
148
+ try {
149
+ if (statSync(oldPath).isFile()) {
150
+ copyFileSync(oldPath, newPath);
151
+ }
152
+ } catch {}
153
+ }
154
+ } catch {}
155
+ }
156
+ mkdirSync(newDir, { recursive: true });
157
+ return newDir;
117
158
  }
118
159
  function getDatabase(path) {
119
160
  const resolvedPath = path ?? process.env["BROWSER_DB_PATH"] ?? join(getDataDir(), "browser.db");
@@ -640,10 +681,15 @@ class BrowserPool {
640
681
  this.pool.push({ browser, inUse: true, createdAt: Date.now() });
641
682
  return browser;
642
683
  }
643
- return new Promise((resolve) => {
684
+ return new Promise((resolve, reject) => {
685
+ const timeout = setTimeout(() => {
686
+ clearInterval(interval);
687
+ reject(new BrowserError("Browser pool exhausted \u2014 no browser became available within 30s", "POOL_TIMEOUT", true));
688
+ }, 30000);
644
689
  const interval = setInterval(() => {
645
690
  const free = this.pool.find((e) => !e.inUse);
646
691
  if (free) {
692
+ clearTimeout(timeout);
647
693
  clearInterval(interval);
648
694
  free.inUse = true;
649
695
  resolve(free.browser);
@@ -684,7 +730,7 @@ function isLightpandaAvailable() {
684
730
  const paths = [
685
731
  "/usr/local/bin/lightpanda",
686
732
  "/usr/bin/lightpanda",
687
- `${process.env["HOME"]}/.browser/bin/lightpanda`
733
+ `${process.env["HOME"]}/.hasna/browser/bin/lightpanda`
688
734
  ];
689
735
  return paths.some((p) => {
690
736
  try {
@@ -703,7 +749,7 @@ function getLightpandaBinaryPath() {
703
749
  "lightpanda",
704
750
  "/usr/local/bin/lightpanda",
705
751
  "/usr/bin/lightpanda",
706
- `${process.env["HOME"]}/.browser/bin/lightpanda`
752
+ `${process.env["HOME"]}/.hasna/browser/bin/lightpanda`
707
753
  ];
708
754
  for (const p of paths) {
709
755
  try {
@@ -777,18 +823,18 @@ var init_lightpanda = __esm(() => {
777
823
  // src/engines/bun-webview.ts
778
824
  import { join as join2 } from "path";
779
825
  import { mkdirSync as mkdirSync2 } from "fs";
780
- import { homedir as homedir2 } from "os";
781
826
  function isBunWebViewAvailable() {
782
827
  return typeof globalThis.Bun !== "undefined" && typeof globalThis.Bun.WebView !== "undefined";
783
828
  }
784
829
  function getProfileDir(profileName) {
785
- const base = process.env["BROWSER_DATA_DIR"] ?? join2(homedir2(), ".browser");
830
+ const base = getDataDir();
786
831
  const dir = join2(base, "profiles", profileName);
787
832
  mkdirSync2(dir, { recursive: true });
788
833
  return dir;
789
834
  }
790
835
  var BunWebViewSession;
791
836
  var init_bun_webview = __esm(() => {
837
+ init_schema();
792
838
  BunWebViewSession = class BunWebViewSession {
793
839
  view;
794
840
  _sessionId;
@@ -1246,6 +1292,7 @@ function enableNetworkLogging(page, sessionId) {
1246
1292
  };
1247
1293
  const onResponse = (res) => {
1248
1294
  const start = requestStart.get(res.url()) ?? Date.now();
1295
+ requestStart.delete(res.url());
1249
1296
  const duration = Date.now() - start;
1250
1297
  const req = res.request();
1251
1298
  try {
@@ -1262,11 +1309,17 @@ function enableNetworkLogging(page, sessionId) {
1262
1309
  });
1263
1310
  } catch {}
1264
1311
  };
1312
+ const onRequestFailed = (req) => {
1313
+ requestStart.delete(req.url());
1314
+ };
1265
1315
  page.on("request", onRequest);
1266
1316
  page.on("response", onResponse);
1317
+ page.on("requestfailed", onRequestFailed);
1267
1318
  return () => {
1268
1319
  page.off("request", onRequest);
1269
1320
  page.off("response", onResponse);
1321
+ page.off("requestfailed", onRequestFailed);
1322
+ requestStart.clear();
1270
1323
  };
1271
1324
  }
1272
1325
  async function addInterceptRule(page, rule) {
@@ -1300,6 +1353,7 @@ function startHAR(page) {
1300
1353
  const start = requestStart.get(key);
1301
1354
  if (!start)
1302
1355
  return;
1356
+ requestStart.delete(key);
1303
1357
  const duration = Date.now() - start.time;
1304
1358
  const entry = {
1305
1359
  startedDateTime: new Date(start.time).toISOString(),
@@ -1322,15 +1376,20 @@ function startHAR(page) {
1322
1376
  timings: { send: 0, wait: duration, receive: 0 }
1323
1377
  };
1324
1378
  entries.push(entry);
1325
- requestStart.delete(key);
1379
+ };
1380
+ const onRequestFailed = (req) => {
1381
+ requestStart.delete(req.url() + req.method());
1326
1382
  };
1327
1383
  page.on("request", onRequest);
1328
1384
  page.on("response", onResponse);
1385
+ page.on("requestfailed", onRequestFailed);
1329
1386
  return {
1330
1387
  entries,
1331
1388
  stop: () => {
1332
1389
  page.off("request", onRequest);
1333
1390
  page.off("response", onResponse);
1391
+ page.off("requestfailed", onRequestFailed);
1392
+ requestStart.clear();
1334
1393
  return {
1335
1394
  log: {
1336
1395
  version: "1.2",
@@ -1696,9 +1755,8 @@ __export(exports_storage_state, {
1696
1755
  listStates: () => listStates,
1697
1756
  deleteState: () => deleteState
1698
1757
  });
1699
- import { mkdirSync as mkdirSync3, existsSync, readdirSync, unlinkSync } from "fs";
1758
+ import { mkdirSync as mkdirSync3, existsSync as existsSync2, readdirSync as readdirSync2, unlinkSync } from "fs";
1700
1759
  import { join as join3 } from "path";
1701
- import { homedir as homedir3 } from "os";
1702
1760
  function ensureDir() {
1703
1761
  mkdirSync3(STATES_DIR, { recursive: true });
1704
1762
  }
@@ -1716,11 +1774,11 @@ async function saveStateFromPage(page, name) {
1716
1774
  }
1717
1775
  function loadStatePath(name) {
1718
1776
  const path = statePath(name);
1719
- return existsSync(path) ? path : null;
1777
+ return existsSync2(path) ? path : null;
1720
1778
  }
1721
1779
  function listStates() {
1722
1780
  ensureDir();
1723
- return readdirSync(STATES_DIR).filter((f) => f.endsWith(".json")).map((f) => {
1781
+ return readdirSync2(STATES_DIR).filter((f) => f.endsWith(".json")).map((f) => {
1724
1782
  const path = join3(STATES_DIR, f);
1725
1783
  const stat = Bun.file(path);
1726
1784
  return {
@@ -1732,7 +1790,7 @@ function listStates() {
1732
1790
  }
1733
1791
  function deleteState(name) {
1734
1792
  const path = statePath(name);
1735
- if (existsSync(path)) {
1793
+ if (existsSync2(path)) {
1736
1794
  unlinkSync(path);
1737
1795
  return true;
1738
1796
  }
@@ -1740,7 +1798,8 @@ function deleteState(name) {
1740
1798
  }
1741
1799
  var STATES_DIR;
1742
1800
  var init_storage_state = __esm(() => {
1743
- STATES_DIR = join3(process.env["BROWSER_DATA_DIR"] ?? join3(homedir3(), ".browser"), "states");
1801
+ init_schema();
1802
+ STATES_DIR = join3(getDataDir(), "states");
1744
1803
  });
1745
1804
 
1746
1805
  // src/lib/session.ts
@@ -9789,6 +9848,121 @@ var init_recorder = __esm(() => {
9789
9848
  activeRecordings = new Map;
9790
9849
  });
9791
9850
 
9851
+ // src/db/agents.ts
9852
+ import { randomUUID as randomUUID7 } from "crypto";
9853
+ function registerAgent(name, opts = {}) {
9854
+ const db = getDatabase();
9855
+ const existing = db.query("SELECT * FROM agents WHERE name = ?").get(name);
9856
+ if (existing) {
9857
+ db.prepare("UPDATE agents SET last_seen = datetime('now'), session_id = ?, project_id = ?, working_dir = ? WHERE name = ?").run(opts.sessionId ?? existing.session_id ?? null, opts.projectId ?? existing.project_id ?? null, opts.workingDir ?? existing.working_dir ?? null, name);
9858
+ return getAgentByName(name);
9859
+ }
9860
+ const id = randomUUID7();
9861
+ db.prepare("INSERT INTO agents (id, name, description, session_id, project_id, working_dir) VALUES (?, ?, ?, ?, ?, ?)").run(id, name, opts.description ?? null, opts.sessionId ?? null, opts.projectId ?? null, opts.workingDir ?? null);
9862
+ return getAgent(id);
9863
+ }
9864
+ function heartbeat(agentId) {
9865
+ const db = getDatabase();
9866
+ const agent = db.query("SELECT * FROM agents WHERE id = ?").get(agentId);
9867
+ if (!agent)
9868
+ throw new AgentNotFoundError(agentId);
9869
+ db.prepare("UPDATE agents SET last_seen = datetime('now') WHERE id = ?").run(agentId);
9870
+ db.prepare("INSERT INTO heartbeats (id, agent_id, session_id) VALUES (?, ?, ?)").run(randomUUID7(), agentId, agent.session_id ?? null);
9871
+ }
9872
+ function getAgent(id) {
9873
+ const db = getDatabase();
9874
+ const row = db.query("SELECT * FROM agents WHERE id = ?").get(id);
9875
+ if (!row)
9876
+ throw new AgentNotFoundError(id);
9877
+ return row;
9878
+ }
9879
+ function getAgentByName(name) {
9880
+ const db = getDatabase();
9881
+ return db.query("SELECT * FROM agents WHERE name = ?").get(name) ?? null;
9882
+ }
9883
+ function listAgents(projectId) {
9884
+ const db = getDatabase();
9885
+ if (projectId) {
9886
+ return db.query("SELECT * FROM agents WHERE project_id = ? ORDER BY last_seen DESC").all(projectId);
9887
+ }
9888
+ return db.query("SELECT * FROM agents ORDER BY last_seen DESC").all();
9889
+ }
9890
+ function updateAgent(id, data) {
9891
+ const db = getDatabase();
9892
+ const fields = [];
9893
+ const values = [];
9894
+ if (data.name !== undefined) {
9895
+ fields.push("name = ?");
9896
+ values.push(data.name ?? null);
9897
+ }
9898
+ if (data.description !== undefined) {
9899
+ fields.push("description = ?");
9900
+ values.push(data.description ?? null);
9901
+ }
9902
+ if (data.session_id !== undefined) {
9903
+ fields.push("session_id = ?");
9904
+ values.push(data.session_id ?? null);
9905
+ }
9906
+ if (data.project_id !== undefined) {
9907
+ fields.push("project_id = ?");
9908
+ values.push(data.project_id ?? null);
9909
+ }
9910
+ if (data.working_dir !== undefined) {
9911
+ fields.push("working_dir = ?");
9912
+ values.push(data.working_dir ?? null);
9913
+ }
9914
+ if (fields.length === 0)
9915
+ return getAgent(id);
9916
+ values.push(id);
9917
+ db.prepare(`UPDATE agents SET ${fields.join(", ")} WHERE id = ?`).run(...values);
9918
+ return getAgent(id);
9919
+ }
9920
+ function deleteAgent(id) {
9921
+ const db = getDatabase();
9922
+ db.prepare("DELETE FROM agents WHERE id = ?").run(id);
9923
+ }
9924
+ function cleanStaleAgents(thresholdMs) {
9925
+ const db = getDatabase();
9926
+ const cutoff = new Date(Date.now() - thresholdMs).toISOString().replace("T", " ").split(".")[0];
9927
+ const result = db.prepare("DELETE FROM agents WHERE last_seen < ?").run(cutoff);
9928
+ return result.changes;
9929
+ }
9930
+ var init_agents = __esm(() => {
9931
+ init_schema();
9932
+ init_types();
9933
+ });
9934
+
9935
+ // src/lib/agents.ts
9936
+ var exports_agents = {};
9937
+ __export(exports_agents, {
9938
+ updateAgent: () => updateAgent,
9939
+ registerAgent: () => registerAgent2,
9940
+ listAgents: () => listAgents,
9941
+ isAgentStale: () => isAgentStale,
9942
+ heartbeat: () => heartbeat2,
9943
+ getAgentByName: () => getAgentByName,
9944
+ getAgent: () => getAgent,
9945
+ getActiveAgents: () => getActiveAgents,
9946
+ deleteAgent: () => deleteAgent,
9947
+ cleanStaleAgents: () => cleanStaleAgents
9948
+ });
9949
+ function registerAgent2(name, opts = {}) {
9950
+ return registerAgent(name, opts);
9951
+ }
9952
+ function heartbeat2(agentId) {
9953
+ heartbeat(agentId);
9954
+ }
9955
+ function isAgentStale(agent, thresholdMs = 5 * 60 * 1000) {
9956
+ const lastSeen = new Date(agent.last_seen).getTime();
9957
+ return Date.now() - lastSeen > thresholdMs;
9958
+ }
9959
+ function getActiveAgents(thresholdMs = 5 * 60 * 1000) {
9960
+ return listAgents().filter((a) => !isAgentStale(a, thresholdMs));
9961
+ }
9962
+ var init_agents2 = __esm(() => {
9963
+ init_agents();
9964
+ });
9965
+
9792
9966
  // src/lib/profiles.ts
9793
9967
  var exports_profiles = {};
9794
9968
  __export(exports_profiles, {
@@ -9798,11 +9972,10 @@ __export(exports_profiles, {
9798
9972
  deleteProfile: () => deleteProfile,
9799
9973
  applyProfile: () => applyProfile
9800
9974
  });
9801
- import { mkdirSync as mkdirSync8, existsSync as existsSync4, readdirSync as readdirSync3, rmSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
9975
+ import { mkdirSync as mkdirSync8, existsSync as existsSync5, readdirSync as readdirSync4, rmSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
9802
9976
  import { join as join8 } from "path";
9803
- import { homedir as homedir8 } from "os";
9804
9977
  function getProfilesDir() {
9805
- const dataDir = process.env["BROWSER_DATA_DIR"] ?? join8(homedir8(), ".browser");
9978
+ const dataDir = getDataDir();
9806
9979
  const dir = join8(dataDir, "profiles");
9807
9980
  mkdirSync8(dir, { recursive: true });
9808
9981
  return dir;
@@ -9841,17 +10014,17 @@ async function saveProfile(page, name) {
9841
10014
  }
9842
10015
  function loadProfile(name) {
9843
10016
  const dir = getProfileDir2(name);
9844
- if (!existsSync4(dir)) {
10017
+ if (!existsSync5(dir)) {
9845
10018
  throw new Error(`Profile not found: ${name}`);
9846
10019
  }
9847
10020
  const cookiesPath = join8(dir, "cookies.json");
9848
10021
  const storagePath = join8(dir, "storage.json");
9849
10022
  const metaPath2 = join8(dir, "meta.json");
9850
- const cookies = existsSync4(cookiesPath) ? JSON.parse(readFileSync2(cookiesPath, "utf8")) : [];
9851
- const localStorage2 = existsSync4(storagePath) ? JSON.parse(readFileSync2(storagePath, "utf8")) : {};
10023
+ const cookies = existsSync5(cookiesPath) ? JSON.parse(readFileSync2(cookiesPath, "utf8")) : [];
10024
+ const localStorage2 = existsSync5(storagePath) ? JSON.parse(readFileSync2(storagePath, "utf8")) : {};
9852
10025
  let savedAt = new Date().toISOString();
9853
10026
  let url;
9854
- if (existsSync4(metaPath2)) {
10027
+ if (existsSync5(metaPath2)) {
9855
10028
  const meta = JSON.parse(readFileSync2(metaPath2, "utf8"));
9856
10029
  savedAt = meta.saved_at ?? savedAt;
9857
10030
  url = meta.url;
@@ -9879,9 +10052,9 @@ async function applyProfile(page, profileData) {
9879
10052
  }
9880
10053
  function listProfiles() {
9881
10054
  const dir = getProfilesDir();
9882
- if (!existsSync4(dir))
10055
+ if (!existsSync5(dir))
9883
10056
  return [];
9884
- const entries = readdirSync3(dir, { withFileTypes: true });
10057
+ const entries = readdirSync4(dir, { withFileTypes: true });
9885
10058
  const profiles = [];
9886
10059
  for (const entry of entries) {
9887
10060
  if (!entry.isDirectory())
@@ -9894,18 +10067,18 @@ function listProfiles() {
9894
10067
  let storageKeyCount = 0;
9895
10068
  try {
9896
10069
  const metaPath2 = join8(profileDir, "meta.json");
9897
- if (existsSync4(metaPath2)) {
10070
+ if (existsSync5(metaPath2)) {
9898
10071
  const meta = JSON.parse(readFileSync2(metaPath2, "utf8"));
9899
10072
  savedAt = meta.saved_at ?? "";
9900
10073
  url = meta.url;
9901
10074
  }
9902
10075
  const cookiesPath = join8(profileDir, "cookies.json");
9903
- if (existsSync4(cookiesPath)) {
10076
+ if (existsSync5(cookiesPath)) {
9904
10077
  const cookies = JSON.parse(readFileSync2(cookiesPath, "utf8"));
9905
10078
  cookieCount = Array.isArray(cookies) ? cookies.length : 0;
9906
10079
  }
9907
10080
  const storagePath = join8(profileDir, "storage.json");
9908
- if (existsSync4(storagePath)) {
10081
+ if (existsSync5(storagePath)) {
9909
10082
  const storage = JSON.parse(readFileSync2(storagePath, "utf8"));
9910
10083
  storageKeyCount = Object.keys(storage).length;
9911
10084
  }
@@ -9922,7 +10095,7 @@ function listProfiles() {
9922
10095
  }
9923
10096
  function deleteProfile(name) {
9924
10097
  const dir = getProfileDir2(name);
9925
- if (!existsSync4(dir))
10098
+ if (!existsSync5(dir))
9926
10099
  return false;
9927
10100
  try {
9928
10101
  rmSync(dir, { recursive: true, force: true });
@@ -9931,7 +10104,9 @@ function deleteProfile(name) {
9931
10104
  return false;
9932
10105
  }
9933
10106
  }
9934
- var init_profiles = () => {};
10107
+ var init_profiles = __esm(() => {
10108
+ init_schema();
10109
+ });
9935
10110
 
9936
10111
  // src/lib/vision-fallback.ts
9937
10112
  var exports_vision_fallback = {};
@@ -10492,16 +10667,22 @@ var init_deep_performance = __esm(() => {
10492
10667
  };
10493
10668
  });
10494
10669
 
10495
- // ../open-skills/dist/index.js
10670
+ // node_modules/@hasna/skills/dist/index.js
10496
10671
  var exports_dist = {};
10497
10672
  __export(exports_dist, {
10673
+ validateCron: () => validateCron,
10498
10674
  skillExists: () => skillExists,
10675
+ setScheduleEnabled: () => setScheduleEnabled,
10499
10676
  searchSkills: () => searchSkills,
10500
10677
  saveConfig: () => saveConfig,
10501
10678
  runSkill: () => runSkill,
10502
10679
  removeSkillForAgent: () => removeSkillForAgent,
10503
10680
  removeSkill: () => removeSkill,
10681
+ removeSchedule: () => removeSchedule,
10682
+ recordScheduleRun: () => recordScheduleRun,
10683
+ loadRegistry: () => loadRegistry,
10504
10684
  loadConfig: () => loadConfig,
10685
+ listSchedules: () => listSchedules,
10505
10686
  installSkills: () => installSkills,
10506
10687
  installSkillForAgent: () => installSkillForAgent,
10507
10688
  installSkill: () => installSkill,
@@ -10512,8 +10693,10 @@ __export(exports_dist, {
10512
10693
  getSkillDocs: () => getSkillDocs,
10513
10694
  getSkillBestDoc: () => getSkillBestDoc,
10514
10695
  getSkill: () => getSkill,
10696
+ getNextRun: () => getNextRun,
10515
10697
  getInstalledSkills: () => getInstalledSkills,
10516
10698
  getInstallMeta: () => getInstallMeta,
10699
+ getDueSchedules: () => getDueSchedules,
10517
10700
  getDisabledSkills: () => getDisabledSkills,
10518
10701
  getConfigPath: () => getConfigPath,
10519
10702
  getAllTags: () => getAllTags,
@@ -10521,23 +10704,111 @@ __export(exports_dist, {
10521
10704
  getAgentSkillPath: () => getAgentSkillPath,
10522
10705
  generateSkillMd: () => generateSkillMd,
10523
10706
  generateEnvExample: () => generateEnvExample,
10707
+ findSimilarSkills: () => findSimilarSkills,
10524
10708
  enableSkill: () => enableSkill,
10525
10709
  disableSkill: () => disableSkill,
10710
+ clearRegistryCache: () => clearRegistryCache,
10711
+ addSchedule: () => addSchedule,
10526
10712
  SKILLS: () => SKILLS,
10527
10713
  CATEGORIES: () => CATEGORIES,
10528
10714
  AGENT_TARGETS: () => AGENT_TARGETS
10529
10715
  });
10530
- import { existsSync as existsSync5, cpSync, mkdirSync as mkdirSync9, writeFileSync as writeFileSync3, rmSync as rmSync2, readdirSync as readdirSync4, statSync as statSync2, readFileSync as readFileSync3, accessSync, constants } from "fs";
10531
- import { join as join9, dirname } from "path";
10532
- import { homedir as homedir9 } from "os";
10533
- import { fileURLToPath } from "url";
10534
- import { existsSync as existsSync22, readFileSync as readFileSync22, readdirSync as readdirSync22 } from "fs";
10535
- import { join as join22 } from "path";
10536
- import { existsSync as existsSync32, readFileSync as readFileSync32, writeFileSync as writeFileSync22, mkdirSync as mkdirSync22 } from "fs";
10537
- import { join as join32, dirname as dirname2 } from "path";
10716
+ import { existsSync as existsSync6, readFileSync as readFileSync3, readdirSync as readdirSync5 } from "fs";
10717
+ import { join as join9 } from "path";
10718
+ import { homedir as homedir2 } from "os";
10719
+ 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";
10720
+ import { join as join22, dirname } from "path";
10538
10721
  import { homedir as homedir22 } from "os";
10722
+ import { fileURLToPath } from "url";
10723
+ import { existsSync as existsSync32, readFileSync as readFileSync32, readdirSync as readdirSync32 } from "fs";
10724
+ import { join as join32 } from "path";
10725
+ import { existsSync as existsSync42, readFileSync as readFileSync4, writeFileSync as writeFileSync22, mkdirSync as mkdirSync22 } from "fs";
10726
+ import { join as join42, dirname as dirname2 } from "path";
10727
+ import { homedir as homedir3 } from "os";
10728
+ import { existsSync as existsSync52, readFileSync as readFileSync5, writeFileSync as writeFileSync32, mkdirSync as mkdirSync32 } from "fs";
10729
+ import { join as join52 } from "path";
10730
+ function parseSkillMdFrontmatter(content) {
10731
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
10732
+ if (!match)
10733
+ return null;
10734
+ const result = {};
10735
+ for (const line of match[1].split(`
10736
+ `)) {
10737
+ const colon = line.indexOf(":");
10738
+ if (colon === -1)
10739
+ continue;
10740
+ const key = line.slice(0, colon).trim();
10741
+ const value = line.slice(colon + 1).trim();
10742
+ if (!key || !value)
10743
+ continue;
10744
+ if (key === "name")
10745
+ result.name = value;
10746
+ else if (key === "description")
10747
+ result.description = value;
10748
+ else if (key === "displayName" || key === "display_name")
10749
+ result.displayName = value;
10750
+ else if (key === "category")
10751
+ result.category = value;
10752
+ else if (key === "tags") {
10753
+ result.tags = value.replace(/[\[\]]/g, "").split(",").map((t) => t.trim()).filter(Boolean);
10754
+ }
10755
+ }
10756
+ return Object.keys(result).length > 0 ? result : null;
10757
+ }
10758
+ function discoverSkillsInDir(dir) {
10759
+ if (!existsSync6(dir))
10760
+ return [];
10761
+ const result = [];
10762
+ try {
10763
+ const entries = readdirSync5(dir, { withFileTypes: true });
10764
+ for (const entry of entries) {
10765
+ if (!entry.isDirectory())
10766
+ continue;
10767
+ const skillMdPath = join9(dir, entry.name, "SKILL.md");
10768
+ if (!existsSync6(skillMdPath))
10769
+ continue;
10770
+ let content;
10771
+ try {
10772
+ content = readFileSync3(skillMdPath, "utf-8");
10773
+ } catch {
10774
+ continue;
10775
+ }
10776
+ const fm = parseSkillMdFrontmatter(content);
10777
+ if (!fm?.name)
10778
+ continue;
10779
+ const name = fm.name.replace(/^skill-/, "");
10780
+ result.push({
10781
+ name,
10782
+ displayName: fm.displayName || name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
10783
+ description: fm.description || "",
10784
+ category: fm.category || "Development Tools",
10785
+ tags: fm.tags || [],
10786
+ source: "custom"
10787
+ });
10788
+ }
10789
+ } catch {}
10790
+ return result;
10791
+ }
10792
+ function loadRegistry(cwd) {
10793
+ const now = Date.now();
10794
+ if (_registryCache && now - _registryCacheTime < REGISTRY_CACHE_TTL) {
10795
+ return _registryCache;
10796
+ }
10797
+ const official = SKILLS.map((s) => ({ ...s, source: "official" }));
10798
+ const globalCustom = discoverSkillsInDir(join9(homedir2(), ".skills"));
10799
+ const projectCustom = discoverSkillsInDir(join9(cwd || process.cwd(), ".skills", "custom-skills"));
10800
+ const customNames = new Set([...globalCustom, ...projectCustom].map((s) => s.name));
10801
+ const filtered = official.filter((s) => !customNames.has(s.name));
10802
+ _registryCache = [...filtered, ...globalCustom, ...projectCustom];
10803
+ _registryCacheTime = now;
10804
+ return _registryCache;
10805
+ }
10806
+ function clearRegistryCache() {
10807
+ _registryCache = null;
10808
+ _registryCacheTime = 0;
10809
+ }
10539
10810
  function getSkillsByCategory(category) {
10540
- return SKILLS.filter((s) => s.category === category);
10811
+ return loadRegistry().filter((s) => s.category === category);
10541
10812
  }
10542
10813
  function editDistance(a, b) {
10543
10814
  if (a === b)
@@ -10583,7 +10854,7 @@ function searchSkills(query) {
10583
10854
  if (words.length === 0)
10584
10855
  return [];
10585
10856
  const scored = [];
10586
- for (const skill of SKILLS) {
10857
+ for (const skill of loadRegistry()) {
10587
10858
  const nameLower = skill.name.toLowerCase();
10588
10859
  const displayNameLower = skill.displayName.toLowerCase();
10589
10860
  const descriptionLower = skill.description.toLowerCase();
@@ -10619,56 +10890,71 @@ function searchSkills(query) {
10619
10890
  return scored.map((s) => s.skill);
10620
10891
  }
10621
10892
  function getSkill(name) {
10622
- return SKILLS.find((s) => s.name === name);
10893
+ return loadRegistry().find((s) => s.name === name);
10623
10894
  }
10624
10895
  function getSkillsByTag(tag) {
10625
10896
  const needle = tag.toLowerCase();
10626
- return SKILLS.filter((s) => s.tags.some((t) => t.toLowerCase().includes(needle)));
10897
+ return loadRegistry().filter((s) => s.tags.some((t) => t.toLowerCase().includes(needle)));
10627
10898
  }
10628
10899
  function getAllTags() {
10629
10900
  const tagSet = new Set;
10630
- for (const skill of SKILLS) {
10901
+ for (const skill of loadRegistry()) {
10631
10902
  for (const tag of skill.tags) {
10632
10903
  tagSet.add(tag.toLowerCase());
10633
10904
  }
10634
10905
  }
10635
10906
  return Array.from(tagSet).sort();
10636
10907
  }
10908
+ function levenshtein(a, b) {
10909
+ const m = a.length, n = b.length;
10910
+ const dp = Array.from({ length: m + 1 }, (_, i) => Array.from({ length: n + 1 }, (_2, j) => i === 0 ? j : j === 0 ? i : 0));
10911
+ for (let i = 1;i <= m; i++) {
10912
+ for (let j = 1;j <= n; j++) {
10913
+ 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]);
10914
+ }
10915
+ }
10916
+ return dp[m][n];
10917
+ }
10918
+ function findSimilarSkills(query, maxResults = 3) {
10919
+ const q = query.toLowerCase();
10920
+ 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);
10921
+ return scored.slice(0, maxResults).map((s) => s.name);
10922
+ }
10637
10923
  function normalizeSkillName(name) {
10638
10924
  return name.startsWith("skill-") ? name : `skill-${name}`;
10639
10925
  }
10640
10926
  function findSkillsDir() {
10641
10927
  let dir = __dirname2;
10642
10928
  for (let i = 0;i < 5; i++) {
10643
- const candidate = join9(dir, "skills");
10644
- if (existsSync5(candidate)) {
10929
+ const candidate = join22(dir, "skills");
10930
+ if (existsSync22(candidate) && !dir.includes(".skills")) {
10645
10931
  return candidate;
10646
10932
  }
10647
10933
  dir = dirname(dir);
10648
10934
  }
10649
- return join9(__dirname2, "..", "skills");
10935
+ return join22(__dirname2, "..", "skills");
10650
10936
  }
10651
10937
  function getSkillPath(name) {
10652
10938
  const skillName = normalizeSkillName(name);
10653
- return join9(SKILLS_DIR, skillName);
10939
+ return join22(SKILLS_DIR, skillName);
10654
10940
  }
10655
10941
  function skillExists(name) {
10656
- return existsSync5(getSkillPath(name));
10942
+ return existsSync22(getSkillPath(name));
10657
10943
  }
10658
10944
  function installSkill(name, options = {}) {
10659
10945
  const { targetDir = process.cwd(), overwrite = false } = options;
10660
10946
  const skillName = normalizeSkillName(name);
10661
10947
  const sourcePath = getSkillPath(name);
10662
- const destDir = join9(targetDir, ".skills");
10663
- const destPath = join9(destDir, skillName);
10664
- if (!existsSync5(sourcePath)) {
10948
+ const destDir = join22(targetDir, ".skills");
10949
+ const destPath = join22(destDir, skillName);
10950
+ if (!existsSync22(sourcePath)) {
10665
10951
  return {
10666
10952
  skill: name,
10667
10953
  success: false,
10668
10954
  error: `Skill '${name}' not found`
10669
10955
  };
10670
10956
  }
10671
- if (existsSync5(destPath) && !overwrite) {
10957
+ if (existsSync22(destPath) && !overwrite) {
10672
10958
  return {
10673
10959
  skill: name,
10674
10960
  success: false,
@@ -10677,10 +10963,10 @@ function installSkill(name, options = {}) {
10677
10963
  };
10678
10964
  }
10679
10965
  try {
10680
- if (!existsSync5(destDir)) {
10966
+ if (!existsSync22(destDir)) {
10681
10967
  mkdirSync9(destDir, { recursive: true });
10682
10968
  }
10683
- if (existsSync5(destPath) && overwrite) {
10969
+ if (existsSync22(destPath) && overwrite) {
10684
10970
  rmSync2(destPath, { recursive: true, force: true });
10685
10971
  }
10686
10972
  cpSync(sourcePath, destPath, {
@@ -10719,10 +11005,10 @@ function installSkills(names, options = {}) {
10719
11005
  return names.map((name) => installSkill(name, options));
10720
11006
  }
10721
11007
  function updateSkillsIndex(skillsDir) {
10722
- const indexPath = join9(skillsDir, "index.ts");
11008
+ const indexPath = join22(skillsDir, "index.ts");
10723
11009
  const meta = loadMeta(skillsDir);
10724
11010
  const disabledSet = new Set(meta.disabled || []);
10725
- const skills = readdirSync4(skillsDir).filter((f) => f.startsWith("skill-") && !f.includes(".") && !disabledSet.has(f.replace("skill-", "")));
11011
+ const skills = readdirSync22(skillsDir).filter((f) => f.startsWith("skill-") && !f.includes(".") && !disabledSet.has(f.replace("skill-", "")));
10726
11012
  const exports = skills.map((s) => {
10727
11013
  const name = s.replace("skill-", "").replace(/-/g, "_");
10728
11014
  return `export * as ${name} from './${s}/src/index.js';`;
@@ -10738,13 +11024,13 @@ ${exports}
10738
11024
  writeFileSync3(indexPath, content);
10739
11025
  }
10740
11026
  function getMetaPath(skillsDir) {
10741
- return join9(skillsDir, ".meta.json");
11027
+ return join22(skillsDir, ".meta.json");
10742
11028
  }
10743
11029
  function loadMeta(skillsDir) {
10744
11030
  const metaPath2 = getMetaPath(skillsDir);
10745
- if (existsSync5(metaPath2)) {
11031
+ if (existsSync22(metaPath2)) {
10746
11032
  try {
10747
- return JSON.parse(readFileSync3(metaPath2, "utf-8"));
11033
+ return JSON.parse(readFileSync22(metaPath2, "utf-8"));
10748
11034
  } catch {}
10749
11035
  }
10750
11036
  return { skills: {} };
@@ -10757,9 +11043,9 @@ function recordInstall(skillsDir, name) {
10757
11043
  const skillName = normalizeSkillName(name);
10758
11044
  let version = "unknown";
10759
11045
  try {
10760
- const pkgPath = join9(skillsDir, skillName, "package.json");
10761
- if (existsSync5(pkgPath)) {
10762
- const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
11046
+ const pkgPath = join22(skillsDir, skillName, "package.json");
11047
+ if (existsSync22(pkgPath)) {
11048
+ const pkg = JSON.parse(readFileSync22(pkgPath, "utf-8"));
10763
11049
  version = pkg.version || "unknown";
10764
11050
  }
10765
11051
  } catch {}
@@ -10772,12 +11058,12 @@ function recordRemove(skillsDir, name) {
10772
11058
  saveMeta(skillsDir, meta);
10773
11059
  }
10774
11060
  function getInstallMeta(targetDir = process.cwd()) {
10775
- return loadMeta(join9(targetDir, ".skills"));
11061
+ return loadMeta(join22(targetDir, ".skills"));
10776
11062
  }
10777
11063
  function disableSkill(name, targetDir = process.cwd()) {
10778
- const skillsDir = join9(targetDir, ".skills");
11064
+ const skillsDir = join22(targetDir, ".skills");
10779
11065
  const skillName = normalizeSkillName(name);
10780
- if (!existsSync5(join9(skillsDir, skillName)))
11066
+ if (!existsSync22(join22(skillsDir, skillName)))
10781
11067
  return false;
10782
11068
  const meta = loadMeta(skillsDir);
10783
11069
  const disabled = new Set(meta.disabled || []);
@@ -10790,7 +11076,7 @@ function disableSkill(name, targetDir = process.cwd()) {
10790
11076
  return true;
10791
11077
  }
10792
11078
  function enableSkill(name, targetDir = process.cwd()) {
10793
- const skillsDir = join9(targetDir, ".skills");
11079
+ const skillsDir = join22(targetDir, ".skills");
10794
11080
  const meta = loadMeta(skillsDir);
10795
11081
  const disabled = new Set(meta.disabled || []);
10796
11082
  if (!disabled.has(name))
@@ -10802,24 +11088,24 @@ function enableSkill(name, targetDir = process.cwd()) {
10802
11088
  return true;
10803
11089
  }
10804
11090
  function getDisabledSkills(targetDir = process.cwd()) {
10805
- const meta = loadMeta(join9(targetDir, ".skills"));
11091
+ const meta = loadMeta(join22(targetDir, ".skills"));
10806
11092
  return meta.disabled || [];
10807
11093
  }
10808
11094
  function getInstalledSkills(targetDir = process.cwd()) {
10809
- const skillsDir = join9(targetDir, ".skills");
10810
- if (!existsSync5(skillsDir)) {
11095
+ const skillsDir = join22(targetDir, ".skills");
11096
+ if (!existsSync22(skillsDir)) {
10811
11097
  return [];
10812
11098
  }
10813
- return readdirSync4(skillsDir).filter((f) => {
10814
- const fullPath = join9(skillsDir, f);
10815
- return f.startsWith("skill-") && statSync2(fullPath).isDirectory();
11099
+ return readdirSync22(skillsDir).filter((f) => {
11100
+ const fullPath = join22(skillsDir, f);
11101
+ return f.startsWith("skill-") && statSync3(fullPath).isDirectory();
10816
11102
  }).map((f) => f.replace("skill-", ""));
10817
11103
  }
10818
11104
  function removeSkill(name, targetDir = process.cwd()) {
10819
11105
  const skillName = normalizeSkillName(name);
10820
- const skillsDir = join9(targetDir, ".skills");
10821
- const skillPath = join9(skillsDir, skillName);
10822
- if (!existsSync5(skillPath)) {
11106
+ const skillsDir = join22(targetDir, ".skills");
11107
+ const skillPath = join22(skillsDir, skillName);
11108
+ if (!existsSync22(skillPath)) {
10823
11109
  return false;
10824
11110
  }
10825
11111
  rmSync2(skillPath, { recursive: true, force: true });
@@ -10828,27 +11114,31 @@ function removeSkill(name, targetDir = process.cwd()) {
10828
11114
  return true;
10829
11115
  }
10830
11116
  function getAgentSkillsDir(agent, scope = "global", projectDir) {
10831
- const agentDir = `.${agent}`;
10832
- if (scope === "project") {
10833
- return join9(projectDir || process.cwd(), agentDir, "skills");
11117
+ const base = projectDir || process.cwd();
11118
+ switch (agent) {
11119
+ case "pi":
11120
+ return scope === "project" ? join22(base, ".pi", "skills") : join22(homedir22(), ".pi", "agent", "skills");
11121
+ case "opencode":
11122
+ return scope === "project" ? join22(base, ".opencode", "skills") : join22(homedir22(), ".opencode", "skills");
11123
+ default:
11124
+ return scope === "project" ? join22(base, `.${agent}`, "skills") : join22(homedir22(), `.${agent}`, "skills");
10834
11125
  }
10835
- return join9(homedir9(), agentDir, "skills");
10836
11126
  }
10837
11127
  function getAgentSkillPath(name, agent, scope = "global", projectDir) {
10838
11128
  const skillName = normalizeSkillName(name);
10839
- return join9(getAgentSkillsDir(agent, scope, projectDir), skillName);
11129
+ return join22(getAgentSkillsDir(agent, scope, projectDir), skillName);
10840
11130
  }
10841
11131
  function installSkillForAgent(name, options, generateSkillMd) {
10842
11132
  const { agent, scope = "global", projectDir } = options;
10843
11133
  const skillName = normalizeSkillName(name);
10844
11134
  const sourcePath = getSkillPath(name);
10845
- if (!existsSync5(sourcePath)) {
11135
+ if (!existsSync22(sourcePath)) {
10846
11136
  return { skill: name, success: false, error: `Skill '${name}' not found` };
10847
11137
  }
10848
11138
  let skillMdContent = null;
10849
- const skillMdPath = join9(sourcePath, "SKILL.md");
10850
- if (existsSync5(skillMdPath)) {
10851
- skillMdContent = readFileSync3(skillMdPath, "utf-8");
11139
+ const skillMdPath = join22(sourcePath, "SKILL.md");
11140
+ if (existsSync22(skillMdPath)) {
11141
+ skillMdContent = readFileSync22(skillMdPath, "utf-8");
10852
11142
  } else if (generateSkillMd) {
10853
11143
  skillMdContent = generateSkillMd(name);
10854
11144
  }
@@ -10857,17 +11147,12 @@ function installSkillForAgent(name, options, generateSkillMd) {
10857
11147
  }
10858
11148
  const destDir = getAgentSkillPath(name, agent, scope, projectDir);
10859
11149
  if (scope === "global") {
10860
- const agentBaseDir = join9(homedir9(), `.${agent}`);
10861
- if (!existsSync5(agentBaseDir)) {
10862
- const agentLabels = {
10863
- claude: "Claude Code",
10864
- codex: "Codex CLI",
10865
- gemini: "Gemini CLI"
10866
- };
11150
+ const agentBaseDir = agent === "pi" ? join22(homedir22(), ".pi", "agent") : join22(homedir22(), `.${agent}`);
11151
+ if (!existsSync22(agentBaseDir)) {
10867
11152
  return {
10868
11153
  skill: name,
10869
11154
  success: false,
10870
- error: `Agent directory ${agentBaseDir} does not exist. Is ${agentLabels[agent]} installed?`
11155
+ error: `Agent directory ${agentBaseDir} does not exist. Is ${AGENT_LABELS[agent]} installed?`
10871
11156
  };
10872
11157
  }
10873
11158
  try {
@@ -10882,7 +11167,7 @@ function installSkillForAgent(name, options, generateSkillMd) {
10882
11167
  }
10883
11168
  try {
10884
11169
  mkdirSync9(destDir, { recursive: true });
10885
- writeFileSync3(join9(destDir, "SKILL.md"), skillMdContent);
11170
+ writeFileSync3(join22(destDir, "SKILL.md"), skillMdContent);
10886
11171
  return { skill: name, success: true, path: destDir };
10887
11172
  } catch (error) {
10888
11173
  return {
@@ -10895,7 +11180,7 @@ function installSkillForAgent(name, options, generateSkillMd) {
10895
11180
  function removeSkillForAgent(name, options) {
10896
11181
  const { agent, scope = "global", projectDir } = options;
10897
11182
  const destDir = getAgentSkillPath(name, agent, scope, projectDir);
10898
- if (!existsSync5(destDir)) {
11183
+ if (!existsSync22(destDir)) {
10899
11184
  return false;
10900
11185
  }
10901
11186
  rmSync2(destDir, { recursive: true, force: true });
@@ -10903,12 +11188,12 @@ function removeSkillForAgent(name, options) {
10903
11188
  }
10904
11189
  function getSkillDocs(name) {
10905
11190
  const skillPath = getSkillPath(name);
10906
- if (!existsSync22(skillPath))
11191
+ if (!existsSync32(skillPath))
10907
11192
  return null;
10908
11193
  return {
10909
- skillMd: readIfExists(join22(skillPath, "SKILL.md")),
10910
- readme: readIfExists(join22(skillPath, "README.md")),
10911
- claudeMd: readIfExists(join22(skillPath, "CLAUDE.md"))
11194
+ skillMd: readIfExists(join32(skillPath, "SKILL.md")),
11195
+ readme: readIfExists(join32(skillPath, "README.md")),
11196
+ claudeMd: readIfExists(join32(skillPath, "CLAUDE.md"))
10912
11197
  };
10913
11198
  }
10914
11199
  function getSkillBestDoc(name) {
@@ -10919,11 +11204,11 @@ function getSkillBestDoc(name) {
10919
11204
  }
10920
11205
  function getSkillRequirements(name) {
10921
11206
  const skillPath = getSkillPath(name);
10922
- if (!existsSync22(skillPath))
11207
+ if (!existsSync32(skillPath))
10923
11208
  return null;
10924
11209
  const texts = [];
10925
11210
  for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
10926
- const content = readIfExists(join22(skillPath, file));
11211
+ const content = readIfExists(join32(skillPath, file));
10927
11212
  if (content)
10928
11213
  texts.push(content);
10929
11214
  }
@@ -10950,10 +11235,10 @@ function getSkillRequirements(name) {
10950
11235
  }
10951
11236
  let cliCommand = null;
10952
11237
  let dependencies = {};
10953
- const pkgPath = join22(skillPath, "package.json");
10954
- if (existsSync22(pkgPath)) {
11238
+ const pkgPath = join32(skillPath, "package.json");
11239
+ if (existsSync32(pkgPath)) {
10955
11240
  try {
10956
- const pkg = JSON.parse(readFileSync22(pkgPath, "utf-8"));
11241
+ const pkg = JSON.parse(readFileSync32(pkgPath, "utf-8"));
10957
11242
  if (pkg.bin) {
10958
11243
  const binKeys = Object.keys(pkg.bin);
10959
11244
  if (binKeys.length > 0)
@@ -10973,25 +11258,25 @@ async function runSkill(name, args, options = {}) {
10973
11258
  const skillName = normalizeSkillName(name);
10974
11259
  let skillPath;
10975
11260
  if (options.installed) {
10976
- skillPath = join22(process.cwd(), ".skills", skillName);
11261
+ skillPath = join32(process.cwd(), ".skills", skillName);
10977
11262
  } else {
10978
- const installedPath = join22(process.cwd(), ".skills", skillName);
10979
- if (existsSync22(installedPath)) {
11263
+ const installedPath = join32(process.cwd(), ".skills", skillName);
11264
+ if (existsSync32(installedPath)) {
10980
11265
  skillPath = installedPath;
10981
11266
  } else {
10982
11267
  skillPath = getSkillPath(name);
10983
11268
  }
10984
11269
  }
10985
- if (!existsSync22(skillPath)) {
11270
+ if (!existsSync32(skillPath)) {
10986
11271
  return { exitCode: 1, error: `Skill '${name}' not found` };
10987
11272
  }
10988
- const pkgPath = join22(skillPath, "package.json");
10989
- if (!existsSync22(pkgPath)) {
11273
+ const pkgPath = join32(skillPath, "package.json");
11274
+ if (!existsSync32(pkgPath)) {
10990
11275
  return { exitCode: 1, error: `No package.json in skill '${name}'` };
10991
11276
  }
10992
11277
  let entryPoint;
10993
11278
  try {
10994
- const pkg = JSON.parse(readFileSync22(pkgPath, "utf-8"));
11279
+ const pkg = JSON.parse(readFileSync32(pkgPath, "utf-8"));
10995
11280
  if (pkg.bin) {
10996
11281
  const binValues = Object.values(pkg.bin);
10997
11282
  entryPoint = binValues[0];
@@ -11005,12 +11290,12 @@ async function runSkill(name, args, options = {}) {
11005
11290
  } catch {
11006
11291
  return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
11007
11292
  }
11008
- const entryPath = join22(skillPath, entryPoint);
11009
- if (!existsSync22(entryPath)) {
11293
+ const entryPath = join32(skillPath, entryPoint);
11294
+ if (!existsSync32(entryPath)) {
11010
11295
  return { exitCode: 1, error: `Entry point '${entryPoint}' not found in skill '${name}'` };
11011
11296
  }
11012
- const nodeModules = join22(skillPath, "node_modules");
11013
- if (!existsSync22(nodeModules)) {
11297
+ const nodeModules = join32(skillPath, "node_modules");
11298
+ if (!existsSync32(nodeModules)) {
11014
11299
  const install = Bun.spawn(["bun", "install", "--no-save"], {
11015
11300
  cwd: skillPath,
11016
11301
  stdout: "pipe",
@@ -11028,17 +11313,17 @@ async function runSkill(name, args, options = {}) {
11028
11313
  return { exitCode };
11029
11314
  }
11030
11315
  function generateEnvExample(targetDir = process.cwd()) {
11031
- const skillsDir = join22(targetDir, ".skills");
11032
- if (!existsSync22(skillsDir))
11316
+ const skillsDir = join32(targetDir, ".skills");
11317
+ if (!existsSync32(skillsDir))
11033
11318
  return "";
11034
- const dirs = readdirSync22(skillsDir).filter((f) => f.startsWith("skill-") && existsSync22(join22(skillsDir, f, "package.json")));
11319
+ const dirs = readdirSync32(skillsDir).filter((f) => f.startsWith("skill-") && existsSync32(join32(skillsDir, f, "package.json")));
11035
11320
  const envMap = new Map;
11036
11321
  for (const dir of dirs) {
11037
11322
  const skillName = dir.replace("skill-", "");
11038
- const skillPath = join22(skillsDir, dir);
11323
+ const skillPath = join32(skillsDir, dir);
11039
11324
  const texts = [];
11040
11325
  for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example"]) {
11041
- const content = readIfExists(join22(skillPath, file));
11326
+ const content = readIfExists(join32(skillPath, file));
11042
11327
  if (content)
11043
11328
  texts.push(content);
11044
11329
  }
@@ -11083,7 +11368,7 @@ function generateSkillMd(name) {
11083
11368
  if (!meta)
11084
11369
  return null;
11085
11370
  const skillPath = getSkillPath(name);
11086
- if (!existsSync22(skillPath))
11371
+ if (!existsSync32(skillPath))
11087
11372
  return null;
11088
11373
  const frontmatter = [
11089
11374
  "---",
@@ -11092,13 +11377,13 @@ function generateSkillMd(name) {
11092
11377
  "---"
11093
11378
  ].join(`
11094
11379
  `);
11095
- const readme = readIfExists(join22(skillPath, "README.md"));
11096
- const claudeMd = readIfExists(join22(skillPath, "CLAUDE.md"));
11380
+ const readme = readIfExists(join32(skillPath, "README.md"));
11381
+ const claudeMd = readIfExists(join32(skillPath, "CLAUDE.md"));
11097
11382
  let cliCommand = null;
11098
- const pkgPath = join22(skillPath, "package.json");
11099
- if (existsSync22(pkgPath)) {
11383
+ const pkgPath = join32(skillPath, "package.json");
11384
+ if (existsSync32(pkgPath)) {
11100
11385
  try {
11101
- const pkg = JSON.parse(readFileSync22(pkgPath, "utf-8"));
11386
+ const pkg = JSON.parse(readFileSync32(pkgPath, "utf-8"));
11102
11387
  if (pkg.bin) {
11103
11388
  const binKeys = Object.keys(pkg.bin);
11104
11389
  if (binKeys.length > 0)
@@ -11171,23 +11456,23 @@ function extractEnvVars(text) {
11171
11456
  }
11172
11457
  function readIfExists(path) {
11173
11458
  try {
11174
- if (existsSync22(path)) {
11175
- return readFileSync22(path, "utf-8");
11459
+ if (existsSync32(path)) {
11460
+ return readFileSync32(path, "utf-8");
11176
11461
  }
11177
11462
  } catch {}
11178
11463
  return null;
11179
11464
  }
11180
11465
  function getConfigPath(scope) {
11181
11466
  if (scope === "global") {
11182
- return join32(homedir22(), ".skillsrc");
11467
+ return join42(homedir3(), ".skillsrc");
11183
11468
  }
11184
- return join32(process.cwd(), "skills.config.json");
11469
+ return join42(process.cwd(), "skills.config.json");
11185
11470
  }
11186
11471
  function readConfigFile(path) {
11187
- if (!existsSync32(path))
11472
+ if (!existsSync42(path))
11188
11473
  return {};
11189
11474
  try {
11190
- const raw = readFileSync32(path, "utf-8");
11475
+ const raw = readFileSync4(path, "utf-8");
11191
11476
  const parsed = JSON.parse(raw);
11192
11477
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
11193
11478
  return {};
@@ -11218,9 +11503,9 @@ function saveConfig(key, value, scope = "project") {
11218
11503
  }
11219
11504
  const filePath = getConfigPath(scope);
11220
11505
  let existing = {};
11221
- if (existsSync32(filePath)) {
11506
+ if (existsSync42(filePath)) {
11222
11507
  try {
11223
- existing = JSON.parse(readFileSync32(filePath, "utf-8"));
11508
+ existing = JSON.parse(readFileSync4(filePath, "utf-8"));
11224
11509
  if (typeof existing !== "object" || existing === null || Array.isArray(existing)) {
11225
11510
  existing = {};
11226
11511
  }
@@ -11229,7 +11514,7 @@ function saveConfig(key, value, scope = "project") {
11229
11514
  }
11230
11515
  } else {
11231
11516
  const dir = dirname2(filePath);
11232
- if (!existsSync32(dir)) {
11517
+ if (!existsSync42(dir)) {
11233
11518
  mkdirSync22(dir, { recursive: true });
11234
11519
  }
11235
11520
  }
@@ -11237,7 +11522,158 @@ function saveConfig(key, value, scope = "project") {
11237
11522
  writeFileSync22(filePath, JSON.stringify(existing, null, 2) + `
11238
11523
  `);
11239
11524
  }
11240
- var CATEGORIES, SKILLS, __dirname2, SKILLS_DIR, AGENT_TARGETS, ENV_VAR_PATTERN, GENERIC_ENV_PATTERN, VALID_KEYS;
11525
+ function getSchedulesPath(targetDir = process.cwd()) {
11526
+ return join52(targetDir, ".skills", "schedules.json");
11527
+ }
11528
+ function loadSchedules(targetDir = process.cwd()) {
11529
+ const path = getSchedulesPath(targetDir);
11530
+ if (existsSync52(path)) {
11531
+ try {
11532
+ return JSON.parse(readFileSync5(path, "utf-8"));
11533
+ } catch {}
11534
+ }
11535
+ return { version: 1, schedules: [] };
11536
+ }
11537
+ function saveSchedules(data, targetDir = process.cwd()) {
11538
+ const path = getSchedulesPath(targetDir);
11539
+ const dir = join52(targetDir, ".skills");
11540
+ if (!existsSync52(dir))
11541
+ mkdirSync32(dir, { recursive: true });
11542
+ writeFileSync32(path, JSON.stringify(data, null, 2));
11543
+ }
11544
+ function validateCron(expr) {
11545
+ const fields = expr.trim().split(/\s+/);
11546
+ if (fields.length !== 5) {
11547
+ return { valid: false, error: `Expected 5 fields, got ${fields.length}. Format: "minute hour day-of-month month day-of-week"` };
11548
+ }
11549
+ return { valid: true };
11550
+ }
11551
+ function getNextRun(cron, from = new Date) {
11552
+ const { valid } = validateCron(cron);
11553
+ if (!valid)
11554
+ return null;
11555
+ const [minuteF, hourF, domF, monthF, dowF] = cron.trim().split(/\s+/);
11556
+ function parseField(f, min, max) {
11557
+ if (f === "*")
11558
+ return Array.from({ length: max - min + 1 }, (_, i) => i + min);
11559
+ if (f.startsWith("*/")) {
11560
+ const step = parseInt(f.slice(2));
11561
+ if (isNaN(step))
11562
+ return [];
11563
+ const vals = [];
11564
+ for (let i = min;i <= max; i += step)
11565
+ vals.push(i);
11566
+ return vals;
11567
+ }
11568
+ return f.split(",").flatMap((part) => {
11569
+ if (part.includes("-")) {
11570
+ const [lo, hi] = part.split("-").map(Number);
11571
+ return Array.from({ length: hi - lo + 1 }, (_, i) => i + lo);
11572
+ }
11573
+ const n = parseInt(part);
11574
+ return isNaN(n) ? [] : [n];
11575
+ });
11576
+ }
11577
+ const minutes = parseField(minuteF, 0, 59);
11578
+ const hours = parseField(hourF, 0, 23);
11579
+ const doms = parseField(domF, 1, 31);
11580
+ const months = parseField(monthF, 1, 12);
11581
+ const dows = parseField(dowF, 0, 6);
11582
+ const candidate = new Date(from);
11583
+ candidate.setSeconds(0, 0);
11584
+ candidate.setMinutes(candidate.getMinutes() + 1);
11585
+ const limit = new Date(from);
11586
+ limit.setFullYear(limit.getFullYear() + 1);
11587
+ while (candidate < limit) {
11588
+ const month = candidate.getMonth() + 1;
11589
+ const dom = candidate.getDate();
11590
+ const dow = candidate.getDay();
11591
+ const hour = candidate.getHours();
11592
+ const minute = candidate.getMinutes();
11593
+ if (!months.includes(month)) {
11594
+ candidate.setMonth(candidate.getMonth() + 1, 1);
11595
+ candidate.setHours(0, 0, 0, 0);
11596
+ continue;
11597
+ }
11598
+ if (!doms.includes(dom) || !dows.includes(dow)) {
11599
+ candidate.setDate(candidate.getDate() + 1);
11600
+ candidate.setHours(0, 0, 0, 0);
11601
+ continue;
11602
+ }
11603
+ if (!hours.includes(hour)) {
11604
+ candidate.setHours(candidate.getHours() + 1, 0, 0, 0);
11605
+ continue;
11606
+ }
11607
+ if (!minutes.includes(minute)) {
11608
+ candidate.setMinutes(candidate.getMinutes() + 1, 0, 0);
11609
+ continue;
11610
+ }
11611
+ return new Date(candidate);
11612
+ }
11613
+ return null;
11614
+ }
11615
+ function addSchedule(skill, cron, options = {}) {
11616
+ const { valid, error } = validateCron(cron);
11617
+ if (!valid)
11618
+ return { schedule: null, error };
11619
+ const data = loadSchedules(options.targetDir);
11620
+ const id = `${skill}-${Date.now()}`;
11621
+ const now = new Date;
11622
+ const nextRun = getNextRun(cron, now);
11623
+ const schedule = {
11624
+ id,
11625
+ name: options.name || `${skill} (${cron})`,
11626
+ skill,
11627
+ cron,
11628
+ args: options.args,
11629
+ enabled: true,
11630
+ createdAt: now.toISOString(),
11631
+ nextRun: nextRun?.toISOString()
11632
+ };
11633
+ data.schedules.push(schedule);
11634
+ saveSchedules(data, options.targetDir);
11635
+ return { schedule };
11636
+ }
11637
+ function listSchedules(targetDir) {
11638
+ return loadSchedules(targetDir).schedules;
11639
+ }
11640
+ function removeSchedule(idOrName, targetDir) {
11641
+ const data = loadSchedules(targetDir);
11642
+ const before = data.schedules.length;
11643
+ data.schedules = data.schedules.filter((s) => s.id !== idOrName && s.name !== idOrName);
11644
+ if (data.schedules.length === before)
11645
+ return false;
11646
+ saveSchedules(data, targetDir);
11647
+ return true;
11648
+ }
11649
+ function setScheduleEnabled(idOrName, enabled, targetDir) {
11650
+ const data = loadSchedules(targetDir);
11651
+ const schedule = data.schedules.find((s) => s.id === idOrName || s.name === idOrName);
11652
+ if (!schedule)
11653
+ return false;
11654
+ schedule.enabled = enabled;
11655
+ if (enabled) {
11656
+ schedule.nextRun = getNextRun(schedule.cron)?.toISOString();
11657
+ }
11658
+ saveSchedules(data, targetDir);
11659
+ return true;
11660
+ }
11661
+ function getDueSchedules(targetDir) {
11662
+ const now = new Date;
11663
+ return listSchedules(targetDir).filter((s) => s.enabled && s.nextRun && new Date(s.nextRun) <= now);
11664
+ }
11665
+ function recordScheduleRun(id, status, targetDir) {
11666
+ const data = loadSchedules(targetDir);
11667
+ const schedule = data.schedules.find((s) => s.id === id);
11668
+ if (!schedule)
11669
+ return;
11670
+ const now = new Date;
11671
+ schedule.lastRun = now.toISOString();
11672
+ schedule.lastRunStatus = status;
11673
+ schedule.nextRun = getNextRun(schedule.cron, now)?.toISOString();
11674
+ saveSchedules(data, targetDir);
11675
+ }
11676
+ 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;
11241
11677
  var init_dist = __esm(() => {
11242
11678
  CATEGORIES = [
11243
11679
  "Development Tools",
@@ -12275,6 +12711,20 @@ var init_dist = __esm(() => {
12275
12711
  category: "Design & Branding",
12276
12712
  tags: ["testimonials", "graphics", "social-proof", "marketing"]
12277
12713
  },
12714
+ {
12715
+ name: "colorextract",
12716
+ displayName: "Color Extract",
12717
+ description: "Extract complete color palettes from screenshots and images using Claude Vision. Outputs open-styles compatible profiles.",
12718
+ category: "Design & Branding",
12719
+ tags: ["colors", "palette", "design", "vision", "screenshot", "extract", "open-styles"]
12720
+ },
12721
+ {
12722
+ name: "siteanalyze",
12723
+ displayName: "Site Analyze",
12724
+ description: "Analyze any website's design system \u2014 detects shadcn/ui, Tailwind, extracts colors, typography, and components via Playwright + Claude Vision.",
12725
+ category: "Design & Branding",
12726
+ tags: ["design", "shadcn", "tailwind", "colors", "typography", "playwright", "analysis", "open-styles"]
12727
+ },
12278
12728
  {
12279
12729
  name: "browse",
12280
12730
  displayName: "Browse",
@@ -12677,7 +13127,14 @@ var init_dist = __esm(() => {
12677
13127
  ];
12678
13128
  __dirname2 = dirname(fileURLToPath(import.meta.url));
12679
13129
  SKILLS_DIR = findSkillsDir();
12680
- AGENT_TARGETS = ["claude", "codex", "gemini"];
13130
+ AGENT_TARGETS = ["claude", "codex", "gemini", "pi", "opencode"];
13131
+ AGENT_LABELS = {
13132
+ claude: "Claude Code",
13133
+ codex: "Codex CLI",
13134
+ gemini: "Gemini CLI",
13135
+ pi: "pi.dev",
13136
+ opencode: "OpenCode"
13137
+ };
12681
13138
  ENV_VAR_PATTERN = /\b([A-Z][A-Z0-9_]{2,}(?:_API_KEY|_KEY|_TOKEN|_SECRET|_URL|_ID|_PASSWORD|_ENDPOINT|_REGION|_BUCKET))\b/g;
12682
13139
  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;
12683
13140
  VALID_KEYS = {
@@ -12693,21 +13150,21 @@ __export(exports_auth, {
12693
13150
  loginWithCredentials: () => loginWithCredentials,
12694
13151
  getCredentials: () => getCredentials
12695
13152
  });
12696
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
13153
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
12697
13154
  import { join as join10 } from "path";
12698
- import { homedir as homedir10 } from "os";
13155
+ import { homedir as homedir4 } from "os";
12699
13156
  async function getCredentials(service) {
12700
13157
  try {
12701
- const { getSecret } = await import(`${homedir10()}/Workspace/hasna/opensource/opensourcedev/open-secrets/src/store.js`);
13158
+ const { getSecret } = await import(`${homedir4()}/Workspace/hasna/opensource/opensourcedev/open-secrets/src/store.js`);
12702
13159
  const email = getSecret(`${service}_email`) ?? getSecret(`${service}_username`) ?? getSecret(`${service}_login`);
12703
13160
  const password = getSecret(`${service}_password`) ?? getSecret(`${service}_pass`);
12704
13161
  if (email?.value && password?.value) {
12705
13162
  return { email: email.value, password: password.value };
12706
13163
  }
12707
13164
  } catch {}
12708
- const secretsPath = join10(homedir10(), ".secrets");
12709
- if (existsSync6(secretsPath)) {
12710
- const content = readFileSync4(secretsPath, "utf8");
13165
+ const secretsPath = join10(homedir4(), ".secrets");
13166
+ if (existsSync7(secretsPath)) {
13167
+ const content = readFileSync6(secretsPath, "utf8");
12711
13168
  const lines = content.split(`
12712
13169
  `);
12713
13170
  const prefix = service.toUpperCase().replace(/[^A-Z0-9]/g, "_");
@@ -12901,6 +13358,7 @@ var init_skills_runner = __esm(() => {
12901
13358
  var exports_cron_manager = {};
12902
13359
  __export(exports_cron_manager, {
12903
13360
  runCronJobNow: () => runCronJobNow,
13361
+ pruneCronEvents: () => pruneCronEvents,
12904
13362
  loadCronJobsOnStartup: () => loadCronJobsOnStartup,
12905
13363
  listCronJobs: () => listCronJobs,
12906
13364
  getCronJob: () => getCronJob,
@@ -12993,6 +13451,13 @@ function getCronEvents(jobId, limit = 10) {
12993
13451
  const rows = db.query("SELECT * FROM cron_events WHERE job_id = ? ORDER BY started_at DESC LIMIT ?").all(jobId, limit);
12994
13452
  return rows.map((r) => ({ ...r, success: r.success === 1, result: r.result ? JSON.parse(r.result) : undefined }));
12995
13453
  }
13454
+ function pruneCronEvents(retentionDays = 7) {
13455
+ ensureCronTable();
13456
+ const db = getDatabase();
13457
+ const cutoff = new Date(Date.now() - retentionDays * 86400000).toISOString();
13458
+ const result = db.prepare("DELETE FROM cron_events WHERE started_at < ?").run(cutoff);
13459
+ return result.changes;
13460
+ }
12996
13461
  async function executeCronJob(job) {
12997
13462
  ensureCronTable();
12998
13463
  const db = getDatabase();
@@ -13061,6 +13526,9 @@ function unregisterCronJob(id) {
13061
13526
  }
13062
13527
  function loadCronJobsOnStartup() {
13063
13528
  try {
13529
+ const pruned = pruneCronEvents();
13530
+ if (pruned > 0)
13531
+ console.error(`[browser] Pruned ${pruned} old cron event(s)`);
13064
13532
  const jobs = listCronJobs();
13065
13533
  for (const job of jobs) {
13066
13534
  if (job.enabled)
@@ -13297,9 +13765,9 @@ async function tryReplayAuth(page, domain) {
13297
13765
  return { replayed: false };
13298
13766
  if (flow.storage_state_path) {
13299
13767
  try {
13300
- const { existsSync: existsSync7, readFileSync: readFileSync5 } = await import("fs");
13301
- if (existsSync7(flow.storage_state_path)) {
13302
- const state = JSON.parse(readFileSync5(flow.storage_state_path, "utf8"));
13768
+ const { existsSync: existsSync8, readFileSync: readFileSync7 } = await import("fs");
13769
+ if (existsSync8(flow.storage_state_path)) {
13770
+ const state = JSON.parse(readFileSync7(flow.storage_state_path, "utf8"));
13303
13771
  if (state.cookies?.length) {
13304
13772
  await page.context().addCookies(state.cookies);
13305
13773
  await page.reload();
@@ -13460,17 +13928,17 @@ function listRuns(scriptId) {
13460
13928
  }));
13461
13929
  }
13462
13930
  function migrateJsonScripts() {
13463
- const { existsSync: existsSync7, readdirSync: readdirSync5, readFileSync: readFileSync5 } = __require("fs");
13931
+ const { existsSync: existsSync8, readdirSync: readdirSync6, readFileSync: readFileSync7 } = __require("fs");
13464
13932
  const { join: join11 } = __require("path");
13465
- const { getDataDir: getDataDir4 } = (init_schema(), __toCommonJS(exports_schema));
13466
- const dir = join11(getDataDir4(), "scripts");
13467
- if (!existsSync7(dir))
13933
+ const { getDataDir: getDataDir2 } = (init_schema(), __toCommonJS(exports_schema));
13934
+ const dir = join11(getDataDir2(), "scripts");
13935
+ if (!existsSync8(dir))
13468
13936
  return 0;
13469
- const files = readdirSync5(dir).filter((f) => f.endsWith(".json"));
13937
+ const files = readdirSync6(dir).filter((f) => f.endsWith(".json"));
13470
13938
  let migrated = 0;
13471
13939
  for (const file of files) {
13472
13940
  try {
13473
- const raw = JSON.parse(readFileSync5(join11(dir, file), "utf8"));
13941
+ const raw = JSON.parse(readFileSync7(join11(dir, file), "utf8"));
13474
13942
  if (!raw.name || !raw.steps)
13475
13943
  continue;
13476
13944
  if (getScriptByName(raw.name))
@@ -13913,7 +14381,6 @@ __export(exports_datasets, {
13913
14381
  import { randomUUID as randomUUID15 } from "crypto";
13914
14382
  import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync10 } from "fs";
13915
14383
  import { join as join11 } from "path";
13916
- import { homedir as homedir11 } from "os";
13917
14384
  function saveDataset(data) {
13918
14385
  const db = getDatabase();
13919
14386
  const id = randomUUID15();
@@ -13951,7 +14418,7 @@ function exportDataset(name, format) {
13951
14418
  const dataset = getDatasetByName(name);
13952
14419
  if (!dataset)
13953
14420
  throw new Error(`Dataset '${name}' not found`);
13954
- const dir = join11(process.env["BROWSER_DATA_DIR"] ?? join11(homedir11(), ".browser"), "exports");
14421
+ const dir = join11(getDataDir(), "exports");
13955
14422
  mkdirSync10(dir, { recursive: true });
13956
14423
  const filename = `${name}.${format}`;
13957
14424
  const path = join11(dir, filename);
@@ -13981,9 +14448,10 @@ function exportDataset(name, format) {
13981
14448
  }
13982
14449
  var init_datasets = __esm(() => {
13983
14450
  init_schema();
14451
+ init_schema();
13984
14452
  });
13985
14453
 
13986
- // ../open-mementos/dist/index.js
14454
+ // node_modules/@hasna/mementos/dist/index.js
13987
14455
  var exports_dist2 = {};
13988
14456
  __export(exports_dist2, {
13989
14457
  withMemoryLock: () => withMemoryLock,
@@ -14093,17 +14561,20 @@ __export(exports_dist2, {
14093
14561
  DEFAULT_CONFIG: () => DEFAULT_CONFIG
14094
14562
  });
14095
14563
  import { Database as Database2 } from "bun:sqlite";
14096
- import { existsSync as existsSync7, mkdirSync as mkdirSync11 } from "fs";
14564
+ import { existsSync as existsSync8, mkdirSync as mkdirSync11 } from "fs";
14097
14565
  import { dirname as dirname3, join as join12, resolve as resolve2 } from "path";
14098
- import { existsSync as existsSync23, mkdirSync as mkdirSync23, readFileSync as readFileSync5, readdirSync as readdirSync5, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3 } from "fs";
14099
- import { homedir as homedir12 } from "os";
14566
+ import { existsSync as existsSync23, mkdirSync as mkdirSync23, readFileSync as readFileSync8, readdirSync as readdirSync6, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3 } from "fs";
14567
+ import { homedir as homedir6 } from "os";
14100
14568
  import { basename as basename2, dirname as dirname22, join as join23, resolve as resolve22 } from "path";
14101
- import { existsSync as existsSync33, mkdirSync as mkdirSync32, readFileSync as readFileSync23, writeFileSync as writeFileSync23 } from "fs";
14569
+ import { existsSync as existsSync33, mkdirSync as mkdirSync33, readFileSync as readFileSync23, writeFileSync as writeFileSync23 } from "fs";
14102
14570
  import { homedir as homedir23 } from "os";
14103
14571
  import { join as join33 } from "path";
14104
- import { existsSync as existsSync42, mkdirSync as mkdirSync42, readFileSync as readFileSync33, writeFileSync as writeFileSync32 } from "fs";
14572
+ import { existsSync as existsSync43, mkdirSync as mkdirSync42, readFileSync as readFileSync33, writeFileSync as writeFileSync33 } from "fs";
14105
14573
  import { homedir as homedir32 } from "os";
14106
- import { join as join42 } from "path";
14574
+ import { join as join43 } from "path";
14575
+ function __exportSetter2(name, newValue) {
14576
+ this[name] = __returnValue2.bind(null, newValue);
14577
+ }
14107
14578
  function isInMemoryDb(path) {
14108
14579
  return path === ":memory:" || path.startsWith("file::memory:");
14109
14580
  }
@@ -14111,7 +14582,7 @@ function findNearestMementosDb(startDir) {
14111
14582
  let dir = resolve2(startDir);
14112
14583
  while (true) {
14113
14584
  const candidate = join12(dir, ".mementos", "mementos.db");
14114
- if (existsSync7(candidate))
14585
+ if (existsSync8(candidate))
14115
14586
  return candidate;
14116
14587
  const parent = dirname3(dir);
14117
14588
  if (parent === dir)
@@ -14123,7 +14594,7 @@ function findNearestMementosDb(startDir) {
14123
14594
  function findGitRoot(startDir) {
14124
14595
  let dir = resolve2(startDir);
14125
14596
  while (true) {
14126
- if (existsSync7(join12(dir, ".git")))
14597
+ if (existsSync8(join12(dir, ".git")))
14127
14598
  return dir;
14128
14599
  const parent = dirname3(dir);
14129
14600
  if (parent === dir)
@@ -14153,7 +14624,7 @@ function ensureDir2(filePath) {
14153
14624
  if (isInMemoryDb(filePath))
14154
14625
  return;
14155
14626
  const dir = dirname3(resolve2(filePath));
14156
- if (!existsSync7(dir)) {
14627
+ if (!existsSync8(dir)) {
14157
14628
  mkdirSync11(dir, { recursive: true });
14158
14629
  }
14159
14630
  }
@@ -16004,11 +16475,11 @@ function isValidCategory(value) {
16004
16475
  return VALID_CATEGORIES.includes(value);
16005
16476
  }
16006
16477
  function loadConfig2() {
16007
- const configPath = join23(homedir12(), ".mementos", "config.json");
16478
+ const configPath = join23(homedir6(), ".mementos", "config.json");
16008
16479
  let fileConfig = {};
16009
16480
  if (existsSync23(configPath)) {
16010
16481
  try {
16011
- const raw = readFileSync5(configPath, "utf-8");
16482
+ const raw = readFileSync8(configPath, "utf-8");
16012
16483
  fileConfig = JSON.parse(raw);
16013
16484
  } catch {}
16014
16485
  }
@@ -16031,17 +16502,17 @@ function loadConfig2() {
16031
16502
  return merged;
16032
16503
  }
16033
16504
  function profilesDir() {
16034
- return join23(homedir12(), ".mementos", "profiles");
16505
+ return join23(homedir6(), ".mementos", "profiles");
16035
16506
  }
16036
16507
  function globalConfigPath() {
16037
- return join23(homedir12(), ".mementos", "config.json");
16508
+ return join23(homedir6(), ".mementos", "config.json");
16038
16509
  }
16039
16510
  function readGlobalConfig() {
16040
16511
  const p = globalConfigPath();
16041
16512
  if (!existsSync23(p))
16042
16513
  return {};
16043
16514
  try {
16044
- return JSON.parse(readFileSync5(p, "utf-8"));
16515
+ return JSON.parse(readFileSync8(p, "utf-8"));
16045
16516
  } catch {
16046
16517
  return {};
16047
16518
  }
@@ -16071,7 +16542,7 @@ function listProfiles2() {
16071
16542
  const dir = profilesDir();
16072
16543
  if (!existsSync23(dir))
16073
16544
  return [];
16074
- return readdirSync5(dir).filter((f) => f.endsWith(".db")).map((f) => basename2(f, ".db")).sort();
16545
+ return readdirSync6(dir).filter((f) => f.endsWith(".db")).map((f) => basename2(f, ".db")).sort();
16075
16546
  }
16076
16547
  function deleteProfile2(name) {
16077
16548
  const dbPath = join23(profilesDir(), `${name}.db`);
@@ -16433,7 +16904,7 @@ function runCleanup(config, db) {
16433
16904
  function getAgentSyncDir(agentName) {
16434
16905
  const dir = join33(homedir23(), ".mementos", "agents", agentName);
16435
16906
  if (!existsSync33(dir)) {
16436
- mkdirSync32(dir, { recursive: true });
16907
+ mkdirSync33(dir, { recursive: true });
16437
16908
  }
16438
16909
  return dir;
16439
16910
  }
@@ -17186,7 +17657,7 @@ ${matched.map((m) => `- ${m.key}: ${m.value.slice(0, 120)}${m.value.length > 120
17186
17657
  };
17187
17658
  }
17188
17659
  function readConfig() {
17189
- if (!existsSync42(CONFIG_PATH))
17660
+ if (!existsSync43(CONFIG_PATH))
17190
17661
  return {};
17191
17662
  try {
17192
17663
  const raw = readFileSync33(CONFIG_PATH, "utf-8");
@@ -17196,10 +17667,10 @@ function readConfig() {
17196
17667
  }
17197
17668
  }
17198
17669
  function writeConfig(config) {
17199
- if (!existsSync42(CONFIG_DIR)) {
17670
+ if (!existsSync43(CONFIG_DIR)) {
17200
17671
  mkdirSync42(CONFIG_DIR, { recursive: true });
17201
17672
  }
17202
- writeFileSync32(CONFIG_PATH, JSON.stringify(config, null, 2) + `
17673
+ writeFileSync33(CONFIG_PATH, JSON.stringify(config, null, 2) + `
17203
17674
  `, "utf-8");
17204
17675
  }
17205
17676
  function getActiveModel() {
@@ -17216,13 +17687,13 @@ function clearActiveModel() {
17216
17687
  delete config.activeModel;
17217
17688
  writeConfig(config);
17218
17689
  }
17219
- var __defProp2, __export2 = (target, all) => {
17690
+ var __defProp2, __returnValue2 = (v) => v, __export2 = (target, all) => {
17220
17691
  for (var name in all)
17221
17692
  __defProp2(target, name, {
17222
17693
  get: all[name],
17223
17694
  enumerable: true,
17224
17695
  configurable: true,
17225
- set: (newValue) => all[name] = () => newValue
17696
+ set: __exportSetter2.bind(all, name)
17226
17697
  });
17227
17698
  }, __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.
17228
17699
  Given text, extract facts worth remembering as structured JSON.
@@ -18558,8 +19029,8 @@ Return only a number 0-10.`);
18558
19029
  keepLonger: true
18559
19030
  };
18560
19031
  _stats = { checked: 0, skipped: 0, updated: 0 };
18561
- CONFIG_DIR = join42(homedir32(), ".mementos");
18562
- CONFIG_PATH = join42(CONFIG_DIR, "config.json");
19032
+ CONFIG_DIR = join43(homedir32(), ".mementos");
19033
+ CONFIG_PATH = join43(CONFIG_DIR, "config.json");
18563
19034
  });
18564
19035
 
18565
19036
  // src/lib/page-memory.ts
@@ -18637,7 +19108,7 @@ var init_page_memory = __esm(() => {
18637
19108
  inMemoryCache = new Map;
18638
19109
  });
18639
19110
 
18640
- // ../open-conversations/dist/index.js
19111
+ // node_modules/@hasna/conversations/dist/index.js
18641
19112
  var exports_dist3 = {};
18642
19113
  __export(exports_dist3, {
18643
19114
  useSpaceMessages: () => useSpaceMessages,
@@ -18646,6 +19117,7 @@ __export(exports_dist3, {
18646
19117
  updateProject: () => updateProject,
18647
19118
  unpinMessage: () => unpinMessage,
18648
19119
  unarchiveSpace: () => unarchiveSpace,
19120
+ tryBulkAcquireLock: () => tryBulkAcquireLock,
18649
19121
  startPolling: () => startPolling,
18650
19122
  sendMessage: () => sendMessage,
18651
19123
  searchMessages: () => searchMessages,
@@ -18654,6 +19126,7 @@ __export(exports_dist3, {
18654
19126
  renameAgent: () => renameAgent,
18655
19127
  removeReaction: () => removeReaction,
18656
19128
  removePresence: () => removePresence,
19129
+ releaseStaleAgentLocks: () => releaseStaleAgentLocks,
18657
19130
  releaseLock: () => releaseLock2,
18658
19131
  registerAgent: () => registerAgent4,
18659
19132
  readMessages: () => readMessages,
@@ -18665,6 +19138,7 @@ __export(exports_dist3, {
18665
19138
  listSpaces: () => listSpaces,
18666
19139
  listSessions: () => listSessions3,
18667
19140
  listProjects: () => listProjects3,
19141
+ listLocksEnriched: () => listLocksEnriched,
18668
19142
  listLocks: () => listLocks,
18669
19143
  listHotSessions: () => listHotSessions,
18670
19144
  listAgents: () => listAgents3,
@@ -18716,22 +19190,28 @@ __export(exports_dist3, {
18716
19190
  import { Database as Database3 } from "bun:sqlite";
18717
19191
  import { mkdirSync as mkdirSync12 } from "fs";
18718
19192
  import { join as join13, dirname as dirname5 } from "path";
18719
- import { homedir as homedir13 } from "os";
19193
+ import { homedir as homedir7 } from "os";
18720
19194
  import { randomUUID as randomUUID16 } from "crypto";
18721
- import { mkdirSync as mkdirSync24, copyFileSync as copyFileSync3, statSync as statSync3 } from "fs";
19195
+ import { mkdirSync as mkdirSync24, copyFileSync as copyFileSync4, statSync as statSync4 } from "fs";
18722
19196
  import { join as join34 } from "path";
18723
19197
  import { homedir as homedir33 } from "os";
18724
- import { readFileSync as readFileSync6 } from "fs";
19198
+ import { readFileSync as readFileSync9 } from "fs";
18725
19199
  import { join as join24 } from "path";
18726
19200
  import { homedir as homedir24 } from "os";
18727
19201
  import { randomUUID as randomUUID22 } from "crypto";
18728
- import { readFileSync as readFileSync24, writeFileSync as writeFileSync6, mkdirSync as mkdirSync33 } from "fs";
18729
- import { join as join43, dirname as dirname23 } from "path";
19202
+ import { readFileSync as readFileSync24, writeFileSync as writeFileSync6, mkdirSync as mkdirSync34 } from "fs";
19203
+ import { join as join44, dirname as dirname23 } from "path";
18730
19204
  import { homedir as homedir42 } from "os";
19205
+ function __accessProp2(key) {
19206
+ return this[key];
19207
+ }
19208
+ function __exportSetter3(name, newValue) {
19209
+ this[name] = __returnValue3.bind(null, newValue);
19210
+ }
18731
19211
  function getDbPath2() {
18732
19212
  if (process.env.CONVERSATIONS_DB_PATH)
18733
19213
  return process.env.CONVERSATIONS_DB_PATH;
18734
- return join13(homedir13(), ".conversations", "messages.db");
19214
+ return join13(homedir7(), ".conversations", "messages.db");
18735
19215
  }
18736
19216
  function getDb() {
18737
19217
  if (db)
@@ -18883,6 +19363,9 @@ function getDb() {
18883
19363
  if (!spaceColNames.includes("archived_at")) {
18884
19364
  db.exec("ALTER TABLE spaces ADD COLUMN archived_at TEXT");
18885
19365
  }
19366
+ if (!spaceColNames.includes("topic")) {
19367
+ db.exec("ALTER TABLE spaces ADD COLUMN topic TEXT");
19368
+ }
18886
19369
  const msgCols2 = db.prepare("PRAGMA table_info(messages)").all();
18887
19370
  const colNames2 = msgCols2.map((c) => c.name);
18888
19371
  if (!colNames2.includes("edited_at")) {
@@ -18930,6 +19413,30 @@ function getDb() {
18930
19413
  if (!presenceColNames.includes("project_id")) {
18931
19414
  db.exec("ALTER TABLE agent_presence ADD COLUMN project_id TEXT");
18932
19415
  }
19416
+ db.exec(`
19417
+ CREATE TABLE IF NOT EXISTS message_read_receipts (
19418
+ message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
19419
+ agent TEXT NOT NULL,
19420
+ read_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
19421
+ PRIMARY KEY (message_id, agent)
19422
+ )
19423
+ `);
19424
+ db.exec("CREATE INDEX IF NOT EXISTS idx_read_receipts_message ON message_read_receipts(message_id)");
19425
+ db.exec("CREATE INDEX IF NOT EXISTS idx_read_receipts_agent ON message_read_receipts(agent)");
19426
+ db.exec(`
19427
+ CREATE TABLE IF NOT EXISTS message_mentions (
19428
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
19429
+ message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
19430
+ mentioned_agent TEXT NOT NULL,
19431
+ from_agent TEXT NOT NULL,
19432
+ space TEXT,
19433
+ notified_at TEXT,
19434
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now'))
19435
+ )
19436
+ `);
19437
+ db.exec("CREATE INDEX IF NOT EXISTS idx_mentions_agent ON message_mentions(mentioned_agent)");
19438
+ db.exec("CREATE INDEX IF NOT EXISTS idx_mentions_message ON message_mentions(message_id)");
19439
+ db.exec("CREATE INDEX IF NOT EXISTS idx_mentions_notified ON message_mentions(notified_at)");
18933
19440
  const ftsExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='messages_fts'").get();
18934
19441
  if (!ftsExists) {
18935
19442
  db.exec(`
@@ -18979,7 +19486,7 @@ function loadConfig3() {
18979
19486
  if (cachedConfig && now2 - configLoadedAt < CONFIG_CACHE_MS)
18980
19487
  return cachedConfig;
18981
19488
  try {
18982
- const raw = readFileSync6(getConfigPath2(), "utf-8");
19489
+ const raw = readFileSync9(getConfigPath2(), "utf-8");
18983
19490
  cachedConfig = JSON.parse(raw);
18984
19491
  configLoadedAt = now2;
18985
19492
  return cachedConfig;
@@ -19115,8 +19622,8 @@ function sendMessage(opts) {
19115
19622
  const attachmentInfos = [];
19116
19623
  for (const att of opts.attachments) {
19117
19624
  const destPath = join34(attachmentsDir, att.name);
19118
- copyFileSync3(att.source_path, destPath);
19119
- const stat = statSync3(destPath);
19625
+ copyFileSync4(att.source_path, destPath);
19626
+ const stat = statSync4(destPath);
19120
19627
  attachmentInfos.push({
19121
19628
  name: att.name,
19122
19629
  path: destPath,
@@ -19128,6 +19635,12 @@ function sendMessage(opts) {
19128
19635
  db2.prepare("UPDATE messages SET attachments = ? WHERE id = ?").run(attachmentsJson, message.id);
19129
19636
  message.attachments = attachmentInfos;
19130
19637
  }
19638
+ if (opts.space) {
19639
+ const mentions = parseMentions(opts.content);
19640
+ if (mentions.length > 0) {
19641
+ processMentions(message.id, opts.from, opts.space, mentions, db2);
19642
+ }
19643
+ }
19131
19644
  fireWebhooks(message);
19132
19645
  return message;
19133
19646
  }
@@ -19166,11 +19679,34 @@ function readMessages(opts = {}) {
19166
19679
  if (opts.unread_only) {
19167
19680
  conditions.push("read_at IS NULL");
19168
19681
  }
19682
+ if (opts.threads_only) {
19683
+ conditions.push("reply_to IS NULL");
19684
+ }
19685
+ if (opts.mentions_only) {
19686
+ conditions.push(`id IN (SELECT message_id FROM message_mentions WHERE mentioned_agent = ?)`);
19687
+ params.push(opts.mentions_only.toLowerCase());
19688
+ }
19689
+ const isLatest = opts.latest && opts.latest > 0;
19169
19690
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
19170
- const resolvedLimit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
19171
- const order = opts.order?.toLowerCase() === "desc" ? "DESC" : "ASC";
19172
- const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at ${order}, id ${order} LIMIT ${resolvedLimit}`).all(...params);
19173
- const messages = rows.map(parseMessage);
19691
+ const resolvedLimit = isLatest ? Math.floor(opts.latest) : Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
19692
+ const order = isLatest ? "DESC" : opts.order?.toLowerCase() === "desc" ? "DESC" : "ASC";
19693
+ const resolvedOffset = opts.offset && opts.offset > 0 ? Math.floor(opts.offset) : 0;
19694
+ const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at ${order}, id ${order} LIMIT ${resolvedLimit} OFFSET ${resolvedOffset}`).all(...params);
19695
+ let messages = rows.map(parseMessage);
19696
+ if (opts.include_reply_counts && messages.length > 0) {
19697
+ const db22 = getDb();
19698
+ 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));
19699
+ const countMap = new Map(counts.map((r) => [r.reply_to, r.c]));
19700
+ messages = messages.map((m) => ({ ...m, reply_count: countMap.get(m.id) ?? 0 }));
19701
+ }
19702
+ if (opts.max_content_length && opts.max_content_length > 0) {
19703
+ messages = messages.map((m) => {
19704
+ if (m.content.length > opts.max_content_length) {
19705
+ return { ...m, content: m.content.slice(0, opts.max_content_length) + "\u2026", truncated: true };
19706
+ }
19707
+ return m;
19708
+ });
19709
+ }
19174
19710
  if (opts.compact)
19175
19711
  return messages.map(compactMessage);
19176
19712
  return messages;
@@ -19351,6 +19887,14 @@ function searchMessages(opts) {
19351
19887
  extraWhere += " AND m.to_agent = ?";
19352
19888
  ftsParams.push(opts.to);
19353
19889
  }
19890
+ if (opts.since) {
19891
+ extraWhere += " AND m.created_at >= ?";
19892
+ ftsParams.push(opts.since);
19893
+ }
19894
+ if (opts.until) {
19895
+ extraWhere += " AND m.created_at <= ?";
19896
+ ftsParams.push(opts.until);
19897
+ }
19354
19898
  const orderClause = sortByRelevance ? "ORDER BY rank" : "ORDER BY m.created_at DESC, m.id DESC";
19355
19899
  const rows2 = db2.prepare(`SELECT m.*, rank,
19356
19900
  snippet(messages_fts, 0, '**', '**', '...', 20) as snippet
@@ -19383,6 +19927,14 @@ function searchMessages(opts) {
19383
19927
  conditions.push("to_agent = ?");
19384
19928
  params.push(opts.to);
19385
19929
  }
19930
+ if (opts.since) {
19931
+ conditions.push("created_at >= ?");
19932
+ params.push(opts.since);
19933
+ }
19934
+ if (opts.until) {
19935
+ conditions.push("created_at <= ?");
19936
+ params.push(opts.until);
19937
+ }
19386
19938
  const where = `WHERE ${conditions.join(" AND ")}`;
19387
19939
  const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at DESC, id DESC LIMIT ${limit}`).all(...params);
19388
19940
  return rows.map((row) => {
@@ -19390,6 +19942,26 @@ function searchMessages(opts) {
19390
19942
  return { ...msg, snippet: null, relevance_score: 0 };
19391
19943
  });
19392
19944
  }
19945
+ function parseMentions(content) {
19946
+ const matches = content.match(/@([a-zA-Z0-9_-]+)/g) ?? [];
19947
+ return [...new Set(matches.map((m) => m.slice(1).toLowerCase()))];
19948
+ }
19949
+ async function processMentions(messageId, fromAgent, space, mentionedAgents, db2) {
19950
+ const stmt = db2.prepare("INSERT INTO message_mentions (message_id, mentioned_agent, from_agent, space) VALUES (?, ?, ?, ?)");
19951
+ for (const agent of mentionedAgents) {
19952
+ try {
19953
+ stmt.run(messageId, agent, fromAgent, space);
19954
+ if (agent !== fromAgent.toLowerCase()) {
19955
+ sendMessage({
19956
+ from: fromAgent,
19957
+ to: agent,
19958
+ content: `You were mentioned in #${space} by ${fromAgent} (message #${messageId})`,
19959
+ metadata: { type: "mention_notification", source_message_id: messageId, space }
19960
+ });
19961
+ }
19962
+ } catch {}
19963
+ }
19964
+ }
19393
19965
  function listSessions3(agent) {
19394
19966
  const db2 = getDb();
19395
19967
  const agentFilter = agent ? "WHERE from_agent = ? OR to_agent = ?" : "";
@@ -19924,7 +20496,7 @@ function getAutoName() {
19924
20496
  }
19925
20497
  cachedAutoName = name;
19926
20498
  try {
19927
- mkdirSync33(dirname23(AGENT_ID_FILE), { recursive: true });
20499
+ mkdirSync34(dirname23(AGENT_ID_FILE), { recursive: true });
19928
20500
  writeFileSync6(AGENT_ID_FILE, name + `
19929
20501
  `, "utf-8");
19930
20502
  } catch {}
@@ -20116,6 +20688,7 @@ function acquireLock2(resourceType, resourceId, agentId, lockType = "advisory",
20116
20688
  const db2 = getDb();
20117
20689
  return db2.transaction(() => {
20118
20690
  cleanExpiredLocks2();
20691
+ releaseStaleAgentLocks();
20119
20692
  const existing = db2.prepare(`
20120
20693
  SELECT * FROM resource_locks
20121
20694
  WHERE resource_type = ? AND resource_id = ? AND lock_type = ?
@@ -20142,6 +20715,55 @@ function acquireLock2(resourceType, resourceId, agentId, lockType = "advisory",
20142
20715
  return { acquired: true, lock };
20143
20716
  }).immediate();
20144
20717
  }
20718
+ function bulkAcquireLock(resources, agentId) {
20719
+ const db2 = getDb();
20720
+ return db2.transaction(() => {
20721
+ cleanExpiredLocks2();
20722
+ releaseStaleAgentLocks();
20723
+ const acquired = [];
20724
+ for (const { resource_type, resource_id, lock_type = "advisory", expiry_ms = DEFAULT_LOCK_EXPIRY_MS } of resources) {
20725
+ const existing = db2.prepare(`
20726
+ SELECT * FROM resource_locks
20727
+ WHERE resource_type = ? AND resource_id = ? AND lock_type = ?
20728
+ `).get(resource_type, resource_id, lock_type);
20729
+ if (existing && existing.agent_id !== agentId) {
20730
+ throw { _bulkConflict: true, resource_type, resource_id, held_by: existing.agent_id };
20731
+ }
20732
+ const expiresAt = new Date(Date.now() + expiry_ms).toISOString().slice(0, -1);
20733
+ if (existing) {
20734
+ db2.prepare(`
20735
+ UPDATE resource_locks SET expires_at = ?, locked_at = strftime('%Y-%m-%dT%H:%M:%f', 'now')
20736
+ WHERE resource_type = ? AND resource_id = ? AND lock_type = ?
20737
+ `).run(expiresAt, resource_type, resource_id, lock_type);
20738
+ } else {
20739
+ db2.prepare(`
20740
+ INSERT INTO resource_locks (resource_type, resource_id, agent_id, lock_type, locked_at, expires_at)
20741
+ VALUES (?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%f', 'now'), ?)
20742
+ `).run(resource_type, resource_id, agentId, lock_type, expiresAt);
20743
+ }
20744
+ const lock = db2.prepare(`
20745
+ SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND lock_type = ?
20746
+ `).get(resource_type, resource_id, lock_type);
20747
+ acquired.push(lock);
20748
+ }
20749
+ return { acquired: true, locks: acquired };
20750
+ }).immediate();
20751
+ }
20752
+ function tryBulkAcquireLock(resources, agentId) {
20753
+ try {
20754
+ return bulkAcquireLock(resources, agentId);
20755
+ } catch (err2) {
20756
+ const e = err2;
20757
+ if (e?._bulkConflict) {
20758
+ return {
20759
+ acquired: false,
20760
+ locks: [],
20761
+ blocked_by: { resource_type: e.resource_type, resource_id: e.resource_id, held_by: e.held_by }
20762
+ };
20763
+ }
20764
+ throw err2;
20765
+ }
20766
+ }
20145
20767
  function releaseLock2(resourceType, resourceId, agentId) {
20146
20768
  const db2 = getDb();
20147
20769
  const result = db2.prepare(`
@@ -20153,6 +20775,7 @@ function releaseLock2(resourceType, resourceId, agentId) {
20153
20775
  function checkLock2(resourceType, resourceId) {
20154
20776
  const db2 = getDb();
20155
20777
  cleanExpiredLocks2();
20778
+ releaseStaleAgentLocks();
20156
20779
  return db2.prepare(`
20157
20780
  SELECT * FROM resource_locks
20158
20781
  WHERE resource_type = ? AND resource_id = ?
@@ -20160,6 +20783,17 @@ function checkLock2(resourceType, resourceId) {
20160
20783
  LIMIT 1
20161
20784
  `).get(resourceType, resourceId);
20162
20785
  }
20786
+ function releaseStaleAgentLocks() {
20787
+ const db2 = getDb();
20788
+ const result = db2.prepare(`
20789
+ DELETE FROM resource_locks
20790
+ WHERE LOWER(agent_id) IN (
20791
+ SELECT LOWER(agent) FROM agent_presence
20792
+ WHERE last_seen_at < strftime('%Y-%m-%dT%H:%M:%f', 'now', '-${STALE_HEARTBEAT_SECONDS} seconds')
20793
+ )
20794
+ `).run();
20795
+ return result.changes;
20796
+ }
20163
20797
  function cleanExpiredLocks2() {
20164
20798
  const db2 = getDb();
20165
20799
  const result = db2.prepare(`
@@ -20170,6 +20804,7 @@ function cleanExpiredLocks2() {
20170
20804
  function listLocks(opts) {
20171
20805
  const db2 = getDb();
20172
20806
  cleanExpiredLocks2();
20807
+ releaseStaleAgentLocks();
20173
20808
  let query = "SELECT * FROM resource_locks WHERE 1=1";
20174
20809
  const params = [];
20175
20810
  if (opts?.resource_type) {
@@ -20183,6 +20818,31 @@ function listLocks(opts) {
20183
20818
  query += " ORDER BY locked_at ASC";
20184
20819
  return db2.prepare(query).all(...params);
20185
20820
  }
20821
+ function listLocksEnriched(opts) {
20822
+ const locks = listLocks(opts);
20823
+ const db2 = getDb();
20824
+ const nowMs = Date.now();
20825
+ return locks.map((lock) => {
20826
+ const lockedMs = new Date(lock.locked_at + "Z").getTime();
20827
+ const expiresMs = new Date(lock.expires_at + "Z").getTime();
20828
+ const presenceRow = db2.prepare(`
20829
+ SELECT role, status, last_seen_at, project_id FROM agent_presence WHERE LOWER(agent) = LOWER(?)
20830
+ `).get(lock.agent_id);
20831
+ const agent = presenceRow ? {
20832
+ role: presenceRow.role ?? null,
20833
+ status: presenceRow.status ?? null,
20834
+ online: presenceRow.last_seen_at ? nowMs - new Date(presenceRow.last_seen_at + "Z").getTime() < 60000 : false,
20835
+ last_seen_at: presenceRow.last_seen_at ?? null,
20836
+ project_id: presenceRow.project_id ?? null
20837
+ } : null;
20838
+ return {
20839
+ ...lock,
20840
+ locked_seconds_ago: Math.round((nowMs - lockedMs) / 1000),
20841
+ expires_in_seconds: Math.round((expiresMs - nowMs) / 1000),
20842
+ agent
20843
+ };
20844
+ });
20845
+ }
20186
20846
  function computeHotness(sessionId) {
20187
20847
  const db2 = getDb();
20188
20848
  const base = db2.prepare(`
@@ -20531,37 +21191,49 @@ function getGraphStats() {
20531
21191
  map[r.relation] = r.c;
20532
21192
  return { total_edges: total, by_relation: map };
20533
21193
  }
20534
- var __create2, __getProtoOf2, __defProp3, __getOwnPropNames2, __getOwnPropDesc2, __hasOwnProp2, __toESM2 = (mod, isNodeMode, target) => {
21194
+ var __create2, __getProtoOf2, __defProp3, __getOwnPropNames2, __getOwnPropDesc2, __hasOwnProp2, __toESMCache_node2, __toESMCache_esm2, __toESM2 = (mod, isNodeMode, target) => {
21195
+ var canCache = mod != null && typeof mod === "object";
21196
+ if (canCache) {
21197
+ var cache = isNodeMode ? __toESMCache_node2 ??= new WeakMap : __toESMCache_esm2 ??= new WeakMap;
21198
+ var cached = cache.get(mod);
21199
+ if (cached)
21200
+ return cached;
21201
+ }
20535
21202
  target = mod != null ? __create2(__getProtoOf2(mod)) : {};
20536
21203
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp3(target, "default", { value: mod, enumerable: true }) : target;
20537
21204
  for (let key of __getOwnPropNames2(mod))
20538
21205
  if (!__hasOwnProp2.call(to, key))
20539
21206
  __defProp3(to, key, {
20540
- get: () => mod[key],
21207
+ get: __accessProp2.bind(mod, key),
20541
21208
  enumerable: true
20542
21209
  });
21210
+ if (canCache)
21211
+ cache.set(mod, to);
20543
21212
  return to;
20544
- }, __moduleCache2, __toCommonJS2 = (from) => {
20545
- var entry = __moduleCache2.get(from), desc;
21213
+ }, __toCommonJS2 = (from) => {
21214
+ var entry = (__moduleCache2 ??= new WeakMap).get(from), desc;
20546
21215
  if (entry)
20547
21216
  return entry;
20548
21217
  entry = __defProp3({}, "__esModule", { value: true });
20549
- if (from && typeof from === "object" || typeof from === "function")
20550
- __getOwnPropNames2(from).map((key) => !__hasOwnProp2.call(entry, key) && __defProp3(entry, key, {
20551
- get: () => from[key],
20552
- enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable
20553
- }));
21218
+ if (from && typeof from === "object" || typeof from === "function") {
21219
+ for (var key of __getOwnPropNames2(from))
21220
+ if (!__hasOwnProp2.call(entry, key))
21221
+ __defProp3(entry, key, {
21222
+ get: __accessProp2.bind(from, key),
21223
+ enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable
21224
+ });
21225
+ }
20554
21226
  __moduleCache2.set(from, entry);
20555
21227
  return entry;
20556
- }, __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports), __export3 = (target, all) => {
21228
+ }, __moduleCache2, __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports), __returnValue3 = (v) => v, __export3 = (target, all) => {
20557
21229
  for (var name in all)
20558
21230
  __defProp3(target, name, {
20559
21231
  get: all[name],
20560
21232
  enumerable: true,
20561
21233
  configurable: true,
20562
- set: (newValue) => all[name] = () => newValue
21234
+ set: __exportSetter3.bind(all, name)
20563
21235
  });
20564
- }, 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;
21236
+ }, 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;
20565
21237
  var init_dist3 = __esm(() => {
20566
21238
  __create2 = Object.create;
20567
21239
  __getProtoOf2 = Object.getPrototypeOf;
@@ -20569,7 +21241,6 @@ var init_dist3 = __esm(() => {
20569
21241
  __getOwnPropNames2 = Object.getOwnPropertyNames;
20570
21242
  __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
20571
21243
  __hasOwnProp2 = Object.prototype.hasOwnProperty;
20572
- __moduleCache2 = /* @__PURE__ */ new WeakMap;
20573
21244
  exports_db = {};
20574
21245
  __export3(exports_db, {
20575
21246
  getDbPath: () => getDbPath2,
@@ -22736,12 +23407,13 @@ Check the top-level render call using <` + parentName + ">.";
22736
23407
  "zinc-eagle",
22737
23408
  "zone-fox"
22738
23409
  ];
22739
- AGENT_ID_FILE = join43(homedir42(), ".conversations", "agent-id");
23410
+ AGENT_ID_FILE = join44(homedir42(), ".conversations", "agent-id");
22740
23411
  init_db();
22741
23412
  init_db();
22742
23413
  CONFLICT_THRESHOLD_SECONDS = 30 * 60;
22743
23414
  init_db();
22744
23415
  DEFAULT_LOCK_EXPIRY_MS = 5 * 60 * 1000;
23416
+ STALE_HEARTBEAT_SECONDS = 30 * 60;
22745
23417
  init_db();
22746
23418
  init_db();
22747
23419
  STOPWORDS = new Set([
@@ -22938,7 +23610,7 @@ var init_coordination = __esm(() => {
22938
23610
  activeNavigations = new Map;
22939
23611
  });
22940
23612
 
22941
- // ../open-todos/dist/index.js
23613
+ // node_modules/@hasna/todos/dist/index.js
22942
23614
  var exports_dist4 = {};
22943
23615
  __export(exports_dist4, {
22944
23616
  uuid: () => uuid2,
@@ -22965,6 +23637,7 @@ __export(exports_dist4, {
22965
23637
  setTaskStatus: () => setTaskStatus,
22966
23638
  setTaskPriority: () => setTaskPriority,
22967
23639
  setBudget: () => setBudget,
23640
+ setActiveModel: () => setActiveModel2,
22968
23641
  searchTasks: () => searchTasks,
22969
23642
  scoreTask: () => scoreTask,
22970
23643
  saveSnapshot: () => saveSnapshot,
@@ -23064,6 +23737,8 @@ __export(exports_dist4, {
23064
23737
  getAgentByName: () => getAgentByName2,
23065
23738
  getAgent: () => getAgent3,
23066
23739
  getActiveWork: () => getActiveWork,
23740
+ getActiveModel: () => getActiveModel2,
23741
+ gatherTrainingData: () => gatherTrainingData2,
23067
23742
  findTasksByFile: () => findTasksByFile,
23068
23743
  findRelatedTaskIds: () => findRelatedTaskIds,
23069
23744
  findPath: () => findPath2,
@@ -23101,6 +23776,7 @@ __export(exports_dist4, {
23101
23776
  closeDatabase: () => closeDatabase2,
23102
23777
  cloneTask: () => cloneTask,
23103
23778
  clearChecklist: () => clearChecklist,
23779
+ clearActiveModel: () => clearActiveModel2,
23104
23780
  cleanExpiredLocks: () => cleanExpiredLocks3,
23105
23781
  claimOrSteal: () => claimOrSteal,
23106
23782
  claimNextTask: () => claimNextTask,
@@ -23135,22 +23811,26 @@ __export(exports_dist4, {
23135
23811
  LockError: () => LockError,
23136
23812
  EXTRACT_TAGS: () => EXTRACT_TAGS,
23137
23813
  DependencyCycleError: () => DependencyCycleError,
23814
+ DEFAULT_MODEL: () => DEFAULT_MODEL3,
23138
23815
  CompletionGuardError: () => CompletionGuardError,
23139
23816
  AgentNotFoundError: () => AgentNotFoundError2
23140
23817
  });
23141
23818
  import { Database as Database4 } from "bun:sqlite";
23142
- import { existsSync as existsSync8, mkdirSync as mkdirSync13 } from "fs";
23819
+ import { existsSync as existsSync9, mkdirSync as mkdirSync13 } from "fs";
23143
23820
  import { dirname as dirname6, join as join14, resolve as resolve3 } from "path";
23144
23821
  import { existsSync as existsSync34 } from "fs";
23145
23822
  import { join as join35 } from "path";
23146
- import { existsSync as existsSync24, mkdirSync as mkdirSync25, readFileSync as readFileSync7, readdirSync as readdirSync6, statSync as statSync4, writeFileSync as writeFileSync7 } from "fs";
23823
+ import { existsSync as existsSync24, mkdirSync as mkdirSync25, readFileSync as readFileSync10, readdirSync as readdirSync7, statSync as statSync5, writeFileSync as writeFileSync7 } from "fs";
23147
23824
  import { join as join25 } from "path";
23148
- import { existsSync as existsSync43, readFileSync as readFileSync25, readdirSync as readdirSync23, writeFileSync as writeFileSync24 } from "fs";
23149
- import { join as join44 } from "path";
23150
- import { existsSync as existsSync52 } from "fs";
23151
- import { join as join52 } from "path";
23152
- import { readFileSync as readFileSync34, statSync as statSync22 } from "fs";
23153
- import { relative, resolve as resolve23, join as join62 } from "path";
23825
+ import { existsSync as existsSync44, mkdirSync as mkdirSync35, readFileSync as readFileSync25, writeFileSync as writeFileSync24 } from "fs";
23826
+ import { homedir as homedir8 } from "os";
23827
+ import { join as join45 } from "path";
23828
+ import { existsSync as existsSync53, readFileSync as readFileSync34, readdirSync as readdirSync23, writeFileSync as writeFileSync34 } from "fs";
23829
+ import { join as join53 } from "path";
23830
+ import { existsSync as existsSync62 } from "fs";
23831
+ import { join as join62 } from "path";
23832
+ import { readFileSync as readFileSync42, statSync as statSync22 } from "fs";
23833
+ import { relative, resolve as resolve23, join as join72 } from "path";
23154
23834
  import { execSync as execSync2 } from "child_process";
23155
23835
 
23156
23836
  class TodosClient {
@@ -23374,7 +24054,7 @@ function findNearestTodosDb(startDir) {
23374
24054
  let dir = resolve3(startDir);
23375
24055
  while (true) {
23376
24056
  const candidate = join14(dir, ".todos", "todos.db");
23377
- if (existsSync8(candidate))
24057
+ if (existsSync9(candidate))
23378
24058
  return candidate;
23379
24059
  const parent = dirname6(dir);
23380
24060
  if (parent === dir)
@@ -23386,7 +24066,7 @@ function findNearestTodosDb(startDir) {
23386
24066
  function findGitRoot2(startDir) {
23387
24067
  let dir = resolve3(startDir);
23388
24068
  while (true) {
23389
- if (existsSync8(join14(dir, ".git")))
24069
+ if (existsSync9(join14(dir, ".git")))
23390
24070
  return dir;
23391
24071
  const parent = dirname6(dir);
23392
24072
  if (parent === dir)
@@ -23416,7 +24096,7 @@ function ensureDir3(filePath) {
23416
24096
  if (isInMemoryDb2(filePath))
23417
24097
  return;
23418
24098
  const dir = dirname6(resolve3(filePath));
23419
- if (!existsSync8(dir)) {
24099
+ if (!existsSync9(dir)) {
23420
24100
  mkdirSync13(dir, { recursive: true });
23421
24101
  }
23422
24102
  }
@@ -23883,11 +24563,11 @@ function ensureDir23(dir) {
23883
24563
  function listJsonFiles(dir) {
23884
24564
  if (!existsSync24(dir))
23885
24565
  return [];
23886
- return readdirSync6(dir).filter((f) => f.endsWith(".json"));
24566
+ return readdirSync7(dir).filter((f) => f.endsWith(".json"));
23887
24567
  }
23888
24568
  function readJsonFile(path) {
23889
24569
  try {
23890
- return JSON.parse(readFileSync7(path, "utf-8"));
24570
+ return JSON.parse(readFileSync10(path, "utf-8"));
23891
24571
  } catch {
23892
24572
  return null;
23893
24573
  }
@@ -23900,7 +24580,7 @@ function readHighWaterMark(dir) {
23900
24580
  const path = join25(dir, ".highwatermark");
23901
24581
  if (!existsSync24(path))
23902
24582
  return 1;
23903
- const val = parseInt(readFileSync7(path, "utf-8").trim(), 10);
24583
+ const val = parseInt(readFileSync10(path, "utf-8").trim(), 10);
23904
24584
  return isNaN(val) ? 1 : val;
23905
24585
  }
23906
24586
  function writeHighWaterMark(dir, value) {
@@ -23908,7 +24588,7 @@ function writeHighWaterMark(dir, value) {
23908
24588
  }
23909
24589
  function getFileMtimeMs(path) {
23910
24590
  try {
23911
- return statSync4(path).mtimeMs;
24591
+ return statSync5(path).mtimeMs;
23912
24592
  } catch {
23913
24593
  return null;
23914
24594
  }
@@ -25881,6 +26561,91 @@ function deleteSession2(id, db2) {
25881
26561
  const result = d.run("DELETE FROM sessions WHERE id = ?", [id]);
25882
26562
  return result.changes > 0;
25883
26563
  }
26564
+ function taskToCreateExample(task) {
26565
+ const userMsg = `Create a task: ${task.title}${task.description ? `
26566
+
26567
+ Description: ${task.description}` : ""}`;
26568
+ const taskDetails = {
26569
+ id: task.short_id ?? task.id,
26570
+ title: task.title,
26571
+ description: task.description ?? "",
26572
+ status: task.status,
26573
+ priority: task.priority,
26574
+ tags: task.tags,
26575
+ created_at: task.created_at
26576
+ };
26577
+ return {
26578
+ messages: [
26579
+ { role: "system", content: SYSTEM_PROMPT2 },
26580
+ { role: "user", content: userMsg },
26581
+ {
26582
+ role: "assistant",
26583
+ content: `Created task: ${JSON.stringify(taskDetails, null, 2)}`
26584
+ }
26585
+ ]
26586
+ };
26587
+ }
26588
+ function taskToStatusUpdateExample(task) {
26589
+ if (!task.completed_at && task.status === "pending")
26590
+ return null;
26591
+ const id = task.short_id ?? task.id;
26592
+ return {
26593
+ messages: [
26594
+ { role: "system", content: SYSTEM_PROMPT2 },
26595
+ { role: "user", content: `Mark task ${id} as ${task.status}` },
26596
+ {
26597
+ role: "assistant",
26598
+ content: `Task ${id} has been updated to status: ${task.status}. ${task.completed_at ? `Completed at: ${task.completed_at}` : ""}`.trim()
26599
+ }
26600
+ ]
26601
+ };
26602
+ }
26603
+ function taskToSearchExample(tasks, query) {
26604
+ const matched = tasks.filter((t) => t.title.toLowerCase().includes(query.toLowerCase())).slice(0, 5);
26605
+ return {
26606
+ messages: [
26607
+ { role: "system", content: SYSTEM_PROMPT2 },
26608
+ { role: "user", content: `Search tasks for: "${query}"` },
26609
+ {
26610
+ role: "assistant",
26611
+ content: matched.length > 0 ? `Found ${matched.length} task(s):
26612
+ ${matched.map((t) => `- [${t.short_id ?? t.id}] ${t.title} (${t.status})`).join(`
26613
+ `)}` : `No tasks found matching "${query}".`
26614
+ }
26615
+ ]
26616
+ };
26617
+ }
26618
+ function readConfig2() {
26619
+ if (!existsSync44(CONFIG_PATH2))
26620
+ return {};
26621
+ try {
26622
+ const raw = readFileSync25(CONFIG_PATH2, "utf-8");
26623
+ return JSON.parse(raw);
26624
+ } catch {
26625
+ return {};
26626
+ }
26627
+ }
26628
+ function writeConfig2(config) {
26629
+ if (!existsSync44(CONFIG_DIR2)) {
26630
+ mkdirSync35(CONFIG_DIR2, { recursive: true });
26631
+ }
26632
+ writeFileSync24(CONFIG_PATH2, JSON.stringify(config, null, 2) + `
26633
+ `, "utf-8");
26634
+ }
26635
+ function getActiveModel2() {
26636
+ const config = readConfig2();
26637
+ return config.activeModel ?? DEFAULT_MODEL3;
26638
+ }
26639
+ function setActiveModel2(modelId) {
26640
+ const config = readConfig2();
26641
+ config.activeModel = modelId;
26642
+ writeConfig2(config);
26643
+ }
26644
+ function clearActiveModel2() {
26645
+ const config = readConfig2();
26646
+ delete config.activeModel;
26647
+ writeConfig2(config);
26648
+ }
25884
26649
  function createHandoff(input, db2) {
25885
26650
  const d = db2 || getDatabase3();
25886
26651
  const id = uuid2();
@@ -26695,13 +27460,13 @@ function searchTasks(options, projectId, taskListId, db2) {
26695
27460
  return rows.map(rowToTask3);
26696
27461
  }
26697
27462
  function getTaskListDir(taskListId) {
26698
- return join44(HOME, ".claude", "tasks", taskListId);
27463
+ return join53(HOME, ".claude", "tasks", taskListId);
26699
27464
  }
26700
27465
  function readClaudeTask(dir, filename) {
26701
- return readJsonFile(join44(dir, filename));
27466
+ return readJsonFile(join53(dir, filename));
26702
27467
  }
26703
27468
  function writeClaudeTask(dir, task) {
26704
- writeJsonFile(join44(dir, `${task.id}.json`), task);
27469
+ writeJsonFile(join53(dir, `${task.id}.json`), task);
26705
27470
  }
26706
27471
  function toClaudeStatus(status) {
26707
27472
  if (status === "pending" || status === "in_progress" || status === "completed") {
@@ -26713,14 +27478,14 @@ function toSqliteStatus(status) {
26713
27478
  return status;
26714
27479
  }
26715
27480
  function readPrefixCounter(dir) {
26716
- const path = join44(dir, ".prefix-counter");
26717
- if (!existsSync43(path))
27481
+ const path = join53(dir, ".prefix-counter");
27482
+ if (!existsSync53(path))
26718
27483
  return 0;
26719
- const val = parseInt(readFileSync25(path, "utf-8").trim(), 10);
27484
+ const val = parseInt(readFileSync34(path, "utf-8").trim(), 10);
26720
27485
  return isNaN(val) ? 0 : val;
26721
27486
  }
26722
27487
  function writePrefixCounter(dir, value) {
26723
- writeFileSync24(join44(dir, ".prefix-counter"), String(value));
27488
+ writeFileSync34(join53(dir, ".prefix-counter"), String(value));
26724
27489
  }
26725
27490
  function formatPrefixedSubject(title, prefix, counter) {
26726
27491
  const padded = String(counter).padStart(5, "0");
@@ -26747,7 +27512,7 @@ function taskToClaudeTask(task, claudeTaskId, existingMeta) {
26747
27512
  }
26748
27513
  function pushToClaudeTaskList(taskListId, projectId, options = {}) {
26749
27514
  const dir = getTaskListDir(taskListId);
26750
- if (!existsSync43(dir))
27515
+ if (!existsSync53(dir))
26751
27516
  ensureDir23(dir);
26752
27517
  const filter = {};
26753
27518
  if (projectId)
@@ -26756,7 +27521,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
26756
27521
  const existingByTodosId = new Map;
26757
27522
  const files = listJsonFiles(dir);
26758
27523
  for (const f of files) {
26759
- const path = join44(dir, f);
27524
+ const path = join53(dir, f);
26760
27525
  const ct = readClaudeTask(dir, f);
26761
27526
  if (ct?.metadata?.["todos_id"]) {
26762
27527
  existingByTodosId.set(ct.metadata["todos_id"], { task: ct, mtimeMs: getFileMtimeMs(path) });
@@ -26843,7 +27608,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
26843
27608
  }
26844
27609
  function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
26845
27610
  const dir = getTaskListDir(taskListId);
26846
- if (!existsSync43(dir)) {
27611
+ if (!existsSync53(dir)) {
26847
27612
  return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
26848
27613
  }
26849
27614
  const files = readdirSync23(dir).filter((f) => f.endsWith(".json"));
@@ -26863,7 +27628,7 @@ function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
26863
27628
  }
26864
27629
  for (const f of files) {
26865
27630
  try {
26866
- const filePath = join44(dir, f);
27631
+ const filePath = join53(dir, f);
26867
27632
  const ct = readClaudeTask(dir, f);
26868
27633
  if (!ct)
26869
27634
  continue;
@@ -26931,16 +27696,16 @@ function syncClaudeTaskList(taskListId, projectId, options = {}) {
26931
27696
  }
26932
27697
  function agentBaseDir(agent) {
26933
27698
  const key = `TODOS_${agent.toUpperCase()}_TASKS_DIR`;
26934
- return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] || join52(HOME, ".todos", "agents");
27699
+ return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] || join62(HOME, ".todos", "agents");
26935
27700
  }
26936
27701
  function getTaskListDir2(agent, taskListId) {
26937
- return join52(agentBaseDir(agent), agent, taskListId);
27702
+ return join62(agentBaseDir(agent), agent, taskListId);
26938
27703
  }
26939
27704
  function readAgentTask(dir, filename) {
26940
- return readJsonFile(join52(dir, filename));
27705
+ return readJsonFile(join62(dir, filename));
26941
27706
  }
26942
27707
  function writeAgentTask(dir, task) {
26943
- writeJsonFile(join52(dir, `${task.id}.json`), task);
27708
+ writeJsonFile(join62(dir, `${task.id}.json`), task);
26944
27709
  }
26945
27710
  function taskToAgentTask(task, externalId, existingMeta) {
26946
27711
  return {
@@ -26965,7 +27730,7 @@ function metadataKey(agent) {
26965
27730
  }
26966
27731
  function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
26967
27732
  const dir = getTaskListDir2(agent, taskListId);
26968
- if (!existsSync52(dir))
27733
+ if (!existsSync62(dir))
26969
27734
  ensureDir23(dir);
26970
27735
  const filter = {};
26971
27736
  if (projectId)
@@ -26974,7 +27739,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
26974
27739
  const existingByTodosId = new Map;
26975
27740
  const files = listJsonFiles(dir);
26976
27741
  for (const f of files) {
26977
- const path = join52(dir, f);
27742
+ const path = join62(dir, f);
26978
27743
  const at = readAgentTask(dir, f);
26979
27744
  if (at?.metadata?.["todos_id"]) {
26980
27745
  existingByTodosId.set(at.metadata["todos_id"], { task: at, mtimeMs: getFileMtimeMs(path) });
@@ -27048,7 +27813,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
27048
27813
  }
27049
27814
  function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
27050
27815
  const dir = getTaskListDir2(agent, taskListId);
27051
- if (!existsSync52(dir)) {
27816
+ if (!existsSync62(dir)) {
27052
27817
  return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
27053
27818
  }
27054
27819
  const files = listJsonFiles(dir);
@@ -27067,7 +27832,7 @@ function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
27067
27832
  }
27068
27833
  for (const f of files) {
27069
27834
  try {
27070
- const filePath = join52(dir, f);
27835
+ const filePath = join62(dir, f);
27071
27836
  const at = readAgentTask(dir, f);
27072
27837
  if (!at)
27073
27838
  continue;
@@ -27272,9 +28037,9 @@ function extractTodos(options, db2) {
27272
28037
  const files = collectFiles(basePath, extensions);
27273
28038
  const allComments = [];
27274
28039
  for (const file of files) {
27275
- const fullPath = statSync22(basePath).isFile() ? basePath : join62(basePath, file);
28040
+ const fullPath = statSync22(basePath).isFile() ? basePath : join72(basePath, file);
27276
28041
  try {
27277
- const source = readFileSync34(fullPath, "utf-8");
28042
+ const source = readFileSync42(fullPath, "utf-8");
27278
28043
  const relPath = statSync22(basePath).isFile() ? relative(resolve23(basePath, ".."), fullPath) : file;
27279
28044
  const comments = extractFromSource(source, relPath, tags);
27280
28045
  allComments.push(...comments);
@@ -27634,7 +28399,29 @@ function checkBudget(agentId, db2) {
27634
28399
  }
27635
28400
  return { allowed: true, current_concurrent: concurrent, max_concurrent: budget.max_concurrent };
27636
28401
  }
27637
- 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;
28402
+ 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 = {}) => {
28403
+ const allTasks = listTasks({});
28404
+ const filtered = options.since ? allTasks.filter((t) => new Date(t.created_at) >= options.since) : allTasks;
28405
+ const sorted = filtered.slice().sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
28406
+ const fetchSet = options.limit ? sorted.slice(0, options.limit * 2) : sorted;
28407
+ const examples = [];
28408
+ for (const task of fetchSet) {
28409
+ examples.push(taskToCreateExample(task));
28410
+ const statusEx = taskToStatusUpdateExample(task);
28411
+ if (statusEx)
28412
+ examples.push(statusEx);
28413
+ }
28414
+ const searchTerms = ["urgent", "fix", "implement", "create", "update", "review"];
28415
+ for (const term of searchTerms) {
28416
+ examples.push(taskToSearchExample(sorted, term));
28417
+ }
28418
+ const finalExamples = options.limit ? examples.slice(0, options.limit) : examples;
28419
+ return {
28420
+ source: "todos",
28421
+ examples: finalExamples,
28422
+ count: finalExamples.length
28423
+ };
28424
+ }, DEFAULT_MODEL3 = "gpt-4o-mini", CONFIG_DIR2, CONFIG_PATH2, RELATIONSHIP_TYPES, EXTRACT_TAGS, DEFAULT_EXTENSIONS, SKIP_DIRS;
27638
28425
  var init_dist4 = __esm(() => {
27639
28426
  MIGRATIONS2 = [
27640
28427
  `
@@ -28311,6 +29098,8 @@ var init_dist4 = __esm(() => {
28311
29098
  saturday: 6,
28312
29099
  sat: 6
28313
29100
  };
29101
+ CONFIG_DIR2 = join45(homedir8(), ".todos");
29102
+ CONFIG_PATH2 = join45(CONFIG_DIR2, "config.json");
28314
29103
  RELATIONSHIP_TYPES = [
28315
29104
  "related_to",
28316
29105
  "conflicts_with",
@@ -28634,7 +29423,7 @@ ${snap.tree.slice(0, 2000)}`;
28634
29423
  const response = await client.messages.create({
28635
29424
  model,
28636
29425
  max_tokens: 512,
28637
- system: SYSTEM_PROMPT2,
29426
+ system: SYSTEM_PROMPT3,
28638
29427
  messages: [{
28639
29428
  role: "user",
28640
29429
  content: `Task: ${task}
@@ -28699,7 +29488,7 @@ What actions should I take next? Return JSON array.`
28699
29488
  }
28700
29489
  return { success: false, result: null, steps_taken: steps.length, steps, cost_estimate: totalTokens / 1000 * 0.00025, error: `Reached max steps (${maxSteps}) without completing task` };
28701
29490
  }
28702
- var SYSTEM_PROMPT2 = `You are a browser automation agent. Given a task and the current page state, decide which browser actions to take.
29491
+ var SYSTEM_PROMPT3 = `You are a browser automation agent. Given a task and the current page state, decide which browser actions to take.
28703
29492
 
28704
29493
  Return a JSON array of at most 3 actions to execute next:
28705
29494
  [{"tool": "navigate|click|type|scroll|evaluate|done", "args": {...}, "reason": "..."}]
@@ -28711,7 +29500,7 @@ var init_ai_task = () => {};
28711
29500
  // src/mcp/index.ts
28712
29501
  import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
28713
29502
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
28714
- import { readFileSync as readFileSync8 } from "fs";
29503
+ import { readFileSync as readFileSync11 } from "fs";
28715
29504
  import { join as join15 } from "path";
28716
29505
 
28717
29506
  // node_modules/zod/v3/external.js
@@ -32695,15 +33484,12 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
32695
33484
  // src/lib/screenshot.ts
32696
33485
  init_types();
32697
33486
  init_gallery();
33487
+ init_schema();
32698
33488
  var import_sharp = __toESM(require_lib(), 1);
32699
33489
  import { join as join4 } from "path";
32700
33490
  import { mkdirSync as mkdirSync4 } from "fs";
32701
- import { homedir as homedir4 } from "os";
32702
- function getDataDir2() {
32703
- return process.env["BROWSER_DATA_DIR"] ?? join4(homedir4(), ".browser");
32704
- }
32705
33491
  function getScreenshotDir(projectId) {
32706
- const base = join4(getDataDir2(), "screenshots");
33492
+ const base = join4(getDataDir(), "screenshots");
32707
33493
  const date = new Date().toISOString().split("T")[0];
32708
33494
  const dir = projectId ? join4(base, projectId, date) : join4(base, date);
32709
33495
  mkdirSync4(dir, { recursive: true });
@@ -32838,7 +33624,7 @@ async function takeScreenshot(page, opts) {
32838
33624
  }
32839
33625
  async function generatePDF(page, opts) {
32840
33626
  try {
32841
- const base = join4(getDataDir2(), "pdfs");
33627
+ const base = join4(getDataDir(), "pdfs");
32842
33628
  const date = new Date().toISOString().split("T")[0];
32843
33629
  const dir = opts?.projectId ? join4(base, opts.projectId, date) : join4(base, date);
32844
33630
  mkdirSync4(dir, { recursive: true });
@@ -33054,55 +33840,10 @@ async function crawl(startUrl, opts = {}) {
33054
33840
  });
33055
33841
  return result;
33056
33842
  }
33057
- // src/db/agents.ts
33058
- init_schema();
33059
- init_types();
33060
- import { randomUUID as randomUUID7 } from "crypto";
33061
- function registerAgent(name, opts = {}) {
33062
- const db = getDatabase();
33063
- const existing = db.query("SELECT * FROM agents WHERE name = ?").get(name);
33064
- if (existing) {
33065
- db.prepare("UPDATE agents SET last_seen = datetime('now'), session_id = ?, project_id = ?, working_dir = ? WHERE name = ?").run(opts.sessionId ?? existing.session_id ?? null, opts.projectId ?? existing.project_id ?? null, opts.workingDir ?? existing.working_dir ?? null, name);
33066
- return getAgentByName(name);
33067
- }
33068
- const id = randomUUID7();
33069
- db.prepare("INSERT INTO agents (id, name, description, session_id, project_id, working_dir) VALUES (?, ?, ?, ?, ?, ?)").run(id, name, opts.description ?? null, opts.sessionId ?? null, opts.projectId ?? null, opts.workingDir ?? null);
33070
- return getAgent(id);
33071
- }
33072
- function heartbeat(agentId) {
33073
- const db = getDatabase();
33074
- const agent = db.query("SELECT * FROM agents WHERE id = ?").get(agentId);
33075
- if (!agent)
33076
- throw new AgentNotFoundError(agentId);
33077
- db.prepare("UPDATE agents SET last_seen = datetime('now') WHERE id = ?").run(agentId);
33078
- db.prepare("INSERT INTO heartbeats (id, agent_id, session_id) VALUES (?, ?, ?)").run(randomUUID7(), agentId, agent.session_id ?? null);
33079
- }
33080
- function getAgent(id) {
33081
- const db = getDatabase();
33082
- const row = db.query("SELECT * FROM agents WHERE id = ?").get(id);
33083
- if (!row)
33084
- throw new AgentNotFoundError(id);
33085
- return row;
33086
- }
33087
- function getAgentByName(name) {
33088
- const db = getDatabase();
33089
- return db.query("SELECT * FROM agents WHERE name = ?").get(name) ?? null;
33090
- }
33091
- function listAgents(projectId) {
33092
- const db = getDatabase();
33093
- if (projectId) {
33094
- return db.query("SELECT * FROM agents WHERE project_id = ? ORDER BY last_seen DESC").all(projectId);
33095
- }
33096
- return db.query("SELECT * FROM agents ORDER BY last_seen DESC").all();
33097
- }
33098
33843
 
33099
- // src/lib/agents.ts
33100
- function registerAgent2(name, opts = {}) {
33101
- return registerAgent(name, opts);
33102
- }
33103
- function heartbeat2(agentId) {
33104
- heartbeat(agentId);
33105
- }
33844
+ // src/mcp/helpers.ts
33845
+ init_agents2();
33846
+
33106
33847
  // src/db/projects.ts
33107
33848
  init_schema();
33108
33849
  init_types();
@@ -33138,15 +33879,12 @@ init_console_log();
33138
33879
  init_gallery();
33139
33880
 
33140
33881
  // src/lib/downloads.ts
33882
+ init_schema();
33141
33883
  import { randomUUID as randomUUID9 } from "crypto";
33142
33884
  import { join as join5, basename, extname } from "path";
33143
- import { mkdirSync as mkdirSync5, existsSync as existsSync2, readdirSync as readdirSync2, statSync, unlinkSync as unlinkSync2, copyFileSync, writeFileSync, readFileSync } from "fs";
33144
- import { homedir as homedir5 } from "os";
33145
- function getDataDir3() {
33146
- return process.env["BROWSER_DATA_DIR"] ?? join5(homedir5(), ".browser");
33147
- }
33885
+ import { mkdirSync as mkdirSync5, existsSync as existsSync3, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2, copyFileSync as copyFileSync2, writeFileSync, readFileSync } from "fs";
33148
33886
  function getDownloadsDir(sessionId) {
33149
- const base = join5(getDataDir3(), "downloads");
33887
+ const base = join5(getDataDir(), "downloads");
33150
33888
  const dir = sessionId ? join5(base, sessionId) : base;
33151
33889
  mkdirSync5(dir, { recursive: true });
33152
33890
  return dir;
@@ -33189,20 +33927,20 @@ function listDownloads(sessionId) {
33189
33927
  const dir = getDownloadsDir(sessionId);
33190
33928
  const results = [];
33191
33929
  function scanDir(d) {
33192
- if (!existsSync2(d))
33930
+ if (!existsSync3(d))
33193
33931
  return;
33194
- const entries = readdirSync2(d);
33932
+ const entries = readdirSync3(d);
33195
33933
  for (const entry of entries) {
33196
33934
  if (entry.endsWith(".meta.json"))
33197
33935
  continue;
33198
33936
  const full = join5(d, entry);
33199
- const stat = statSync(full);
33937
+ const stat = statSync2(full);
33200
33938
  if (stat.isDirectory()) {
33201
33939
  scanDir(full);
33202
33940
  continue;
33203
33941
  }
33204
33942
  const mpath = metaPath(full);
33205
- if (!existsSync2(mpath))
33943
+ if (!existsSync3(mpath))
33206
33944
  continue;
33207
33945
  try {
33208
33946
  const meta = JSON.parse(readFileSync(mpath, "utf8"));
@@ -33233,7 +33971,7 @@ function deleteDownload(id, sessionId) {
33233
33971
  return false;
33234
33972
  try {
33235
33973
  unlinkSync2(file.path);
33236
- if (existsSync2(file.meta_path))
33974
+ if (existsSync3(file.meta_path))
33237
33975
  unlinkSync2(file.meta_path);
33238
33976
  return true;
33239
33977
  } catch {
@@ -33257,7 +33995,7 @@ function exportToPath(id, targetPath, sessionId) {
33257
33995
  const file = getDownload(id, sessionId);
33258
33996
  if (!file)
33259
33997
  throw new Error(`Download not found: ${id}`);
33260
- copyFileSync(file.path, targetPath);
33998
+ copyFileSync2(file.path, targetPath);
33261
33999
  return targetPath;
33262
34000
  }
33263
34001
  function detectType(filename) {
@@ -33278,10 +34016,10 @@ function detectType(filename) {
33278
34016
  return map[ext] ?? "file";
33279
34017
  }
33280
34018
  // src/lib/gallery-diff.ts
34019
+ init_schema();
33281
34020
  var import_sharp2 = __toESM(require_lib(), 1);
33282
34021
  import { join as join6 } from "path";
33283
34022
  import { mkdirSync as mkdirSync6 } from "fs";
33284
- import { homedir as homedir6 } from "os";
33285
34023
  async function diffImages(path1, path2) {
33286
34024
  const img1 = import_sharp2.default(path1);
33287
34025
  const img2 = import_sharp2.default(path2);
@@ -33312,7 +34050,7 @@ async function diffImages(path1, path2) {
33312
34050
  diffBuffer[i + 2] = Math.round(raw1[i + 2] * 0.4);
33313
34051
  }
33314
34052
  }
33315
- const dataDir = process.env["BROWSER_DATA_DIR"] ?? join6(homedir6(), ".browser");
34053
+ const dataDir = getDataDir();
33316
34054
  const diffDir = join6(dataDir, "diffs");
33317
34055
  mkdirSync6(diffDir, { recursive: true });
33318
34056
  const diffPath = join6(diffDir, `diff-${Date.now()}.webp`);
@@ -33331,9 +34069,9 @@ async function diffImages(path1, path2) {
33331
34069
  init_snapshot();
33332
34070
 
33333
34071
  // src/lib/files-integration.ts
34072
+ init_schema();
33334
34073
  import { join as join7 } from "path";
33335
- import { mkdirSync as mkdirSync7, copyFileSync as copyFileSync2 } from "fs";
33336
- import { homedir as homedir7 } from "os";
34074
+ import { mkdirSync as mkdirSync7, copyFileSync as copyFileSync3 } from "fs";
33337
34075
  async function persistFile(localPath, opts) {
33338
34076
  try {
33339
34077
  const mod = await import("@hasna/files");
@@ -33342,13 +34080,13 @@ async function persistFile(localPath, opts) {
33342
34080
  return { id: ref.id, path: ref.path ?? localPath, permanent: true, provider: "open-files" };
33343
34081
  }
33344
34082
  } catch {}
33345
- const dataDir = process.env["BROWSER_DATA_DIR"] ?? join7(homedir7(), ".browser");
34083
+ const dataDir = getDataDir();
33346
34084
  const date = new Date().toISOString().split("T")[0];
33347
34085
  const dir = join7(dataDir, "persistent", date);
33348
34086
  mkdirSync7(dir, { recursive: true });
33349
34087
  const filename = localPath.split("/").pop() ?? "file";
33350
34088
  const targetPath = join7(dir, filename);
33351
- copyFileSync2(localPath, targetPath);
34089
+ copyFileSync3(localPath, targetPath);
33352
34090
  return {
33353
34091
  id: `local-${Date.now()}`,
33354
34092
  path: targetPath,
@@ -35234,7 +35972,7 @@ function register6(server) {
35234
35972
 
35235
35973
  // src/mcp/meta.ts
35236
35974
  function register7(server) {
35237
- server.tool("browser_register_agent", "Register an agent with the browser service", {
35975
+ server.tool("register_agent", "Register an agent session. Returns agent_id. Auto-triggers a heartbeat.", {
35238
35976
  name: exports_external.string(),
35239
35977
  description: exports_external.string().optional(),
35240
35978
  session_id: exports_external.string().optional().optional(),
@@ -35248,7 +35986,7 @@ function register7(server) {
35248
35986
  return err(e);
35249
35987
  }
35250
35988
  });
35251
- server.tool("browser_heartbeat", "Send a heartbeat for an agent", { agent_id: exports_external.string() }, async ({ agent_id }) => {
35989
+ server.tool("heartbeat", "Update last_seen_at to signal agent is active.", { agent_id: exports_external.string() }, async ({ agent_id }) => {
35252
35990
  try {
35253
35991
  heartbeat2(agent_id);
35254
35992
  return json({ ok: true, agent_id, timestamp: new Date().toISOString() });
@@ -35256,13 +35994,22 @@ function register7(server) {
35256
35994
  return err(e);
35257
35995
  }
35258
35996
  });
35259
- server.tool("browser_agent_list", "List registered agents", { project_id: exports_external.string().optional() }, async ({ project_id }) => {
35997
+ server.tool("list_agents", "List all registered agents.", { project_id: exports_external.string().optional() }, async ({ project_id }) => {
35260
35998
  try {
35261
35999
  return json({ agents: listAgents(project_id) });
35262
36000
  } catch (e) {
35263
36001
  return err(e);
35264
36002
  }
35265
36003
  });
36004
+ 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 }) => {
36005
+ try {
36006
+ const { updateAgent: update } = await Promise.resolve().then(() => (init_agents2(), exports_agents));
36007
+ update(agent_id, { project_id: project_id ?? null });
36008
+ return json({ ok: true, agent_id, project_id });
36009
+ } catch (e) {
36010
+ return err(e);
36011
+ }
36012
+ });
35266
36013
  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 }) => {
35267
36014
  try {
35268
36015
  const project = ensureProject(name, path, description);
@@ -35562,9 +36309,10 @@ function register7(server) {
35562
36309
  { tool: "browser_crawl", description: "Crawl a URL recursively" }
35563
36310
  ],
35564
36311
  Agent: [
35565
- { tool: "browser_register_agent", description: "Register an agent" },
35566
- { tool: "browser_heartbeat", description: "Send agent heartbeat" },
35567
- { tool: "browser_agent_list", description: "List registered agents" }
36312
+ { tool: "register_agent", description: "Register an agent session" },
36313
+ { tool: "heartbeat", description: "Update agent last_seen_at" },
36314
+ { tool: "list_agents", description: "List registered agents" },
36315
+ { tool: "set_focus", description: "Set active project context" }
35568
36316
  ],
35569
36317
  Project: [
35570
36318
  { tool: "browser_project_create", description: "Create or ensure a project" },
@@ -35630,16 +36378,16 @@ function register7(server) {
35630
36378
  });
35631
36379
  server.tool("browser_version", "Get the running browser MCP version, tool count, and environment info. Use this to verify which binary is active.", {}, async () => {
35632
36380
  try {
35633
- const { getDataDir: getDataDir4 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
36381
+ const { getDataDir: getDataDir2 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
35634
36382
  const toolCount = Object.keys(server._registeredTools ?? {}).length;
35635
- const { readFileSync: readFileSync8 } = await import("fs");
36383
+ const { readFileSync: readFileSync7 } = await import("fs");
35636
36384
  const { join: join15 } = await import("path");
35637
- const _pkg = JSON.parse(readFileSync8(join15(import.meta.dir, "../../package.json"), "utf8"));
36385
+ const _pkg = JSON.parse(readFileSync7(join15(import.meta.dir, "../../package.json"), "utf8"));
35638
36386
  return json({
35639
36387
  version: _pkg.version,
35640
36388
  mcp_tools_count: toolCount,
35641
36389
  bun_version: Bun.version,
35642
- data_dir: getDataDir4(),
36390
+ data_dir: getDataDir2(),
35643
36391
  node_env: process.env["NODE_ENV"] ?? "production"
35644
36392
  });
35645
36393
  } catch (e) {
@@ -36013,7 +36761,7 @@ function register8(server) {
36013
36761
  }
36014
36762
 
36015
36763
  // src/mcp/index.ts
36016
- var _pkg = JSON.parse(readFileSync8(join15(import.meta.dir, "../../package.json"), "utf8"));
36764
+ var _pkg = JSON.parse(readFileSync11(join15(import.meta.dir, "../../package.json"), "utf8"));
36017
36765
  var server = new McpServer2({
36018
36766
  name: "@hasna/browser",
36019
36767
  version: "0.0.1"