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
package/dist/index.js CHANGED
@@ -450,6 +450,7 @@ __export(config_exports, {
450
450
  setApiKey: () => setApiKey,
451
451
  setMcpServers: () => setMcpServers,
452
452
  setPublicUrl: () => setPublicUrl,
453
+ setSkillsAdditionalDirectories: () => setSkillsAdditionalDirectories,
453
454
  setSlackConfig: () => setSlackConfig,
454
455
  setWebhookToken: () => setWebhookToken
455
456
  });
@@ -739,6 +740,40 @@ function setWebhookToken(token) {
739
740
  console.warn("[config] failed to persist webhook token:", err?.message || err);
740
741
  }
741
742
  }
743
+ function setSkillsAdditionalDirectories(directories) {
744
+ if (cachedConfig) {
745
+ const cur = cachedConfig.skills || {};
746
+ cachedConfig.skills = { ...cur, additionalDirectories: directories };
747
+ try {
748
+ const discovered = discoverSkillDirectories(cachedConfig.resolvedWorkingDirectory);
749
+ const additionalAbs = directories.map((d) => resolve(cachedConfig.resolvedWorkingDirectory, d)).filter((d) => existsSync(d));
750
+ cachedConfig.discoveredSkills = discovered;
751
+ cachedConfig.resolvedSkillsDirectories = [
752
+ ...discovered.allDirectories,
753
+ ...additionalAbs
754
+ ];
755
+ } catch {
756
+ }
757
+ }
758
+ try {
759
+ const cwdPath = resolve(process.cwd(), "sparkecoder.config.json");
760
+ const target = existsSync(cwdPath) ? cwdPath : join(ensureAppDataDirectory(), "sparkecoder.config.json");
761
+ let raw = {};
762
+ if (existsSync(target)) {
763
+ try {
764
+ raw = JSON.parse(readFileSync(target, "utf-8"));
765
+ } catch {
766
+ raw = {};
767
+ }
768
+ } else {
769
+ raw = createDefaultConfig();
770
+ }
771
+ raw.skills = { ...raw.skills || {}, additionalDirectories: directories };
772
+ writeFileSync(target, JSON.stringify(raw, null, 2));
773
+ } catch (err) {
774
+ console.warn("[config] failed to persist skill directories:", err?.message || err);
775
+ }
776
+ }
742
777
  function setPublicUrl(publicUrl) {
743
778
  if (cachedConfig) {
744
779
  cachedConfig.server = { ...cachedConfig.server ?? {}, publicUrl };
@@ -3030,7 +3065,7 @@ async function createClient(serverId, handle, root) {
3030
3065
  },
3031
3066
  async waitForDiagnostics(filePath, timeoutMs = 5e3) {
3032
3067
  const normalized = normalizePath(filePath);
3033
- return new Promise((resolve12) => {
3068
+ return new Promise((resolve13) => {
3034
3069
  const startTime = Date.now();
3035
3070
  let debounceTimer;
3036
3071
  let resolved = false;
@@ -3049,7 +3084,7 @@ async function createClient(serverId, handle, root) {
3049
3084
  if (resolved) return;
3050
3085
  resolved = true;
3051
3086
  cleanup2();
3052
- resolve12(diagnostics.get(normalized) || []);
3087
+ resolve13(diagnostics.get(normalized) || []);
3053
3088
  };
3054
3089
  const onDiagnostic = () => {
3055
3090
  if (debounceTimer) clearTimeout(debounceTimer);
@@ -3424,7 +3459,7 @@ Working directory: ${options.workingDirectory}`,
3424
3459
  isChunked: true
3425
3460
  });
3426
3461
  if (chunkCount > 1) {
3427
- await new Promise((resolve12) => setTimeout(resolve12, 0));
3462
+ await new Promise((resolve13) => setTimeout(resolve13, 0));
3428
3463
  }
3429
3464
  }
3430
3465
  }
@@ -4043,7 +4078,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
4043
4078
  if (!existsSync10(directory)) {
4044
4079
  return [];
4045
4080
  }
4046
- const skills = [];
4081
+ const skills2 = [];
4047
4082
  const entries = await readdir(directory, { withFileTypes: true });
4048
4083
  for (const entry2 of entries) {
4049
4084
  let filePath;
@@ -4067,7 +4102,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
4067
4102
  if (parsed) {
4068
4103
  const alwaysApply = forceAlwaysApply || parsed.metadata.alwaysApply;
4069
4104
  const loadType = alwaysApply ? "always" : defaultLoadType;
4070
- skills.push({
4105
+ skills2.push({
4071
4106
  name: parsed.metadata.name,
4072
4107
  description: parsed.metadata.description,
4073
4108
  filePath,
@@ -4081,7 +4116,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
4081
4116
  } else {
4082
4117
  const name = getSkillNameFromPath(filePath);
4083
4118
  const firstParagraph = content.split("\n\n")[0]?.slice(0, 200) || "No description";
4084
- skills.push({
4119
+ skills2.push({
4085
4120
  name,
4086
4121
  description: firstParagraph.replace(/^#\s*/, "").trim(),
4087
4122
  filePath,
@@ -4094,7 +4129,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
4094
4129
  });
4095
4130
  }
4096
4131
  }
4097
- return skills.filter(
4132
+ return skills2.filter(
4098
4133
  (s) => s.platforms.length === 0 || s.platforms.includes(process.platform)
4099
4134
  );
4100
4135
  }
@@ -4102,8 +4137,8 @@ async function loadAllSkills(directories) {
4102
4137
  const allSkills = [];
4103
4138
  const seenNames = /* @__PURE__ */ new Set();
4104
4139
  for (const dir of directories) {
4105
- const skills = await loadSkillsFromDirectory(dir);
4106
- for (const skill of skills) {
4140
+ const skills2 = await loadSkillsFromDirectory(dir);
4141
+ for (const skill of skills2) {
4107
4142
  if (!seenNames.has(skill.name.toLowerCase())) {
4108
4143
  seenNames.add(skill.name.toLowerCase());
4109
4144
  allSkills.push(skill);
@@ -4116,12 +4151,12 @@ async function loadAllSkillsFromDiscovered(discovered) {
4116
4151
  const allSkills = [];
4117
4152
  const seenNames = /* @__PURE__ */ new Set();
4118
4153
  for (const { path, priority } of discovered.alwaysLoadedDirs) {
4119
- const skills = await loadSkillsFromDirectory(path, {
4154
+ const skills2 = await loadSkillsFromDirectory(path, {
4120
4155
  priority,
4121
4156
  defaultLoadType: "always",
4122
4157
  forceAlwaysApply: true
4123
4158
  });
4124
- for (const skill of skills) {
4159
+ for (const skill of skills2) {
4125
4160
  if (!seenNames.has(skill.name.toLowerCase())) {
4126
4161
  seenNames.add(skill.name.toLowerCase());
4127
4162
  allSkills.push(skill);
@@ -4129,12 +4164,12 @@ async function loadAllSkillsFromDiscovered(discovered) {
4129
4164
  }
4130
4165
  }
4131
4166
  for (const { path, priority } of discovered.onDemandDirs) {
4132
- const skills = await loadSkillsFromDirectory(path, {
4167
+ const skills2 = await loadSkillsFromDirectory(path, {
4133
4168
  priority,
4134
4169
  defaultLoadType: "on_demand",
4135
4170
  forceAlwaysApply: false
4136
4171
  });
4137
- for (const skill of skills) {
4172
+ for (const skill of skills2) {
4138
4173
  if (!seenNames.has(skill.name.toLowerCase())) {
4139
4174
  seenNames.add(skill.name.toLowerCase());
4140
4175
  allSkills.push(skill);
@@ -4159,7 +4194,7 @@ async function loadAllSkillsFromDiscovered(discovered) {
4159
4194
  all: allSkills
4160
4195
  };
4161
4196
  }
4162
- async function getGlobMatchedSkills(skills, activeFiles, workingDirectory) {
4197
+ async function getGlobMatchedSkills(skills2, activeFiles, workingDirectory) {
4163
4198
  if (activeFiles.length === 0) {
4164
4199
  return [];
4165
4200
  }
@@ -4169,7 +4204,7 @@ async function getGlobMatchedSkills(skills, activeFiles, workingDirectory) {
4169
4204
  }
4170
4205
  return f;
4171
4206
  });
4172
- const matchedSkills = skills.filter((skill) => {
4207
+ const matchedSkills = skills2.filter((skill) => {
4173
4208
  if (skill.alwaysApply || skill.loadType === "always") {
4174
4209
  return false;
4175
4210
  }
@@ -4215,8 +4250,8 @@ async function loadSkillContent(skillName, directories) {
4215
4250
  content: parsed ? parsed.body : content
4216
4251
  };
4217
4252
  }
4218
- function formatSkillsForContext(skills) {
4219
- const onDemandSkills = skills.filter((s) => !s.alwaysApply && s.loadType !== "always");
4253
+ function formatSkillsForContext(skills2) {
4254
+ const onDemandSkills = skills2.filter((s) => !s.alwaysApply && s.loadType !== "always");
4220
4255
  if (onDemandSkills.length === 0) {
4221
4256
  return "No on-demand skills available.";
4222
4257
  }
@@ -4227,12 +4262,12 @@ function formatSkillsForContext(skills) {
4227
4262
  }
4228
4263
  return lines.join("\n");
4229
4264
  }
4230
- function formatAlwaysLoadedSkills(skills) {
4231
- if (skills.length === 0) {
4265
+ function formatAlwaysLoadedSkills(skills2) {
4266
+ if (skills2.length === 0) {
4232
4267
  return "";
4233
4268
  }
4234
4269
  const sections = [];
4235
- for (const skill of skills) {
4270
+ for (const skill of skills2) {
4236
4271
  sections.push(`### ${skill.name}
4237
4272
 
4238
4273
  ${skill.content}`);
@@ -4241,12 +4276,12 @@ ${skill.content}`);
4241
4276
 
4242
4277
  ${sections.join("\n\n---\n\n")}`;
4243
4278
  }
4244
- function formatGlobMatchedSkills(skills) {
4245
- if (skills.length === 0) {
4279
+ function formatGlobMatchedSkills(skills2) {
4280
+ if (skills2.length === 0) {
4246
4281
  return "";
4247
4282
  }
4248
4283
  const sections = [];
4249
- for (const skill of skills) {
4284
+ for (const skill of skills2) {
4250
4285
  sections.push(`### ${skill.name}
4251
4286
 
4252
4287
  ${skill.content}`);
@@ -4288,16 +4323,16 @@ Once loaded, a skill's content will be available in the conversation context.`,
4288
4323
  try {
4289
4324
  switch (action) {
4290
4325
  case "list": {
4291
- const skills = await loadAllSkills(options.skillsDirectories);
4326
+ const skills2 = await loadAllSkills(options.skillsDirectories);
4292
4327
  return {
4293
4328
  success: true,
4294
4329
  action: "list",
4295
- skillCount: skills.length,
4296
- skills: skills.map((s) => ({
4330
+ skillCount: skills2.length,
4331
+ skills: skills2.map((s) => ({
4297
4332
  name: s.name,
4298
4333
  description: s.description
4299
4334
  })),
4300
- formatted: formatSkillsForContext(skills)
4335
+ formatted: formatSkillsForContext(skills2)
4301
4336
  };
4302
4337
  }
4303
4338
  case "load": {
@@ -4735,8 +4770,8 @@ var init_subagent = __esm({
4735
4770
  if (eventQueue.length > 0) {
4736
4771
  yield eventQueue.shift();
4737
4772
  } else if (!done) {
4738
- const event = await new Promise((resolve12) => {
4739
- resolveNext = resolve12;
4773
+ const event = await new Promise((resolve13) => {
4774
+ resolveNext = resolve13;
4740
4775
  });
4741
4776
  if (event) {
4742
4777
  yield event;
@@ -6512,8 +6547,8 @@ async function buildSystemPrompt(options) {
6512
6547
  }
6513
6548
  } else {
6514
6549
  const { loadAllSkills: loadAllSkills2 } = await Promise.resolve().then(() => (init_skills(), skills_exports));
6515
- const skills = await loadAllSkills2(skillsDirectories);
6516
- onDemandSkillsContext = formatSkillsForContext(skills);
6550
+ const skills2 = await loadAllSkills2(skillsDirectories);
6551
+ onDemandSkillsContext = formatSkillsForContext(skills2);
6517
6552
  }
6518
6553
  const todos = await todoQueries.getBySession(sessionId);
6519
6554
  const todosContext = formatTodosForContext(todos);
@@ -7819,6 +7854,9 @@ function isSlackConfigured() {
7819
7854
  function getSlackSigningSecret() {
7820
7855
  return readSlackConfig()?.signingSecret ?? null;
7821
7856
  }
7857
+ function getSlackBotToken() {
7858
+ return readSlackConfig()?.botToken ?? null;
7859
+ }
7822
7860
  function getDefaultOrchestratorName() {
7823
7861
  return readSlackConfig()?.defaultOrchestratorName ?? null;
7824
7862
  }
@@ -7873,6 +7911,62 @@ function getSlackAllowlistPolicy() {
7873
7911
  return { allowedUsers: [], allowedChannels: [], allowDmsFromAnyone: true };
7874
7912
  }
7875
7913
  }
7914
+ async function fetchSlackUserName(userId) {
7915
+ const token = getSlackBotToken();
7916
+ if (!token) return null;
7917
+ try {
7918
+ const res = await fetch(`https://slack.com/api/users.info?user=${encodeURIComponent(userId)}`, {
7919
+ headers: { Authorization: `Bearer ${token}` }
7920
+ });
7921
+ const data = await res.json().catch(() => ({}));
7922
+ if (!data?.ok) {
7923
+ console.warn(`[slack] users.info(${userId}) failed: ${data?.error || `HTTP ${res.status}`}`);
7924
+ return null;
7925
+ }
7926
+ const profile = data.user?.profile || {};
7927
+ const name = profile.display_name_normalized || profile.display_name || profile.real_name_normalized || profile.real_name || data.user?.real_name || data.user?.name || null;
7928
+ return name ? String(name) : null;
7929
+ } catch (err) {
7930
+ console.warn(`[slack] users.info(${userId}) error:`, err?.message || err);
7931
+ return null;
7932
+ }
7933
+ }
7934
+ async function resolveSlackUserName(userId) {
7935
+ if (!userId) return null;
7936
+ const now = Date.now();
7937
+ const hit = userNameCache.get(userId);
7938
+ if (hit && hit.expiresAt > now) return hit.name;
7939
+ const inflight = userInflight.get(userId);
7940
+ if (inflight) return inflight;
7941
+ const p = (async () => {
7942
+ const name = await fetchSlackUserName(userId);
7943
+ userNameCache.set(userId, {
7944
+ name,
7945
+ expiresAt: now + (name ? USER_TTL_MS : USER_FAIL_TTL_MS)
7946
+ });
7947
+ userInflight.delete(userId);
7948
+ return name;
7949
+ })();
7950
+ userInflight.set(userId, p);
7951
+ return p;
7952
+ }
7953
+ async function normalizeSlackMentions(text) {
7954
+ if (!text) return text;
7955
+ const userMentionRe = /<@([UW][A-Z0-9]+)(?:\|([^>]+))?>/g;
7956
+ const userIds = /* @__PURE__ */ new Set();
7957
+ for (const m of text.matchAll(userMentionRe)) {
7958
+ if (!m[2]) userIds.add(m[1]);
7959
+ }
7960
+ if (userIds.size > 0) {
7961
+ await Promise.all([...userIds].map((id) => resolveSlackUserName(id)));
7962
+ }
7963
+ return text.replace(userMentionRe, (_full, id, label) => {
7964
+ if (label) return `${label} <@${id}>`;
7965
+ const cached = userNameCache.get(id);
7966
+ const name = cached?.name;
7967
+ return name ? `${name} <@${id}>` : `<@${id}>`;
7968
+ });
7969
+ }
7876
7970
  function getSlackDeniedReplyPolicy() {
7877
7971
  try {
7878
7972
  const cfg = getConfig();
@@ -7885,13 +7979,17 @@ function getSlackDeniedReplyPolicy() {
7885
7979
  return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
7886
7980
  }
7887
7981
  }
7888
- var cachedSelf, selfInflight, DEFAULT_DENIED_TEMPLATE;
7982
+ var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS, userNameCache, userInflight, DEFAULT_DENIED_TEMPLATE;
7889
7983
  var init_client3 = __esm({
7890
7984
  "src/integrations/slack/client.ts"() {
7891
7985
  "use strict";
7892
7986
  init_config();
7893
7987
  cachedSelf = null;
7894
7988
  selfInflight = null;
7989
+ USER_TTL_MS = 60 * 60 * 1e3;
7990
+ USER_FAIL_TTL_MS = 5 * 60 * 1e3;
7991
+ userNameCache = /* @__PURE__ */ new Map();
7992
+ userInflight = /* @__PURE__ */ new Map();
7895
7993
  DEFAULT_DENIED_TEMPLATE = "Sorry, you don't have permission to use this bot. (Contact the bot owner if you think this is a mistake.)";
7896
7994
  }
7897
7995
  });
@@ -7906,9 +8004,6 @@ function markThreadOwned(channel, threadTs) {
7906
8004
  function isThreadOwned(channel, threadTs) {
7907
8005
  return ownedThreads.has(threadKey(channel, threadTs));
7908
8006
  }
7909
- function stripMention(text) {
7910
- return String(text || "").replace(/<@[^>]+>/g, "").trim();
7911
- }
7912
8007
  function isSelfAuthored(event, self) {
7913
8008
  if (!self) return true;
7914
8009
  if (self.botId && event.bot_id && event.bot_id === self.botId) return true;
@@ -7923,14 +8018,22 @@ function slackEventToInboundResult(event, opts = {}) {
7923
8018
  return { event: null, dropReason: "bot_message" };
7924
8019
  }
7925
8020
  if (event.type === "message" && event.subtype && IGNORED_MESSAGE_SUBTYPES.has(event.subtype)) {
7926
- return { event: null, dropReason: "bot_message" };
8021
+ return { event: null, dropReason: "ignored_subtype" };
7927
8022
  }
7928
8023
  const isDm = event.type === "message" && event.channel_type === "im";
7929
8024
  const isThreadReply = event.type === "message" && !isDm && typeof event.thread_ts === "string" && event.thread_ts !== event.ts;
8025
+ 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.
8026
+ typeof event.channel === "string");
7930
8027
  if (event.type !== "app_mention" && !isDm && !isThreadReply) {
8028
+ if (isNonThreadChannelMsg) {
8029
+ return { event: null, dropReason: "non_thread_channel_msg" };
8030
+ }
8031
+ if (event.type !== "message") {
8032
+ return { event: null, dropReason: "non_message_event" };
8033
+ }
7931
8034
  return { event: null, dropReason: "unsupported_type" };
7932
8035
  }
7933
- const text = event.type === "app_mention" ? stripMention(event.text) : (event.text ?? "").trim();
8036
+ const text = (event.text ?? "").trim();
7934
8037
  if (!text) return { event: null, dropReason: "empty_text" };
7935
8038
  const policy = getSlackAllowlistPolicy();
7936
8039
  const userAllowlistActive = policy.allowedUsers.length > 0;
@@ -8913,11 +9016,11 @@ function waitForTaskQuestionAnswer(question) {
8913
9016
  if (pendingQuestions.has(k)) {
8914
9017
  return Promise.reject(new Error(`Question already pending: ${question.questionId}`));
8915
9018
  }
8916
- return new Promise((resolve12, reject) => {
9019
+ return new Promise((resolve13, reject) => {
8917
9020
  pendingQuestions.set(k, {
8918
9021
  ...question,
8919
9022
  createdAt: /* @__PURE__ */ new Date(),
8920
- resolve: resolve12,
9023
+ resolve: resolve13,
8921
9024
  reject
8922
9025
  });
8923
9026
  });
@@ -10066,14 +10169,14 @@ ${p.text}` : p.text;
10066
10169
  const result = await recorder.encode();
10067
10170
  recorder.clear();
10068
10171
  if (!result) return [];
10069
- const { readFile: readFile12, unlink: unlink3 } = await import("fs/promises");
10172
+ const { readFile: readFile13, unlink: unlink4 } = await import("fs/promises");
10070
10173
  const uploadInfo = await storageQueries2.getUploadUrl(
10071
10174
  this.session.id,
10072
10175
  `browser-recording-${Date.now()}.mp4`,
10073
10176
  "video/mp4",
10074
10177
  "browser-recording"
10075
10178
  );
10076
- const fileData = await readFile12(result.path);
10179
+ const fileData = await readFile13(result.path);
10077
10180
  await fetch(uploadInfo.uploadUrl, {
10078
10181
  method: "PUT",
10079
10182
  headers: { "Content-Type": "video/mp4" },
@@ -10081,7 +10184,7 @@ ${p.text}` : p.text;
10081
10184
  });
10082
10185
  await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: result.sizeBytes });
10083
10186
  const dlInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
10084
- await unlink3(result.path).catch(() => {
10187
+ await unlink4(result.path).catch(() => {
10085
10188
  });
10086
10189
  console.log(`[TASK] Browser recording uploaded (${result.sizeBytes} bytes)`);
10087
10190
  return [dlInfo.downloadUrl];
@@ -10099,13 +10202,13 @@ ${p.text}` : p.text;
10099
10202
  try {
10100
10203
  const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
10101
10204
  if (!isRemoteConfigured2()) return [];
10102
- const { readFile: readFile12 } = await import("fs/promises");
10103
- const { join: join16, basename: basename6 } = await import("path");
10205
+ const { readFile: readFile13 } = await import("fs/promises");
10206
+ const { join: join17, basename: basename7 } = await import("path");
10104
10207
  const urls = [];
10105
10208
  for (const filePath of filePaths) {
10106
10209
  try {
10107
- const fullPath = filePath.startsWith("/") ? filePath : join16(this.session.workingDirectory, filePath);
10108
- const fileName = basename6(fullPath);
10210
+ const fullPath = filePath.startsWith("/") ? filePath : join17(this.session.workingDirectory, filePath);
10211
+ const fileName = basename7(fullPath);
10109
10212
  const ext = fileName.split(".").pop()?.toLowerCase() || "";
10110
10213
  const mimeMap = {
10111
10214
  pdf: "application/pdf",
@@ -10129,7 +10232,7 @@ ${p.text}` : p.text;
10129
10232
  contentType,
10130
10233
  "task-output"
10131
10234
  );
10132
- const fileData = await readFile12(fullPath);
10235
+ const fileData = await readFile13(fullPath);
10133
10236
  await fetch(uploadInfo.uploadUrl, {
10134
10237
  method: "PUT",
10135
10238
  headers: { "Content-Type": contentType },
@@ -10178,8 +10281,8 @@ ${p.text}` : p.text;
10178
10281
  this.pendingApprovals.set(toolCallId, await execution);
10179
10282
  options.onApprovalRequired?.(await execution);
10180
10283
  await sessionQueries.updateStatus(this.session.id, "waiting");
10181
- const approved = await new Promise((resolve12) => {
10182
- approvalResolvers.set(toolCallId, { resolve: resolve12, sessionId: this.session.id });
10284
+ const approved = await new Promise((resolve13) => {
10285
+ approvalResolvers.set(toolCallId, { resolve: resolve13, sessionId: this.session.id });
10183
10286
  });
10184
10287
  const resolverData = approvalResolvers.get(toolCallId);
10185
10288
  approvalResolvers.delete(toolCallId);
@@ -10285,8 +10388,8 @@ async function withSessionLock(sessionId, fn) {
10285
10388
  state2.pending++;
10286
10389
  const prev = state2.tail;
10287
10390
  let release;
10288
- const next = new Promise((resolve12) => {
10289
- release = resolve12;
10391
+ const next = new Promise((resolve13) => {
10392
+ release = resolve13;
10290
10393
  });
10291
10394
  state2.tail = prev.then(() => next);
10292
10395
  await prev;
@@ -10679,12 +10782,12 @@ init_agent();
10679
10782
 
10680
10783
  // src/server/index.ts
10681
10784
  import "dotenv/config";
10682
- import { Hono as Hono9 } from "hono";
10785
+ import { Hono as Hono10 } from "hono";
10683
10786
  import { serve } from "@hono/node-server";
10684
10787
  import { cors } from "hono/cors";
10685
10788
  import { logger } from "hono/logger";
10686
- import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
10687
- import { resolve as resolve11, dirname as dirname8, join as join15 } from "path";
10789
+ import { existsSync as existsSync21, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
10790
+ import { resolve as resolve12, dirname as dirname9, join as join16 } from "path";
10688
10791
  import { spawn as spawn2 } from "child_process";
10689
10792
  import { createServer as createNetServer } from "net";
10690
10793
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -11859,7 +11962,7 @@ async function emitSyntheticToolStreaming(writeSSE, toolCallStarts, toolCallId,
11859
11962
  toolCallId,
11860
11963
  argsTextDelta: chunk
11861
11964
  }));
11862
- await new Promise((resolve12) => setTimeout(resolve12, 0));
11965
+ await new Promise((resolve13) => setTimeout(resolve13, 0));
11863
11966
  }
11864
11967
  }
11865
11968
  function buildDevtoolsContextXml(sessionId) {
@@ -12114,7 +12217,7 @@ ${prompt}` });
12114
12217
  chunkIndex,
12115
12218
  chunkCount
12116
12219
  }));
12117
- await new Promise((resolve12) => setTimeout(resolve12, 0));
12220
+ await new Promise((resolve13) => setTimeout(resolve13, 0));
12118
12221
  }
12119
12222
  const browserPort = progress.data?.browserStreamPort;
12120
12223
  const browserClosed = progress.data?.browserClosed;
@@ -12661,7 +12764,7 @@ agents.post(
12661
12764
  chunkIndex,
12662
12765
  chunkCount
12663
12766
  }));
12664
- await new Promise((resolve12) => setTimeout(resolve12, 0));
12767
+ await new Promise((resolve13) => setTimeout(resolve13, 0));
12665
12768
  }
12666
12769
  const browserPort = progress.data?.browserStreamPort;
12667
12770
  const browserClosed = progress.data?.browserClosed;
@@ -13674,16 +13777,18 @@ init_webhook_events();
13674
13777
  init_inbox();
13675
13778
  var recentlyHandled = /* @__PURE__ */ new Map();
13676
13779
  var MAX_RECENT = 1e3;
13677
- function alreadyHandled(channel, ts) {
13780
+ function wasHandled(channel, ts) {
13678
13781
  if (!channel || !ts) return false;
13782
+ return recentlyHandled.has(`${channel}\u241F${ts}`);
13783
+ }
13784
+ function markHandled(channel, ts) {
13785
+ if (!channel || !ts) return;
13679
13786
  const key2 = `${channel}\u241F${ts}`;
13680
- if (recentlyHandled.has(key2)) return true;
13681
13787
  recentlyHandled.set(key2, Date.now());
13682
13788
  if (recentlyHandled.size > MAX_RECENT) {
13683
13789
  const oldest = recentlyHandled.keys().next().value;
13684
13790
  if (oldest) recentlyHandled.delete(oldest);
13685
13791
  }
13686
- return false;
13687
13792
  }
13688
13793
  var slack = new Hono6();
13689
13794
  slack.post("/events", async (c) => {
@@ -13720,7 +13825,7 @@ slack.post("/events", async (c) => {
13720
13825
  textSnippet: typeof ev.text === "string" ? ev.text : void 0,
13721
13826
  meta: { ts: ev.ts, thread_ts: ev.thread_ts, team: ev.team, event_subtype: ev.subtype }
13722
13827
  });
13723
- if (alreadyHandled(ev.channel, ev.ts)) {
13828
+ if (wasHandled(ev.channel, ev.ts)) {
13724
13829
  updateEvent(auditId, { status: "dropped", dropReason: "duplicate_delivery" });
13725
13830
  return c.json({ ok: true });
13726
13831
  }
@@ -13744,7 +13849,18 @@ slack.post("/events", async (c) => {
13744
13849
  }
13745
13850
  const orchestratorId = await findOrCreateOrchestratorId();
13746
13851
  if (orchestratorId) {
13852
+ inbound.content = await normalizeSlackMentions(inbound.content);
13853
+ if (ev.user) {
13854
+ const speakerName = await resolveSlackUserName(ev.user);
13855
+ if (speakerName) {
13856
+ inbound.content = inbound.content.replace(
13857
+ `user=${ev.user}`,
13858
+ `user=${speakerName} <@${ev.user}>`
13859
+ );
13860
+ }
13861
+ }
13747
13862
  pushToInbox(orchestratorId, inbound);
13863
+ markHandled(ev.channel, ev.ts);
13748
13864
  updateEvent(auditId, { status: "routed", sessionId: orchestratorId });
13749
13865
  } else {
13750
13866
  updateEvent(auditId, { status: "error", error: "no orchestrator session available" });
@@ -14156,6 +14272,224 @@ mcpRouter.post("/:id/test", async (c) => {
14156
14272
  return c.json(result);
14157
14273
  });
14158
14274
 
14275
+ // src/server/routes/skills.ts
14276
+ init_config();
14277
+ init_skills();
14278
+ import { Hono as Hono9 } from "hono";
14279
+ import { zValidator as zValidator7 } from "@hono/zod-validator";
14280
+ import { z as z22 } from "zod";
14281
+ import { existsSync as existsSync20, statSync as statSync3 } from "fs";
14282
+ import { readFile as readFile12, writeFile as writeFile6, unlink as unlink3, mkdir as mkdir5 } from "fs/promises";
14283
+ import { resolve as resolve11, join as join15, basename as basename6, dirname as dirname8, extname as extname9 } from "path";
14284
+ var skills = new Hono9();
14285
+ function encodeId(filePath) {
14286
+ return Buffer.from(filePath, "utf-8").toString("base64url");
14287
+ }
14288
+ function decodeId(id) {
14289
+ try {
14290
+ const decoded = Buffer.from(id, "base64url").toString("utf-8");
14291
+ return decoded || null;
14292
+ } catch {
14293
+ return null;
14294
+ }
14295
+ }
14296
+ function listAllDirectories() {
14297
+ const cfg = getConfig();
14298
+ const discovered = discoverSkillDirectories(cfg.resolvedWorkingDirectory);
14299
+ const out = [];
14300
+ for (const { path, priority } of discovered.alwaysLoadedDirs) {
14301
+ out.push({
14302
+ path,
14303
+ source: pathToSource(path),
14304
+ label: pathToLabel(path),
14305
+ priority,
14306
+ alwaysApply: true
14307
+ });
14308
+ }
14309
+ for (const { path, priority } of discovered.onDemandDirs) {
14310
+ out.push({
14311
+ path,
14312
+ source: pathToSource(path),
14313
+ label: pathToLabel(path),
14314
+ priority,
14315
+ alwaysApply: false
14316
+ });
14317
+ }
14318
+ const additional = cfg.skills?.additionalDirectories || [];
14319
+ for (const dir of additional) {
14320
+ const abs = resolve11(cfg.resolvedWorkingDirectory, dir);
14321
+ if (out.some((d) => d.path === abs)) continue;
14322
+ out.push({
14323
+ path: abs,
14324
+ source: "additional",
14325
+ label: pathToLabel(abs),
14326
+ priority: 50,
14327
+ alwaysApply: false
14328
+ });
14329
+ }
14330
+ return out;
14331
+ }
14332
+ function pathToSource(path) {
14333
+ if (path.includes("/skills/default") || path.endsWith("/skills/default")) return "builtin";
14334
+ return "project";
14335
+ }
14336
+ function pathToLabel(path) {
14337
+ if (path.includes("/skills/default")) return "Built-in (default)";
14338
+ if (path.includes("/.sparkecoder/rules")) return ".sparkecoder/rules";
14339
+ if (path.includes("/.sparkecoder/skills")) return ".sparkecoder/skills";
14340
+ if (path.includes("/.cursor/rules")) return ".cursor/rules";
14341
+ if (path.includes("/.claude/skills")) return ".claude/skills";
14342
+ return basename6(dirname8(path)) + "/" + basename6(path);
14343
+ }
14344
+ skills.get("/", async (c) => {
14345
+ const dirs = listAllDirectories();
14346
+ const allSkills = [];
14347
+ for (const dir of dirs) {
14348
+ if (!existsSync20(dir.path)) continue;
14349
+ try {
14350
+ const list = await loadSkillsFromDirectory(dir.path, {
14351
+ priority: dir.priority,
14352
+ defaultLoadType: dir.alwaysApply ? "always" : "on_demand",
14353
+ forceAlwaysApply: dir.alwaysApply
14354
+ });
14355
+ for (const s of list) {
14356
+ let size = 0;
14357
+ try {
14358
+ size = statSync3(s.filePath).size;
14359
+ } catch {
14360
+ }
14361
+ allSkills.push({
14362
+ id: encodeId(s.filePath),
14363
+ name: s.name,
14364
+ description: s.description,
14365
+ filePath: s.filePath,
14366
+ fileName: basename6(s.filePath),
14367
+ sourceDir: dir.path,
14368
+ sourceLabel: dir.label,
14369
+ sourceType: dir.source,
14370
+ alwaysApply: s.alwaysApply,
14371
+ globs: s.globs,
14372
+ sizeBytes: size
14373
+ });
14374
+ }
14375
+ } catch (err) {
14376
+ console.warn("[skills] failed to read", dir.path, err?.message || err);
14377
+ }
14378
+ }
14379
+ return c.json({
14380
+ directories: dirs.map((d) => ({
14381
+ path: d.path,
14382
+ label: d.label,
14383
+ source: d.source,
14384
+ alwaysApply: d.alwaysApply,
14385
+ exists: existsSync20(d.path),
14386
+ writable: isWritable(d.path)
14387
+ })),
14388
+ skills: allSkills
14389
+ });
14390
+ });
14391
+ function isWritable(dir) {
14392
+ try {
14393
+ if (!existsSync20(dir)) return false;
14394
+ if (dir.includes("/skills/default")) return false;
14395
+ return true;
14396
+ } catch {
14397
+ return false;
14398
+ }
14399
+ }
14400
+ skills.get("/:id", async (c) => {
14401
+ const filePath = decodeId(c.req.param("id"));
14402
+ if (!filePath || !existsSync20(filePath)) {
14403
+ return c.json({ error: "skill not found" }, 404);
14404
+ }
14405
+ const content = await readFile12(filePath, "utf-8");
14406
+ return c.json({
14407
+ id: c.req.param("id"),
14408
+ filePath,
14409
+ fileName: basename6(filePath),
14410
+ content,
14411
+ writable: !filePath.includes("/skills/default")
14412
+ });
14413
+ });
14414
+ skills.post(
14415
+ "/",
14416
+ zValidator7("json", z22.object({
14417
+ directory: z22.string().min(1),
14418
+ fileName: z22.string().min(1),
14419
+ content: z22.string()
14420
+ })),
14421
+ async (c) => {
14422
+ const { directory, fileName, content } = c.req.valid("json");
14423
+ const cfg = getConfig();
14424
+ const targetDir = resolve11(cfg.resolvedWorkingDirectory, directory);
14425
+ if (targetDir.includes("/skills/default")) {
14426
+ return c.json({ error: "cannot write into built-in skills directory" }, 400);
14427
+ }
14428
+ const safeName = basename6(fileName).replace(/[^A-Za-z0-9._-]/g, "-");
14429
+ const ext = extname9(safeName).toLowerCase();
14430
+ const finalName = ext === ".md" || ext === ".mdc" ? safeName : `${safeName}.md`;
14431
+ const filePath = join15(targetDir, finalName);
14432
+ if (existsSync20(filePath)) {
14433
+ return c.json({ error: `file already exists: ${finalName}` }, 409);
14434
+ }
14435
+ try {
14436
+ await mkdir5(targetDir, { recursive: true });
14437
+ await writeFile6(filePath, content, "utf-8");
14438
+ } catch (err) {
14439
+ return c.json({ error: err?.message || "write failed" }, 500);
14440
+ }
14441
+ return c.json({ id: encodeId(filePath), filePath, fileName: finalName }, 201);
14442
+ }
14443
+ );
14444
+ skills.put(
14445
+ "/:id",
14446
+ zValidator7("json", z22.object({ content: z22.string() })),
14447
+ async (c) => {
14448
+ const filePath = decodeId(c.req.param("id"));
14449
+ if (!filePath || !existsSync20(filePath)) return c.json({ error: "skill not found" }, 404);
14450
+ if (filePath.includes("/skills/default")) {
14451
+ return c.json({ error: "built-in skills are read-only" }, 400);
14452
+ }
14453
+ await writeFile6(filePath, c.req.valid("json").content, "utf-8");
14454
+ return c.json({ ok: true });
14455
+ }
14456
+ );
14457
+ skills.delete("/:id", async (c) => {
14458
+ const filePath = decodeId(c.req.param("id"));
14459
+ if (!filePath || !existsSync20(filePath)) return c.json({ error: "skill not found" }, 404);
14460
+ if (filePath.includes("/skills/default")) {
14461
+ return c.json({ error: "built-in skills are read-only" }, 400);
14462
+ }
14463
+ await unlink3(filePath);
14464
+ return c.json({ ok: true });
14465
+ });
14466
+ skills.post(
14467
+ "/directories",
14468
+ zValidator7("json", z22.object({ path: z22.string().min(1) })),
14469
+ (c) => {
14470
+ const cfg = getConfig();
14471
+ const inputPath = c.req.valid("json").path.trim();
14472
+ const abs = resolve11(cfg.resolvedWorkingDirectory, inputPath);
14473
+ const current = cfg.skills?.additionalDirectories || [];
14474
+ if (current.some((d) => resolve11(cfg.resolvedWorkingDirectory, d) === abs)) {
14475
+ return c.json({ error: "directory already added" }, 409);
14476
+ }
14477
+ const next = [...current, abs];
14478
+ setSkillsAdditionalDirectories(next);
14479
+ return c.json({ ok: true, path: abs, exists: existsSync20(abs) }, 201);
14480
+ }
14481
+ );
14482
+ skills.delete("/directories", (c) => {
14483
+ const inputPath = c.req.query("path");
14484
+ if (!inputPath) return c.json({ error: "path query param required" }, 400);
14485
+ const cfg = getConfig();
14486
+ const abs = resolve11(cfg.resolvedWorkingDirectory, inputPath);
14487
+ const current = cfg.skills?.additionalDirectories || [];
14488
+ const next = current.filter((d) => resolve11(cfg.resolvedWorkingDirectory, d) !== abs);
14489
+ setSkillsAdditionalDirectories(next);
14490
+ return c.json({ ok: true });
14491
+ });
14492
+
14159
14493
  // src/server/auth/cf-access.ts
14160
14494
  init_config();
14161
14495
  import { createRemoteJWKSet, jwtVerify } from "jose";
@@ -14316,13 +14650,13 @@ var DEFAULT_WEB_PORT = 6969;
14316
14650
  var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
14317
14651
  function getWebDirectory() {
14318
14652
  try {
14319
- const currentDir = dirname8(fileURLToPath4(import.meta.url));
14320
- const webDir = resolve11(currentDir, "..", "web");
14321
- if (existsSync20(webDir) && existsSync20(join15(webDir, "package.json"))) {
14653
+ const currentDir = dirname9(fileURLToPath4(import.meta.url));
14654
+ const webDir = resolve12(currentDir, "..", "web");
14655
+ if (existsSync21(webDir) && existsSync21(join16(webDir, "package.json"))) {
14322
14656
  return webDir;
14323
14657
  }
14324
- const altWebDir = resolve11(currentDir, "..", "..", "web");
14325
- if (existsSync20(altWebDir) && existsSync20(join15(altWebDir, "package.json"))) {
14658
+ const altWebDir = resolve12(currentDir, "..", "..", "web");
14659
+ if (existsSync21(altWebDir) && existsSync21(join16(altWebDir, "package.json"))) {
14326
14660
  return altWebDir;
14327
14661
  }
14328
14662
  return null;
@@ -14345,18 +14679,18 @@ async function isSparkcoderWebRunning(port) {
14345
14679
  }
14346
14680
  }
14347
14681
  function isPortInUse(port) {
14348
- return new Promise((resolve12) => {
14682
+ return new Promise((resolve13) => {
14349
14683
  const server = createNetServer();
14350
14684
  server.once("error", (err) => {
14351
14685
  if (err.code === "EADDRINUSE") {
14352
- resolve12(true);
14686
+ resolve13(true);
14353
14687
  } else {
14354
- resolve12(false);
14688
+ resolve13(false);
14355
14689
  }
14356
14690
  });
14357
14691
  server.once("listening", () => {
14358
14692
  server.close();
14359
- resolve12(false);
14693
+ resolve13(false);
14360
14694
  });
14361
14695
  server.listen(port, "0.0.0.0");
14362
14696
  });
@@ -14380,30 +14714,30 @@ async function findWebPort(preferredPort) {
14380
14714
  return { port: preferredPort, alreadyRunning: false };
14381
14715
  }
14382
14716
  function hasProductionBuild(webDir) {
14383
- const buildIdPath = join15(webDir, ".next", "BUILD_ID");
14384
- return existsSync20(buildIdPath);
14717
+ const buildIdPath = join16(webDir, ".next", "BUILD_ID");
14718
+ return existsSync21(buildIdPath);
14385
14719
  }
14386
14720
  function hasSourceFiles(webDir) {
14387
- const appDir = join15(webDir, "src", "app");
14388
- const pagesDir = join15(webDir, "src", "pages");
14389
- const rootAppDir = join15(webDir, "app");
14390
- const rootPagesDir = join15(webDir, "pages");
14391
- return existsSync20(appDir) || existsSync20(pagesDir) || existsSync20(rootAppDir) || existsSync20(rootPagesDir);
14721
+ const appDir = join16(webDir, "src", "app");
14722
+ const pagesDir = join16(webDir, "src", "pages");
14723
+ const rootAppDir = join16(webDir, "app");
14724
+ const rootPagesDir = join16(webDir, "pages");
14725
+ return existsSync21(appDir) || existsSync21(pagesDir) || existsSync21(rootAppDir) || existsSync21(rootPagesDir);
14392
14726
  }
14393
14727
  function getStandaloneServerPath(webDir) {
14394
14728
  const possiblePaths2 = [
14395
- join15(webDir, ".next", "standalone", "server.js"),
14396
- join15(webDir, ".next", "standalone", "web", "server.js")
14729
+ join16(webDir, ".next", "standalone", "server.js"),
14730
+ join16(webDir, ".next", "standalone", "web", "server.js")
14397
14731
  ];
14398
14732
  for (const serverPath of possiblePaths2) {
14399
- if (existsSync20(serverPath)) {
14733
+ if (existsSync21(serverPath)) {
14400
14734
  return serverPath;
14401
14735
  }
14402
14736
  }
14403
14737
  return null;
14404
14738
  }
14405
14739
  function runCommand(command, args, cwd, env) {
14406
- return new Promise((resolve12) => {
14740
+ return new Promise((resolve13) => {
14407
14741
  const child = spawn2(command, args, {
14408
14742
  cwd,
14409
14743
  stdio: ["ignore", "pipe", "pipe"],
@@ -14418,10 +14752,10 @@ function runCommand(command, args, cwd, env) {
14418
14752
  output += data.toString();
14419
14753
  });
14420
14754
  child.on("close", (code) => {
14421
- resolve12({ success: code === 0, output });
14755
+ resolve13({ success: code === 0, output });
14422
14756
  });
14423
14757
  child.on("error", (err) => {
14424
- resolve12({ success: false, output: err.message });
14758
+ resolve13({ success: false, output: err.message });
14425
14759
  });
14426
14760
  });
14427
14761
  }
@@ -14436,13 +14770,13 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14436
14770
  if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
14437
14771
  return { process: null, port: actualPort };
14438
14772
  }
14439
- const usePnpm = existsSync20(join15(webDir, "pnpm-lock.yaml"));
14440
- const useNpm = !usePnpm && existsSync20(join15(webDir, "package-lock.json"));
14773
+ const usePnpm = existsSync21(join16(webDir, "pnpm-lock.yaml"));
14774
+ const useNpm = !usePnpm && existsSync21(join16(webDir, "package-lock.json"));
14441
14775
  const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
14442
14776
  const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
14443
14777
  const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
14444
14778
  const runtimeConfig = { apiBaseUrl: apiUrl };
14445
- const runtimeConfigPath = join15(webDir, "runtime-config.json");
14779
+ const runtimeConfigPath = join16(webDir, "runtime-config.json");
14446
14780
  try {
14447
14781
  writeFileSync6(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
14448
14782
  if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
@@ -14464,7 +14798,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14464
14798
  if (standaloneServerPath) {
14465
14799
  command = "node";
14466
14800
  args = ["server.js"];
14467
- cwd = dirname8(standaloneServerPath);
14801
+ cwd = dirname9(standaloneServerPath);
14468
14802
  webEnv.PORT = String(actualPort);
14469
14803
  webEnv.HOSTNAME = "0.0.0.0";
14470
14804
  if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
@@ -14505,10 +14839,10 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14505
14839
  let started = false;
14506
14840
  let exited = false;
14507
14841
  let exitCode = null;
14508
- const startedPromise = new Promise((resolve12) => {
14842
+ const startedPromise = new Promise((resolve13) => {
14509
14843
  const timeout = setTimeout(() => {
14510
14844
  if (!started && !exited) {
14511
- resolve12(false);
14845
+ resolve13(false);
14512
14846
  }
14513
14847
  }, startupTimeout);
14514
14848
  child.stdout?.on("data", (data) => {
@@ -14522,7 +14856,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14522
14856
  if (!started && (output.includes("Ready") || output.includes("started") || output.includes("localhost"))) {
14523
14857
  started = true;
14524
14858
  clearTimeout(timeout);
14525
- resolve12(true);
14859
+ resolve13(true);
14526
14860
  }
14527
14861
  });
14528
14862
  child.stderr?.on("data", (data) => {
@@ -14534,14 +14868,14 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14534
14868
  child.on("error", (err) => {
14535
14869
  if (!quiet) console.error(` \u274C Web UI spawn error: ${err.message}`);
14536
14870
  clearTimeout(timeout);
14537
- resolve12(false);
14871
+ resolve13(false);
14538
14872
  });
14539
14873
  child.on("exit", (code) => {
14540
14874
  exited = true;
14541
14875
  exitCode = code;
14542
14876
  if (!started) {
14543
14877
  clearTimeout(timeout);
14544
- resolve12(false);
14878
+ resolve13(false);
14545
14879
  }
14546
14880
  webUIProcess = null;
14547
14881
  });
@@ -14564,7 +14898,7 @@ function stopWebUI() {
14564
14898
  }
14565
14899
  }
14566
14900
  async function createApp(options = {}) {
14567
- const app = new Hono9();
14901
+ const app = new Hono10();
14568
14902
  app.use("*", cors({
14569
14903
  origin: "*",
14570
14904
  // Allow all origins
@@ -14592,6 +14926,7 @@ async function createApp(options = {}) {
14592
14926
  app.route("/api/schedules", schedulesRouter);
14593
14927
  app.route("/api/orchestrator", orchestratorRouter);
14594
14928
  app.route("/api/mcp", mcpRouter);
14929
+ app.route("/api/skills", skills);
14595
14930
  app.route("/api/webhooks", webhooksRouter);
14596
14931
  const config = getConfig();
14597
14932
  const webhookToken = config?.webhooks?.token;
@@ -14657,7 +14992,7 @@ async function startServer(options = {}) {
14657
14992
  if (options.workingDirectory) {
14658
14993
  config.resolvedWorkingDirectory = options.workingDirectory;
14659
14994
  }
14660
- if (!existsSync20(config.resolvedWorkingDirectory)) {
14995
+ if (!existsSync21(config.resolvedWorkingDirectory)) {
14661
14996
  mkdirSync9(config.resolvedWorkingDirectory, { recursive: true });
14662
14997
  if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
14663
14998
  }