sparkecoder 0.1.119 → 0.1.121

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 (155) hide show
  1. package/dist/agent/index.d.ts +3 -3
  2. package/dist/agent/index.js +2 -0
  3. package/dist/agent/index.js.map +1 -1
  4. package/dist/cli.js +470 -135
  5. package/dist/cli.js.map +1 -1
  6. package/dist/db/index.d.ts +2 -2
  7. package/dist/{index-Bcz0aCAR.d.ts → index-DczYH89U.d.ts} +104 -104
  8. package/dist/index.d.ts +5 -5
  9. package/dist/index.js +429 -94
  10. package/dist/index.js.map +1 -1
  11. package/dist/{schema-BWbWmfDQ.d.ts → schema-DxrKyetI.d.ts} +3 -3
  12. package/dist/{search-DOzC4ojH.d.ts → search-CVVfuBPZ.d.ts} +4 -4
  13. package/dist/server/index.js +429 -94
  14. package/dist/server/index.js.map +1 -1
  15. package/dist/tools/index.d.ts +3 -3
  16. package/dist/tools/index.js.map +1 -1
  17. package/package.json +1 -1
  18. package/web/.next/BUILD_ID +1 -1
  19. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  20. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  21. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  22. package/web/.next/standalone/web/.next/server/app/(main)/agents/page_client-reference-manifest.js +1 -1
  23. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  24. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
  25. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  26. package/web/.next/standalone/web/.next/server/app/(main)/settings/page.js.nft.json +1 -1
  27. package/web/.next/standalone/web/.next/server/app/(main)/settings/page_client-reference-manifest.js +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  29. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  30. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  31. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  32. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  34. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  35. package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  36. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  37. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
  38. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  39. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  41. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  42. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  43. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  44. package/web/.next/standalone/web/.next/server/app/agents.html +1 -1
  45. package/web/.next/standalone/web/.next/server/app/agents.rsc +2 -2
  46. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents/__PAGE__.segment.rsc +1 -1
  47. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents.segment.rsc +1 -1
  48. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p.segment.rsc +1 -1
  49. package/web/.next/standalone/web/.next/server/app/agents.segments/_full.segment.rsc +2 -2
  50. package/web/.next/standalone/web/.next/server/app/agents.segments/_head.segment.rsc +1 -1
  51. package/web/.next/standalone/web/.next/server/app/agents.segments/_index.segment.rsc +2 -2
  52. package/web/.next/standalone/web/.next/server/app/agents.segments/_tree.segment.rsc +2 -2
  53. package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
  54. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  55. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +2 -2
  56. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
  57. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  58. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
  59. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
  60. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  61. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  62. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  63. package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
  64. package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
  65. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  66. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +2 -2
  67. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
  68. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  69. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
  70. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
  71. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  72. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  73. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  74. package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
  75. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  76. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +2 -2
  77. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
  78. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  79. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
  80. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
  81. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  82. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  83. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  84. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  85. package/web/.next/standalone/web/.next/server/app/docs.rsc +2 -2
  86. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
  87. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  88. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
  89. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
  90. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  91. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  92. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  93. package/web/.next/standalone/web/.next/server/app/index.rsc +2 -2
  94. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
  95. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
  96. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +2 -2
  97. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  98. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
  99. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  100. package/web/.next/standalone/web/.next/server/app/settings.html +1 -1
  101. package/web/.next/standalone/web/.next/server/app/settings.rsc +3 -3
  102. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings/__PAGE__.segment.rsc +2 -2
  103. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings.segment.rsc +1 -1
  104. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p.segment.rsc +1 -1
  105. package/web/.next/standalone/web/.next/server/app/settings.segments/_full.segment.rsc +3 -3
  106. package/web/.next/standalone/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  107. package/web/.next/standalone/web/.next/server/app/settings.segments/_index.segment.rsc +2 -2
  108. package/web/.next/standalone/web/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  109. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_lucide-react_dist_esm_icons_6ab1f7b7._.js +3 -0
  110. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__f3e6443f._.js → [root-of-the-server]__4de426bd._.js} +2 -2
  111. package/web/.next/standalone/web/.next/server/chunks/ssr/web_62ca4286._.js +3 -0
  112. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_settings_page_tsx_eb320e07._.js +3 -1
  113. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  114. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  115. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  116. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  117. package/web/.next/standalone/web/.next/static/chunks/74ae1f17d607b2fc.js +7 -0
  118. package/web/.next/standalone/web/.next/static/chunks/883ea0d08f88e366.js +1 -0
  119. package/web/.next/standalone/web/.next/static/chunks/{44c575e006ddb992.js → 91988e253d5fa420.js} +4 -4
  120. package/web/.next/standalone/web/.next/static/chunks/9b88f148788e4504.js +3 -0
  121. package/web/.next/standalone/web/.next/static/chunks/acb0fc66f5414af6.css +1 -0
  122. package/web/.next/standalone/web/.next/static/static/chunks/74ae1f17d607b2fc.js +7 -0
  123. package/web/.next/standalone/web/.next/static/static/chunks/883ea0d08f88e366.js +1 -0
  124. package/web/.next/standalone/web/.next/static/static/chunks/{44c575e006ddb992.js → 91988e253d5fa420.js} +4 -4
  125. package/web/.next/standalone/web/.next/static/static/chunks/9b88f148788e4504.js +3 -0
  126. package/web/.next/standalone/web/.next/static/static/chunks/acb0fc66f5414af6.css +1 -0
  127. package/web/.next/standalone/web/src/app/(main)/settings/page.tsx +464 -1
  128. package/web/.next/static/chunks/74ae1f17d607b2fc.js +7 -0
  129. package/web/.next/static/chunks/883ea0d08f88e366.js +1 -0
  130. package/web/.next/static/chunks/{44c575e006ddb992.js → 91988e253d5fa420.js} +4 -4
  131. package/web/.next/static/chunks/9b88f148788e4504.js +3 -0
  132. package/web/.next/static/chunks/acb0fc66f5414af6.css +1 -0
  133. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_lucide-react_dist_esm_icons_7340c8b3._.js +0 -3
  134. package/web/.next/standalone/web/.next/server/chunks/ssr/web_41927ef5._.js +0 -3
  135. package/web/.next/standalone/web/.next/static/chunks/2c3c1d478808e4e4.js +0 -1
  136. package/web/.next/standalone/web/.next/static/chunks/3b0501ec3249235f.js +0 -7
  137. package/web/.next/standalone/web/.next/static/chunks/9a3afb48c245fb2a.js +0 -1
  138. package/web/.next/standalone/web/.next/static/chunks/b3bc7244f3477729.css +0 -1
  139. package/web/.next/standalone/web/.next/static/static/chunks/2c3c1d478808e4e4.js +0 -1
  140. package/web/.next/standalone/web/.next/static/static/chunks/3b0501ec3249235f.js +0 -7
  141. package/web/.next/standalone/web/.next/static/static/chunks/9a3afb48c245fb2a.js +0 -1
  142. package/web/.next/standalone/web/.next/static/static/chunks/b3bc7244f3477729.css +0 -1
  143. package/web/.next/static/chunks/2c3c1d478808e4e4.js +0 -1
  144. package/web/.next/static/chunks/3b0501ec3249235f.js +0 -7
  145. package/web/.next/static/chunks/9a3afb48c245fb2a.js +0 -1
  146. package/web/.next/static/chunks/b3bc7244f3477729.css +0 -1
  147. /package/web/.next/standalone/web/.next/static/{Bt00m8W4k5F79ALhN700F → static/wP9z41wtqT4k-O6AlEXqw}/_buildManifest.js +0 -0
  148. /package/web/.next/standalone/web/.next/static/{Bt00m8W4k5F79ALhN700F → static/wP9z41wtqT4k-O6AlEXqw}/_clientMiddlewareManifest.json +0 -0
  149. /package/web/.next/standalone/web/.next/static/{Bt00m8W4k5F79ALhN700F → static/wP9z41wtqT4k-O6AlEXqw}/_ssgManifest.js +0 -0
  150. /package/web/.next/standalone/web/.next/static/{static/Bt00m8W4k5F79ALhN700F → wP9z41wtqT4k-O6AlEXqw}/_buildManifest.js +0 -0
  151. /package/web/.next/standalone/web/.next/static/{static/Bt00m8W4k5F79ALhN700F → wP9z41wtqT4k-O6AlEXqw}/_clientMiddlewareManifest.json +0 -0
  152. /package/web/.next/standalone/web/.next/static/{static/Bt00m8W4k5F79ALhN700F → wP9z41wtqT4k-O6AlEXqw}/_ssgManifest.js +0 -0
  153. /package/web/.next/static/{Bt00m8W4k5F79ALhN700F → wP9z41wtqT4k-O6AlEXqw}/_buildManifest.js +0 -0
  154. /package/web/.next/static/{Bt00m8W4k5F79ALhN700F → wP9z41wtqT4k-O6AlEXqw}/_clientMiddlewareManifest.json +0 -0
  155. /package/web/.next/static/{Bt00m8W4k5F79ALhN700F → wP9z41wtqT4k-O6AlEXqw}/_ssgManifest.js +0 -0
@@ -963,6 +963,7 @@ __export(config_exports, {
963
963
  setApiKey: () => setApiKey,
964
964
  setMcpServers: () => setMcpServers,
965
965
  setPublicUrl: () => setPublicUrl,
966
+ setSkillsAdditionalDirectories: () => setSkillsAdditionalDirectories,
966
967
  setSlackConfig: () => setSlackConfig,
967
968
  setWebhookToken: () => setWebhookToken
968
969
  });
@@ -1252,6 +1253,40 @@ function setWebhookToken(token) {
1252
1253
  console.warn("[config] failed to persist webhook token:", err?.message || err);
1253
1254
  }
1254
1255
  }
1256
+ function setSkillsAdditionalDirectories(directories) {
1257
+ if (cachedConfig) {
1258
+ const cur = cachedConfig.skills || {};
1259
+ cachedConfig.skills = { ...cur, additionalDirectories: directories };
1260
+ try {
1261
+ const discovered = discoverSkillDirectories(cachedConfig.resolvedWorkingDirectory);
1262
+ const additionalAbs = directories.map((d) => resolve(cachedConfig.resolvedWorkingDirectory, d)).filter((d) => existsSync(d));
1263
+ cachedConfig.discoveredSkills = discovered;
1264
+ cachedConfig.resolvedSkillsDirectories = [
1265
+ ...discovered.allDirectories,
1266
+ ...additionalAbs
1267
+ ];
1268
+ } catch {
1269
+ }
1270
+ }
1271
+ try {
1272
+ const cwdPath = resolve(process.cwd(), "sparkecoder.config.json");
1273
+ const target = existsSync(cwdPath) ? cwdPath : join(ensureAppDataDirectory(), "sparkecoder.config.json");
1274
+ let raw = {};
1275
+ if (existsSync(target)) {
1276
+ try {
1277
+ raw = JSON.parse(readFileSync(target, "utf-8"));
1278
+ } catch {
1279
+ raw = {};
1280
+ }
1281
+ } else {
1282
+ raw = createDefaultConfig();
1283
+ }
1284
+ raw.skills = { ...raw.skills || {}, additionalDirectories: directories };
1285
+ writeFileSync(target, JSON.stringify(raw, null, 2));
1286
+ } catch (err) {
1287
+ console.warn("[config] failed to persist skill directories:", err?.message || err);
1288
+ }
1289
+ }
1255
1290
  function setPublicUrl(publicUrl) {
1256
1291
  if (cachedConfig) {
1257
1292
  cachedConfig.server = { ...cachedConfig.server ?? {}, publicUrl };
@@ -3013,7 +3048,7 @@ async function createClient(serverId, handle, root) {
3013
3048
  },
3014
3049
  async waitForDiagnostics(filePath, timeoutMs = 5e3) {
3015
3050
  const normalized = normalizePath(filePath);
3016
- return new Promise((resolve12) => {
3051
+ return new Promise((resolve13) => {
3017
3052
  const startTime = Date.now();
3018
3053
  let debounceTimer;
3019
3054
  let resolved = false;
@@ -3032,7 +3067,7 @@ async function createClient(serverId, handle, root) {
3032
3067
  if (resolved) return;
3033
3068
  resolved = true;
3034
3069
  cleanup2();
3035
- resolve12(diagnostics.get(normalized) || []);
3070
+ resolve13(diagnostics.get(normalized) || []);
3036
3071
  };
3037
3072
  const onDiagnostic = () => {
3038
3073
  if (debounceTimer) clearTimeout(debounceTimer);
@@ -3407,7 +3442,7 @@ Working directory: ${options.workingDirectory}`,
3407
3442
  isChunked: true
3408
3443
  });
3409
3444
  if (chunkCount > 1) {
3410
- await new Promise((resolve12) => setTimeout(resolve12, 0));
3445
+ await new Promise((resolve13) => setTimeout(resolve13, 0));
3411
3446
  }
3412
3447
  }
3413
3448
  }
@@ -4026,7 +4061,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
4026
4061
  if (!existsSync10(directory)) {
4027
4062
  return [];
4028
4063
  }
4029
- const skills = [];
4064
+ const skills2 = [];
4030
4065
  const entries = await readdir(directory, { withFileTypes: true });
4031
4066
  for (const entry2 of entries) {
4032
4067
  let filePath;
@@ -4050,7 +4085,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
4050
4085
  if (parsed) {
4051
4086
  const alwaysApply = forceAlwaysApply || parsed.metadata.alwaysApply;
4052
4087
  const loadType = alwaysApply ? "always" : defaultLoadType;
4053
- skills.push({
4088
+ skills2.push({
4054
4089
  name: parsed.metadata.name,
4055
4090
  description: parsed.metadata.description,
4056
4091
  filePath,
@@ -4064,7 +4099,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
4064
4099
  } else {
4065
4100
  const name = getSkillNameFromPath(filePath);
4066
4101
  const firstParagraph = content.split("\n\n")[0]?.slice(0, 200) || "No description";
4067
- skills.push({
4102
+ skills2.push({
4068
4103
  name,
4069
4104
  description: firstParagraph.replace(/^#\s*/, "").trim(),
4070
4105
  filePath,
@@ -4077,7 +4112,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
4077
4112
  });
4078
4113
  }
4079
4114
  }
4080
- return skills.filter(
4115
+ return skills2.filter(
4081
4116
  (s) => s.platforms.length === 0 || s.platforms.includes(process.platform)
4082
4117
  );
4083
4118
  }
@@ -4085,8 +4120,8 @@ async function loadAllSkills(directories) {
4085
4120
  const allSkills = [];
4086
4121
  const seenNames = /* @__PURE__ */ new Set();
4087
4122
  for (const dir of directories) {
4088
- const skills = await loadSkillsFromDirectory(dir);
4089
- for (const skill of skills) {
4123
+ const skills2 = await loadSkillsFromDirectory(dir);
4124
+ for (const skill of skills2) {
4090
4125
  if (!seenNames.has(skill.name.toLowerCase())) {
4091
4126
  seenNames.add(skill.name.toLowerCase());
4092
4127
  allSkills.push(skill);
@@ -4099,12 +4134,12 @@ async function loadAllSkillsFromDiscovered(discovered) {
4099
4134
  const allSkills = [];
4100
4135
  const seenNames = /* @__PURE__ */ new Set();
4101
4136
  for (const { path, priority } of discovered.alwaysLoadedDirs) {
4102
- const skills = await loadSkillsFromDirectory(path, {
4137
+ const skills2 = await loadSkillsFromDirectory(path, {
4103
4138
  priority,
4104
4139
  defaultLoadType: "always",
4105
4140
  forceAlwaysApply: true
4106
4141
  });
4107
- for (const skill of skills) {
4142
+ for (const skill of skills2) {
4108
4143
  if (!seenNames.has(skill.name.toLowerCase())) {
4109
4144
  seenNames.add(skill.name.toLowerCase());
4110
4145
  allSkills.push(skill);
@@ -4112,12 +4147,12 @@ async function loadAllSkillsFromDiscovered(discovered) {
4112
4147
  }
4113
4148
  }
4114
4149
  for (const { path, priority } of discovered.onDemandDirs) {
4115
- const skills = await loadSkillsFromDirectory(path, {
4150
+ const skills2 = await loadSkillsFromDirectory(path, {
4116
4151
  priority,
4117
4152
  defaultLoadType: "on_demand",
4118
4153
  forceAlwaysApply: false
4119
4154
  });
4120
- for (const skill of skills) {
4155
+ for (const skill of skills2) {
4121
4156
  if (!seenNames.has(skill.name.toLowerCase())) {
4122
4157
  seenNames.add(skill.name.toLowerCase());
4123
4158
  allSkills.push(skill);
@@ -4142,7 +4177,7 @@ async function loadAllSkillsFromDiscovered(discovered) {
4142
4177
  all: allSkills
4143
4178
  };
4144
4179
  }
4145
- async function getGlobMatchedSkills(skills, activeFiles, workingDirectory) {
4180
+ async function getGlobMatchedSkills(skills2, activeFiles, workingDirectory) {
4146
4181
  if (activeFiles.length === 0) {
4147
4182
  return [];
4148
4183
  }
@@ -4152,7 +4187,7 @@ async function getGlobMatchedSkills(skills, activeFiles, workingDirectory) {
4152
4187
  }
4153
4188
  return f;
4154
4189
  });
4155
- const matchedSkills = skills.filter((skill) => {
4190
+ const matchedSkills = skills2.filter((skill) => {
4156
4191
  if (skill.alwaysApply || skill.loadType === "always") {
4157
4192
  return false;
4158
4193
  }
@@ -4198,8 +4233,8 @@ async function loadSkillContent(skillName, directories) {
4198
4233
  content: parsed ? parsed.body : content
4199
4234
  };
4200
4235
  }
4201
- function formatSkillsForContext(skills) {
4202
- const onDemandSkills = skills.filter((s) => !s.alwaysApply && s.loadType !== "always");
4236
+ function formatSkillsForContext(skills2) {
4237
+ const onDemandSkills = skills2.filter((s) => !s.alwaysApply && s.loadType !== "always");
4203
4238
  if (onDemandSkills.length === 0) {
4204
4239
  return "No on-demand skills available.";
4205
4240
  }
@@ -4210,12 +4245,12 @@ function formatSkillsForContext(skills) {
4210
4245
  }
4211
4246
  return lines.join("\n");
4212
4247
  }
4213
- function formatAlwaysLoadedSkills(skills) {
4214
- if (skills.length === 0) {
4248
+ function formatAlwaysLoadedSkills(skills2) {
4249
+ if (skills2.length === 0) {
4215
4250
  return "";
4216
4251
  }
4217
4252
  const sections = [];
4218
- for (const skill of skills) {
4253
+ for (const skill of skills2) {
4219
4254
  sections.push(`### ${skill.name}
4220
4255
 
4221
4256
  ${skill.content}`);
@@ -4224,12 +4259,12 @@ ${skill.content}`);
4224
4259
 
4225
4260
  ${sections.join("\n\n---\n\n")}`;
4226
4261
  }
4227
- function formatGlobMatchedSkills(skills) {
4228
- if (skills.length === 0) {
4262
+ function formatGlobMatchedSkills(skills2) {
4263
+ if (skills2.length === 0) {
4229
4264
  return "";
4230
4265
  }
4231
4266
  const sections = [];
4232
- for (const skill of skills) {
4267
+ for (const skill of skills2) {
4233
4268
  sections.push(`### ${skill.name}
4234
4269
 
4235
4270
  ${skill.content}`);
@@ -4271,16 +4306,16 @@ Once loaded, a skill's content will be available in the conversation context.`,
4271
4306
  try {
4272
4307
  switch (action) {
4273
4308
  case "list": {
4274
- const skills = await loadAllSkills(options.skillsDirectories);
4309
+ const skills2 = await loadAllSkills(options.skillsDirectories);
4275
4310
  return {
4276
4311
  success: true,
4277
4312
  action: "list",
4278
- skillCount: skills.length,
4279
- skills: skills.map((s) => ({
4313
+ skillCount: skills2.length,
4314
+ skills: skills2.map((s) => ({
4280
4315
  name: s.name,
4281
4316
  description: s.description
4282
4317
  })),
4283
- formatted: formatSkillsForContext(skills)
4318
+ formatted: formatSkillsForContext(skills2)
4284
4319
  };
4285
4320
  }
4286
4321
  case "load": {
@@ -4718,8 +4753,8 @@ var init_subagent = __esm({
4718
4753
  if (eventQueue.length > 0) {
4719
4754
  yield eventQueue.shift();
4720
4755
  } else if (!done) {
4721
- const event = await new Promise((resolve12) => {
4722
- resolveNext = resolve12;
4756
+ const event = await new Promise((resolve13) => {
4757
+ resolveNext = resolve13;
4723
4758
  });
4724
4759
  if (event) {
4725
4760
  yield event;
@@ -6495,8 +6530,8 @@ async function buildSystemPrompt(options) {
6495
6530
  }
6496
6531
  } else {
6497
6532
  const { loadAllSkills: loadAllSkills2 } = await Promise.resolve().then(() => (init_skills(), skills_exports));
6498
- const skills = await loadAllSkills2(skillsDirectories);
6499
- onDemandSkillsContext = formatSkillsForContext(skills);
6533
+ const skills2 = await loadAllSkills2(skillsDirectories);
6534
+ onDemandSkillsContext = formatSkillsForContext(skills2);
6500
6535
  }
6501
6536
  const todos = await todoQueries.getBySession(sessionId);
6502
6537
  const todosContext = formatTodosForContext(todos);
@@ -7802,6 +7837,9 @@ function isSlackConfigured() {
7802
7837
  function getSlackSigningSecret() {
7803
7838
  return readSlackConfig()?.signingSecret ?? null;
7804
7839
  }
7840
+ function getSlackBotToken() {
7841
+ return readSlackConfig()?.botToken ?? null;
7842
+ }
7805
7843
  function getDefaultOrchestratorName() {
7806
7844
  return readSlackConfig()?.defaultOrchestratorName ?? null;
7807
7845
  }
@@ -7856,6 +7894,62 @@ function getSlackAllowlistPolicy() {
7856
7894
  return { allowedUsers: [], allowedChannels: [], allowDmsFromAnyone: true };
7857
7895
  }
7858
7896
  }
7897
+ async function fetchSlackUserName(userId) {
7898
+ const token = getSlackBotToken();
7899
+ if (!token) return null;
7900
+ try {
7901
+ const res = await fetch(`https://slack.com/api/users.info?user=${encodeURIComponent(userId)}`, {
7902
+ headers: { Authorization: `Bearer ${token}` }
7903
+ });
7904
+ const data = await res.json().catch(() => ({}));
7905
+ if (!data?.ok) {
7906
+ console.warn(`[slack] users.info(${userId}) failed: ${data?.error || `HTTP ${res.status}`}`);
7907
+ return null;
7908
+ }
7909
+ const profile = data.user?.profile || {};
7910
+ const name = profile.display_name_normalized || profile.display_name || profile.real_name_normalized || profile.real_name || data.user?.real_name || data.user?.name || null;
7911
+ return name ? String(name) : null;
7912
+ } catch (err) {
7913
+ console.warn(`[slack] users.info(${userId}) error:`, err?.message || err);
7914
+ return null;
7915
+ }
7916
+ }
7917
+ async function resolveSlackUserName(userId) {
7918
+ if (!userId) return null;
7919
+ const now = Date.now();
7920
+ const hit = userNameCache.get(userId);
7921
+ if (hit && hit.expiresAt > now) return hit.name;
7922
+ const inflight = userInflight.get(userId);
7923
+ if (inflight) return inflight;
7924
+ const p = (async () => {
7925
+ const name = await fetchSlackUserName(userId);
7926
+ userNameCache.set(userId, {
7927
+ name,
7928
+ expiresAt: now + (name ? USER_TTL_MS : USER_FAIL_TTL_MS)
7929
+ });
7930
+ userInflight.delete(userId);
7931
+ return name;
7932
+ })();
7933
+ userInflight.set(userId, p);
7934
+ return p;
7935
+ }
7936
+ async function normalizeSlackMentions(text) {
7937
+ if (!text) return text;
7938
+ const userMentionRe = /<@([UW][A-Z0-9]+)(?:\|([^>]+))?>/g;
7939
+ const userIds = /* @__PURE__ */ new Set();
7940
+ for (const m of text.matchAll(userMentionRe)) {
7941
+ if (!m[2]) userIds.add(m[1]);
7942
+ }
7943
+ if (userIds.size > 0) {
7944
+ await Promise.all([...userIds].map((id) => resolveSlackUserName(id)));
7945
+ }
7946
+ return text.replace(userMentionRe, (_full, id, label) => {
7947
+ if (label) return `${label} <@${id}>`;
7948
+ const cached = userNameCache.get(id);
7949
+ const name = cached?.name;
7950
+ return name ? `${name} <@${id}>` : `<@${id}>`;
7951
+ });
7952
+ }
7859
7953
  function getSlackDeniedReplyPolicy() {
7860
7954
  try {
7861
7955
  const cfg = getConfig();
@@ -7868,13 +7962,17 @@ function getSlackDeniedReplyPolicy() {
7868
7962
  return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
7869
7963
  }
7870
7964
  }
7871
- var cachedSelf, selfInflight, DEFAULT_DENIED_TEMPLATE;
7965
+ var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS, userNameCache, userInflight, DEFAULT_DENIED_TEMPLATE;
7872
7966
  var init_client3 = __esm({
7873
7967
  "src/integrations/slack/client.ts"() {
7874
7968
  "use strict";
7875
7969
  init_config();
7876
7970
  cachedSelf = null;
7877
7971
  selfInflight = null;
7972
+ USER_TTL_MS = 60 * 60 * 1e3;
7973
+ USER_FAIL_TTL_MS = 5 * 60 * 1e3;
7974
+ userNameCache = /* @__PURE__ */ new Map();
7975
+ userInflight = /* @__PURE__ */ new Map();
7878
7976
  DEFAULT_DENIED_TEMPLATE = "Sorry, you don't have permission to use this bot. (Contact the bot owner if you think this is a mistake.)";
7879
7977
  }
7880
7978
  });
@@ -7889,9 +7987,6 @@ function markThreadOwned(channel, threadTs) {
7889
7987
  function isThreadOwned(channel, threadTs) {
7890
7988
  return ownedThreads.has(threadKey(channel, threadTs));
7891
7989
  }
7892
- function stripMention(text) {
7893
- return String(text || "").replace(/<@[^>]+>/g, "").trim();
7894
- }
7895
7990
  function isSelfAuthored(event, self) {
7896
7991
  if (!self) return true;
7897
7992
  if (self.botId && event.bot_id && event.bot_id === self.botId) return true;
@@ -7906,14 +8001,22 @@ function slackEventToInboundResult(event, opts = {}) {
7906
8001
  return { event: null, dropReason: "bot_message" };
7907
8002
  }
7908
8003
  if (event.type === "message" && event.subtype && IGNORED_MESSAGE_SUBTYPES.has(event.subtype)) {
7909
- return { event: null, dropReason: "bot_message" };
8004
+ return { event: null, dropReason: "ignored_subtype" };
7910
8005
  }
7911
8006
  const isDm = event.type === "message" && event.channel_type === "im";
7912
8007
  const isThreadReply = event.type === "message" && !isDm && typeof event.thread_ts === "string" && event.thread_ts !== event.ts;
8008
+ const isNonThreadChannelMsg = event.type === "message" && !isDm && !isThreadReply && (event.channel_type === "channel" || event.channel_type === "group" || event.channel_type === "mpim" || // Some payload shapes omit channel_type for channel messages.
8009
+ typeof event.channel === "string");
7913
8010
  if (event.type !== "app_mention" && !isDm && !isThreadReply) {
8011
+ if (isNonThreadChannelMsg) {
8012
+ return { event: null, dropReason: "non_thread_channel_msg" };
8013
+ }
8014
+ if (event.type !== "message") {
8015
+ return { event: null, dropReason: "non_message_event" };
8016
+ }
7914
8017
  return { event: null, dropReason: "unsupported_type" };
7915
8018
  }
7916
- const text = event.type === "app_mention" ? stripMention(event.text) : (event.text ?? "").trim();
8019
+ const text = (event.text ?? "").trim();
7917
8020
  if (!text) return { event: null, dropReason: "empty_text" };
7918
8021
  const policy = getSlackAllowlistPolicy();
7919
8022
  const userAllowlistActive = policy.allowedUsers.length > 0;
@@ -8896,11 +8999,11 @@ function waitForTaskQuestionAnswer(question) {
8896
8999
  if (pendingQuestions.has(k)) {
8897
9000
  return Promise.reject(new Error(`Question already pending: ${question.questionId}`));
8898
9001
  }
8899
- return new Promise((resolve12, reject) => {
9002
+ return new Promise((resolve13, reject) => {
8900
9003
  pendingQuestions.set(k, {
8901
9004
  ...question,
8902
9005
  createdAt: /* @__PURE__ */ new Date(),
8903
- resolve: resolve12,
9006
+ resolve: resolve13,
8904
9007
  reject
8905
9008
  });
8906
9009
  });
@@ -10049,14 +10152,14 @@ ${p.text}` : p.text;
10049
10152
  const result = await recorder.encode();
10050
10153
  recorder.clear();
10051
10154
  if (!result) return [];
10052
- const { readFile: readFile12, unlink: unlink3 } = await import("fs/promises");
10155
+ const { readFile: readFile13, unlink: unlink4 } = await import("fs/promises");
10053
10156
  const uploadInfo = await storageQueries2.getUploadUrl(
10054
10157
  this.session.id,
10055
10158
  `browser-recording-${Date.now()}.mp4`,
10056
10159
  "video/mp4",
10057
10160
  "browser-recording"
10058
10161
  );
10059
- const fileData = await readFile12(result.path);
10162
+ const fileData = await readFile13(result.path);
10060
10163
  await fetch(uploadInfo.uploadUrl, {
10061
10164
  method: "PUT",
10062
10165
  headers: { "Content-Type": "video/mp4" },
@@ -10064,7 +10167,7 @@ ${p.text}` : p.text;
10064
10167
  });
10065
10168
  await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: result.sizeBytes });
10066
10169
  const dlInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
10067
- await unlink3(result.path).catch(() => {
10170
+ await unlink4(result.path).catch(() => {
10068
10171
  });
10069
10172
  console.log(`[TASK] Browser recording uploaded (${result.sizeBytes} bytes)`);
10070
10173
  return [dlInfo.downloadUrl];
@@ -10082,13 +10185,13 @@ ${p.text}` : p.text;
10082
10185
  try {
10083
10186
  const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
10084
10187
  if (!isRemoteConfigured2()) return [];
10085
- const { readFile: readFile12 } = await import("fs/promises");
10086
- const { join: join16, basename: basename6 } = await import("path");
10188
+ const { readFile: readFile13 } = await import("fs/promises");
10189
+ const { join: join17, basename: basename7 } = await import("path");
10087
10190
  const urls = [];
10088
10191
  for (const filePath of filePaths) {
10089
10192
  try {
10090
- const fullPath = filePath.startsWith("/") ? filePath : join16(this.session.workingDirectory, filePath);
10091
- const fileName = basename6(fullPath);
10193
+ const fullPath = filePath.startsWith("/") ? filePath : join17(this.session.workingDirectory, filePath);
10194
+ const fileName = basename7(fullPath);
10092
10195
  const ext = fileName.split(".").pop()?.toLowerCase() || "";
10093
10196
  const mimeMap = {
10094
10197
  pdf: "application/pdf",
@@ -10112,7 +10215,7 @@ ${p.text}` : p.text;
10112
10215
  contentType,
10113
10216
  "task-output"
10114
10217
  );
10115
- const fileData = await readFile12(fullPath);
10218
+ const fileData = await readFile13(fullPath);
10116
10219
  await fetch(uploadInfo.uploadUrl, {
10117
10220
  method: "PUT",
10118
10221
  headers: { "Content-Type": contentType },
@@ -10161,8 +10264,8 @@ ${p.text}` : p.text;
10161
10264
  this.pendingApprovals.set(toolCallId, await execution);
10162
10265
  options.onApprovalRequired?.(await execution);
10163
10266
  await sessionQueries.updateStatus(this.session.id, "waiting");
10164
- const approved = await new Promise((resolve12) => {
10165
- approvalResolvers.set(toolCallId, { resolve: resolve12, sessionId: this.session.id });
10267
+ const approved = await new Promise((resolve13) => {
10268
+ approvalResolvers.set(toolCallId, { resolve: resolve13, sessionId: this.session.id });
10166
10269
  });
10167
10270
  const resolverData = approvalResolvers.get(toolCallId);
10168
10271
  approvalResolvers.delete(toolCallId);
@@ -10268,8 +10371,8 @@ async function withSessionLock(sessionId, fn) {
10268
10371
  state2.pending++;
10269
10372
  const prev = state2.tail;
10270
10373
  let release;
10271
- const next = new Promise((resolve12) => {
10272
- release = resolve12;
10374
+ const next = new Promise((resolve13) => {
10375
+ release = resolve13;
10273
10376
  });
10274
10377
  state2.tail = prev.then(() => next);
10275
10378
  await prev;
@@ -10659,12 +10762,12 @@ var init_scheduler = __esm({
10659
10762
 
10660
10763
  // src/server/index.ts
10661
10764
  import "dotenv/config";
10662
- import { Hono as Hono9 } from "hono";
10765
+ import { Hono as Hono10 } from "hono";
10663
10766
  import { serve } from "@hono/node-server";
10664
10767
  import { cors } from "hono/cors";
10665
10768
  import { logger } from "hono/logger";
10666
- import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
10667
- import { resolve as resolve11, dirname as dirname8, join as join15 } from "path";
10769
+ import { existsSync as existsSync21, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
10770
+ import { resolve as resolve12, dirname as dirname9, join as join16 } from "path";
10668
10771
  import { spawn as spawn2 } from "child_process";
10669
10772
  import { createServer as createNetServer } from "net";
10670
10773
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -11839,7 +11942,7 @@ async function emitSyntheticToolStreaming(writeSSE, toolCallStarts, toolCallId,
11839
11942
  toolCallId,
11840
11943
  argsTextDelta: chunk
11841
11944
  }));
11842
- await new Promise((resolve12) => setTimeout(resolve12, 0));
11945
+ await new Promise((resolve13) => setTimeout(resolve13, 0));
11843
11946
  }
11844
11947
  }
11845
11948
  function buildDevtoolsContextXml(sessionId) {
@@ -12094,7 +12197,7 @@ ${prompt}` });
12094
12197
  chunkIndex,
12095
12198
  chunkCount
12096
12199
  }));
12097
- await new Promise((resolve12) => setTimeout(resolve12, 0));
12200
+ await new Promise((resolve13) => setTimeout(resolve13, 0));
12098
12201
  }
12099
12202
  const browserPort = progress.data?.browserStreamPort;
12100
12203
  const browserClosed = progress.data?.browserClosed;
@@ -12641,7 +12744,7 @@ agents.post(
12641
12744
  chunkIndex,
12642
12745
  chunkCount
12643
12746
  }));
12644
- await new Promise((resolve12) => setTimeout(resolve12, 0));
12747
+ await new Promise((resolve13) => setTimeout(resolve13, 0));
12645
12748
  }
12646
12749
  const browserPort = progress.data?.browserStreamPort;
12647
12750
  const browserClosed = progress.data?.browserClosed;
@@ -13654,16 +13757,18 @@ init_webhook_events();
13654
13757
  init_inbox();
13655
13758
  var recentlyHandled = /* @__PURE__ */ new Map();
13656
13759
  var MAX_RECENT = 1e3;
13657
- function alreadyHandled(channel, ts) {
13760
+ function wasHandled(channel, ts) {
13658
13761
  if (!channel || !ts) return false;
13762
+ return recentlyHandled.has(`${channel}\u241F${ts}`);
13763
+ }
13764
+ function markHandled(channel, ts) {
13765
+ if (!channel || !ts) return;
13659
13766
  const key2 = `${channel}\u241F${ts}`;
13660
- if (recentlyHandled.has(key2)) return true;
13661
13767
  recentlyHandled.set(key2, Date.now());
13662
13768
  if (recentlyHandled.size > MAX_RECENT) {
13663
13769
  const oldest = recentlyHandled.keys().next().value;
13664
13770
  if (oldest) recentlyHandled.delete(oldest);
13665
13771
  }
13666
- return false;
13667
13772
  }
13668
13773
  var slack = new Hono6();
13669
13774
  slack.post("/events", async (c) => {
@@ -13700,7 +13805,7 @@ slack.post("/events", async (c) => {
13700
13805
  textSnippet: typeof ev.text === "string" ? ev.text : void 0,
13701
13806
  meta: { ts: ev.ts, thread_ts: ev.thread_ts, team: ev.team, event_subtype: ev.subtype }
13702
13807
  });
13703
- if (alreadyHandled(ev.channel, ev.ts)) {
13808
+ if (wasHandled(ev.channel, ev.ts)) {
13704
13809
  updateEvent(auditId, { status: "dropped", dropReason: "duplicate_delivery" });
13705
13810
  return c.json({ ok: true });
13706
13811
  }
@@ -13724,7 +13829,18 @@ slack.post("/events", async (c) => {
13724
13829
  }
13725
13830
  const orchestratorId = await findOrCreateOrchestratorId();
13726
13831
  if (orchestratorId) {
13832
+ inbound.content = await normalizeSlackMentions(inbound.content);
13833
+ if (ev.user) {
13834
+ const speakerName = await resolveSlackUserName(ev.user);
13835
+ if (speakerName) {
13836
+ inbound.content = inbound.content.replace(
13837
+ `user=${ev.user}`,
13838
+ `user=${speakerName} <@${ev.user}>`
13839
+ );
13840
+ }
13841
+ }
13727
13842
  pushToInbox(orchestratorId, inbound);
13843
+ markHandled(ev.channel, ev.ts);
13728
13844
  updateEvent(auditId, { status: "routed", sessionId: orchestratorId });
13729
13845
  } else {
13730
13846
  updateEvent(auditId, { status: "error", error: "no orchestrator session available" });
@@ -14136,6 +14252,224 @@ mcpRouter.post("/:id/test", async (c) => {
14136
14252
  return c.json(result);
14137
14253
  });
14138
14254
 
14255
+ // src/server/routes/skills.ts
14256
+ init_config();
14257
+ init_skills();
14258
+ import { Hono as Hono9 } from "hono";
14259
+ import { zValidator as zValidator7 } from "@hono/zod-validator";
14260
+ import { z as z22 } from "zod";
14261
+ import { existsSync as existsSync20, statSync as statSync3 } from "fs";
14262
+ import { readFile as readFile12, writeFile as writeFile6, unlink as unlink3, mkdir as mkdir5 } from "fs/promises";
14263
+ import { resolve as resolve11, join as join15, basename as basename6, dirname as dirname8, extname as extname9 } from "path";
14264
+ var skills = new Hono9();
14265
+ function encodeId(filePath) {
14266
+ return Buffer.from(filePath, "utf-8").toString("base64url");
14267
+ }
14268
+ function decodeId(id) {
14269
+ try {
14270
+ const decoded = Buffer.from(id, "base64url").toString("utf-8");
14271
+ return decoded || null;
14272
+ } catch {
14273
+ return null;
14274
+ }
14275
+ }
14276
+ function listAllDirectories() {
14277
+ const cfg = getConfig();
14278
+ const discovered = discoverSkillDirectories(cfg.resolvedWorkingDirectory);
14279
+ const out = [];
14280
+ for (const { path, priority } of discovered.alwaysLoadedDirs) {
14281
+ out.push({
14282
+ path,
14283
+ source: pathToSource(path),
14284
+ label: pathToLabel(path),
14285
+ priority,
14286
+ alwaysApply: true
14287
+ });
14288
+ }
14289
+ for (const { path, priority } of discovered.onDemandDirs) {
14290
+ out.push({
14291
+ path,
14292
+ source: pathToSource(path),
14293
+ label: pathToLabel(path),
14294
+ priority,
14295
+ alwaysApply: false
14296
+ });
14297
+ }
14298
+ const additional = cfg.skills?.additionalDirectories || [];
14299
+ for (const dir of additional) {
14300
+ const abs = resolve11(cfg.resolvedWorkingDirectory, dir);
14301
+ if (out.some((d) => d.path === abs)) continue;
14302
+ out.push({
14303
+ path: abs,
14304
+ source: "additional",
14305
+ label: pathToLabel(abs),
14306
+ priority: 50,
14307
+ alwaysApply: false
14308
+ });
14309
+ }
14310
+ return out;
14311
+ }
14312
+ function pathToSource(path) {
14313
+ if (path.includes("/skills/default") || path.endsWith("/skills/default")) return "builtin";
14314
+ return "project";
14315
+ }
14316
+ function pathToLabel(path) {
14317
+ if (path.includes("/skills/default")) return "Built-in (default)";
14318
+ if (path.includes("/.sparkecoder/rules")) return ".sparkecoder/rules";
14319
+ if (path.includes("/.sparkecoder/skills")) return ".sparkecoder/skills";
14320
+ if (path.includes("/.cursor/rules")) return ".cursor/rules";
14321
+ if (path.includes("/.claude/skills")) return ".claude/skills";
14322
+ return basename6(dirname8(path)) + "/" + basename6(path);
14323
+ }
14324
+ skills.get("/", async (c) => {
14325
+ const dirs = listAllDirectories();
14326
+ const allSkills = [];
14327
+ for (const dir of dirs) {
14328
+ if (!existsSync20(dir.path)) continue;
14329
+ try {
14330
+ const list = await loadSkillsFromDirectory(dir.path, {
14331
+ priority: dir.priority,
14332
+ defaultLoadType: dir.alwaysApply ? "always" : "on_demand",
14333
+ forceAlwaysApply: dir.alwaysApply
14334
+ });
14335
+ for (const s of list) {
14336
+ let size = 0;
14337
+ try {
14338
+ size = statSync3(s.filePath).size;
14339
+ } catch {
14340
+ }
14341
+ allSkills.push({
14342
+ id: encodeId(s.filePath),
14343
+ name: s.name,
14344
+ description: s.description,
14345
+ filePath: s.filePath,
14346
+ fileName: basename6(s.filePath),
14347
+ sourceDir: dir.path,
14348
+ sourceLabel: dir.label,
14349
+ sourceType: dir.source,
14350
+ alwaysApply: s.alwaysApply,
14351
+ globs: s.globs,
14352
+ sizeBytes: size
14353
+ });
14354
+ }
14355
+ } catch (err) {
14356
+ console.warn("[skills] failed to read", dir.path, err?.message || err);
14357
+ }
14358
+ }
14359
+ return c.json({
14360
+ directories: dirs.map((d) => ({
14361
+ path: d.path,
14362
+ label: d.label,
14363
+ source: d.source,
14364
+ alwaysApply: d.alwaysApply,
14365
+ exists: existsSync20(d.path),
14366
+ writable: isWritable(d.path)
14367
+ })),
14368
+ skills: allSkills
14369
+ });
14370
+ });
14371
+ function isWritable(dir) {
14372
+ try {
14373
+ if (!existsSync20(dir)) return false;
14374
+ if (dir.includes("/skills/default")) return false;
14375
+ return true;
14376
+ } catch {
14377
+ return false;
14378
+ }
14379
+ }
14380
+ skills.get("/:id", async (c) => {
14381
+ const filePath = decodeId(c.req.param("id"));
14382
+ if (!filePath || !existsSync20(filePath)) {
14383
+ return c.json({ error: "skill not found" }, 404);
14384
+ }
14385
+ const content = await readFile12(filePath, "utf-8");
14386
+ return c.json({
14387
+ id: c.req.param("id"),
14388
+ filePath,
14389
+ fileName: basename6(filePath),
14390
+ content,
14391
+ writable: !filePath.includes("/skills/default")
14392
+ });
14393
+ });
14394
+ skills.post(
14395
+ "/",
14396
+ zValidator7("json", z22.object({
14397
+ directory: z22.string().min(1),
14398
+ fileName: z22.string().min(1),
14399
+ content: z22.string()
14400
+ })),
14401
+ async (c) => {
14402
+ const { directory, fileName, content } = c.req.valid("json");
14403
+ const cfg = getConfig();
14404
+ const targetDir = resolve11(cfg.resolvedWorkingDirectory, directory);
14405
+ if (targetDir.includes("/skills/default")) {
14406
+ return c.json({ error: "cannot write into built-in skills directory" }, 400);
14407
+ }
14408
+ const safeName = basename6(fileName).replace(/[^A-Za-z0-9._-]/g, "-");
14409
+ const ext = extname9(safeName).toLowerCase();
14410
+ const finalName = ext === ".md" || ext === ".mdc" ? safeName : `${safeName}.md`;
14411
+ const filePath = join15(targetDir, finalName);
14412
+ if (existsSync20(filePath)) {
14413
+ return c.json({ error: `file already exists: ${finalName}` }, 409);
14414
+ }
14415
+ try {
14416
+ await mkdir5(targetDir, { recursive: true });
14417
+ await writeFile6(filePath, content, "utf-8");
14418
+ } catch (err) {
14419
+ return c.json({ error: err?.message || "write failed" }, 500);
14420
+ }
14421
+ return c.json({ id: encodeId(filePath), filePath, fileName: finalName }, 201);
14422
+ }
14423
+ );
14424
+ skills.put(
14425
+ "/:id",
14426
+ zValidator7("json", z22.object({ content: z22.string() })),
14427
+ async (c) => {
14428
+ const filePath = decodeId(c.req.param("id"));
14429
+ if (!filePath || !existsSync20(filePath)) return c.json({ error: "skill not found" }, 404);
14430
+ if (filePath.includes("/skills/default")) {
14431
+ return c.json({ error: "built-in skills are read-only" }, 400);
14432
+ }
14433
+ await writeFile6(filePath, c.req.valid("json").content, "utf-8");
14434
+ return c.json({ ok: true });
14435
+ }
14436
+ );
14437
+ skills.delete("/:id", async (c) => {
14438
+ const filePath = decodeId(c.req.param("id"));
14439
+ if (!filePath || !existsSync20(filePath)) return c.json({ error: "skill not found" }, 404);
14440
+ if (filePath.includes("/skills/default")) {
14441
+ return c.json({ error: "built-in skills are read-only" }, 400);
14442
+ }
14443
+ await unlink3(filePath);
14444
+ return c.json({ ok: true });
14445
+ });
14446
+ skills.post(
14447
+ "/directories",
14448
+ zValidator7("json", z22.object({ path: z22.string().min(1) })),
14449
+ (c) => {
14450
+ const cfg = getConfig();
14451
+ const inputPath = c.req.valid("json").path.trim();
14452
+ const abs = resolve11(cfg.resolvedWorkingDirectory, inputPath);
14453
+ const current = cfg.skills?.additionalDirectories || [];
14454
+ if (current.some((d) => resolve11(cfg.resolvedWorkingDirectory, d) === abs)) {
14455
+ return c.json({ error: "directory already added" }, 409);
14456
+ }
14457
+ const next = [...current, abs];
14458
+ setSkillsAdditionalDirectories(next);
14459
+ return c.json({ ok: true, path: abs, exists: existsSync20(abs) }, 201);
14460
+ }
14461
+ );
14462
+ skills.delete("/directories", (c) => {
14463
+ const inputPath = c.req.query("path");
14464
+ if (!inputPath) return c.json({ error: "path query param required" }, 400);
14465
+ const cfg = getConfig();
14466
+ const abs = resolve11(cfg.resolvedWorkingDirectory, inputPath);
14467
+ const current = cfg.skills?.additionalDirectories || [];
14468
+ const next = current.filter((d) => resolve11(cfg.resolvedWorkingDirectory, d) !== abs);
14469
+ setSkillsAdditionalDirectories(next);
14470
+ return c.json({ ok: true });
14471
+ });
14472
+
14139
14473
  // src/server/auth/cf-access.ts
14140
14474
  init_config();
14141
14475
  import { createRemoteJWKSet, jwtVerify } from "jose";
@@ -14296,13 +14630,13 @@ var DEFAULT_WEB_PORT = 6969;
14296
14630
  var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
14297
14631
  function getWebDirectory() {
14298
14632
  try {
14299
- const currentDir = dirname8(fileURLToPath4(import.meta.url));
14300
- const webDir = resolve11(currentDir, "..", "web");
14301
- if (existsSync20(webDir) && existsSync20(join15(webDir, "package.json"))) {
14633
+ const currentDir = dirname9(fileURLToPath4(import.meta.url));
14634
+ const webDir = resolve12(currentDir, "..", "web");
14635
+ if (existsSync21(webDir) && existsSync21(join16(webDir, "package.json"))) {
14302
14636
  return webDir;
14303
14637
  }
14304
- const altWebDir = resolve11(currentDir, "..", "..", "web");
14305
- if (existsSync20(altWebDir) && existsSync20(join15(altWebDir, "package.json"))) {
14638
+ const altWebDir = resolve12(currentDir, "..", "..", "web");
14639
+ if (existsSync21(altWebDir) && existsSync21(join16(altWebDir, "package.json"))) {
14306
14640
  return altWebDir;
14307
14641
  }
14308
14642
  return null;
@@ -14325,18 +14659,18 @@ async function isSparkcoderWebRunning(port) {
14325
14659
  }
14326
14660
  }
14327
14661
  function isPortInUse(port) {
14328
- return new Promise((resolve12) => {
14662
+ return new Promise((resolve13) => {
14329
14663
  const server = createNetServer();
14330
14664
  server.once("error", (err) => {
14331
14665
  if (err.code === "EADDRINUSE") {
14332
- resolve12(true);
14666
+ resolve13(true);
14333
14667
  } else {
14334
- resolve12(false);
14668
+ resolve13(false);
14335
14669
  }
14336
14670
  });
14337
14671
  server.once("listening", () => {
14338
14672
  server.close();
14339
- resolve12(false);
14673
+ resolve13(false);
14340
14674
  });
14341
14675
  server.listen(port, "0.0.0.0");
14342
14676
  });
@@ -14360,30 +14694,30 @@ async function findWebPort(preferredPort) {
14360
14694
  return { port: preferredPort, alreadyRunning: false };
14361
14695
  }
14362
14696
  function hasProductionBuild(webDir) {
14363
- const buildIdPath = join15(webDir, ".next", "BUILD_ID");
14364
- return existsSync20(buildIdPath);
14697
+ const buildIdPath = join16(webDir, ".next", "BUILD_ID");
14698
+ return existsSync21(buildIdPath);
14365
14699
  }
14366
14700
  function hasSourceFiles(webDir) {
14367
- const appDir = join15(webDir, "src", "app");
14368
- const pagesDir = join15(webDir, "src", "pages");
14369
- const rootAppDir = join15(webDir, "app");
14370
- const rootPagesDir = join15(webDir, "pages");
14371
- return existsSync20(appDir) || existsSync20(pagesDir) || existsSync20(rootAppDir) || existsSync20(rootPagesDir);
14701
+ const appDir = join16(webDir, "src", "app");
14702
+ const pagesDir = join16(webDir, "src", "pages");
14703
+ const rootAppDir = join16(webDir, "app");
14704
+ const rootPagesDir = join16(webDir, "pages");
14705
+ return existsSync21(appDir) || existsSync21(pagesDir) || existsSync21(rootAppDir) || existsSync21(rootPagesDir);
14372
14706
  }
14373
14707
  function getStandaloneServerPath(webDir) {
14374
14708
  const possiblePaths2 = [
14375
- join15(webDir, ".next", "standalone", "server.js"),
14376
- join15(webDir, ".next", "standalone", "web", "server.js")
14709
+ join16(webDir, ".next", "standalone", "server.js"),
14710
+ join16(webDir, ".next", "standalone", "web", "server.js")
14377
14711
  ];
14378
14712
  for (const serverPath of possiblePaths2) {
14379
- if (existsSync20(serverPath)) {
14713
+ if (existsSync21(serverPath)) {
14380
14714
  return serverPath;
14381
14715
  }
14382
14716
  }
14383
14717
  return null;
14384
14718
  }
14385
14719
  function runCommand(command, args, cwd, env) {
14386
- return new Promise((resolve12) => {
14720
+ return new Promise((resolve13) => {
14387
14721
  const child = spawn2(command, args, {
14388
14722
  cwd,
14389
14723
  stdio: ["ignore", "pipe", "pipe"],
@@ -14398,10 +14732,10 @@ function runCommand(command, args, cwd, env) {
14398
14732
  output += data.toString();
14399
14733
  });
14400
14734
  child.on("close", (code) => {
14401
- resolve12({ success: code === 0, output });
14735
+ resolve13({ success: code === 0, output });
14402
14736
  });
14403
14737
  child.on("error", (err) => {
14404
- resolve12({ success: false, output: err.message });
14738
+ resolve13({ success: false, output: err.message });
14405
14739
  });
14406
14740
  });
14407
14741
  }
@@ -14416,13 +14750,13 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14416
14750
  if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
14417
14751
  return { process: null, port: actualPort };
14418
14752
  }
14419
- const usePnpm = existsSync20(join15(webDir, "pnpm-lock.yaml"));
14420
- const useNpm = !usePnpm && existsSync20(join15(webDir, "package-lock.json"));
14753
+ const usePnpm = existsSync21(join16(webDir, "pnpm-lock.yaml"));
14754
+ const useNpm = !usePnpm && existsSync21(join16(webDir, "package-lock.json"));
14421
14755
  const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
14422
14756
  const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
14423
14757
  const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
14424
14758
  const runtimeConfig = { apiBaseUrl: apiUrl };
14425
- const runtimeConfigPath = join15(webDir, "runtime-config.json");
14759
+ const runtimeConfigPath = join16(webDir, "runtime-config.json");
14426
14760
  try {
14427
14761
  writeFileSync6(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
14428
14762
  if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
@@ -14444,7 +14778,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14444
14778
  if (standaloneServerPath) {
14445
14779
  command = "node";
14446
14780
  args = ["server.js"];
14447
- cwd = dirname8(standaloneServerPath);
14781
+ cwd = dirname9(standaloneServerPath);
14448
14782
  webEnv.PORT = String(actualPort);
14449
14783
  webEnv.HOSTNAME = "0.0.0.0";
14450
14784
  if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
@@ -14485,10 +14819,10 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14485
14819
  let started = false;
14486
14820
  let exited = false;
14487
14821
  let exitCode = null;
14488
- const startedPromise = new Promise((resolve12) => {
14822
+ const startedPromise = new Promise((resolve13) => {
14489
14823
  const timeout = setTimeout(() => {
14490
14824
  if (!started && !exited) {
14491
- resolve12(false);
14825
+ resolve13(false);
14492
14826
  }
14493
14827
  }, startupTimeout);
14494
14828
  child.stdout?.on("data", (data) => {
@@ -14502,7 +14836,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14502
14836
  if (!started && (output.includes("Ready") || output.includes("started") || output.includes("localhost"))) {
14503
14837
  started = true;
14504
14838
  clearTimeout(timeout);
14505
- resolve12(true);
14839
+ resolve13(true);
14506
14840
  }
14507
14841
  });
14508
14842
  child.stderr?.on("data", (data) => {
@@ -14514,14 +14848,14 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14514
14848
  child.on("error", (err) => {
14515
14849
  if (!quiet) console.error(` \u274C Web UI spawn error: ${err.message}`);
14516
14850
  clearTimeout(timeout);
14517
- resolve12(false);
14851
+ resolve13(false);
14518
14852
  });
14519
14853
  child.on("exit", (code) => {
14520
14854
  exited = true;
14521
14855
  exitCode = code;
14522
14856
  if (!started) {
14523
14857
  clearTimeout(timeout);
14524
- resolve12(false);
14858
+ resolve13(false);
14525
14859
  }
14526
14860
  webUIProcess = null;
14527
14861
  });
@@ -14544,7 +14878,7 @@ function stopWebUI() {
14544
14878
  }
14545
14879
  }
14546
14880
  async function createApp(options = {}) {
14547
- const app = new Hono9();
14881
+ const app = new Hono10();
14548
14882
  app.use("*", cors({
14549
14883
  origin: "*",
14550
14884
  // Allow all origins
@@ -14572,6 +14906,7 @@ async function createApp(options = {}) {
14572
14906
  app.route("/api/schedules", schedulesRouter);
14573
14907
  app.route("/api/orchestrator", orchestratorRouter);
14574
14908
  app.route("/api/mcp", mcpRouter);
14909
+ app.route("/api/skills", skills);
14575
14910
  app.route("/api/webhooks", webhooksRouter);
14576
14911
  const config = getConfig();
14577
14912
  const webhookToken = config?.webhooks?.token;
@@ -14637,7 +14972,7 @@ async function startServer(options = {}) {
14637
14972
  if (options.workingDirectory) {
14638
14973
  config.resolvedWorkingDirectory = options.workingDirectory;
14639
14974
  }
14640
- if (!existsSync20(config.resolvedWorkingDirectory)) {
14975
+ if (!existsSync21(config.resolvedWorkingDirectory)) {
14641
14976
  mkdirSync9(config.resolvedWorkingDirectory, { recursive: true });
14642
14977
  if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
14643
14978
  }