sparkecoder 0.1.130 → 0.1.132
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.
- package/README.md +3 -3
- package/dist/agent/index.d.ts +3 -3
- package/dist/agent/index.js +1480 -638
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +2281 -808
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js.map +1 -1
- package/dist/{index-Bcz0aCAR.d.ts → index-BM99kjgq.d.ts} +177 -103
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2215 -780
- package/dist/index.js.map +1 -1
- package/dist/{schema-BWbWmfDQ.d.ts → schema-Dz-wABVY.d.ts} +27 -4
- package/dist/{search-DOzC4ojH.d.ts → search-CVVfuBPZ.d.ts} +4 -4
- package/dist/server/index.js +2215 -780
- package/dist/server/index.js.map +1 -1
- package/dist/skills/default/build-context-and-solve-issue.md +74 -0
- package/dist/skills/default/doublecheck.md +95 -0
- package/dist/tools/index.d.ts +3 -3
- package/dist/tools/index.js +11 -2
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
- package/src/skills/default/build-context-and-solve-issue.md +74 -0
- package/src/skills/default/doublecheck.md +95 -0
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/(main)/agents/page/next-font-manifest.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/agents/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/page/next-font-manifest.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page/next-font-manifest.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/settings/page/next-font-manifest.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/settings/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found/page/next-font-manifest.json +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/agents.html +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.rsc +6 -6
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/agents.segments/_full.segment.rsc +6 -6
- package/web/.next/standalone/web/.next/server/app/agents.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/agents.segments/_tree.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/api/config/route.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation/page/next-font-manifest.json +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.html +4 -4
- package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +6 -6
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +6 -6
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +20 -21
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/page/next-font-manifest.json +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills/page/next-font-manifest.json +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools/page/next-font-manifest.json +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.html +3 -3
- package/web/.next/standalone/web/.next/server/app/docs.rsc +5 -5
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +5 -5
- package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +6 -6
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +6 -6
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/settings.html +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.rsc +6 -6
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/settings.segments/_full.segment.rsc +6 -6
- package/web/.next/standalone/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/settings.segments/_tree.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__36edac7c._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__397fadd4._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__70cecda8._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__be5e2967._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_layout_tsx_453f6492._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_page_tsx_5ac4794b._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_settings_page_tsx_eb320e07._.js +2 -2
- package/web/.next/standalone/web/.next/server/next-font-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/next-font-manifest.json +9 -9
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/standalone/web/.next/static/chunks/185f69f6478ba713.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/20ca4e35e9bb3e94.js +3 -0
- package/web/.next/standalone/web/.next/static/chunks/{a7d5d0791c8c6223.css → 34d933785a17edf3.css} +1 -1
- package/web/.next/standalone/web/.next/static/chunks/7549a5b7c7f6786e.js +1 -0
- package/web/.next/standalone/web/.next/static/{static/chunks/c5dd884b71007965.js → chunks/a839c83078c56476.js} +1 -1
- package/web/.next/standalone/web/.next/static/media/4fa387ec64143e14-s.3b336396.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/5ce348bf30bf5439-s.56c1f21e.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/6306c77e7c8268e4-s.e3369375.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/797e433ab948586e-s.p.29207c2f.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/7d817b4c03b0c5f1-s.a40b9a8b.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/bbc41e54d2fcbd21-s.fe42ddf4.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/chunks/185f69f6478ba713.js +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/20ca4e35e9bb3e94.js +3 -0
- package/web/.next/standalone/web/.next/static/static/chunks/{a7d5d0791c8c6223.css → 34d933785a17edf3.css} +1 -1
- package/web/.next/standalone/web/.next/static/static/chunks/7549a5b7c7f6786e.js +1 -0
- package/web/.next/{static/chunks/c5dd884b71007965.js → standalone/web/.next/static/static/chunks/a839c83078c56476.js} +1 -1
- package/web/.next/standalone/web/.next/static/static/media/4fa387ec64143e14-s.3b336396.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/media/5ce348bf30bf5439-s.56c1f21e.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/media/6306c77e7c8268e4-s.e3369375.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/media/797e433ab948586e-s.p.29207c2f.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/media/7d817b4c03b0c5f1-s.a40b9a8b.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/media/bbc41e54d2fcbd21-s.fe42ddf4.woff2 +0 -0
- package/web/.next/standalone/web/package-lock.json +21 -21
- package/web/.next/standalone/web/runtime-config.json +2 -1
- package/web/.next/standalone/web/src/app/(main)/page.tsx +2 -2
- package/web/.next/standalone/web/src/app/(main)/settings/page.tsx +111 -11
- package/web/.next/standalone/web/src/app/__sfapi/[...path]/route.ts +96 -0
- package/web/.next/standalone/web/src/app/api/config/route.ts +5 -12
- package/web/.next/standalone/web/src/app/docs/installation/page.mdx +2 -2
- package/web/.next/standalone/web/src/app/docs/page.mdx +1 -1
- package/web/.next/standalone/web/src/components/sessions-sidebar.tsx +1 -1
- package/web/.next/standalone/web/src/lib/config.ts +26 -16
- package/web/.next/static/chunks/185f69f6478ba713.js +1 -0
- package/web/.next/static/chunks/20ca4e35e9bb3e94.js +3 -0
- package/web/.next/static/chunks/{a7d5d0791c8c6223.css → 34d933785a17edf3.css} +1 -1
- package/web/.next/static/chunks/7549a5b7c7f6786e.js +1 -0
- package/web/.next/{standalone/web/.next/static/chunks/c5dd884b71007965.js → static/chunks/a839c83078c56476.js} +1 -1
- package/web/.next/static/media/4fa387ec64143e14-s.3b336396.woff2 +0 -0
- package/web/.next/static/media/5ce348bf30bf5439-s.56c1f21e.woff2 +0 -0
- package/web/.next/static/media/6306c77e7c8268e4-s.e3369375.woff2 +0 -0
- package/web/.next/static/media/797e433ab948586e-s.p.29207c2f.woff2 +0 -0
- package/web/.next/static/media/7d817b4c03b0c5f1-s.a40b9a8b.woff2 +0 -0
- package/web/.next/static/media/bbc41e54d2fcbd21-s.fe42ddf4.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/chunks/9b88f148788e4504.js +0 -3
- package/web/.next/standalone/web/.next/static/chunks/b203b9aa975135d3.js +0 -1
- package/web/.next/standalone/web/.next/static/chunks/ea89ca7892d8c557.js +0 -1
- package/web/.next/standalone/web/.next/static/media/4fa387ec64143e14-s.c36e1862.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/5ce348bf30bf5439-s.ebceb24d.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/6306c77e7c8268e4-s.ff4a2084.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/797e433ab948586e-s.p.479bea2b.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/7d817b4c03b0c5f1-s.f377b9c4.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/bbc41e54d2fcbd21-s.d1207556.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/chunks/9b88f148788e4504.js +0 -3
- package/web/.next/standalone/web/.next/static/static/chunks/b203b9aa975135d3.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/ea89ca7892d8c557.js +0 -1
- package/web/.next/standalone/web/.next/static/static/media/4fa387ec64143e14-s.c36e1862.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/media/5ce348bf30bf5439-s.ebceb24d.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/media/6306c77e7c8268e4-s.ff4a2084.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/media/797e433ab948586e-s.p.479bea2b.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/media/7d817b4c03b0c5f1-s.f377b9c4.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/media/bbc41e54d2fcbd21-s.d1207556.woff2 +0 -0
- package/web/.next/static/chunks/9b88f148788e4504.js +0 -3
- package/web/.next/static/chunks/b203b9aa975135d3.js +0 -1
- package/web/.next/static/chunks/ea89ca7892d8c557.js +0 -1
- package/web/.next/static/media/4fa387ec64143e14-s.c36e1862.woff2 +0 -0
- package/web/.next/static/media/5ce348bf30bf5439-s.ebceb24d.woff2 +0 -0
- package/web/.next/static/media/6306c77e7c8268e4-s.ff4a2084.woff2 +0 -0
- package/web/.next/static/media/797e433ab948586e-s.p.479bea2b.woff2 +0 -0
- package/web/.next/static/media/7d817b4c03b0c5f1-s.f377b9c4.woff2 +0 -0
- package/web/.next/static/media/bbc41e54d2fcbd21-s.d1207556.woff2 +0 -0
- /package/web/.next/standalone/web/.next/static/{2mUQ8I-TCRE5uOBFhIWag → WaAcu3X3K00MDvfn1ik7H}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{2mUQ8I-TCRE5uOBFhIWag → WaAcu3X3K00MDvfn1ik7H}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{2mUQ8I-TCRE5uOBFhIWag → WaAcu3X3K00MDvfn1ik7H}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{2mUQ8I-TCRE5uOBFhIWag → WaAcu3X3K00MDvfn1ik7H}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{2mUQ8I-TCRE5uOBFhIWag → WaAcu3X3K00MDvfn1ik7H}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/static/{2mUQ8I-TCRE5uOBFhIWag → WaAcu3X3K00MDvfn1ik7H}/_ssgManifest.js +0 -0
- /package/web/.next/static/{2mUQ8I-TCRE5uOBFhIWag → WaAcu3X3K00MDvfn1ik7H}/_buildManifest.js +0 -0
- /package/web/.next/static/{2mUQ8I-TCRE5uOBFhIWag → WaAcu3X3K00MDvfn1ik7H}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{2mUQ8I-TCRE5uOBFhIWag → WaAcu3X3K00MDvfn1ik7H}/_ssgManifest.js +0 -0
|
@@ -29,7 +29,7 @@ interface IntegrationsResponse {
|
|
|
29
29
|
deniedReplyTemplate: string;
|
|
30
30
|
};
|
|
31
31
|
cloudflared: { publicBaseUrl: string | null; hint: string };
|
|
32
|
-
cfAccess: { enabled: boolean; teamDomain: string | null; allowedEmails: string[] };
|
|
32
|
+
cfAccess: { enabled: boolean; teamDomain: string | null; audTag: string | null; allowedEmails: string[] };
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
interface ScheduleRow {
|
|
@@ -269,7 +269,7 @@ function IntegrationsSection() {
|
|
|
269
269
|
<section className="space-y-4">
|
|
270
270
|
<SectionHeader title="Integrations" description="External channels the orchestrator can send and receive messages on." />
|
|
271
271
|
<SlackCard data={data} onChange={refresh} />
|
|
272
|
-
<CloudflaredCard data={data} />
|
|
272
|
+
<CloudflaredCard data={data} onChange={refresh} />
|
|
273
273
|
</section>
|
|
274
274
|
);
|
|
275
275
|
}
|
|
@@ -648,7 +648,7 @@ function buildSlackManifest(eventsUrl: string, agentName: string): string {
|
|
|
648
648
|
return JSON.stringify(manifest, null, 2);
|
|
649
649
|
}
|
|
650
650
|
|
|
651
|
-
function CloudflaredCard({ data }: { data: IntegrationsResponse }) {
|
|
651
|
+
function CloudflaredCard({ data, onChange }: { data: IntegrationsResponse; onChange: () => void }) {
|
|
652
652
|
return (
|
|
653
653
|
<Card>
|
|
654
654
|
<CardHeader icon={<Cloud className="size-4 text-orange-500" />} title="Cloudflared tunnel"
|
|
@@ -665,18 +665,118 @@ function CloudflaredCard({ data }: { data: IntegrationsResponse }) {
|
|
|
665
665
|
<code className="px-1 rounded bg-muted">{data.cloudflared.publicBaseUrl}</code>
|
|
666
666
|
</div>
|
|
667
667
|
)}
|
|
668
|
-
|
|
669
|
-
<
|
|
670
|
-
|
|
671
|
-
{data.cfAccess.allowedEmails.length > 0 && (
|
|
672
|
-
<span className="text-muted-foreground"> · {data.cfAccess.allowedEmails.length} allowed email(s)</span>
|
|
673
|
-
)}
|
|
674
|
-
</div>
|
|
675
|
-
)}
|
|
668
|
+
<div className="mt-3 border-t border-border/40 pt-3">
|
|
669
|
+
<CfAccessEditor data={data} onChange={onChange} />
|
|
670
|
+
</div>
|
|
676
671
|
</Card>
|
|
677
672
|
);
|
|
678
673
|
}
|
|
679
674
|
|
|
675
|
+
function CfAccessEditor({ data, onChange }: { data: IntegrationsResponse; onChange: () => void }) {
|
|
676
|
+
const [enabled, setEnabled] = useState(data.cfAccess.enabled);
|
|
677
|
+
const [teamDomain, setTeamDomain] = useState(data.cfAccess.teamDomain ?? '');
|
|
678
|
+
const [audTag, setAudTag] = useState(data.cfAccess.audTag ?? '');
|
|
679
|
+
const [allowedEmails, setAllowedEmails] = useState(data.cfAccess.allowedEmails.join(', '));
|
|
680
|
+
const [saving, setSaving] = useState(false);
|
|
681
|
+
const [saved, setSaved] = useState(false);
|
|
682
|
+
const [error, setError] = useState<string | null>(null);
|
|
683
|
+
|
|
684
|
+
const initialEmails = data.cfAccess.allowedEmails.join(', ');
|
|
685
|
+
const dirty =
|
|
686
|
+
enabled !== data.cfAccess.enabled ||
|
|
687
|
+
teamDomain.trim() !== (data.cfAccess.teamDomain ?? '') ||
|
|
688
|
+
audTag.trim() !== (data.cfAccess.audTag ?? '') ||
|
|
689
|
+
allowedEmails.trim() !== initialEmails.trim();
|
|
690
|
+
|
|
691
|
+
const save = async () => {
|
|
692
|
+
setSaving(true); setSaved(false); setError(null);
|
|
693
|
+
try {
|
|
694
|
+
const splitList = (s: string) =>
|
|
695
|
+
s.split(/[\s,]+/).map((x) => x.trim().toLowerCase()).filter(Boolean);
|
|
696
|
+
const res = await fetch(api('/api/integrations/cf-access'), {
|
|
697
|
+
method: 'POST',
|
|
698
|
+
headers: { 'Content-Type': 'application/json' },
|
|
699
|
+
body: JSON.stringify({
|
|
700
|
+
enabled,
|
|
701
|
+
teamDomain: teamDomain.trim(),
|
|
702
|
+
audTag: audTag.trim(),
|
|
703
|
+
allowedEmails: splitList(allowedEmails),
|
|
704
|
+
}),
|
|
705
|
+
});
|
|
706
|
+
if (!res.ok) {
|
|
707
|
+
const body = await res.json().catch(() => ({}));
|
|
708
|
+
throw new Error(body?.error || `Request failed (${res.status})`);
|
|
709
|
+
}
|
|
710
|
+
onChange();
|
|
711
|
+
setSaved(true); setTimeout(() => setSaved(false), 1500);
|
|
712
|
+
} catch (err) {
|
|
713
|
+
setError(err instanceof Error ? err.message : 'Failed to save');
|
|
714
|
+
} finally { setSaving(false); }
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
return (
|
|
718
|
+
<div className="space-y-3">
|
|
719
|
+
<div className="flex items-center gap-2">
|
|
720
|
+
<ShieldCheck className="size-3.5 text-emerald-500" />
|
|
721
|
+
<span className="text-xs font-medium">Cloudflare Access</span>
|
|
722
|
+
</div>
|
|
723
|
+
<p className="text-xs text-muted-foreground">
|
|
724
|
+
Restrict who can reach your public tunnel URL by email. Localhost always bypasses this,
|
|
725
|
+
so you can't lock yourself out of your own machine.
|
|
726
|
+
</p>
|
|
727
|
+
|
|
728
|
+
<label className="flex items-start gap-2 cursor-pointer">
|
|
729
|
+
<input
|
|
730
|
+
type="checkbox"
|
|
731
|
+
checked={enabled}
|
|
732
|
+
onChange={(e) => setEnabled(e.target.checked)}
|
|
733
|
+
className="mt-1"
|
|
734
|
+
/>
|
|
735
|
+
<div className="text-xs">
|
|
736
|
+
<span className="font-medium">Enforce Cloudflare Access</span>
|
|
737
|
+
<p className="text-muted-foreground">
|
|
738
|
+
When on, every non-local request must carry a valid Cloudflare Access JWT whose email is in the allowlist below.
|
|
739
|
+
</p>
|
|
740
|
+
</div>
|
|
741
|
+
</label>
|
|
742
|
+
|
|
743
|
+
<div>
|
|
744
|
+
<Label className="text-xs">Team domain</Label>
|
|
745
|
+
<p className="text-[11px] text-muted-foreground mb-1">e.g. <code>yourteam.cloudflareaccess.com</code></p>
|
|
746
|
+
<Input value={teamDomain} onChange={(e) => setTeamDomain(e.target.value)} placeholder="yourteam.cloudflareaccess.com" className="text-xs font-mono" />
|
|
747
|
+
</div>
|
|
748
|
+
|
|
749
|
+
<div>
|
|
750
|
+
<Label className="text-xs">Application AUD tag</Label>
|
|
751
|
+
<p className="text-[11px] text-muted-foreground mb-1">From the Cloudflare Access application (Access → Applications).</p>
|
|
752
|
+
<Input value={audTag} onChange={(e) => setAudTag(e.target.value)} placeholder="abc123..." className="text-xs font-mono" />
|
|
753
|
+
</div>
|
|
754
|
+
|
|
755
|
+
<div>
|
|
756
|
+
<Label className="text-xs">Allowed emails or domains</Label>
|
|
757
|
+
<p className="text-[11px] text-muted-foreground mb-1">
|
|
758
|
+
Comma or space separated. Use a full address (<code>you@example.com</code>) or a bare domain (<code>studyfetch.com</code>) to allow everyone on it. Leave blank to allow any email that passes your Cloudflare Access policy.
|
|
759
|
+
</p>
|
|
760
|
+
<Input value={allowedEmails} onChange={(e) => setAllowedEmails(e.target.value)} placeholder="studyfetch.com, you@example.com" className="text-xs font-mono" />
|
|
761
|
+
</div>
|
|
762
|
+
|
|
763
|
+
<p className="text-[11px] text-muted-foreground">
|
|
764
|
+
Heads up: this is one of two layers. You must also add the same emails to an
|
|
765
|
+
“Allow” policy in the Cloudflare dashboard — that's the real gate.
|
|
766
|
+
</p>
|
|
767
|
+
|
|
768
|
+
{error && <p className="text-xs text-red-500">{error}</p>}
|
|
769
|
+
|
|
770
|
+
<div className="flex items-center gap-2 pt-1">
|
|
771
|
+
<Button size="sm" onClick={save} disabled={!dirty || saving}>
|
|
772
|
+
{saving ? <Loader2 className="size-3.5 animate-spin" /> : 'Save'}
|
|
773
|
+
</Button>
|
|
774
|
+
{saved && <span className="text-xs text-emerald-500">Saved ✓</span>}
|
|
775
|
+
</div>
|
|
776
|
+
</div>
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
|
|
680
780
|
// ============================================================
|
|
681
781
|
// Schedules
|
|
682
782
|
// ============================================================
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Same-origin reverse proxy to the local sparkecoder API.
|
|
7
|
+
*
|
|
8
|
+
* The browser calls `/__sfapi/<path>` on the WEB origin (e.g.
|
|
9
|
+
* `https://sf-abc.sparkecode.com/__sfapi/sessions`). This route forwards the
|
|
10
|
+
* request to the local API over loopback (`http://127.0.0.1:3141/sessions`).
|
|
11
|
+
*
|
|
12
|
+
* Why: when Cloudflare Access is enabled, the web app and the `api-<host>`
|
|
13
|
+
* subdomain are SEPARATE Access applications. A browser `fetch()` to the API
|
|
14
|
+
* subdomain gets an interactive Access login redirect it can't follow (CORS /
|
|
15
|
+
* "access control checks" errors). Proxying through the web origin keeps the
|
|
16
|
+
* browser on ONE Access-gated origin (the web app, already logged in), and the
|
|
17
|
+
* loopback hop is exempt from Access on the device.
|
|
18
|
+
*
|
|
19
|
+
* Streams responses (SSE) and forwards request bodies, methods, and query.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export const dynamic = 'force-dynamic';
|
|
23
|
+
|
|
24
|
+
const DEFAULT_LOCAL_API = 'http://127.0.0.1:3141';
|
|
25
|
+
|
|
26
|
+
function getLocalApiBase(): string {
|
|
27
|
+
const cwd = process.cwd();
|
|
28
|
+
const candidates = [
|
|
29
|
+
path.join(cwd, 'runtime-config.json'),
|
|
30
|
+
path.join(cwd, '..', 'runtime-config.json'),
|
|
31
|
+
path.join(cwd, '..', '..', 'runtime-config.json'),
|
|
32
|
+
path.join(cwd, '..', '..', '..', 'runtime-config.json'),
|
|
33
|
+
];
|
|
34
|
+
for (const p of candidates) {
|
|
35
|
+
try {
|
|
36
|
+
if (fs.existsSync(p)) {
|
|
37
|
+
const cfg = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
38
|
+
// Prefer the explicit loopback URL; fall back to apiBaseUrl only if it
|
|
39
|
+
// is itself a localhost URL (never proxy to a public api-<host>, that
|
|
40
|
+
// would re-trigger the Access redirect server-side).
|
|
41
|
+
if (cfg.localApiBaseUrl) return cfg.localApiBaseUrl;
|
|
42
|
+
if (typeof cfg.apiBaseUrl === 'string' && /^https?:\/\/(localhost|127\.0\.0\.1)/.test(cfg.apiBaseUrl)) {
|
|
43
|
+
return cfg.apiBaseUrl;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// try next candidate
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return DEFAULT_LOCAL_API;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function proxy(req: NextRequest, ctx: { params: Promise<{ path?: string[] }> }) {
|
|
54
|
+
const { path: parts = [] } = await ctx.params;
|
|
55
|
+
const base = getLocalApiBase().replace(/\/$/, '');
|
|
56
|
+
const search = req.nextUrl.search || '';
|
|
57
|
+
const target = `${base}/${parts.join('/')}${search}`;
|
|
58
|
+
|
|
59
|
+
const headers = new Headers();
|
|
60
|
+
const ct = req.headers.get('content-type');
|
|
61
|
+
if (ct) headers.set('content-type', ct);
|
|
62
|
+
const accept = req.headers.get('accept');
|
|
63
|
+
if (accept) headers.set('accept', accept);
|
|
64
|
+
|
|
65
|
+
const init: RequestInit = { method: req.method, headers, redirect: 'manual' };
|
|
66
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
67
|
+
init.body = await req.arrayBuffer();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let upstream: Response;
|
|
71
|
+
try {
|
|
72
|
+
upstream = await fetch(target, init);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
return new Response(
|
|
75
|
+
JSON.stringify({ error: 'local API unreachable', detail: err instanceof Error ? err.message : String(err) }),
|
|
76
|
+
{ status: 502, headers: { 'content-type': 'application/json' } },
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Stream the response body through (SSE, downloads, JSON all work). Pass
|
|
81
|
+
// only safe headers; let the runtime handle transfer-encoding/length.
|
|
82
|
+
const respHeaders = new Headers();
|
|
83
|
+
for (const h of ['content-type', 'cache-control', 'content-disposition']) {
|
|
84
|
+
const v = upstream.headers.get(h);
|
|
85
|
+
if (v) respHeaders.set(h, v);
|
|
86
|
+
}
|
|
87
|
+
return new Response(upstream.body, { status: upstream.status, headers: respHeaders });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const GET = proxy;
|
|
91
|
+
export const POST = proxy;
|
|
92
|
+
export const PUT = proxy;
|
|
93
|
+
export const PATCH = proxy;
|
|
94
|
+
export const DELETE = proxy;
|
|
95
|
+
export const HEAD = proxy;
|
|
96
|
+
export const OPTIONS = proxy;
|
|
@@ -11,16 +11,9 @@ import * as path from 'path';
|
|
|
11
11
|
|
|
12
12
|
// Available models for the UI
|
|
13
13
|
const AVAILABLE_MODELS = [
|
|
14
|
-
{ id: '
|
|
15
|
-
{ id: '
|
|
16
|
-
{ id: '
|
|
17
|
-
{ id: 'anthropic/claude-sonnet-4', name: 'Claude Sonnet 4', provider: 'Anthropic' },
|
|
18
|
-
{ id: 'anthropic/claude-haiku-4-5', name: 'Claude Haiku 4.5', provider: 'Anthropic' },
|
|
19
|
-
{ id: 'openai/gpt-5-2-codex', name: 'GPT-5.2 Codex', provider: 'OpenAI' },
|
|
20
|
-
{ id: 'openai/gpt-4o', name: 'GPT-4o', provider: 'OpenAI' },
|
|
21
|
-
{ id: 'google/gemini-3-pro-preview', name: 'Gemini 3 Pro Preview', provider: 'Google' },
|
|
22
|
-
{ id: 'google/gemini-3-flash', name: 'Gemini 3 Flash', provider: 'Google' },
|
|
23
|
-
{ id: 'xai/grok-code-fast-1', name: 'Grok Code Fast', provider: 'xAI' },
|
|
14
|
+
{ id: 'claude-opus-4-8', name: 'Claude Opus 4.8', provider: 'Anthropic' },
|
|
15
|
+
{ id: 'gpt-5.5', name: 'GPT-5.5', provider: 'OpenAI' },
|
|
16
|
+
{ id: 'claude-fable-5', name: 'Claude Fable 5', provider: 'Anthropic' },
|
|
24
17
|
];
|
|
25
18
|
|
|
26
19
|
// Available tools
|
|
@@ -88,7 +81,7 @@ export async function GET() {
|
|
|
88
81
|
return NextResponse.json({
|
|
89
82
|
availableModels: AVAILABLE_MODELS,
|
|
90
83
|
availableTools: AVAILABLE_TOOLS,
|
|
91
|
-
defaultModel: healthData.config?.defaultModel || '
|
|
84
|
+
defaultModel: healthData.config?.defaultModel || 'gpt-5.5',
|
|
92
85
|
defaultToolApprovals: healthData.config?.defaultToolApprovals || {},
|
|
93
86
|
serverConnected: true,
|
|
94
87
|
apiKeyConfigured: healthData.apiKeyConfigured ?? false,
|
|
@@ -98,7 +91,7 @@ export async function GET() {
|
|
|
98
91
|
return NextResponse.json({
|
|
99
92
|
availableModels: AVAILABLE_MODELS,
|
|
100
93
|
availableTools: AVAILABLE_TOOLS,
|
|
101
|
-
defaultModel: '
|
|
94
|
+
defaultModel: 'gpt-5.5',
|
|
102
95
|
defaultToolApprovals: {},
|
|
103
96
|
serverConnected: false,
|
|
104
97
|
apiKeyConfigured: false,
|
|
@@ -57,7 +57,7 @@ This creates `sparkecoder.config.json` with default settings:
|
|
|
57
57
|
|
|
58
58
|
```json
|
|
59
59
|
{
|
|
60
|
-
"defaultModel": "
|
|
60
|
+
"defaultModel": "gpt-5.5",
|
|
61
61
|
"workingDirectory": ".",
|
|
62
62
|
"toolApprovals": {
|
|
63
63
|
"bash": true,
|
|
@@ -84,7 +84,7 @@ This starts the interactive chat interface. You can also:
|
|
|
84
84
|
sparkecoder -w /path/to/project
|
|
85
85
|
|
|
86
86
|
# Use a specific model
|
|
87
|
-
sparkecoder -m
|
|
87
|
+
sparkecoder -m gpt-5.5
|
|
88
88
|
|
|
89
89
|
# Resume a previous session
|
|
90
90
|
sparkecoder -s session_id
|
|
@@ -186,7 +186,7 @@ export function SessionsSidebar() {
|
|
|
186
186
|
try {
|
|
187
187
|
const session = await createSession({
|
|
188
188
|
name: `Session ${sessions.length + 1}`,
|
|
189
|
-
model: config?.defaultModel || '
|
|
189
|
+
model: config?.defaultModel || 'gpt-5.5',
|
|
190
190
|
toolApprovals, // Use current tool approval settings from global config
|
|
191
191
|
});
|
|
192
192
|
mutateSessions(); // Refresh the sessions list
|
|
@@ -46,11 +46,12 @@ function deriveApiUrlFromLocation(): string | null {
|
|
|
46
46
|
return `${protocol}//${hostname}:${apiPort}`;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
// Tunneled / DNS hostname.
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
// Tunneled / DNS hostname. Route API calls back through THIS origin via the
|
|
50
|
+
// `/__sfapi` reverse proxy instead of the separate `api-<host>` subdomain.
|
|
51
|
+
// With Cloudflare Access enabled, api-<host> is its own Access app and a
|
|
52
|
+
// browser fetch to it gets an interactive login redirect it can't follow.
|
|
53
|
+
// Staying same-origin uses the web app's existing Access session, no CORS.
|
|
54
|
+
return `${window.location.origin}/__sfapi`;
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
// Flag to track if we've initialized
|
|
@@ -97,9 +98,23 @@ export function getApiUrl(): string {
|
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
const stored = localStorage.getItem(STORAGE_KEY);
|
|
100
|
-
if (stored) return stored;
|
|
101
|
-
|
|
102
101
|
const derived = deriveApiUrlFromLocation();
|
|
102
|
+
|
|
103
|
+
if (stored) {
|
|
104
|
+
// Self-heal stale values: if a previous session cached the separate
|
|
105
|
+
// `api-<host>` origin (now broken behind Cloudflare Access) but we can
|
|
106
|
+
// derive a same-origin proxy URL, prefer the proxy.
|
|
107
|
+
try {
|
|
108
|
+
const u = new URL(stored);
|
|
109
|
+
if (u.hostname.startsWith('api-') && derived && derived.endsWith('/__sfapi')) {
|
|
110
|
+
return derived;
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
// stored isn't an absolute URL — fall through and use it as-is
|
|
114
|
+
}
|
|
115
|
+
return stored;
|
|
116
|
+
}
|
|
117
|
+
|
|
103
118
|
if (derived) return derived;
|
|
104
119
|
|
|
105
120
|
return DEFAULT_API_URL;
|
|
@@ -147,21 +162,16 @@ export interface AppConfig {
|
|
|
147
162
|
|
|
148
163
|
const DEFAULT_CONFIG: AppConfig = {
|
|
149
164
|
availableModels: [
|
|
150
|
-
{ id: '
|
|
151
|
-
{ id: '
|
|
152
|
-
{ id: '
|
|
153
|
-
{ id: 'anthropic/claude-sonnet-4', name: 'Claude Sonnet 4', provider: 'Anthropic' },
|
|
154
|
-
{ id: 'anthropic/claude-haiku-4-5', name: 'Claude Haiku 4.5', provider: 'Anthropic' },
|
|
155
|
-
{ id: 'openai/gpt-5-2-codex', name: 'GPT-5.2 Codex', provider: 'OpenAI' },
|
|
156
|
-
{ id: 'google/gemini-3-flash', name: 'Gemini 3 Flash', provider: 'Google' },
|
|
157
|
-
{ id: 'xai/grok-code-fast-1', name: 'Grok Code Fast', provider: 'xAI' },
|
|
165
|
+
{ id: 'claude-opus-4-8', name: 'Claude Opus 4.8', provider: 'Anthropic' },
|
|
166
|
+
{ id: 'gpt-5.5', name: 'GPT-5.5', provider: 'OpenAI' },
|
|
167
|
+
{ id: 'claude-fable-5', name: 'Claude Fable 5', provider: 'Anthropic' },
|
|
158
168
|
],
|
|
159
169
|
availableTools: [
|
|
160
170
|
{ id: 'bash', name: 'Bash', description: 'Execute shell commands', dangerous: true },
|
|
161
171
|
{ id: 'write_file', name: 'Write File', description: 'Create or edit files', dangerous: true },
|
|
162
172
|
{ id: 'read_file', name: 'Read File', description: 'Read file contents', dangerous: false },
|
|
163
173
|
],
|
|
164
|
-
defaultModel: '
|
|
174
|
+
defaultModel: 'gpt-5.5',
|
|
165
175
|
defaultToolApprovals: {},
|
|
166
176
|
serverConnected: false,
|
|
167
177
|
apiKeyConfigured: false,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,739963,e=>{"use strict";let t="sparkecoder_api_url",i="http://localhost:3141",o=!1;function a(){if(o)return;o=!0;let e=new URLSearchParams(window.location.search),i=e.get("apiUrl");if(i){localStorage.setItem(t,i),e.delete("apiUrl");let o=e.toString()?`${window.location.pathname}?${e.toString()}`:window.location.pathname;window.history.replaceState({},"",o),console.log(`[SparkECoder] API URL set to: ${i}`)}}function l(){o||a();let e=localStorage.getItem(t),l=function(){let{protocol:e,hostname:t,port:i}=window.location;return"localhost"===t||"127.0.0.1"===t||"0.0.0.0"===t?null:/^\d{1,3}(\.\d{1,3}){3}$/.test(t)?`${e}//${t}:${"6969"===i?"3141":i||"3141"}`:`${window.location.origin}/__sfapi`}();if(e){try{if(new URL(e).hostname.startsWith("api-")&&l&&l.endsWith("/__sfapi"))return l}catch{}return e}return l||i}function r(e){localStorage.setItem(t,e)}function n(){localStorage.removeItem(t)}let s={availableModels:[{id:"claude-opus-4-8",name:"Claude Opus 4.8",provider:"Anthropic"},{id:"gpt-5.5",name:"GPT-5.5",provider:"OpenAI"},{id:"claude-fable-5",name:"Claude Fable 5",provider:"Anthropic"}],availableTools:[{id:"bash",name:"Bash",description:"Execute shell commands",dangerous:!0},{id:"write_file",name:"Write File",description:"Create or edit files",dangerous:!0},{id:"read_file",name:"Read File",description:"Read file contents",dangerous:!1}],defaultModel:"gpt-5.5",defaultToolApprovals:{},serverConnected:!1,apiKeyConfigured:!1,apiBaseUrl:i};async function c(){let e=l();try{let t=new AbortController,i=setTimeout(()=>t.abort(),3e3),o=await fetch(`${e}/health`,{signal:t.signal});if(clearTimeout(i),!o.ok)return{...s,apiBaseUrl:e};let a=await o.json();return{...s,defaultModel:a.config?.defaultModel||s.defaultModel,defaultToolApprovals:a.config?.defaultToolApprovals||{},serverConnected:!0,apiKeyConfigured:a.apiKeyConfigured??!1,apiBaseUrl:e}}catch{return{...s,apiBaseUrl:e}}}async function d(){return l()}function u(){}e.s(["clearApiUrl",()=>n,"getApiBaseUrl",()=>d,"getApiUrl",()=>l,"getConfig",()=>c,"initApiUrl",()=>a,"resetConfigCache",()=>u,"setApiUrl",()=>r])},760800,e=>{"use strict";var t=e.i(430878),i=e.i(739963);function o(){return(0,t.useEffect)(()=>{(0,i.initApiUrl)()},[]),null}e.s(["ApiInit",()=>o])}]);
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,412490,e=>{"use strict";var s=e.i(414294),t=e.i(430878),a=e.i(461957),r=e.i(292511);let l=(0,r.default)("slack",[["rect",{width:"3",height:"8",x:"13",y:"2",rx:"1.5",key:"diqz80"}],["path",{d:"M19 8.5V10h1.5A1.5 1.5 0 1 0 19 8.5",key:"183iwg"}],["rect",{width:"3",height:"8",x:"8",y:"14",rx:"1.5",key:"hqg7r1"}],["path",{d:"M5 15.5V14H3.5A1.5 1.5 0 1 0 5 15.5",key:"76g71w"}],["rect",{width:"8",height:"3",x:"14",y:"13",rx:"1.5",key:"1kmz0a"}],["path",{d:"M15.5 19H14v1.5a1.5 1.5 0 1 0 1.5-1.5",key:"jc4sz0"}],["rect",{width:"8",height:"3",x:"2",y:"8",rx:"1.5",key:"1omvl4"}],["path",{d:"M8.5 5H10V3.5A1.5 1.5 0 1 0 8.5 5",key:"16f3cl"}]]),n=(0,r.default)("cloud",[["path",{d:"M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z",key:"p7xjir"}]]),i=(0,r.default)("calendar",[["path",{d:"M8 2v4",key:"1cmpym"}],["path",{d:"M16 2v4",key:"4m81vk"}],["rect",{width:"18",height:"18",x:"3",y:"4",rx:"2",key:"1hopcy"}],["path",{d:"M3 10h18",key:"8toen8"}]]),o=(0,r.default)("webhook",[["path",{d:"M18 16.98h-5.99c-1.1 0-1.95.94-2.48 1.9A4 4 0 0 1 2 17c.01-.7.2-1.4.57-2",key:"q3hayz"}],["path",{d:"m6 17 3.13-5.78c.53-.97.1-2.18-.5-3.1a4 4 0 1 1 6.89-4.06",key:"1go1hn"}],["path",{d:"m12 6 3.13 5.73C15.66 12.7 16.9 13 18 13a4 4 0 0 1 0 8",key:"qlwsc0"}]]);var d=e.i(367887);let c=(0,r.default)("rotate-cw",[["path",{d:"M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8",key:"1p45f6"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}]]);var x=e.i(904781),m=e.i(996720),u=e.i(719583),h=e.i(991234),p=e.i(775377),j=e.i(266514);let f=(0,r.default)("cpu",[["path",{d:"M12 20v2",key:"1lh1kg"}],["path",{d:"M12 2v2",key:"tus03m"}],["path",{d:"M17 20v2",key:"1rnc9c"}],["path",{d:"M17 2v2",key:"11trls"}],["path",{d:"M2 12h2",key:"1t8f8n"}],["path",{d:"M2 17h2",key:"7oei6x"}],["path",{d:"M2 7h2",key:"asdhe0"}],["path",{d:"M20 12h2",key:"1q8mjw"}],["path",{d:"M20 17h2",key:"1fpfkl"}],["path",{d:"M20 7h2",key:"1o8tra"}],["path",{d:"M7 20v2",key:"4gnj0m"}],["path",{d:"M7 2v2",key:"1i4yhu"}],["rect",{x:"4",y:"4",width:"16",height:"16",rx:"2",key:"1vbyd7"}],["rect",{x:"8",y:"8",width:"8",height:"8",rx:"1",key:"z9xiuo"}]]);var g=e.i(846193),b=e.i(978964),b=b,v=e.i(707387);let N=(0,r.default)("shield-check",[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]]),y=(0,r.default)("plug",[["path",{d:"M12 22v-5",key:"1ega77"}],["path",{d:"M15 8V2",key:"18g5xt"}],["path",{d:"M17 8a1 1 0 0 1 1 1v4a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1z",key:"1xoxul"}],["path",{d:"M9 8V2",key:"14iosj"}]]);var k=e.i(114507),k=k,w=e.i(263845),w=w,S=e.i(501253),S=S,C=e.i(482306),z=e.i(210162),z=z,L=e.i(101291),L=L;let A=(0,r.default)("folder-plus",[["path",{d:"M12 10v6",key:"1bos4e"}],["path",{d:"M9 13h6",key:"1uhe8q"}],["path",{d:"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z",key:"1kt360"}]]);var T=e.i(681711),T=T,B=e.i(99027);let M=(0,r.default)("save",[["path",{d:"M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z",key:"1c8476"}],["path",{d:"M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7",key:"1ydtos"}],["path",{d:"M7 3v4a1 1 0 0 0 1 1h7",key:"t51u73"}]]);var D=e.i(865373);let E=(0,r.default)("upload",[["path",{d:"M12 3v12",key:"1x0j5s"}],["path",{d:"m17 8-5-5-5 5",key:"7q97r8"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}]]);var O=e.i(666904),P=e.i(77982),I=e.i(634066),U=e.i(739963),$=e.i(555552);let _=e=>`${(0,U.getApiUrl)()}${e}`;async function R(e,s){return(await fetch(_(e),{...s,headers:{"Content-Type":"application/json",...s?.headers||{}}})).json()}let F=[{id:"general",label:"General",icon:j.Bot},{id:"integrations",label:"Integrations",icon:l},{id:"mcp",label:"MCP",icon:y},{id:"skills",label:"Skills",icon:L.default},{id:"schedules",label:"Schedules",icon:i},{id:"webhooks",label:"Webhooks",icon:o},{id:"audit",label:"Audit log",icon:k.default},{id:"models",label:"Models",icon:f},{id:"apikeys",label:"API Keys",icon:g.Key}];function J(){let[e,a]=(0,t.useState)("general"),[r,l]=(0,t.useState)(null);return(0,t.useEffect)(()=>{let e=!1;return(async()=>{try{let s,t=await fetch(_("/health"));if(!t.ok)return;let a=await t.json();try{let e=a&&a.config?.remoteServer?.url;if(e){let t=await fetch(`${e.replace(/\/$/,"")}/health`);t.ok&&(s=(await t.json()).version)}}catch{}e||l({version:a?.version,remoteVersion:s})}catch{}})(),()=>{e=!0}},[]),(0,s.jsxs)("div",{className:"h-full flex bg-background",children:[(0,s.jsxs)("nav",{className:"w-56 shrink-0 border-r border-border/60 p-3 overflow-y-auto",children:[(0,s.jsxs)("div",{className:"px-2 py-2 mb-2",children:[(0,s.jsx)("h1",{className:"text-base font-semibold",children:"Settings"}),(0,s.jsx)("p",{className:"text-[11px] text-muted-foreground mt-0.5",children:"Configure your orchestrator and channels."}),r?.version&&(0,s.jsxs)("p",{className:"text-[10px] text-muted-foreground/70 mt-1 font-mono",children:["sparkecoder v",r.version,r.remoteVersion&&(0,s.jsxs)(s.Fragment,{children:[" · remote v",r.remoteVersion]})]})]}),(0,s.jsx)("ul",{className:"space-y-0.5",children:F.map(t=>{let r=t.icon,l=e===t.id;return(0,s.jsx)("li",{children:(0,s.jsxs)("button",{type:"button",onClick:()=>a(t.id),className:(0,$.cn)("w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm transition-colors",l?"bg-accent text-foreground":"hover:bg-accent/60 text-foreground/80"),children:[(0,s.jsx)(r,{className:(0,$.cn)("size-4 shrink-0",l?"text-primary":"text-muted-foreground")}),(0,s.jsx)("span",{children:t.label})]})},t.id)})})]}),(0,s.jsx)("div",{className:"flex-1 min-w-0 overflow-y-auto",children:(0,s.jsxs)("div",{className:"max-w-3xl mx-auto p-6",children:["general"===e&&(0,s.jsx)(q,{}),"integrations"===e&&(0,s.jsx)(V,{}),"mcp"===e&&(0,s.jsx)(ed,{}),"schedules"===e&&(0,s.jsx)(ee,{}),"webhooks"===e&&(0,s.jsx)(ea,{}),"audit"===e&&(0,s.jsx)(eh,{}),"models"===e&&(0,s.jsx)(en,{}),"apikeys"===e&&(0,s.jsx)(ei,{}),"skills"===e&&(0,s.jsx)(ep,{})]})})]})}function H({title:e,description:t}){return(0,s.jsxs)("div",{className:"mb-4",children:[(0,s.jsx)("h2",{className:"text-lg font-semibold",children:e}),t&&(0,s.jsx)("p",{className:"text-sm text-muted-foreground mt-0.5",children:t})]})}function q(){let[e,r]=(0,t.useState)(null),[l,n]=(0,t.useState)(""),[i,o]=(0,t.useState)(""),[d,c]=(0,t.useState)(!1),[x,m]=(0,t.useState)(!1),u=(0,t.useCallback)(async()=>{let e=await R("/api/orchestrator");r(e),n(e.name||""),o(e.personality||"")},[]);(0,t.useEffect)(()=>{u()},[u]);let h=async()=>{c(!0),m(!1);try{await R("/api/orchestrator",{method:"PATCH",body:JSON.stringify({name:l||void 0,personality:i})}),m(!0),setTimeout(()=>m(!1),1500)}finally{c(!1)}};if(!e)return(0,s.jsx)("div",{className:"flex items-center justify-center py-12",children:(0,s.jsx)(a.Loader2,{className:"size-5 animate-spin text-muted-foreground"})});let p=l!==e.name||i!==(e.personality||"");return(0,s.jsxs)("section",{children:[(0,s.jsx)(H,{title:"Orchestrator",description:"Your supervisor agent's name and personality. The personality is appended to its system prompt."}),(0,s.jsxs)("div",{className:"space-y-4",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-sm font-medium",children:"Agent name"}),(0,s.jsx)("p",{className:"text-xs text-muted-foreground mb-1.5",children:"Shown in the sidebar and chat header."}),(0,s.jsx)(P.Input,{value:l,onChange:e=>n(e.target.value),placeholder:"Orchestrator",className:"max-w-md"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-sm font-medium",children:"Personality / persona"}),(0,s.jsxs)("p",{className:"text-xs text-muted-foreground mb-1.5",children:["Optional. Free-form instructions appended to your orchestrator's system prompt every turn. Examples: ",(0,s.jsx)("em",{children:'"speak in haiku"'}),", ",(0,s.jsx)("em",{children:'"be terse and professional"'}),", ",(0,s.jsx)("em",{children:'"respond in Korean"'}),",",(0,s.jsx)("em",{children:'"you are a senior engineer who values shipping over perfection"'}),"."]}),(0,s.jsx)("textarea",{value:i,onChange:e=>o(e.target.value),rows:6,placeholder:"(blank = default tone)",className:"w-full rounded-md border border-input bg-background px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-primary/40"})]}),(0,s.jsxs)("div",{className:"flex items-center gap-3 pt-1",children:[(0,s.jsxs)(O.Button,{onClick:h,disabled:!p||d,children:[d?(0,s.jsx)(a.Loader2,{className:"size-4 animate-spin mr-2"}):null,"Save"]}),x&&(0,s.jsx)("span",{className:"text-xs text-emerald-500",children:"Saved ✓"})]}),(0,s.jsxs)("div",{className:"rounded-md border border-border/40 bg-muted/30 p-3 text-xs space-y-1",children:[(0,s.jsxs)("p",{children:[(0,s.jsx)("span",{className:"text-muted-foreground",children:"Session id:"})," ",(0,s.jsx)("code",{children:e.id})]}),(0,s.jsxs)("p",{children:[(0,s.jsx)("span",{className:"text-muted-foreground",children:"Model:"})," ",(0,s.jsx)("code",{children:e.model})]}),(0,s.jsxs)("p",{children:[(0,s.jsx)("span",{className:"text-muted-foreground",children:"Working directory:"})," ",(0,s.jsx)("code",{className:"break-all",children:e.workingDirectory})]})]})]})]})}function V(){let[e,r]=(0,t.useState)(null),l=(0,t.useCallback)(async()=>{r(await R("/api/integrations"))},[]);return((0,t.useEffect)(()=>{l()},[l]),e)?(0,s.jsxs)("section",{className:"space-y-4",children:[(0,s.jsx)(H,{title:"Integrations",description:"External channels the orchestrator can send and receive messages on."}),(0,s.jsx)(K,{data:e,onChange:l}),(0,s.jsx)(Z,{data:e,onChange:l})]}):(0,s.jsx)("div",{className:"flex items-center justify-center py-12",children:(0,s.jsx)(a.Loader2,{className:"size-5 animate-spin text-muted-foreground"})})}function K({data:e,onChange:r}){var n;let i,[o,d]=(0,t.useState)(""),[c,x]=(0,t.useState)(""),[m,u]=(0,t.useState)(!1),[j,f]=(0,t.useState)(!1),[g,v]=(0,t.useState)(""),[y,k]=(0,t.useState)(null),[w,S]=(0,t.useState)(!1),C=async()=>{f(!0);try{await R("/api/integrations/slack",{method:"POST",body:JSON.stringify({botToken:o||void 0,signingSecret:c||void 0})}),d(""),x(""),r()}finally{f(!1)}},z=async()=>{confirm("Remove Slack credentials?")&&(await R("/api/integrations/slack",{method:"DELETE"}),r())},L=async()=>{if(!g)return;k("Sending…");let e=await R("/api/integrations/slack/test",{method:"POST",body:JSON.stringify({channel:g})});k(e.ok?"Sent.":`Failed: ${e.error}`)};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(em,{children:[(0,s.jsx)(eu,{icon:(0,s.jsx)(l,{className:"size-4 text-[#4A154B] dark:text-pink-300"}),title:"Slack",status:e.slack.configured?"Configured":"Not configured",statusTone:e.slack.configured?"text-emerald-500":"text-muted-foreground"}),(0,s.jsx)("p",{className:"text-xs text-muted-foreground mb-3",children:"Lets the orchestrator receive @mentions and DMs in Slack and reply in-thread via the messenger tool."}),(0,s.jsx)("div",{className:"flex gap-2 mb-3",children:(0,s.jsxs)(O.Button,{size:"sm",variant:"outline",onClick:()=>S(!0),children:[(0,s.jsx)(b.default,{className:"size-3.5 mr-1.5"}),"Setup instructions"]})}),(0,s.jsxs)("div",{className:"space-y-2",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Bot token (xoxb-...)"}),(0,s.jsxs)("div",{className:"flex gap-2",children:[(0,s.jsx)(P.Input,{type:m?"text":"password",value:o,onChange:e=>d(e.target.value),placeholder:e.slack.botTokenSet?"(set)":"xoxb-...",className:"text-xs"}),(0,s.jsx)(O.Button,{variant:"outline",size:"icon",onClick:()=>u(e=>!e),children:m?(0,s.jsx)(p.EyeOff,{className:"size-3.5"}):(0,s.jsx)(h.Eye,{className:"size-3.5"})})]})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Signing secret"}),(0,s.jsx)(P.Input,{type:"password",value:c,onChange:e=>x(e.target.value),placeholder:e.slack.signingSecretSet?"(set)":"...",className:"text-xs"})]}),(0,s.jsxs)("div",{className:"flex gap-2 pt-1",children:[(0,s.jsx)(O.Button,{size:"sm",onClick:C,disabled:j||!o&&!c,children:j?(0,s.jsx)(a.Loader2,{className:"size-3.5 animate-spin"}):"Save"}),e.slack.configured&&(0,s.jsx)(O.Button,{size:"sm",variant:"outline",onClick:z,children:"Disconnect"})]})]}),(0,s.jsxs)("div",{className:"mt-3 rounded border border-border/40 bg-background/50 p-2 text-xs space-y-1",children:[(0,s.jsxs)("p",{children:[(0,s.jsx)("strong",{children:"Events URL"})," (paste into Slack → Event Subscriptions):"]}),(0,s.jsx)("code",{className:"block px-2 py-1 rounded bg-muted text-[11px] break-all",children:e.slack.eventsUrl}),(0,s.jsxs)("p",{className:"text-muted-foreground",children:["Subscribe to bot events: ",(0,s.jsx)("code",{children:"app_mention"}),", ",(0,s.jsx)("code",{children:"message.im"}),"."]})]}),e.slack.configured&&(0,s.jsxs)("div",{className:"mt-3 flex gap-2 items-end",children:[(0,s.jsxs)("div",{className:"flex-1",children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Test post (channel id or #channel)"}),(0,s.jsx)(P.Input,{value:g,onChange:e=>v(e.target.value),placeholder:"C0123 or #general",className:"text-xs"})]}),(0,s.jsx)(O.Button,{size:"sm",variant:"outline",onClick:L,disabled:!g,children:"Send test"}),y&&(0,s.jsx)("span",{className:"text-xs text-muted-foreground",children:y})]})]}),(0,s.jsxs)(em,{children:[(0,s.jsx)(eu,{icon:(0,s.jsx)(N,{className:"size-4 text-amber-500"}),title:"Slack access control",status:(n=e.slack,i=[],n.allowedUsers.length>0&&i.push(`${n.allowedUsers.length} user(s)`),n.allowedChannels.length>0&&i.push(`${n.allowedChannels.length} channel(s)`),n.allowDmsFromAnyone||i.push("DMs locked"),0===i.length?"Open to anyone":i.join(" · ")),statusTone:"text-muted-foreground"}),(0,s.jsx)(W,{data:e,onChange:r})]}),w&&(0,s.jsx)(Y,{eventsUrl:e.slack.eventsUrl,agentName:e.slack.defaultOrchestratorName||"orchestrator",onClose:()=>S(!1)})]})}function W({data:e,onChange:r}){let[l,n]=(0,t.useState)(e.slack.allowedUsers.join(", ")),[i,o]=(0,t.useState)(e.slack.allowedChannels.join(", ")),[d,c]=(0,t.useState)(e.slack.allowDmsFromAnyone),[x,m]=(0,t.useState)(e.slack.deniedReplyEnabled),[u,h]=(0,t.useState)(e.slack.deniedReplyTemplate),[p,j]=(0,t.useState)(!1),[f,g]=(0,t.useState)(!1),b=l.trim()!==e.slack.allowedUsers.join(", ").trim()||i.trim()!==e.slack.allowedChannels.join(", ").trim()||d!==e.slack.allowDmsFromAnyone||x!==e.slack.deniedReplyEnabled||u!==e.slack.deniedReplyTemplate,v=async()=>{j(!0),g(!1);try{let e=e=>e.split(/[\s,]+/).map(e=>e.trim()).filter(Boolean);await R("/api/integrations/slack",{method:"POST",body:JSON.stringify({allowedUsers:e(l),allowedChannels:e(i),allowDmsFromAnyone:d,deniedReplyEnabled:x,deniedReplyTemplate:u})}),r(),g(!0),setTimeout(()=>g(!1),1500)}finally{j(!1)}};return(0,s.jsxs)("div",{className:"space-y-3",children:[(0,s.jsx)("p",{className:"text-xs text-muted-foreground",children:"Empty lists = no allowlist (accept anyone Slack delivers an event for). When a list is set, only matching users / channels can talk to the orchestrator."}),(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Allowed users (Slack user IDs)"}),(0,s.jsxs)("p",{className:"text-[11px] text-muted-foreground mb-1",children:["Comma or space separated. e.g. ",(0,s.jsx)("code",{children:"U01ABCD"}),", ",(0,s.jsx)("code",{children:"U09XYZ"}),". Leave blank to allow anyone."]}),(0,s.jsx)(P.Input,{value:l,onChange:e=>n(e.target.value),placeholder:"U01..., U02...",className:"text-xs font-mono"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Allowed channels (Slack channel IDs)"}),(0,s.jsxs)("p",{className:"text-[11px] text-muted-foreground mb-1",children:["Channel IDs only (not ",(0,s.jsx)("code",{children:"#name"}),"). e.g. ",(0,s.jsx)("code",{children:"C012345"}),". Affects @mentions; DMs use the toggle below."]}),(0,s.jsx)(P.Input,{value:i,onChange:e=>o(e.target.value),placeholder:"C012345, C098765",className:"text-xs font-mono"})]}),(0,s.jsxs)("label",{className:"flex items-start gap-2 cursor-pointer",children:[(0,s.jsx)("input",{type:"checkbox",checked:d,onChange:e=>c(e.target.checked),className:"mt-1"}),(0,s.jsxs)("div",{className:"text-xs",children:[(0,s.jsx)("span",{className:"font-medium",children:"Allow DMs from anyone"}),(0,s.jsx)("p",{className:"text-muted-foreground",children:"On: anyone who DMs your bot can talk to it. Off: only users in the allowlist above can DM."})]})]}),(0,s.jsxs)("div",{className:"border-t border-border/40 pt-3",children:[(0,s.jsxs)("label",{className:"flex items-start gap-2 cursor-pointer",children:[(0,s.jsx)("input",{type:"checkbox",checked:x,onChange:e=>m(e.target.checked),className:"mt-1"}),(0,s.jsxs)("div",{className:"text-xs",children:[(0,s.jsx)("span",{className:"font-medium",children:"Auto-reply to denied users"}),(0,s.jsx)("p",{className:"text-muted-foreground",children:"When someone is blocked by the rules above, post a canned reply (no AI) so they know why nothing happened. Posted in-thread for @mentions; in the DM for DMs."})]})]}),(0,s.jsxs)("div",{className:"mt-2 pl-6",children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Reply template"}),(0,s.jsxs)("p",{className:"text-[11px] text-muted-foreground mb-1",children:["Placeholders: ",(0,s.jsx)("code",{className:"px-1 rounded bg-muted",children:"{user}"})," → mentions the requester, ",(0,s.jsx)("code",{className:"px-1 rounded bg-muted",children:"{channel}"})," → mentions the channel."]}),(0,s.jsx)("textarea",{value:u,onChange:e=>h(e.target.value),rows:3,disabled:!x,className:"w-full rounded-md border border-input bg-background px-2 py-1.5 text-xs disabled:opacity-50"})]})]}),(0,s.jsxs)("div",{className:"flex items-center gap-2 pt-1",children:[(0,s.jsx)(O.Button,{size:"sm",onClick:v,disabled:!b||p,children:p?(0,s.jsx)(a.Loader2,{className:"size-3.5 animate-spin"}):"Save"}),f&&(0,s.jsx)("span",{className:"text-xs text-emerald-500",children:"Saved ✓"})]})]})}function Y({eventsUrl:e,agentName:a,onClose:r}){var n;let i,o,[d,c]=(0,t.useState)(null),x=(n=e,JSON.stringify({display_information:{name:o=(i=(a||"orchestrator").trim()||"orchestrator").slice(0,35),description:`${o} — your orchestrator agent. Spawns worker agents, posts updates, answers questions.`,background_color:"#1f1f24"},features:{bot_user:{display_name:i.replace(/[^A-Za-z0-9_-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,80)||"orchestrator",always_online:!0},app_home:{home_tab_enabled:!1,messages_tab_enabled:!0,messages_tab_read_only_enabled:!1}},oauth_config:{scopes:{bot:["app_mentions:read","channels:history","chat:write","groups:history","im:history","im:read","im:write","users:read"]}},settings:{event_subscriptions:{request_url:n,bot_events:["app_mention","message.im","message.channels","message.groups"]},interactivity:{is_enabled:!1},org_deploy_enabled:!1,socket_mode_enabled:!1,token_rotation_enabled:!1}},null,2)),m=async(e,s)=>{await navigator.clipboard.writeText(s),c(e),setTimeout(()=>c(null),1500)};return(0,s.jsx)("div",{className:"fixed inset-0 z-50 bg-black/60 backdrop-blur-sm flex items-center justify-center p-4",onClick:r,children:(0,s.jsxs)("div",{className:"bg-card rounded-lg border border-border max-w-2xl w-full max-h-[85vh] overflow-y-auto shadow-2xl",onClick:e=>e.stopPropagation(),children:[(0,s.jsxs)("div",{className:"flex items-center justify-between px-5 py-3 border-b border-border/60 sticky top-0 bg-card",children:[(0,s.jsxs)("h2",{className:"text-base font-semibold flex items-center gap-2",children:[(0,s.jsx)(l,{className:"size-4 text-[#4A154B] dark:text-pink-300"}),"Slack setup"]}),(0,s.jsx)(O.Button,{size:"icon",variant:"ghost",onClick:r,children:(0,s.jsx)(v.X,{className:"size-4"})})]}),(0,s.jsxs)("div",{className:"p-5 space-y-5 text-sm",children:[(0,s.jsxs)(G,{n:1,title:"Create a Slack app from manifest",children:[(0,s.jsxs)("p",{children:["Go to ",(0,s.jsx)("a",{href:"https://api.slack.com/apps?new_app=1",target:"_blank",rel:"noreferrer",className:"text-primary underline",children:"api.slack.com/apps"}),", click ",(0,s.jsx)("strong",{children:"Create New App"})," → ",(0,s.jsx)("strong",{children:"From a manifest"}),", pick your workspace, paste the YAML below, then create."]}),(0,s.jsx)(X,{label:"Slack manifest",value:x,onCopy:()=>m("manifest",x),copied:"manifest"===d})]}),(0,s.jsxs)(G,{n:2,title:"Grab credentials",children:[(0,s.jsxs)("p",{children:["In your new app's ",(0,s.jsx)("strong",{children:"Basic Information"})," page, copy the ",(0,s.jsx)("strong",{children:"Signing Secret"}),". Then under ",(0,s.jsx)("strong",{children:"OAuth & Permissions"}),", install the app to your workspace and copy the ",(0,s.jsx)("strong",{children:"Bot User OAuth Token"})," (starts with ",(0,s.jsx)("code",{children:"xoxb-"}),")."]}),(0,s.jsx)("p",{className:"text-xs text-muted-foreground",children:"Paste both into the Slack card above, then click Save."})]}),(0,s.jsxs)(G,{n:3,title:"Verify the Events URL",children:[(0,s.jsxs)("p",{children:["Slack will challenge the URL automatically when you create the app from the manifest (it's pre-filled with your URL below). If it shows ",(0,s.jsx)("strong",{children:"Verified"})," you're done."]}),(0,s.jsx)(X,{label:"Events URL (already in the manifest)",value:e,onCopy:()=>m("eventsUrl",e),copied:"eventsUrl"===d,mono:!0}),(0,s.jsx)("p",{className:"text-[11px] text-muted-foreground",children:"If verification fails, your sparkecoder isn't reachable from the public internet. Set up a cloudflared tunnel — see the Cloudflared card."})]}),(0,s.jsx)(G,{n:4,title:"Invite the bot to channels",children:(0,s.jsxs)("p",{children:["In Slack: ",(0,s.jsx)("code",{children:"/invite @your-bot-name"})," in any channel you want it to respond in. (DMs work without an invite.)"]})}),(0,s.jsxs)(G,{n:5,title:"Lock it down (optional but recommended)",children:[(0,s.jsxs)("p",{children:["By default ",(0,s.jsx)("strong",{children:"anyone"})," in your workspace can DM the bot or mention it in any channel it's in. Use the ",(0,s.jsx)("strong",{children:"Slack access control"})," section to limit access to specific Slack user IDs and channel IDs."]}),(0,s.jsx)("p",{className:"text-[11px] text-muted-foreground",children:"Find a user ID: click their name in Slack → ⋮ → Copy member ID. Find a channel ID: click the channel name → About → bottom of the panel."})]})]})]})})}function G({n:e,title:t,children:a}){return(0,s.jsxs)("div",{className:"space-y-2",children:[(0,s.jsxs)("h3",{className:"font-medium flex items-center gap-2",children:[(0,s.jsx)("span",{className:"inline-flex items-center justify-center size-5 rounded-full bg-primary/15 text-primary text-[11px] font-semibold",children:e}),t]}),(0,s.jsx)("div",{className:"pl-7 space-y-2 text-sm text-foreground/90",children:a})]})}function X({label:e,value:t,onCopy:a,copied:r,mono:l}){return(0,s.jsxs)("div",{className:"rounded border border-border/60 bg-background/60 overflow-hidden",children:[(0,s.jsxs)("div",{className:"px-3 py-1.5 flex items-center justify-between border-b border-border/40 bg-muted/40",children:[(0,s.jsx)("span",{className:"text-[11px] text-muted-foreground",children:e}),(0,s.jsxs)(O.Button,{size:"sm",variant:"ghost",onClick:a,className:"h-6 text-[11px]",children:[r?(0,s.jsx)(u.Check,{className:"size-3 mr-1"}):(0,s.jsx)(d.Copy,{className:"size-3 mr-1"}),r?"Copied":"Copy"]})]}),(0,s.jsx)("pre",{className:(0,$.cn)("px-3 py-2 text-[11px] overflow-x-auto whitespace-pre-wrap break-all",l&&"font-mono"),children:t})]})}function Z({data:e,onChange:t}){return(0,s.jsxs)(em,{children:[(0,s.jsx)(eu,{icon:(0,s.jsx)(n,{className:"size-4 text-orange-500"}),title:"Cloudflared tunnel",status:e.cloudflared.publicBaseUrl?"Hostname set":"Local-only",statusTone:e.cloudflared.publicBaseUrl?"text-emerald-500":"text-muted-foreground"}),(0,s.jsxs)("p",{className:"text-xs text-muted-foreground mb-2",children:["Required to receive Slack events and webhook hits from the public internet. Run"," ",(0,s.jsx)("code",{className:"px-1 rounded bg-muted text-[11px]",children:"sparkecoder cloudflared-setup"})," for a copy-paste recipe."]}),e.cloudflared.publicBaseUrl&&(0,s.jsxs)("div",{className:"text-xs",children:[(0,s.jsx)("span",{className:"text-muted-foreground",children:"Public base: "}),(0,s.jsx)("code",{className:"px-1 rounded bg-muted",children:e.cloudflared.publicBaseUrl})]}),(0,s.jsx)("div",{className:"mt-3 border-t border-border/40 pt-3",children:(0,s.jsx)(Q,{data:e,onChange:t})})]})}function Q({data:e,onChange:r}){let[l,n]=(0,t.useState)(e.cfAccess.enabled),[i,o]=(0,t.useState)(e.cfAccess.teamDomain??""),[d,c]=(0,t.useState)(e.cfAccess.audTag??""),[x,m]=(0,t.useState)(e.cfAccess.allowedEmails.join(", ")),[u,h]=(0,t.useState)(!1),[p,j]=(0,t.useState)(!1),[f,g]=(0,t.useState)(null),b=e.cfAccess.allowedEmails.join(", "),v=l!==e.cfAccess.enabled||i.trim()!==(e.cfAccess.teamDomain??"")||d.trim()!==(e.cfAccess.audTag??"")||x.trim()!==b.trim(),y=async()=>{h(!0),j(!1),g(null);try{let e=await fetch(_("/api/integrations/cf-access"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled:l,teamDomain:i.trim(),audTag:d.trim(),allowedEmails:x.split(/[\s,]+/).map(e=>e.trim().toLowerCase()).filter(Boolean)})});if(!e.ok){let s=await e.json().catch(()=>({}));throw Error(s?.error||`Request failed (${e.status})`)}r(),j(!0),setTimeout(()=>j(!1),1500)}catch(e){g(e instanceof Error?e.message:"Failed to save")}finally{h(!1)}};return(0,s.jsxs)("div",{className:"space-y-3",children:[(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)(N,{className:"size-3.5 text-emerald-500"}),(0,s.jsx)("span",{className:"text-xs font-medium",children:"Cloudflare Access"})]}),(0,s.jsx)("p",{className:"text-xs text-muted-foreground",children:"Restrict who can reach your public tunnel URL by email. Localhost always bypasses this, so you can't lock yourself out of your own machine."}),(0,s.jsxs)("label",{className:"flex items-start gap-2 cursor-pointer",children:[(0,s.jsx)("input",{type:"checkbox",checked:l,onChange:e=>n(e.target.checked),className:"mt-1"}),(0,s.jsxs)("div",{className:"text-xs",children:[(0,s.jsx)("span",{className:"font-medium",children:"Enforce Cloudflare Access"}),(0,s.jsx)("p",{className:"text-muted-foreground",children:"When on, every non-local request must carry a valid Cloudflare Access JWT whose email is in the allowlist below."})]})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Team domain"}),(0,s.jsxs)("p",{className:"text-[11px] text-muted-foreground mb-1",children:["e.g. ",(0,s.jsx)("code",{children:"yourteam.cloudflareaccess.com"})]}),(0,s.jsx)(P.Input,{value:i,onChange:e=>o(e.target.value),placeholder:"yourteam.cloudflareaccess.com",className:"text-xs font-mono"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Application AUD tag"}),(0,s.jsx)("p",{className:"text-[11px] text-muted-foreground mb-1",children:"From the Cloudflare Access application (Access → Applications)."}),(0,s.jsx)(P.Input,{value:d,onChange:e=>c(e.target.value),placeholder:"abc123...",className:"text-xs font-mono"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Allowed emails or domains"}),(0,s.jsxs)("p",{className:"text-[11px] text-muted-foreground mb-1",children:["Comma or space separated. Use a full address (",(0,s.jsx)("code",{children:"you@example.com"}),") or a bare domain (",(0,s.jsx)("code",{children:"studyfetch.com"}),") to allow everyone on it. Leave blank to allow any email that passes your Cloudflare Access policy."]}),(0,s.jsx)(P.Input,{value:x,onChange:e=>m(e.target.value),placeholder:"studyfetch.com, you@example.com",className:"text-xs font-mono"})]}),(0,s.jsx)("p",{className:"text-[11px] text-muted-foreground",children:"Heads up: this is one of two layers. You must also add the same emails to an “Allow” policy in the Cloudflare dashboard — that's the real gate."}),f&&(0,s.jsx)("p",{className:"text-xs text-red-500",children:f}),(0,s.jsxs)("div",{className:"flex items-center gap-2 pt-1",children:[(0,s.jsx)(O.Button,{size:"sm",onClick:y,disabled:!v||u,children:u?(0,s.jsx)(a.Loader2,{className:"size-3.5 animate-spin"}):"Save"}),p&&(0,s.jsx)("span",{className:"text-xs text-emerald-500",children:"Saved ✓"})]})]})}function ee(){let[e,a]=(0,t.useState)([]),[r,l]=(0,t.useState)(!1),n=(0,t.useCallback)(async()=>{a((await R("/api/schedules")).schedules||[])},[]);return(0,t.useEffect)(()=>{n()},[n]),(0,s.jsxs)("section",{children:[(0,s.jsxs)("div",{className:"flex items-center justify-between mb-3",children:[(0,s.jsx)(H,{title:"Schedules",description:"Recurring prompts that wake the orchestrator on a cron."}),(0,s.jsxs)(O.Button,{size:"sm",variant:"outline",onClick:()=>l(e=>!e),children:[(0,s.jsx)(m.Plus,{className:"size-3.5 mr-1"}),"New"]})]}),r&&(0,s.jsx)(es,{onCancel:()=>l(!1),onSaved:()=>{l(!1),n()}}),0===e.length?(0,s.jsx)("p",{className:"text-xs text-muted-foreground italic px-1",children:"No schedules yet."}):(0,s.jsx)("div",{className:"space-y-2",children:e.map(e=>(0,s.jsx)(et,{row:e,onChange:n},e.id))})]})}function es({initial:e,onCancel:r,onSaved:l}){let[n,i]=(0,t.useState)(e?.name||""),[o,d]=(0,t.useState)(e?.cron||"0 9 * * 1-5"),[c,x]=(0,t.useState)(e?.prompt||""),[m,u]=(0,t.useState)(e?.replyChannel||""),[h,p]=(0,t.useState)(!1),j=async()=>{if(n&&o&&c){p(!0);try{e?.id?await R(`/api/schedules/${e.id}`,{method:"PATCH",body:JSON.stringify({name:n,cron:o,prompt:c,replyChannel:m||void 0})}):await R("/api/schedules",{method:"POST",body:JSON.stringify({name:n,cron:o,prompt:c,replyChannel:m||void 0})}),l()}finally{p(!1)}}};return(0,s.jsxs)("div",{className:"rounded-lg border border-border/60 bg-card/40 p-3 mb-2 space-y-2",children:[(0,s.jsxs)("div",{className:"grid grid-cols-2 gap-2",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Name"}),(0,s.jsx)(P.Input,{value:n,onChange:e=>i(e.target.value),placeholder:"standup-9am",className:"text-xs"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Cron (5 fields)"}),(0,s.jsx)(P.Input,{value:o,onChange:e=>d(e.target.value),placeholder:"0 9 * * 1-5",className:"text-xs font-mono"})]})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Prompt"}),(0,s.jsx)(P.Input,{value:c,onChange:e=>x(e.target.value),placeholder:"Summarize yesterday's git activity",className:"text-xs"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Default reply channel (optional)"}),(0,s.jsx)(P.Input,{value:m,onChange:e=>u(e.target.value),placeholder:"slack:#standup",className:"text-xs"})]}),(0,s.jsxs)("div",{className:"flex gap-2 pt-1",children:[(0,s.jsx)(O.Button,{size:"sm",onClick:j,disabled:h||!n||!o||!c,children:h?(0,s.jsx)(a.Loader2,{className:"size-3.5 animate-spin"}):"Save"}),(0,s.jsx)(O.Button,{size:"sm",variant:"ghost",onClick:r,children:"Cancel"})]})]})}function et({row:e,onChange:t}){let a=async()=>{await R(`/api/schedules/${e.id}`,{method:"PATCH",body:JSON.stringify({enabled:!e.enabled})}),t()},r=async()=>{confirm(`Delete schedule "${e.name}"?`)&&(await R(`/api/schedules/${e.id}`,{method:"DELETE"}),t())};return(0,s.jsxs)("div",{className:"rounded-md border border-border/60 bg-background/50 p-3 flex items-start gap-3",children:[(0,s.jsxs)("div",{className:"flex-1 min-w-0",children:[(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)("span",{className:"text-sm font-medium",children:e.name}),!e.enabled&&(0,s.jsx)("span",{className:"text-[10px] uppercase tracking-wide text-muted-foreground",children:"paused"})]}),(0,s.jsx)("code",{className:"text-[11px] text-muted-foreground font-mono",children:e.cron}),(0,s.jsx)("p",{className:"text-xs mt-1 line-clamp-2 text-foreground/80",children:e.prompt}),e.replyChannel&&(0,s.jsxs)("p",{className:"text-[10px] text-muted-foreground mt-0.5",children:["replyChannel: ",e.replyChannel]}),e.lastFiredAt&&(0,s.jsxs)("p",{className:"text-[10px] text-muted-foreground mt-0.5",children:["last fired ",new Date(e.lastFiredAt).toLocaleString()]})]}),(0,s.jsxs)("div",{className:"flex flex-col gap-1",children:[(0,s.jsx)(O.Button,{size:"sm",variant:"outline",onClick:a,children:e.enabled?"Pause":"Resume"}),(0,s.jsx)(O.Button,{size:"sm",variant:"ghost",onClick:r,className:"text-rose-500 hover:text-rose-600",children:(0,s.jsx)(x.Trash2,{className:"size-3.5"})})]})]})}function ea(){let[e,a]=(0,t.useState)([]),[r,l]=(0,t.useState)(!1),n=(0,t.useCallback)(async()=>{a((await R("/api/webhooks")).webhooks||[])},[]);return(0,t.useEffect)(()=>{n()},[n]),(0,s.jsxs)("section",{children:[(0,s.jsxs)("div",{className:"flex items-center justify-between mb-3",children:[(0,s.jsx)(H,{title:"Webhooks",description:"Custom token-protected URLs. POST any JSON to ping the orchestrator."}),(0,s.jsxs)(O.Button,{size:"sm",variant:"outline",onClick:()=>l(e=>!e),children:[(0,s.jsx)(m.Plus,{className:"size-3.5 mr-1"}),"New"]})]}),r&&(0,s.jsx)(er,{onCancel:()=>l(!1),onSaved:()=>{l(!1),n()}}),0===e.length?(0,s.jsx)("p",{className:"text-xs text-muted-foreground italic px-1",children:"No webhooks yet."}):(0,s.jsx)("div",{className:"space-y-2",children:e.map(e=>(0,s.jsx)(el,{row:e,onChange:n},e.id))})]})}function er({onCancel:e,onSaved:r}){let[l,n]=(0,t.useState)(""),[i,o]=(0,t.useState)("now"),[d,c]=(0,t.useState)(""),[x,m]=(0,t.useState)(!1),u=async()=>{if(l){m(!0);try{await R("/api/webhooks",{method:"POST",body:JSON.stringify({name:l,wake:i,template:d||void 0})}),r()}finally{m(!1)}}};return(0,s.jsxs)("div",{className:"rounded-lg border border-border/60 bg-card/40 p-3 mb-2 space-y-2",children:[(0,s.jsxs)("div",{className:"grid grid-cols-2 gap-2",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Name"}),(0,s.jsx)(P.Input,{value:l,onChange:e=>n(e.target.value),placeholder:"github-prs",className:"text-xs"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Wake"}),(0,s.jsxs)("select",{value:i,onChange:e=>o(e.target.value),className:"w-full text-xs h-9 rounded-md border border-input bg-background px-2",children:[(0,s.jsx)("option",{value:"now",children:"now — ping orchestrator immediately"}),(0,s.jsx)("option",{value:"next",children:"next — add as context, fire later"})]})]})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Template (optional)"}),(0,s.jsx)(P.Input,{value:d,onChange:e=>c(e.target.value),placeholder:"PR {{pull_request.title}} by {{sender.login}}",className:"text-xs font-mono"})]}),(0,s.jsxs)("div",{className:"flex gap-2 pt-1",children:[(0,s.jsx)(O.Button,{size:"sm",onClick:u,disabled:x||!l,children:x?(0,s.jsx)(a.Loader2,{className:"size-3.5 animate-spin"}):"Create"}),(0,s.jsx)(O.Button,{size:"sm",variant:"ghost",onClick:e,children:"Cancel"})]})]})}function el({row:e,onChange:a}){let[r,l]=(0,t.useState)(!1),n=async()=>{await navigator.clipboard.writeText(e.url),l(!0),setTimeout(()=>l(!1),1500)},i=async()=>{confirm("Rotate the token?")&&(await R(`/api/webhooks/${e.id}`,{method:"PATCH",body:JSON.stringify({rotateToken:!0})}),a())},o=async()=>{confirm(`Delete webhook "${e.name}"?`)&&(await R(`/api/webhooks/${e.id}`,{method:"DELETE"}),a())};return(0,s.jsxs)("div",{className:"rounded-md border border-border/60 bg-background/50 p-3 space-y-2",children:[(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)("span",{className:"text-sm font-medium",children:e.name}),(0,s.jsxs)("span",{className:"text-[10px] uppercase tracking-wide text-muted-foreground",children:["wake=",e.wake]}),(0,s.jsxs)("span",{className:"ml-auto text-[11px] text-muted-foreground",children:[e.hitCount," hit",1===e.hitCount?"":"s"]})]}),(0,s.jsxs)("div",{className:"flex gap-1 items-center",children:[(0,s.jsx)("code",{className:"flex-1 text-[11px] px-2 py-1 rounded bg-muted break-all",children:e.url}),(0,s.jsx)(O.Button,{size:"icon",variant:"outline",onClick:n,children:r?(0,s.jsx)(u.Check,{className:"size-3.5"}):(0,s.jsx)(d.Copy,{className:"size-3.5"})}),(0,s.jsx)(O.Button,{size:"icon",variant:"outline",onClick:i,title:"Rotate token",children:(0,s.jsx)(c,{className:"size-3.5"})}),(0,s.jsx)(O.Button,{size:"icon",variant:"ghost",onClick:o,className:"text-rose-500 hover:text-rose-600",children:(0,s.jsx)(x.Trash2,{className:"size-3.5"})})]}),e.template&&(0,s.jsxs)("p",{className:"text-[10px] text-muted-foreground font-mono",children:["template: ",e.template]}),e.lastHitAt&&(0,s.jsxs)("p",{className:"text-[10px] text-muted-foreground",children:["last hit ",new Date(e.lastHitAt).toLocaleString()]})]})}function en(){let[e,r]=(0,t.useState)(null);if((0,t.useEffect)(()=>{(async()=>{r(await (0,U.getConfig)())})()},[]),!e)return(0,s.jsx)("div",{className:"flex items-center justify-center py-12",children:(0,s.jsx)(a.Loader2,{className:"size-5 animate-spin text-muted-foreground"})});let l=e.availableModels.find(s=>s.id===e.defaultModel);return(0,s.jsxs)("section",{children:[(0,s.jsx)(H,{title:"Models",description:"Default model used for new sessions (orchestrator + workers). You can override per-session from the chat header."}),(0,s.jsxs)("div",{className:"rounded-lg border border-border/60 bg-card/40 p-3 max-w-lg",children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Current default"}),(0,s.jsxs)("p",{className:"text-sm mt-1",children:[(0,s.jsx)("strong",{children:l?.name||e.defaultModel}),l?.provider&&(0,s.jsxs)("span",{className:"text-muted-foreground",children:[" — ",l.provider]})]}),(0,s.jsxs)("p",{className:"text-[11px] text-muted-foreground mt-2",children:["To change the default, set ",(0,s.jsx)("code",{className:"px-1 rounded bg-muted",children:"defaultModel"})," in your"," ",(0,s.jsx)("code",{className:"px-1 rounded bg-muted",children:"sparkecoder.config.json"})," and restart the server."]})]}),(0,s.jsxs)("div",{className:"mt-4",children:[(0,s.jsx)(I.Label,{className:"text-xs uppercase tracking-wide text-muted-foreground",children:"Available models"}),(0,s.jsx)("div",{className:"mt-2 rounded-lg border border-border/60 divide-y divide-border/40",children:e.availableModels.map(t=>(0,s.jsxs)("div",{className:(0,$.cn)("flex items-center justify-between px-3 py-2 text-sm",t.id===e.defaultModel&&"bg-primary/5"),children:[(0,s.jsxs)("div",{children:[(0,s.jsx)("span",{className:"font-medium",children:t.name}),(0,s.jsxs)("span",{className:"text-muted-foreground",children:[" — ",t.provider]}),t.id===e.defaultModel&&(0,s.jsx)("span",{className:"ml-2 text-[10px] uppercase tracking-wide text-primary",children:"default"})]}),(0,s.jsx)("code",{className:"text-[10px] text-muted-foreground font-mono",children:t.id})]},t.id))})]})]})}function ei(){let[e,a]=(0,t.useState)([]),r=(0,t.useCallback)(async()=>{a((await R("/health/api-keys")).providers||[])},[]);return(0,t.useEffect)(()=>{r()},[r]),(0,s.jsxs)("section",{children:[(0,s.jsx)(H,{title:"API Keys",description:"Provider credentials used for AI inference (Vercel AI Gateway etc.). Keys are stored locally."}),(0,s.jsxs)("div",{className:"space-y-2",children:[e.map(e=>(0,s.jsx)(eo,{keyStatus:e,onChange:r},e.provider)),0===e.length&&(0,s.jsx)("p",{className:"text-xs text-muted-foreground italic px-1",children:"No providers configured."})]})]})}function eo({keyStatus:e,onChange:r}){let[l,n]=(0,t.useState)(!1),[i,o]=(0,t.useState)(""),[d,c]=(0,t.useState)(!1),[m,u]=(0,t.useState)(!1),j=async()=>{if(i.trim()){u(!0);try{await R("/health/api-keys",{method:"POST",body:JSON.stringify({provider:e.provider,apiKey:i.trim()})}),o(""),n(!1),r()}finally{u(!1)}}},f=async()=>{confirm(`Remove ${e.provider} API key?`)&&(await fetch(_(`/health/api-keys/${e.provider}`),{method:"DELETE"}),r())};return(0,s.jsxs)("div",{className:"rounded-md border border-border/60 bg-background/50 p-3",children:[(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)("span",{className:"text-sm font-medium capitalize",children:e.provider}),e.configured&&(0,s.jsx)("span",{className:(0,$.cn)("text-[10px] px-1.5 py-0.5 rounded font-medium","env"===e.source?"bg-blue-500/20 text-blue-600 dark:text-blue-300":"bg-emerald-500/20 text-emerald-600 dark:text-emerald-300"),children:"env"===e.source?"from env":"saved"}),(0,s.jsx)("span",{className:"ml-auto text-[10px] text-muted-foreground font-mono",children:e.envVar})]}),l?(0,s.jsxs)("div",{className:"flex gap-2 mt-2",children:[(0,s.jsxs)("div",{className:"relative flex-1",children:[(0,s.jsx)(P.Input,{type:d?"text":"password",value:i,onChange:e=>o(e.target.value),placeholder:"Paste your API key…",className:"pr-8 text-xs font-mono",autoFocus:!0}),(0,s.jsx)(O.Button,{type:"button",variant:"ghost",size:"icon",className:"absolute right-0 top-0 h-full px-2 hover:bg-transparent",onClick:()=>c(e=>!e),children:d?(0,s.jsx)(p.EyeOff,{className:"size-3.5"}):(0,s.jsx)(h.Eye,{className:"size-3.5"})})]}),(0,s.jsx)(O.Button,{size:"sm",onClick:j,disabled:m||!i.trim(),children:m?(0,s.jsx)(a.Loader2,{className:"size-3.5 animate-spin"}):"Save"}),(0,s.jsx)(O.Button,{size:"sm",variant:"ghost",onClick:()=>{n(!1),o("")},children:"Cancel"})]}):(0,s.jsxs)("div",{className:"flex items-center mt-1",children:[(0,s.jsx)("p",{className:"text-[11px] text-muted-foreground font-mono flex-1",children:e.configured?e.maskedKey:"Not configured"}),(0,s.jsx)(O.Button,{size:"sm",variant:"ghost",onClick:()=>n(!0),className:"h-7 text-xs",children:e.configured?"Update":"Add"}),e.configured&&"storage"===e.source&&(0,s.jsx)(O.Button,{size:"icon",variant:"ghost",onClick:f,className:"text-rose-500 hover:text-rose-600",children:(0,s.jsx)(x.Trash2,{className:"size-3.5"})})]})]})}function ed(){let[e,r]=(0,t.useState)([]),[l,n]=(0,t.useState)(!1),[i,o]=(0,t.useState)(!0),d=(0,t.useCallback)(async()=>{o(!0);try{let e=await R("/api/mcp");r(e.servers||[])}finally{o(!1)}},[]);return(0,t.useEffect)(()=>{d()},[d]),(0,s.jsxs)("section",{children:[(0,s.jsxs)("div",{className:"flex items-center justify-between mb-3",children:[(0,s.jsx)(H,{title:"MCP integrations",description:'Model Context Protocol servers. Each adds tools to every agent under "mcp_<name>_<tool>" on the next turn.'}),(0,s.jsxs)(O.Button,{size:"sm",variant:"outline",onClick:()=>n(e=>!e),children:[(0,s.jsx)(m.Plus,{className:"size-3.5 mr-1"}),"New"]})]}),(0,s.jsxs)("p",{className:"text-[11px] text-muted-foreground mb-3 max-w-2xl",children:["For OAuth-protected MCP servers, paste the access token as a header here:",(0,s.jsx)("code",{className:"px-1 rounded bg-muted ml-1",children:"{ Authorization: 'Bearer <your-token>' }"}),". For unauthed servers, leave headers empty. Use ",(0,s.jsx)("strong",{children:"Test"})," to confirm the server is reachable."]}),l&&(0,s.jsx)(ec,{onCancel:()=>n(!1),onSaved:()=>{n(!1),d()}}),i?(0,s.jsx)("div",{className:"flex items-center justify-center py-8",children:(0,s.jsx)(a.Loader2,{className:"size-4 animate-spin text-muted-foreground"})}):0===e.length?(0,s.jsx)("p",{className:"text-xs text-muted-foreground italic px-1",children:"No MCP servers configured."}):(0,s.jsx)("div",{className:"space-y-2",children:e.map(e=>(0,s.jsx)(ex,{row:e,onChange:d},e.id))})]})}function ec({initial:e,onCancel:r,onSaved:l}){let[n,i]=(0,t.useState)(e?.name||""),[o,d]=(0,t.useState)(e?.transport||"http"),[c,x]=(0,t.useState)(e?.url||""),[m,u]=(0,t.useState)(e?.headers?JSON.stringify(e.headers,null,2):""),[h,p]=(0,t.useState)(e?.command||""),[j,f]=(0,t.useState)(e?.args?.join(" ")||""),[g,b]=(0,t.useState)(!1),[v,N]=(0,t.useState)(null),y=async()=>{let s;if(N(null),m.trim())try{let e=JSON.parse(m);e&&"object"==typeof e&&(s=e)}catch{N('Headers must be valid JSON, e.g. {"Authorization":"Bearer xxx"}');return}b(!0);try{let t={name:n,transport:o,enabled:!0};("http"===o||"sse"===o)&&(t.url=c,s&&(t.headers=s)),"stdio"===o&&(t.command=h,t.args=j.split(/\s+/).filter(Boolean)),e?.id?await R(`/api/mcp/${e.id}`,{method:"PATCH",body:JSON.stringify(t)}):await R("/api/mcp",{method:"POST",body:JSON.stringify(t)}),l()}catch(e){N(e?.message||"Save failed")}finally{b(!1)}};return(0,s.jsxs)("div",{className:"rounded-lg border border-border/60 bg-card/40 p-3 mb-3 space-y-2",children:[(0,s.jsxs)("div",{className:"grid grid-cols-3 gap-2",children:[(0,s.jsxs)("div",{className:"col-span-2",children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Name"}),(0,s.jsx)(P.Input,{value:n,onChange:e=>i(e.target.value),placeholder:"github",className:"text-xs"}),(0,s.jsxs)("p",{className:"text-[10px] text-muted-foreground mt-0.5",children:["Used as a tool prefix (e.g. ",(0,s.jsx)("code",{children:"mcp_github_search"}),")."]})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Transport"}),(0,s.jsxs)("select",{value:o,onChange:e=>d(e.target.value),className:"w-full text-xs h-9 rounded-md border border-input bg-background px-2",children:[(0,s.jsx)("option",{value:"http",children:"HTTP (recommended)"}),(0,s.jsx)("option",{value:"sse",children:"SSE"}),(0,s.jsx)("option",{value:"stdio",children:"stdio (local)"})]})]})]}),"stdio"!==o?(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"URL"}),(0,s.jsx)(P.Input,{value:c,onChange:e=>x(e.target.value),placeholder:"https://your-server.com/mcp",className:"text-xs font-mono"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Headers (JSON, optional)"}),(0,s.jsx)("textarea",{value:m,onChange:e=>u(e.target.value),rows:3,placeholder:'{ "Authorization": "Bearer xxx" }',className:"w-full rounded-md border border-input bg-background px-2 py-1.5 text-xs font-mono"})]})]}):(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Command"}),(0,s.jsx)(P.Input,{value:h,onChange:e=>p(e.target.value),placeholder:"node",className:"text-xs font-mono"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Args (space separated)"}),(0,s.jsx)(P.Input,{value:j,onChange:e=>f(e.target.value),placeholder:"src/stdio/dist/server.js",className:"text-xs font-mono"})]})]}),v&&(0,s.jsx)("p",{className:"text-[11px] text-rose-500",children:v}),(0,s.jsxs)("div",{className:"flex gap-2 pt-1",children:[(0,s.jsx)(O.Button,{size:"sm",onClick:y,disabled:g||!n||("stdio"!==o?!c:!h),children:g?(0,s.jsx)(a.Loader2,{className:"size-3.5 animate-spin"}):"Save"}),(0,s.jsx)(O.Button,{size:"sm",variant:"ghost",onClick:r,children:"Cancel"})]})]})}function ex({row:e,onChange:r}){let[l,n]=(0,t.useState)(null),[i,o]=(0,t.useState)(!1),[d,c]=(0,t.useState)(!1),m=async()=>{await R(`/api/mcp/${e.id}`,{method:"PATCH",body:JSON.stringify({enabled:!e.enabled})}),r()},u=async()=>{confirm(`Delete MCP server "${e.name}"?`)&&(await R(`/api/mcp/${e.id}`,{method:"DELETE"}),r())},h=async()=>{o(!0),n(null);try{let s=await R(`/api/mcp/${e.id}/test`,{method:"POST"});n(s)}finally{o(!1)}};return d?(0,s.jsx)(ec,{initial:e,onCancel:()=>c(!1),onSaved:()=>{c(!1),r()}}):(0,s.jsxs)("div",{className:"rounded-md border border-border/60 bg-background/50 p-3",children:[(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)(y,{className:(0,$.cn)("size-4",e.enabled?"text-emerald-500":"text-muted-foreground")}),(0,s.jsx)("span",{className:"text-sm font-medium",children:e.name}),(0,s.jsx)("span",{className:"text-[10px] uppercase tracking-wide text-muted-foreground",children:e.transport}),!e.enabled&&(0,s.jsx)("span",{className:"text-[10px] uppercase tracking-wide text-muted-foreground",children:"disabled"}),(0,s.jsxs)("span",{className:"ml-auto text-[11px] text-muted-foreground font-mono break-all",children:["tool prefix: ",(0,s.jsxs)("code",{children:["mcp_",e.name,"_*"]})]})]}),(0,s.jsxs)("p",{className:"text-[11px] text-muted-foreground mt-1 break-all",children:["stdio"===e.transport?(0,s.jsxs)(s.Fragment,{children:["cmd: ",(0,s.jsxs)("code",{children:[e.command," ",e.args?.join(" ")]})]}):(0,s.jsxs)(s.Fragment,{children:["url: ",(0,s.jsx)("code",{children:e.url})]}),e.headers&&Object.keys(e.headers).length>0&&(0,s.jsxs)("span",{className:"ml-2",children:["headers: ",Object.entries(e.headers).map(([e,s])=>`${e}: ${s}`).join(", ")]})]}),(0,s.jsxs)("div",{className:"flex gap-1 mt-2",children:[(0,s.jsxs)(O.Button,{size:"sm",variant:"outline",onClick:h,disabled:i,children:[i?(0,s.jsx)(a.Loader2,{className:"size-3 animate-spin mr-1"}):null,"Test"]}),(0,s.jsx)(O.Button,{size:"sm",variant:"outline",onClick:m,children:e.enabled?"Disable":"Enable"}),(0,s.jsx)(O.Button,{size:"sm",variant:"ghost",onClick:()=>c(!0),children:"Edit"}),(0,s.jsx)(O.Button,{size:"sm",variant:"ghost",onClick:u,className:"text-rose-500 hover:text-rose-600 ml-auto",children:(0,s.jsx)(x.Trash2,{className:"size-3.5"})})]}),l&&(0,s.jsx)("div",{className:(0,$.cn)("mt-2 rounded border p-2 text-[11px]",l.ok?"border-emerald-500/40 bg-emerald-500/5":"border-rose-500/40 bg-rose-500/5"),children:l.ok?(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)("p",{className:"text-emerald-500",children:["✓ Connected — ",l.toolCount," tool(s) discovered"]}),l.toolNames&&l.toolNames.length>0&&(0,s.jsx)("p",{className:"text-muted-foreground font-mono mt-0.5 break-all",children:l.toolNames.join(", ")})]}):(0,s.jsxs)("p",{className:"text-rose-500",children:["✗ ",l.error]})})]})}function em({children:e}){return(0,s.jsx)("div",{className:"rounded-lg border border-border/60 bg-card/40 p-4",children:e})}function eu({icon:e,title:t,status:a,statusTone:r}){return(0,s.jsxs)("div",{className:"flex items-center gap-2 mb-3",children:[e,(0,s.jsx)("h3",{className:"font-medium",children:t}),(0,s.jsx)("span",{className:(0,$.cn)("ml-auto text-xs",r),children:a})]})}function eh(){let[e,a]=(0,t.useState)([]),[r,l]=(0,t.useState)(0),[n,i]=(0,t.useState)(!0),[o,d]=(0,t.useState)(""),[c,m]=(0,t.useState)(""),[u,h]=(0,t.useState)(""),[p,j]=(0,t.useState)(""),[f,g]=(0,t.useState)(0),[b]=(0,t.useState)(50),[v,N]=(0,t.useState)(null);(0,t.useEffect)(()=>{let e=setTimeout(()=>{j(u),g(0)},250);return()=>clearTimeout(e)},[u]);let y=(0,t.useRef)(!1),k=(0,t.useCallback)(async()=>{y.current||i(!0);try{let e=new URLSearchParams;o&&e.set("source",o),c&&e.set("status",c),p&&e.set("q",p),e.set("limit",String(b)),e.set("offset",String(f*b));let s=await fetch(`${(0,U.getApiUrl)()}/api/integrations/events?${e.toString()}`);if(s.ok){let e=await s.json();a(e.events),l(e.total)}}finally{y.current||i(!1),y.current=!1}},[o,c,p,f,b]);(0,t.useEffect)(()=>{k()},[k]),(0,t.useEffect)(()=>{if(0!==f||o||c||p)return;let e=setInterval(()=>{y.current=!0,k()},5e3);return()=>clearInterval(e)},[f,o,c,p,k]);let L=async()=>{confirm("Clear ALL audit log entries? This cannot be undone.")&&(await fetch(`${(0,U.getApiUrl)()}/api/integrations/events`,{method:"DELETE"}),g(0),k())},A=Math.max(1,Math.ceil(r/b));return(0,s.jsxs)("div",{className:"space-y-3",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)("h2",{className:"text-base font-semibold",children:"Audit log"}),(0,s.jsx)("p",{className:"text-xs text-muted-foreground mt-0.5",children:'Every incoming Slack event, inbox webhook, schedule firing, and orchestrator turn outcome lands here. Use it to debug "why didn\'t the agent respond?".'})]}),(0,s.jsxs)("div",{className:"flex flex-wrap items-center gap-2 p-2 rounded-md border border-border/40 bg-card/40",children:[(0,s.jsxs)("div",{className:"flex items-center gap-1",children:[(0,s.jsx)(z.default,{className:"size-3.5 text-muted-foreground"}),(0,s.jsx)(P.Input,{value:u,onChange:e=>h(e.target.value),placeholder:"search channel, user, text, reason…",className:"h-7 w-56 text-xs"})]}),(0,s.jsxs)("select",{value:o,onChange:e=>{d(e.target.value),g(0)},className:"h-7 px-2 rounded text-xs bg-background border border-border/60",children:[(0,s.jsx)("option",{value:"",children:"all sources"}),(0,s.jsx)("option",{value:"slack",children:"slack"}),(0,s.jsx)("option",{value:"inbox",children:"inbox"}),(0,s.jsx)("option",{value:"schedule",children:"schedule"}),(0,s.jsx)("option",{value:"webhook",children:"webhook"}),(0,s.jsx)("option",{value:"daemon",children:"daemon"})]}),(0,s.jsxs)("select",{value:c,onChange:e=>{m(e.target.value),g(0)},className:"h-7 px-2 rounded text-xs bg-background border border-border/60",children:[(0,s.jsx)("option",{value:"",children:"all statuses"}),(0,s.jsx)("option",{value:"routed",children:"routed"}),(0,s.jsx)("option",{value:"dropped",children:"dropped"}),(0,s.jsx)("option",{value:"error",children:"error"}),(0,s.jsx)("option",{value:"completed",children:"completed"}),(0,s.jsx)("option",{value:"failed",children:"failed"}),(0,s.jsx)("option",{value:"received",children:"received"})]}),(0,s.jsx)(O.Button,{size:"sm",variant:"ghost",onClick:()=>void k(),className:"h-7 px-2",children:(0,s.jsx)(C.RefreshCw,{className:"size-3.5"})}),(0,s.jsx)("span",{className:"text-xs text-muted-foreground ml-auto",children:n?"loading…":`${r} event${1===r?"":"s"}`}),(0,s.jsx)(O.Button,{size:"sm",variant:"ghost",onClick:()=>void L(),className:"h-7 px-2 text-destructive",children:(0,s.jsx)(x.Trash2,{className:"size-3.5"})})]}),(0,s.jsxs)("div",{className:"rounded-md border border-border/40 bg-card/40 overflow-hidden",children:[0===e.length&&!n&&(0,s.jsx)("p",{className:"p-4 text-xs text-muted-foreground text-center",children:"No events match the current filters."}),e.map(e=>(0,s.jsxs)("button",{type:"button",onClick:()=>{window.getSelection()?.toString()||N(s=>s===e.id?null:e.id)},className:"w-full text-left px-3 py-2 border-b border-border/30 last:border-b-0 hover:bg-accent/40 transition-colors",children:[(0,s.jsxs)("div",{className:"flex items-center gap-2 text-xs",children:[(0,s.jsx)("span",{className:"font-mono text-muted-foreground tabular-nums whitespace-nowrap",title:new Date(e.ts).toLocaleString(),children:function(e){let s=new Date(e);if(isNaN(s.getTime()))return e;let t=new Date,a=s.toDateString()===t.toDateString(),r=s.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1});if(a)return r;let l=s.toLocaleDateString([],{month:"short",day:"numeric"});return`${l} ${r}`}(e.ts)}),(0,s.jsx)(eb,{status:e.status}),(0,s.jsx)("span",{className:"font-medium text-muted-foreground uppercase text-[10px] tracking-wide",children:e.source}),e.subtype&&(0,s.jsx)("span",{className:"text-[10px] text-muted-foreground/70",children:e.subtype}),e.channel&&(0,s.jsx)("span",{className:"font-mono text-[10px] text-muted-foreground/80 truncate max-w-[160px]",children:e.channel}),e.user&&(0,s.jsxs)("span",{className:"font-mono text-[10px] text-muted-foreground/80 truncate max-w-[120px]",children:["@",e.user]}),void 0!==e.durationMs&&(0,s.jsxs)("span",{className:"text-[10px] text-muted-foreground/70",children:[(e.durationMs/1e3).toFixed(1),"s"]}),(0,s.jsx)("span",{className:"ml-auto text-[10px] text-muted-foreground/60 font-mono",children:e.id.slice(0,8)})]}),e.textSnippet&&(0,s.jsx)("p",{className:"mt-1 text-xs text-foreground/80 truncate",children:e.textSnippet}),e.dropReason&&(0,s.jsxs)("p",{className:"mt-1 text-[11px] text-yellow-500/90",children:["dropped: ",e.dropReason]}),e.error&&(0,s.jsxs)("p",{className:"mt-1 text-[11px] text-red-500/90",children:["error: ",e.error]}),v===e.id&&(0,s.jsx)("pre",{className:"mt-2 p-2 rounded bg-background/60 text-[10px] font-mono text-muted-foreground overflow-x-auto whitespace-pre-wrap break-all",children:JSON.stringify(e,null,2)})]},e.id))]}),r>b&&(0,s.jsxs)("div",{className:"flex items-center justify-between text-xs",children:[(0,s.jsxs)("span",{className:"text-muted-foreground",children:["Page ",f+1," of ",A]}),(0,s.jsxs)("div",{className:"flex items-center gap-1",children:[(0,s.jsx)(O.Button,{size:"sm",variant:"ghost",disabled:0===f,onClick:()=>g(0),className:"h-7 px-2",children:"First"}),(0,s.jsx)(O.Button,{size:"sm",variant:"ghost",disabled:0===f,onClick:()=>g(e=>e-1),className:"h-7 px-2",children:(0,s.jsx)(w.default,{className:"size-3.5"})}),(0,s.jsx)(O.Button,{size:"sm",variant:"ghost",disabled:f>=A-1,onClick:()=>g(e=>e+1),className:"h-7 px-2",children:(0,s.jsx)(S.default,{className:"size-3.5"})})]})]})]})}function ep(){let[e,r]=(0,t.useState)(null),[l,n]=(0,t.useState)(!0),[i,o]=(0,t.useState)(""),[d,c]=(0,t.useState)(null),[u,h]=(0,t.useState)(null),[p,j]=(0,t.useState)(!1),[f,g]=(0,t.useState)(null),b=(0,t.useCallback)(async()=>{n(!0);try{r(await R("/api/skills"))}finally{n(!1)}},[]);if((0,t.useEffect)(()=>{b()},[b]),l||!e)return(0,s.jsx)("div",{className:"flex items-center justify-center py-12",children:(0,s.jsx)(a.Loader2,{className:"size-5 animate-spin text-muted-foreground"})});let v=e.directories.map(s=>({dir:s,items:e.skills.filter(e=>e.sourceDir===s.path).filter(e=>!i||e.name.toLowerCase().includes(i.toLowerCase())||e.description.toLowerCase().includes(i.toLowerCase()))})),N=async(e,s)=>{if(e&&0!==e.length){for(let t of Array.from(e)){if(!/\.(md|mdc|markdown|txt)$/i.test(t.name))continue;let e=await t.text();try{await R("/api/skills",{method:"POST",body:JSON.stringify({directory:s,fileName:t.name,content:e})})}catch(e){console.error("skill upload failed",e)}}b()}};return(0,s.jsxs)("section",{children:[(0,s.jsxs)("div",{className:"flex items-center justify-between mb-3",children:[(0,s.jsx)(H,{title:"Skills",description:"Markdown skill files the orchestrator loads on demand. Pulled from built-in defaults plus any directories below — drop .md files in or create new ones from here."}),(0,s.jsxs)("div",{className:"flex gap-2",children:[(0,s.jsxs)(O.Button,{size:"sm",variant:"outline",onClick:()=>j(!0),children:[(0,s.jsx)(A,{className:"size-3.5 mr-1"}),"Add folder"]}),(0,s.jsx)(O.Button,{size:"sm",variant:"ghost",onClick:b,children:(0,s.jsx)(C.RefreshCw,{className:"size-3.5"})})]})]}),(0,s.jsx)("div",{className:"mb-3",children:(0,s.jsxs)("div",{className:"relative max-w-sm",children:[(0,s.jsx)(z.default,{className:"absolute left-2 top-1/2 -translate-y-1/2 size-3.5 text-muted-foreground"}),(0,s.jsx)(P.Input,{value:i,onChange:e=>o(e.target.value),placeholder:"Search skills…",className:"pl-7 h-8 text-xs"})]})}),p&&(0,s.jsx)(ej,{onCancel:()=>j(!1),onSaved:()=>{j(!1),b()}}),(0,s.jsx)("div",{className:"space-y-3",children:v.map(({dir:e,items:t})=>(0,s.jsxs)("div",{onDragOver:s=>{e.writable&&(s.preventDefault(),g(e.path))},onDragLeave:()=>g(s=>s===e.path?null:s),onDrop:s=>{s.preventDefault(),g(null),e.writable&&N(s.dataTransfer.files,e.path)},className:(0,$.cn)("rounded-lg border bg-card/40 p-3 transition-colors",f===e.path?"border-primary bg-primary/5":"border-border/60"),children:[(0,s.jsxs)("div",{className:"flex items-center gap-2 mb-2",children:[(0,s.jsx)(B.FolderOpen,{className:(0,$.cn)("size-4",e.alwaysApply?"text-amber-500":"text-muted-foreground")}),(0,s.jsx)("span",{className:"text-sm font-medium",children:e.label}),(0,s.jsx)("span",{className:"text-[10px] uppercase tracking-wide text-muted-foreground",children:e.source}),e.alwaysApply&&(0,s.jsx)("span",{className:"text-[10px] uppercase tracking-wide text-amber-500",children:"always-on"}),!e.exists&&(0,s.jsx)("span",{className:"text-[10px] text-rose-500",children:"missing"}),(0,s.jsx)("code",{className:"ml-auto text-[10px] text-muted-foreground/70 font-mono truncate max-w-[280px]",children:e.path}),e.writable&&(0,s.jsxs)(O.Button,{size:"sm",variant:"ghost",className:"h-6 px-2",onClick:()=>h(e.path),children:[(0,s.jsx)(m.Plus,{className:"size-3 mr-1"}),"New"]}),"additional"===e.source&&(0,s.jsx)(O.Button,{size:"sm",variant:"ghost",className:"h-6 px-1 text-rose-500",onClick:async()=>{confirm(`Stop loading skills from ${e.path}?`)&&(await R(`/api/skills/directories?path=${encodeURIComponent(e.path)}`,{method:"DELETE"}),b())},children:(0,s.jsx)(x.Trash2,{className:"size-3"})})]}),u===e.path&&(0,s.jsx)(ef,{directory:e.path,onCancel:()=>h(null),onSaved:()=>{h(null),b()}}),0===t.length?(0,s.jsx)("p",{className:"text-[11px] text-muted-foreground italic px-1",children:e.writable?"No skills here yet — drop .md files or click New.":"No skills here."}):(0,s.jsx)("div",{className:"divide-y divide-border/30",children:t.map(e=>(0,s.jsxs)("button",{type:"button",onClick:()=>c(e),className:"w-full flex items-start gap-2 px-1 py-2 text-left hover:bg-accent/40 rounded transition-colors",children:[(0,s.jsx)(T.default,{className:"size-3.5 text-muted-foreground shrink-0 mt-0.5"}),(0,s.jsxs)("div",{className:"flex-1 min-w-0",children:[(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)("span",{className:"text-xs font-medium truncate",children:e.name}),(0,s.jsx)("code",{className:"text-[10px] text-muted-foreground font-mono truncate",children:e.fileName}),e.alwaysApply&&(0,s.jsx)("span",{className:"text-[10px] text-amber-500 uppercase tracking-wide",children:"always"}),e.globs.length>0&&(0,s.jsx)("span",{className:"text-[10px] text-muted-foreground",children:e.globs.join(", ")})]}),e.description&&(0,s.jsx)("p",{className:"text-[11px] text-muted-foreground line-clamp-2 mt-0.5",children:e.description})]}),(0,s.jsx)(D.Pencil,{className:"size-3 text-muted-foreground/60 mt-1"})]},e.id))})]},e.path))}),d&&(0,s.jsx)(eg,{skill:d,onClose:()=>c(null),onSaved:()=>{c(null),b()}})]})}function ej({onCancel:e,onSaved:r}){let[l,n]=(0,t.useState)(""),[i,o]=(0,t.useState)(!1),[d,c]=(0,t.useState)(null),x=(0,t.useRef)(null),m=async()=>{if(c(null),l.trim()){o(!0);try{let e=await R("/api/skills/directories",{method:"POST",body:JSON.stringify({path:l.trim()})});if(e.error)return void c(e.error);r()}finally{o(!1)}}};return(0,s.jsxs)("div",{className:"rounded-lg border border-border/60 bg-card/40 p-3 mb-3 space-y-2",children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Directory path"}),(0,s.jsxs)("p",{className:"text-[11px] text-muted-foreground",children:["Absolute path (recommended) or a path relative to the orchestrator's working directory. All ",(0,s.jsx)("code",{children:".md"})," / ",(0,s.jsx)("code",{children:".mdc"})," files inside are picked up automatically."]}),(0,s.jsxs)("div",{className:"flex gap-2",children:[(0,s.jsx)(P.Input,{value:l,onChange:e=>n(e.target.value),placeholder:"/Users/you/skills or ./my-skills",className:"text-xs font-mono"}),(0,s.jsxs)(O.Button,{size:"sm",variant:"outline",onClick:()=>x.current?.click(),title:"Pick a folder to suggest its name (browser security: you'll still need to paste the absolute path)",children:[(0,s.jsx)(B.FolderOpen,{className:"size-3.5 mr-1"}),"Browse"]}),(0,s.jsx)("input",{ref:x,type:"file",webkitdirectory:"",directory:"",multiple:!0,hidden:!0,onChange:e=>{let s=e.target.files;if(!s||0===s.length)return;let t=s[0],a=(t.webkitRelativePath||t.name).split("/")[0];l||n(a)}})]}),d&&(0,s.jsx)("p",{className:"text-[11px] text-rose-500",children:d}),(0,s.jsxs)("div",{className:"flex gap-2 pt-1",children:[(0,s.jsx)(O.Button,{size:"sm",onClick:m,disabled:i||!l.trim(),children:i?(0,s.jsx)(a.Loader2,{className:"size-3.5 animate-spin"}):"Add folder"}),(0,s.jsx)(O.Button,{size:"sm",variant:"ghost",onClick:e,children:"Cancel"})]})]})}function ef({directory:e,onCancel:r,onSaved:l}){let[n,i]=(0,t.useState)(""),[o,d]=(0,t.useState)(""),[c,x]=(0,t.useState)(""),[m,u]=(0,t.useState)(""),[h,p]=(0,t.useState)(!1),[j,f]=(0,t.useState)(null),g=(0,t.useRef)(null),b=async()=>{f(null);let s=n.trim()||(o?o.toLowerCase().replace(/[^a-z0-9]+/g,"-")+".md":"untitled.md");p(!0);try{let t,a=await R("/api/skills",{method:"POST",body:JSON.stringify({directory:e,fileName:s,content:(t=["---"],o&&t.push(`name: ${o}`),c&&t.push(`description: ${c.replace(/\n/g," ")}`),t.push("---",""),t.join("\n")+(m||`# ${o||"New skill"}
|
|
2
|
+
|
|
3
|
+
Describe what this skill does and when to use it.`))})});if(a.error)return void f(a.error);l()}finally{p(!1)}},v=async e=>{let s=e.target.files;if(!s||!s[0])return;let t=s[0];i(t.name),u(await t.text())};return(0,s.jsxs)("div",{className:"rounded-md border border-border/60 bg-background/50 p-3 mb-2 space-y-2",children:[(0,s.jsxs)("div",{className:"grid grid-cols-2 gap-2",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Skill name"}),(0,s.jsx)(P.Input,{value:o,onChange:e=>d(e.target.value),placeholder:"React Performance",className:"text-xs"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"File name"}),(0,s.jsx)(P.Input,{value:n,onChange:e=>i(e.target.value),placeholder:"react-performance.md",className:"text-xs font-mono"})]})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Description"}),(0,s.jsx)(P.Input,{value:c,onChange:e=>x(e.target.value),placeholder:"When to use this skill (one sentence).",className:"text-xs"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(I.Label,{className:"text-xs",children:"Body (markdown)"}),(0,s.jsx)("textarea",{value:m,onChange:e=>u(e.target.value),rows:6,placeholder:"# How to do X Step 1…",className:"w-full rounded-md border border-input bg-background px-2 py-1.5 text-xs font-mono"})]}),j&&(0,s.jsx)("p",{className:"text-[11px] text-rose-500",children:j}),(0,s.jsxs)("div",{className:"flex gap-2 pt-1",children:[(0,s.jsx)(O.Button,{size:"sm",onClick:b,disabled:h,children:h?(0,s.jsx)(a.Loader2,{className:"size-3.5 animate-spin"}):(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(M,{className:"size-3.5 mr-1"}),"Save"]})}),(0,s.jsxs)(O.Button,{size:"sm",variant:"outline",onClick:()=>g.current?.click(),children:[(0,s.jsx)(E,{className:"size-3.5 mr-1"}),"Import .md"]}),(0,s.jsx)("input",{ref:g,type:"file",hidden:!0,accept:".md,.mdc,.markdown,.txt",onChange:v}),(0,s.jsx)(O.Button,{size:"sm",variant:"ghost",onClick:r,children:"Cancel"})]})]})}function eg({skill:e,onClose:r,onSaved:l}){let[n,i]=(0,t.useState)(null),[o,d]=(0,t.useState)(!1),[c,m]=(0,t.useState)(!1),[u,h]=(0,t.useState)(!1),[p,j]=(0,t.useState)("");(0,t.useEffect)(()=>{let s=!1;return(async()=>{let t=await R(`/api/skills/${e.id}`);s||(i(t.content),j(t.content),d(t.writable))})(),()=>{s=!0}},[e.id]);let f=null!==n&&n!==p,g=async()=>{if(null!==n&&o){m(!0);try{await R(`/api/skills/${e.id}`,{method:"PUT",body:JSON.stringify({content:n})}),j(n),h(!0),setTimeout(()=>h(!1),1200)}finally{m(!1)}}},b=async()=>{confirm(`Delete skill "${e.name}"? This removes the file from disk.`)&&(await R(`/api/skills/${e.id}`,{method:"DELETE"}),l())};return(0,s.jsx)("div",{className:"fixed inset-0 z-50 bg-black/60 backdrop-blur-sm flex items-center justify-center p-4",onClick:r,children:(0,s.jsxs)("div",{className:"bg-card rounded-lg border border-border max-w-3xl w-full max-h-[85vh] flex flex-col shadow-2xl",onClick:e=>e.stopPropagation(),children:[(0,s.jsxs)("div",{className:"flex items-center justify-between px-5 py-3 border-b border-border/60",children:[(0,s.jsxs)("div",{className:"min-w-0",children:[(0,s.jsxs)("h2",{className:"text-base font-semibold flex items-center gap-2 truncate",children:[(0,s.jsx)(T.default,{className:"size-4 text-muted-foreground"}),e.name,!o&&(0,s.jsx)("span",{className:"text-[10px] uppercase tracking-wide text-muted-foreground",children:"read-only"})]}),(0,s.jsx)("code",{className:"text-[11px] text-muted-foreground font-mono truncate block",children:e.filePath})]}),(0,s.jsx)(O.Button,{size:"icon",variant:"ghost",onClick:r,children:(0,s.jsx)(v.X,{className:"size-4"})})]}),(0,s.jsx)("div",{className:"flex-1 min-h-0 overflow-hidden flex flex-col p-4",children:null===n?(0,s.jsx)("div",{className:"flex items-center justify-center flex-1",children:(0,s.jsx)(a.Loader2,{className:"size-5 animate-spin text-muted-foreground"})}):(0,s.jsx)("textarea",{value:n,onChange:e=>i(e.target.value),readOnly:!o,spellCheck:!1,className:"flex-1 w-full rounded-md border border-input bg-background px-3 py-2 text-xs font-mono resize-none focus:outline-none focus:ring-2 focus:ring-primary/40"})}),(0,s.jsxs)("div",{className:"flex items-center gap-2 px-5 py-3 border-t border-border/60",children:[o&&(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(O.Button,{size:"sm",onClick:g,disabled:!f||c,children:[c?(0,s.jsx)(a.Loader2,{className:"size-3.5 animate-spin mr-1"}):(0,s.jsx)(M,{className:"size-3.5 mr-1"}),"Save"]}),u&&(0,s.jsx)("span",{className:"text-xs text-emerald-500",children:"Saved ✓"}),(0,s.jsxs)(O.Button,{size:"sm",variant:"ghost",onClick:b,className:"text-rose-500 hover:text-rose-600 ml-auto",children:[(0,s.jsx)(x.Trash2,{className:"size-3.5 mr-1"}),"Delete"]})]}),!o&&(0,s.jsx)("p",{className:"text-[11px] text-muted-foreground",children:"Built-in skills can't be edited from the UI. Copy the contents into a custom folder to override."})]})]})})}function eb({status:e}){return(0,s.jsx)("span",{className:(0,$.cn)("inline-flex items-center px-1.5 py-0.5 rounded text-[10px] border",{routed:"bg-emerald-500/15 text-emerald-400 border-emerald-500/30",completed:"bg-emerald-500/15 text-emerald-400 border-emerald-500/30",dropped:"bg-yellow-500/15 text-yellow-400 border-yellow-500/30",received:"bg-blue-500/15 text-blue-400 border-blue-500/30",error:"bg-red-500/15 text-red-400 border-red-500/30",failed:"bg-red-500/15 text-red-400 border-red-500/30"}[e]),children:e})}e.s(["default",()=>J],412490)}]);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/fef07dbb0973bf53-s.518e079e.woff2)format("woff2");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/8a480f0b521d4e75-s.ea323500.woff2)format("woff2");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/53b9e256198e5412-s.853d50a3.woff2)format("woff2");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/7178b3e590c64307-s.55554cd0.woff2)format("woff2");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/caa3a2e1cccd8315-s.p.3b6cae6d.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Geist Fallback;src:local(Arial);ascent-override:95.94%;descent-override:28.16%;line-gap-override:0.0%;size-adjust:104.76%}.geist_a71539c9-module__T19VSG__className{font-family:Geist,Geist Fallback;font-style:normal}.geist_a71539c9-module__T19VSG__variable{--font-geist-sans:"Geist","Geist Fallback"}
|
|
2
|
-
@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/5ce348bf30bf5439-s.
|
|
2
|
+
@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/5ce348bf30bf5439-s.56c1f21e.woff2)format("woff2");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/4fa387ec64143e14-s.3b336396.woff2)format("woff2");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/6306c77e7c8268e4-s.e3369375.woff2)format("woff2");unicode-range:U+2000-2001,U+2004-2008,U+200A,U+23B8-23BD,U+2500-259F}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/7d817b4c03b0c5f1-s.a40b9a8b.woff2)format("woff2");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/bbc41e54d2fcbd21-s.fe42ddf4.woff2)format("woff2");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/797e433ab948586e-s.p.29207c2f.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Geist Mono Fallback;src:local(Arial);ascent-override:74.67%;descent-override:21.92%;line-gap-override:0.0%;size-adjust:134.59%}.geist_mono_8d43a2aa-module__8Li5zG__className{font-family:Geist Mono,Geist Mono Fallback;font-style:normal}.geist_mono_8d43a2aa-module__8Li5zG__variable{--font-geist-mono:"Geist Mono","Geist Mono Fallback"}
|