vskill 1.0.16 → 1.0.19
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 +27 -3
- package/agents.json +3 -1
- package/dist/agents/agents-registry.d.ts +61 -0
- package/dist/agents/agents-registry.js +203 -0
- package/dist/agents/agents-registry.js.map +1 -1
- package/dist/api/client.d.ts +85 -0
- package/dist/api/client.js +193 -24
- package/dist/api/client.js.map +1 -1
- package/dist/bin.js +0 -0
- package/dist/commands/add.d.ts +7 -0
- package/dist/commands/add.js +94 -1
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/auth.d.ts +23 -0
- package/dist/commands/auth.js +136 -12
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/eval/serve.d.ts +2 -0
- package/dist/commands/eval/serve.js +126 -4
- package/dist/commands/eval/serve.js.map +1 -1
- package/dist/commands/orgs.d.ts +21 -0
- package/dist/commands/orgs.js +164 -0
- package/dist/commands/orgs.js.map +1 -0
- package/dist/commands/skill.js +14 -1
- package/dist/commands/skill.js.map +1 -1
- package/dist/commands/whoami.d.ts +29 -0
- package/dist/commands/whoami.js +119 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/eval/anthropic-catalog.js +32 -2
- package/dist/eval/anthropic-catalog.js.map +1 -1
- package/dist/eval/batch-judge.js +1 -0
- package/dist/eval/batch-judge.js.map +1 -1
- package/dist/eval/llm.d.ts +1 -1
- package/dist/eval/llm.js +104 -2
- package/dist/eval/llm.js.map +1 -1
- package/dist/eval-server/__tests__/helpers/studio-token-test-helpers.d.ts +2 -0
- package/dist/eval-server/__tests__/helpers/studio-token-test-helpers.js +20 -0
- package/dist/eval-server/__tests__/helpers/studio-token-test-helpers.js.map +1 -0
- package/dist/eval-server/active-root-store.d.ts +19 -0
- package/dist/eval-server/active-root-store.js +50 -0
- package/dist/eval-server/active-root-store.js.map +1 -0
- package/dist/eval-server/active-tenant-routes.d.ts +15 -0
- package/dist/eval-server/active-tenant-routes.js +101 -0
- package/dist/eval-server/active-tenant-routes.js.map +1 -0
- package/dist/eval-server/api-routes.d.ts +1 -1
- package/dist/eval-server/api-routes.js +60 -7
- package/dist/eval-server/api-routes.js.map +1 -1
- package/dist/eval-server/authoring-routes.d.ts +1 -1
- package/dist/eval-server/authoring-routes.js +9 -7
- package/dist/eval-server/authoring-routes.js.map +1 -1
- package/dist/eval-server/desktop-open-routes.d.ts +8 -0
- package/dist/eval-server/desktop-open-routes.js +64 -0
- package/dist/eval-server/desktop-open-routes.js.map +1 -0
- package/dist/eval-server/detect-engines-route.d.ts +1 -1
- package/dist/eval-server/detect-engines-route.js +3 -1
- package/dist/eval-server/detect-engines-route.js.map +1 -1
- package/dist/eval-server/eval-server.js +108 -29
- package/dist/eval-server/eval-server.js.map +1 -1
- package/dist/eval-server/export-skill-routes.d.ts +9 -0
- package/dist/eval-server/export-skill-routes.js +81 -0
- package/dist/eval-server/export-skill-routes.js.map +1 -0
- package/dist/eval-server/git-routes.d.ts +7 -6
- package/dist/eval-server/git-routes.js +123 -15
- package/dist/eval-server/git-routes.js.map +1 -1
- package/dist/eval-server/improve-routes.d.ts +1 -1
- package/dist/eval-server/improve-routes.js +4 -1
- package/dist/eval-server/improve-routes.js.map +1 -1
- package/dist/eval-server/install-engine-routes.d.ts +4 -17
- package/dist/eval-server/install-engine-routes.js +10 -125
- package/dist/eval-server/install-engine-routes.js.map +1 -1
- package/dist/eval-server/install-jobs.d.ts +41 -0
- package/dist/eval-server/install-jobs.js +161 -0
- package/dist/eval-server/install-jobs.js.map +1 -0
- package/dist/eval-server/install-skill-routes.d.ts +74 -11
- package/dist/eval-server/install-skill-routes.js +508 -79
- package/dist/eval-server/install-skill-routes.js.map +1 -1
- package/dist/eval-server/install-state-routes.d.ts +5 -1
- package/dist/eval-server/install-state-routes.js +18 -2
- package/dist/eval-server/install-state-routes.js.map +1 -1
- package/dist/eval-server/integration-routes.d.ts +1 -1
- package/dist/eval-server/integration-routes.js +6 -1
- package/dist/eval-server/integration-routes.js.map +1 -1
- package/dist/eval-server/model-compare-routes.d.ts +1 -1
- package/dist/eval-server/model-compare-routes.js +3 -1
- package/dist/eval-server/model-compare-routes.js.map +1 -1
- package/dist/eval-server/oauth-github-routes.d.ts +2 -0
- package/dist/eval-server/oauth-github-routes.js +505 -0
- package/dist/eval-server/oauth-github-routes.js.map +1 -0
- package/dist/eval-server/platform-proxy.d.ts +22 -1
- package/dist/eval-server/platform-proxy.js +183 -22
- package/dist/eval-server/platform-proxy.js.map +1 -1
- package/dist/eval-server/plugin-cli-routes.d.ts +1 -1
- package/dist/eval-server/plugin-cli-routes.js +19 -10
- package/dist/eval-server/plugin-cli-routes.js.map +1 -1
- package/dist/eval-server/remove-skill-routes.d.ts +18 -0
- package/dist/eval-server/remove-skill-routes.js +147 -0
- package/dist/eval-server/remove-skill-routes.js.map +1 -0
- package/dist/eval-server/router.d.ts +17 -3
- package/dist/eval-server/router.js +166 -9
- package/dist/eval-server/router.js.map +1 -1
- package/dist/eval-server/settings-store.js +1 -1
- package/dist/eval-server/settings-store.js.map +1 -1
- package/dist/eval-server/skill-create-routes.d.ts +1 -1
- package/dist/eval-server/skill-create-routes.js +8 -1
- package/dist/eval-server/skill-create-routes.js.map +1 -1
- package/dist/eval-server/supported-agents-routes.d.ts +6 -0
- package/dist/eval-server/supported-agents-routes.js +41 -0
- package/dist/eval-server/supported-agents-routes.js.map +1 -0
- package/dist/eval-server/sweep-routes.d.ts +1 -1
- package/dist/eval-server/sweep-routes.js +5 -1
- package/dist/eval-server/sweep-routes.js.map +1 -1
- package/dist/eval-server/utils/spawn-env.d.ts +1 -0
- package/dist/eval-server/utils/spawn-env.js +47 -0
- package/dist/eval-server/utils/spawn-env.js.map +1 -0
- package/dist/eval-server/workspace-routes.d.ts +12 -0
- package/dist/eval-server/workspace-routes.js +57 -2
- package/dist/eval-server/workspace-routes.js.map +1 -1
- package/dist/eval-ui/assets/AdvancedTab-DOgbx7u0.js +1 -0
- package/dist/eval-ui/assets/{CreateSkillPage-CvdYq8Rr.js → CreateSkillPage-Cv93Croj.js} +3 -3
- package/dist/eval-ui/assets/FindSkillsPalette-BY9DAhHh.js +2 -0
- package/dist/eval-ui/assets/GeneralTab-AwK9sIkP.js +1 -0
- package/dist/eval-ui/assets/PrivacyTab-BtNrxpVV.js +1 -0
- package/dist/eval-ui/assets/{SearchPaletteCore-Bf3PBC64.js → SearchPaletteCore-DMVcq7UB.js} +2 -2
- package/dist/eval-ui/assets/SkillDetailPanel-B_lbhK6q.js +1 -0
- package/dist/eval-ui/assets/UpdateDropdown-4AbjZLpq.js +1 -0
- package/dist/eval-ui/assets/UpdatesTab-DTmo-vVb.js +1 -0
- package/dist/eval-ui/assets/core-DZjBCfjp.js +1 -0
- package/dist/eval-ui/assets/event-QtOCMXAv.js +1 -0
- package/dist/eval-ui/assets/globals-Dpf9KmYH.css +1 -0
- package/dist/eval-ui/assets/globals-hm1COkXX.js +49 -0
- package/dist/eval-ui/assets/index-CUEYzTVL.js +1 -0
- package/dist/eval-ui/assets/index-DDNzcrhv.js +1 -0
- package/dist/eval-ui/assets/index-DhhmQddr.js +1 -0
- package/dist/eval-ui/assets/lifecycle-d1Sm9Hts.css +1 -0
- package/dist/eval-ui/assets/lifecycle-o_IRibOa.js +1 -0
- package/dist/eval-ui/assets/main-tpOyw9SC.js +87 -0
- package/dist/eval-ui/assets/preferences-BHZXB5dL.css +1 -0
- package/dist/eval-ui/assets/preferences-DCdw0Kvu.js +2 -0
- package/dist/eval-ui/assets/useDesktopBridge-9oZFQsrw.js +2 -0
- package/dist/eval-ui/index.html +4 -2
- package/dist/eval-ui/lifecycle.html +33 -0
- package/dist/eval-ui/preferences.html +34 -0
- package/dist/index.js +47 -1
- package/dist/index.js.map +1 -1
- package/dist/installer/bundle-files.d.ts +4 -0
- package/dist/installer/bundle-files.js +97 -0
- package/dist/installer/bundle-files.js.map +1 -0
- package/dist/installer/canonical.d.ts +31 -6
- package/dist/installer/canonical.js +48 -12
- package/dist/installer/canonical.js.map +1 -1
- package/dist/installer/clipboard-export.d.ts +19 -0
- package/dist/installer/clipboard-export.js +88 -0
- package/dist/installer/clipboard-export.js.map +1 -0
- package/dist/installer/multi-install.d.ts +43 -0
- package/dist/installer/multi-install.js +237 -0
- package/dist/installer/multi-install.js.map +1 -0
- package/dist/installer/transformers/aider.d.ts +2 -0
- package/dist/installer/transformers/aider.js +32 -0
- package/dist/installer/transformers/aider.js.map +1 -0
- package/dist/installer/transformers/continue-dev.d.ts +2 -0
- package/dist/installer/transformers/continue-dev.js +6 -0
- package/dist/installer/transformers/continue-dev.js.map +1 -0
- package/dist/installer/transformers/cursor.d.ts +2 -0
- package/dist/installer/transformers/cursor.js +24 -0
- package/dist/installer/transformers/cursor.js.map +1 -0
- package/dist/installer/transformers/github-copilot.d.ts +2 -0
- package/dist/installer/transformers/github-copilot.js +17 -0
- package/dist/installer/transformers/github-copilot.js.map +1 -0
- package/dist/installer/transformers/index.d.ts +78 -0
- package/dist/installer/transformers/index.js +13 -0
- package/dist/installer/transformers/index.js.map +1 -0
- package/dist/installer/transformers/junie.d.ts +2 -0
- package/dist/installer/transformers/junie.js +6 -0
- package/dist/installer/transformers/junie.js.map +1 -0
- package/dist/installer/transformers/kiro.d.ts +2 -0
- package/dist/installer/transformers/kiro.js +6 -0
- package/dist/installer/transformers/kiro.js.map +1 -0
- package/dist/installer/transformers/trae.d.ts +2 -0
- package/dist/installer/transformers/trae.js +6 -0
- package/dist/installer/transformers/trae.js.map +1 -0
- package/dist/installer/transformers/windsurf.d.ts +2 -0
- package/dist/installer/transformers/windsurf.js +12 -0
- package/dist/installer/transformers/windsurf.js.map +1 -0
- package/dist/installer/yaml-safe-mutate.d.ts +19 -0
- package/dist/installer/yaml-safe-mutate.js +184 -0
- package/dist/installer/yaml-safe-mutate.js.map +1 -0
- package/dist/lib/active-tenant.d.ts +36 -0
- package/dist/lib/active-tenant.js +120 -0
- package/dist/lib/active-tenant.js.map +1 -0
- package/dist/lib/keychain.d.ts +15 -2
- package/dist/lib/keychain.js +173 -8
- package/dist/lib/keychain.js.map +1 -1
- package/dist/lib/migration/keychain-migration.d.ts +35 -0
- package/dist/lib/migration/keychain-migration.js +189 -0
- package/dist/lib/migration/keychain-migration.js.map +1 -0
- package/dist/lib/tenant-resolver.d.ts +38 -0
- package/dist/lib/tenant-resolver.js +79 -0
- package/dist/lib/tenant-resolver.js.map +1 -0
- package/dist/studio/lib/ops-log.js +140 -57
- package/dist/studio/lib/ops-log.js.map +1 -1
- package/dist/studio/lib/scope-transfer.d.ts +11 -1
- package/dist/studio/lib/scope-transfer.js +48 -24
- package/dist/studio/lib/scope-transfer.js.map +1 -1
- package/dist/studio/routes/index.d.ts +1 -1
- package/dist/studio/routes/index.js +18 -8
- package/dist/studio/routes/index.js.map +1 -1
- package/dist/studio/routes/ops.js +31 -7
- package/dist/studio/routes/ops.js.map +1 -1
- package/dist/studio/routes/promote.d.ts +1 -1
- package/dist/studio/routes/promote.js +18 -9
- package/dist/studio/routes/promote.js.map +1 -1
- package/dist/studio/routes/revert.d.ts +1 -1
- package/dist/studio/routes/revert.js +15 -2
- package/dist/studio/routes/revert.js.map +1 -1
- package/dist/studio/routes/test-install.d.ts +1 -1
- package/dist/studio/routes/test-install.js +16 -9
- package/dist/studio/routes/test-install.js.map +1 -1
- package/dist/studio-runtime/lockfile.d.ts +51 -0
- package/dist/studio-runtime/lockfile.js +216 -0
- package/dist/studio-runtime/lockfile.js.map +1 -0
- package/package.json +18 -1
- package/dist/eval-ui/assets/FindSkillsPalette-DsSgotS9.js +0 -2
- package/dist/eval-ui/assets/SkillDetailPanel-DAD2yJO-.js +0 -1
- package/dist/eval-ui/assets/UpdateDropdown-h5Hg3h7Z.js +0 -1
- package/dist/eval-ui/assets/index-CKLqBL52.css +0 -1
- package/dist/eval-ui/assets/index-JaDg6FlU.js +0 -124
- package/dist/eval-ui/assets/skill-studio-logo-CRyKgIrg.png +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// 0836 US-002 — test helpers for the X-Studio-Token gate.
|
|
3
|
+
//
|
|
4
|
+
// Tests that drive `Router.handle()` against `/api/*` paths must include the
|
|
5
|
+
// live studio token; otherwise the gate writes 401 and the route handler
|
|
6
|
+
// never runs. Use:
|
|
7
|
+
//
|
|
8
|
+
// import { studioTokenHeaders } from "./helpers/studio-token-test-helpers.js";
|
|
9
|
+
// const req = { ..., headers: { host: "localhost", ...studioTokenHeaders() } };
|
|
10
|
+
//
|
|
11
|
+
// For real-fetch tests:
|
|
12
|
+
//
|
|
13
|
+
// await fetch(url, { headers: studioTokenHeaders() });
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
import { getStudioToken } from "../../router.js";
|
|
16
|
+
/** Returns `{ "x-studio-token": <token> }` for spreading into headers. */
|
|
17
|
+
export function studioTokenHeaders() {
|
|
18
|
+
return { "x-studio-token": getStudioToken() };
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=studio-token-test-helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"studio-token-test-helpers.js","sourceRoot":"","sources":["../../../../src/eval-server/__tests__/helpers/studio-token-test-helpers.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,0DAA0D;AAC1D,EAAE;AACF,6EAA6E;AAC7E,yEAAyE;AACzE,mBAAmB;AACnB,EAAE;AACF,iFAAiF;AACjF,kFAAkF;AAClF,EAAE;AACF,wBAAwB;AACxB,EAAE;AACF,yDAAyD;AACzD,8EAA8E;AAE9E,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,0EAA0E;AAC1E,MAAM,UAAU,kBAAkB;IAChC,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,EAAE,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface ActiveRootStore {
|
|
2
|
+
/** The scan root all route handlers should use, resolved per request. */
|
|
3
|
+
getRoot(): string;
|
|
4
|
+
/** Replace the active scan root (e.g. after a project switch). */
|
|
5
|
+
setRoot(root: string): void;
|
|
6
|
+
/**
|
|
7
|
+
* Re-derive the active root from `~/.vskill/workspace.json`'s active project.
|
|
8
|
+
* Updates the store and returns the new value; if no active project resolves,
|
|
9
|
+
* the current root is kept and returned.
|
|
10
|
+
*/
|
|
11
|
+
reload(workspaceDir: string): string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the active project's path from the workspace registry. Returns null
|
|
15
|
+
* when there is no workspace, no active project, or the active id is dangling.
|
|
16
|
+
* Shared by both boot resolution (eval-server) and runtime reloads (this store).
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveActiveRoot(workspaceDir: string): string | null;
|
|
19
|
+
export declare function createActiveRootStore(initialRoot: string): ActiveRootStore;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// 0863 T-001: ActiveRootStore — runtime-mutable scan root.
|
|
3
|
+
//
|
|
4
|
+
// Before 0863, the eval-server resolved the scan root ONCE at boot and froze
|
|
5
|
+
// it into every route handler's closure, so switching the active project from
|
|
6
|
+
// the UI (POST /api/workspace/active) changed ~/.vskill/workspace.json but the
|
|
7
|
+
// running server kept serving the original folder's skills.
|
|
8
|
+
//
|
|
9
|
+
// This store holds the current scan root in memory and lets handlers read it
|
|
10
|
+
// per-request via `getRoot()`. `POST /api/workspace/active` calls `setRoot()`
|
|
11
|
+
// after persisting, so the next `GET /api/skills` scans the new folder — no
|
|
12
|
+
// process restart, no port change, no studio-token re-mint. The same code path
|
|
13
|
+
// works in `npx vskill studio` (single process) and the desktop Tauri sidecar.
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
import { loadWorkspace } from "./workspace-store.js";
|
|
16
|
+
/**
|
|
17
|
+
* Resolve the active project's path from the workspace registry. Returns null
|
|
18
|
+
* when there is no workspace, no active project, or the active id is dangling.
|
|
19
|
+
* Shared by both boot resolution (eval-server) and runtime reloads (this store).
|
|
20
|
+
*/
|
|
21
|
+
export function resolveActiveRoot(workspaceDir) {
|
|
22
|
+
try {
|
|
23
|
+
const ws = loadWorkspace(workspaceDir);
|
|
24
|
+
if (!ws.activeProjectId)
|
|
25
|
+
return null;
|
|
26
|
+
const active = ws.projects.find((p) => p.id === ws.activeProjectId);
|
|
27
|
+
return active ? active.path : null;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function createActiveRootStore(initialRoot) {
|
|
34
|
+
let current = initialRoot;
|
|
35
|
+
return {
|
|
36
|
+
getRoot() {
|
|
37
|
+
return current;
|
|
38
|
+
},
|
|
39
|
+
setRoot(root) {
|
|
40
|
+
current = root;
|
|
41
|
+
},
|
|
42
|
+
reload(workspaceDir) {
|
|
43
|
+
const resolved = resolveActiveRoot(workspaceDir);
|
|
44
|
+
if (resolved)
|
|
45
|
+
current = resolved;
|
|
46
|
+
return current;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=active-root-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"active-root-store.js","sourceRoot":"","sources":["../../src/eval-server/active-root-store.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,2DAA2D;AAC3D,EAAE;AACF,6EAA6E;AAC7E,8EAA8E;AAC9E,+EAA+E;AAC/E,4DAA4D;AAC5D,EAAE;AACF,6EAA6E;AAC7E,8EAA8E;AAC9E,4EAA4E;AAC5E,+EAA+E;AAC/E,+EAA+E;AAC/E,8EAA8E;AAE9E,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAerD;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,YAAoB;IACpD,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,aAAa,CAAC,YAAY,CAAC,CAAC;QACvC,IAAI,CAAC,EAAE,CAAC,eAAe;YAAE,OAAO,IAAI,CAAC;QACrC,MAAM,MAAM,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,eAAe,CAAC,CAAC;QACpE,OAAO,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,WAAmB;IACvD,IAAI,OAAO,GAAG,WAAW,CAAC;IAC1B,OAAO;QACL,OAAO;YACL,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,OAAO,CAAC,IAAY;YAClB,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;QACD,MAAM,CAAC,YAAoB;YACzB,MAAM,QAAQ,GAAG,iBAAiB,CAAC,YAAY,CAAC,CAAC;YACjD,IAAI,QAAQ;gBAAE,OAAO,GAAG,QAAQ,CAAC;YACjC,OAAO,OAAO,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as http from "node:http";
|
|
2
|
+
import { type ActiveTenantOptions } from "../lib/active-tenant.js";
|
|
3
|
+
export interface ActiveTenantRoutesOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Forward to the helpers (tests). Production passes nothing; the
|
|
6
|
+
* helpers default to `~/.vskill/config.json`.
|
|
7
|
+
*/
|
|
8
|
+
activeTenantOptions?: ActiveTenantOptions;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Returns true when the request was handled (response sent). Returns false
|
|
12
|
+
* when the URL/method doesn't match — the caller (eval-server.ts) falls
|
|
13
|
+
* through to the next handler (proxy / static).
|
|
14
|
+
*/
|
|
15
|
+
export declare function handleActiveTenant(req: http.IncomingMessage, res: http.ServerResponse, opts?: ActiveTenantRoutesOptions): Promise<boolean>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// active-tenant-routes.ts — 0839 T-011 / US-004.
|
|
3
|
+
//
|
|
4
|
+
// Loopback-only `/__internal/active-tenant` GET/POST handler used by the
|
|
5
|
+
// Studio TenantPicker (eval-ui T-012/T-013). Reads/writes
|
|
6
|
+
// `~/.vskill/config.json` via the shared helpers in
|
|
7
|
+
// `../lib/active-tenant.ts`, so the CLI (`vskill orgs use`) and the
|
|
8
|
+
// Studio always agree on which tenant is active.
|
|
9
|
+
//
|
|
10
|
+
// Why the path doesn't start with `/api/`:
|
|
11
|
+
// The studio-token gate in `router.ts` only applies to `/api/*`. This
|
|
12
|
+
// route is a UX convenience that needs to work without the WebView
|
|
13
|
+
// threading the studio token (it predates the gate). We compensate
|
|
14
|
+
// with three lower-bound guards:
|
|
15
|
+
// 1. Loopback-only (req.socket.remoteAddress).
|
|
16
|
+
// 2. The eval-server already binds 127.0.0.1 (US-001 hardening).
|
|
17
|
+
// 3. The handler refuses anything but GET / POST + tiny body.
|
|
18
|
+
//
|
|
19
|
+
// Audit logging is intentionally absent — this is a local-only,
|
|
20
|
+
// non-secret read/write of the user's own config file. Logging it would
|
|
21
|
+
// be noise (and the path itself isn't sensitive).
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
import { readBody, sendJson } from "./router.js";
|
|
24
|
+
import { getActiveTenant, setActiveTenant, } from "../lib/active-tenant.js";
|
|
25
|
+
function isLoopback(req) {
|
|
26
|
+
const addr = req.socket?.remoteAddress ?? "";
|
|
27
|
+
return (addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1");
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Returns true when the request was handled (response sent). Returns false
|
|
31
|
+
* when the URL/method doesn't match — the caller (eval-server.ts) falls
|
|
32
|
+
* through to the next handler (proxy / static).
|
|
33
|
+
*/
|
|
34
|
+
export async function handleActiveTenant(req, res, opts = {}) {
|
|
35
|
+
const url = req.url || "";
|
|
36
|
+
// Path-based match (no query string is honored, but we strip it just in
|
|
37
|
+
// case the caller appended `?_=` for cache-busting).
|
|
38
|
+
const path = url.split("?")[0];
|
|
39
|
+
if (path !== "/__internal/active-tenant")
|
|
40
|
+
return false;
|
|
41
|
+
// Loopback guard — the eval-server binds 127.0.0.1 already, but a
|
|
42
|
+
// misconfigured proxy or future port-forward could subvert that.
|
|
43
|
+
if (!isLoopback(req)) {
|
|
44
|
+
sendJson(res, { ok: false, error: "loopback-only" }, 403, req);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
48
|
+
if (method === "GET") {
|
|
49
|
+
const slug = getActiveTenant(opts.activeTenantOptions);
|
|
50
|
+
sendJson(res, { currentTenant: slug });
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
if (method === "POST") {
|
|
54
|
+
let body;
|
|
55
|
+
try {
|
|
56
|
+
body = await readBody(req);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
sendJson(res, { ok: false, error: "invalid JSON" }, 400, req);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
if (!body || typeof body !== "object") {
|
|
63
|
+
sendJson(res, { ok: false, error: "expected { currentTenant }" }, 400, req);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
const incoming = body.currentTenant;
|
|
67
|
+
// Accept null (clear) and non-empty string. Reject anything else so
|
|
68
|
+
// we don't silently coerce numbers/booleans into config corruption.
|
|
69
|
+
let slug;
|
|
70
|
+
if (incoming === null) {
|
|
71
|
+
slug = null;
|
|
72
|
+
}
|
|
73
|
+
else if (typeof incoming === "string" && incoming.length > 0) {
|
|
74
|
+
// Cheap shape check — slugs are URL-safe DNS labels in the platform
|
|
75
|
+
// schema. Refusing weird input here saves a round-trip to a 4xx
|
|
76
|
+
// tenant lookup later.
|
|
77
|
+
if (!/^[a-z0-9][a-z0-9-]*$/i.test(incoming)) {
|
|
78
|
+
sendJson(res, { ok: false, error: "invalid slug format" }, 400, req);
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
slug = incoming;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
sendJson(res, { ok: false, error: "currentTenant must be string|null" }, 400, req);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
setActiveTenant(slug, opts.activeTenantOptions);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
sendJson(res, { ok: false, error: err.message }, 500, req);
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
sendJson(res, { currentTenant: slug });
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
// Other methods.
|
|
98
|
+
sendJson(res, { ok: false, error: "method not allowed" }, 405, req);
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=active-tenant-routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"active-tenant-routes.js","sourceRoot":"","sources":["../../src/eval-server/active-tenant-routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -214,4 +214,4 @@ export declare function detectAvailableProviders(): Promise<Array<{
|
|
|
214
214
|
models: ModelOption[];
|
|
215
215
|
resolvedModel?: string | null;
|
|
216
216
|
}>>;
|
|
217
|
-
export declare function registerRoutes(router: Router,
|
|
217
|
+
export declare function registerRoutes(router: Router, rootArg: string | (() => string), projectName?: string): void;
|
|
@@ -931,6 +931,12 @@ export const PROVIDER_MODELS = {
|
|
|
931
931
|
// what models the user has loaded. The probe at probeLmStudio() populates
|
|
932
932
|
// this dynamically from GET /v1/models.
|
|
933
933
|
"lm-studio": [],
|
|
934
|
+
// 0857: `stub` is a deterministic TEST SEAM (src/eval/llm.ts createStubClient),
|
|
935
|
+
// NOT a user-facing provider. This empty list exists only to satisfy the
|
|
936
|
+
// `Record<ProviderName, ModelOption[]>` exhaustiveness check — `stub` is
|
|
937
|
+
// deliberately excluded from `detectAvailableProviders()` and
|
|
938
|
+
// `KNOWN_PROVIDER_NAMES`, so it never reaches /api/config or the picker.
|
|
939
|
+
"stub": [],
|
|
934
940
|
};
|
|
935
941
|
// ---------------------------------------------------------------------------
|
|
936
942
|
// Local provider detection caches — avoid 500ms+ probes on every /api/config
|
|
@@ -1148,7 +1154,8 @@ export async function detectAvailableProviders() {
|
|
|
1148
1154
|
});
|
|
1149
1155
|
return providers;
|
|
1150
1156
|
}
|
|
1151
|
-
export function registerRoutes(router,
|
|
1157
|
+
export function registerRoutes(router, rootArg, projectName) {
|
|
1158
|
+
const getRoot = typeof rootArg === "function" ? rootArg : () => rootArg;
|
|
1152
1159
|
// Health check
|
|
1153
1160
|
router.get("/api/health", (_req, res) => {
|
|
1154
1161
|
sendJson(res, { ok: true });
|
|
@@ -1167,6 +1174,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
1167
1174
|
// shared-folder grouping. 30s detection cache (mirrors Ollama/LM Studio
|
|
1168
1175
|
// probe pattern from 0677).
|
|
1169
1176
|
router.get("/api/agents", async (req, res) => {
|
|
1177
|
+
const root = getRoot();
|
|
1170
1178
|
try {
|
|
1171
1179
|
const detected = await detectInstalledAgents();
|
|
1172
1180
|
const detectedBinaries = new Set(detected.map((a) => a.id));
|
|
@@ -1367,6 +1375,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
1367
1375
|
// (e.g. "claude-sonnet"). The frontend round-trips config.model back to
|
|
1368
1376
|
// generate-evals and other endpoints, so it must be a valid CLI model ID.
|
|
1369
1377
|
router.get("/api/config", async (_req, res) => {
|
|
1378
|
+
const root = getRoot();
|
|
1370
1379
|
// 0682 F-001 — Boot-time restoration of .vskill/studio.json selection.
|
|
1371
1380
|
// Use a dedicated `studioLoaded` flag rather than checking
|
|
1372
1381
|
// `!currentOverrides.provider` because the module default
|
|
@@ -1436,6 +1445,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
1436
1445
|
});
|
|
1437
1446
|
// Update config — change provider/model at runtime and persist atomically.
|
|
1438
1447
|
router.post("/api/config", async (req, res) => {
|
|
1448
|
+
const root = getRoot();
|
|
1439
1449
|
const body = (await readBody(req));
|
|
1440
1450
|
// 0682 F-001 (review iter 3): validate the incoming provider against the
|
|
1441
1451
|
// ProviderName union BEFORE mutating currentOverrides. Pre-fix, an
|
|
@@ -1497,6 +1507,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
1497
1507
|
// unknown/missing fields default to `null` (not undefined) so the shape
|
|
1498
1508
|
// remains JSON-stable for all consumers.
|
|
1499
1509
|
router.get("/api/skills", async (req, res) => {
|
|
1510
|
+
const root = getRoot();
|
|
1500
1511
|
// 0686: ?scope=own|installed|global and ?agent=<id> query params.
|
|
1501
1512
|
// When either is present, switch to tri-scope scanning so the response
|
|
1502
1513
|
// carries the new `scope`/`isSymlink`/`symlinkTarget`/`installMethod`/
|
|
@@ -1651,6 +1662,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
1651
1662
|
}
|
|
1652
1663
|
});
|
|
1653
1664
|
router.get("/api/skills/updates", async (req, res) => {
|
|
1665
|
+
const root = getRoot();
|
|
1654
1666
|
try {
|
|
1655
1667
|
const { getOutdatedJson } = await import("../commands/outdated.js");
|
|
1656
1668
|
const { scanSkillInstallLocations } = await import("./utils/scan-install-locations.js");
|
|
@@ -1708,7 +1720,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
1708
1720
|
* git remote parse for skills authored in this repo. See skill-name-resolver.ts.
|
|
1709
1721
|
*/
|
|
1710
1722
|
function resolveSkillApiName(skill, plugin = null) {
|
|
1711
|
-
return resolveSkillApiNameImpl(skill,
|
|
1723
|
+
return resolveSkillApiNameImpl(skill, getRoot(), plugin);
|
|
1712
1724
|
}
|
|
1713
1725
|
// T-009 (proxy) + 0707 T-021 (harden): Versions endpoint
|
|
1714
1726
|
//
|
|
@@ -1727,7 +1739,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
1727
1739
|
// Returns `{ apiPathRoot, origin }` — callers append `/versions` or
|
|
1728
1740
|
// `/versions/diff` as needed.
|
|
1729
1741
|
async function buildSkillApiPath(skill, plugin) {
|
|
1730
|
-
const origin = await resolveSkillOrigin(skill, plugin,
|
|
1742
|
+
const origin = await resolveSkillOrigin(skill, plugin, getRoot());
|
|
1731
1743
|
if (origin.owner && origin.repo) {
|
|
1732
1744
|
return {
|
|
1733
1745
|
apiPathRoot: `/api/v1/skills/${encodeURIComponent(origin.owner)}/${encodeURIComponent(origin.repo)}/${encodeURIComponent(skill)}`,
|
|
@@ -1744,6 +1756,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
1744
1756
|
return { apiPathRoot, origin };
|
|
1745
1757
|
}
|
|
1746
1758
|
router.get("/api/skills/:plugin/:skill/versions", async (req, res, params) => {
|
|
1759
|
+
const root = getRoot();
|
|
1747
1760
|
// 0823: comprehensive origin resolver via shared buildSkillApiPath.
|
|
1748
1761
|
const { apiPathRoot, origin } = await buildSkillApiPath(params.skill, params.plugin);
|
|
1749
1762
|
const apiPath = `${apiPathRoot}/versions`;
|
|
@@ -1906,6 +1919,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
1906
1919
|
//
|
|
1907
1920
|
// Idempotent + side-effect free on disk.
|
|
1908
1921
|
router.post("/api/v1/skills/:id/rescan", async (req, res, params) => {
|
|
1922
|
+
const root = getRoot();
|
|
1909
1923
|
const rawId = String(params.id ?? "");
|
|
1910
1924
|
const decoded = (() => {
|
|
1911
1925
|
try {
|
|
@@ -1992,6 +2006,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
1992
2006
|
sendJson(res, { jobId }, 200, req);
|
|
1993
2007
|
});
|
|
1994
2008
|
router.post("/api/skills/:plugin/:skill/update", async (req, res, params) => {
|
|
2009
|
+
const root = getRoot();
|
|
1995
2010
|
initSSE(res, req);
|
|
1996
2011
|
const skillName = params.skill;
|
|
1997
2012
|
// 0747 grill F-002: reject before doing anything so a malformed slug
|
|
@@ -2071,6 +2086,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
2071
2086
|
// T-012: Batch update SSE + 409 conflict guard
|
|
2072
2087
|
let batchUpdateInProgress = false;
|
|
2073
2088
|
router.post("/api/skills/batch-update", async (req, res) => {
|
|
2089
|
+
const root = getRoot();
|
|
2074
2090
|
if (batchUpdateInProgress) {
|
|
2075
2091
|
sendJson(res, { error: "Update already in progress" }, 409, req);
|
|
2076
2092
|
return;
|
|
@@ -2139,7 +2155,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
2139
2155
|
const skillFsAllowedRoots = () => {
|
|
2140
2156
|
const home = homedir();
|
|
2141
2157
|
return [
|
|
2142
|
-
|
|
2158
|
+
getRoot(),
|
|
2143
2159
|
join(home, ".claude/plugins/cache"),
|
|
2144
2160
|
join(home, ".claude/plugins/marketplaces"),
|
|
2145
2161
|
join(home, ".claude/skills"),
|
|
@@ -2156,7 +2172,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
2156
2172
|
// path via setSkillDirEntry inside ensurePluginCacheEntry.
|
|
2157
2173
|
const resolveSkillDirForFsRoute = async (plugin, skill) => {
|
|
2158
2174
|
await ensurePluginCacheEntry(plugin, skill);
|
|
2159
|
-
return resolveAllowedSkillDir(
|
|
2175
|
+
return resolveAllowedSkillDir(getRoot(), plugin, skill, skillFsAllowedRoots());
|
|
2160
2176
|
};
|
|
2161
2177
|
// Get skill detail
|
|
2162
2178
|
router.get("/api/skills/:plugin/:skill", async (req, res, params) => {
|
|
@@ -2342,6 +2358,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
2342
2358
|
});
|
|
2343
2359
|
// Save (create/overwrite) a file inside a skill directory
|
|
2344
2360
|
router.put("/api/skills/:plugin/:skill/file", async (req, res, params) => {
|
|
2361
|
+
const root = getRoot();
|
|
2345
2362
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
2346
2363
|
if (!resolve(skillDir).startsWith(resolve(root))) {
|
|
2347
2364
|
sendJson(res, { error: "Invalid skill path" }, 400, req);
|
|
@@ -2371,6 +2388,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
2371
2388
|
});
|
|
2372
2389
|
// Delete a source skill (recursively removes its directory)
|
|
2373
2390
|
router.delete("/api/skills/:plugin/:skill", async (req, res, params) => {
|
|
2391
|
+
const root = getRoot();
|
|
2374
2392
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
2375
2393
|
// Path containment guard — prevent path traversal via ".." in params
|
|
2376
2394
|
if (!resolve(skillDir).startsWith(resolve(root))) {
|
|
@@ -2411,6 +2429,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
2411
2429
|
// this route is the canonical uninstall path for lockfile-tracked
|
|
2412
2430
|
// installed skills.
|
|
2413
2431
|
router.post("/api/skills/:plugin/:skill/uninstall", async (req, res, params) => {
|
|
2432
|
+
const root = getRoot();
|
|
2414
2433
|
// Skill-name validation — same kebab-case regex used by skill-create
|
|
2415
2434
|
// routes. Performed BEFORE any filesystem access to defang path-traversal
|
|
2416
2435
|
// attempts (e.g. `../../etc/passwd`).
|
|
@@ -2499,6 +2518,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
2499
2518
|
});
|
|
2500
2519
|
// Get skill description (for activation testing preview)
|
|
2501
2520
|
router.get("/api/skills/:plugin/:skill/description", async (req, res, params) => {
|
|
2521
|
+
const root = getRoot();
|
|
2502
2522
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
2503
2523
|
const skillMdPath = join(skillDir, "SKILL.md");
|
|
2504
2524
|
let skillContent = "";
|
|
@@ -2524,6 +2544,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
2524
2544
|
// generic client errors — 422 Unprocessable Entity is the correct semantic
|
|
2525
2545
|
// for well-formed requests whose payload fails validation.
|
|
2526
2546
|
router.get("/api/skills/:plugin/:skill/evals", async (req, res, params) => {
|
|
2547
|
+
const root = getRoot();
|
|
2527
2548
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
2528
2549
|
const evalsPath = join(skillDir, "evals", "evals.json");
|
|
2529
2550
|
if (!existsSync(evalsPath)) {
|
|
@@ -2548,6 +2569,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
2548
2569
|
});
|
|
2549
2570
|
// Save evals.json
|
|
2550
2571
|
router.put("/api/skills/:plugin/:skill/evals", async (req, res, params) => {
|
|
2572
|
+
const root = getRoot();
|
|
2551
2573
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
2552
2574
|
const body = (await readBody(req));
|
|
2553
2575
|
// Validate before writing
|
|
@@ -2565,6 +2587,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
2565
2587
|
// Generate evals using AI — reads SKILL.md and returns generated EvalsFile
|
|
2566
2588
|
// Accepts optional { provider, model, testType } in request body
|
|
2567
2589
|
router.post("/api/skills/:plugin/:skill/generate-evals", async (req, res, params) => {
|
|
2590
|
+
const root = getRoot();
|
|
2568
2591
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
2569
2592
|
const skillMdPath = join(skillDir, "SKILL.md");
|
|
2570
2593
|
if (!existsSync(skillMdPath)) {
|
|
@@ -2674,6 +2697,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
2674
2697
|
});
|
|
2675
2698
|
// Run benchmark (SSE) — optionally accepts { eval_ids, concurrency, judgeModel, noCache }
|
|
2676
2699
|
router.post("/api/skills/:plugin/:skill/benchmark", async (req, res, params) => {
|
|
2700
|
+
const root = getRoot();
|
|
2677
2701
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
2678
2702
|
let aborted = false;
|
|
2679
2703
|
res.on("close", () => { aborted = true; });
|
|
@@ -2725,6 +2749,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
2725
2749
|
});
|
|
2726
2750
|
// Run baseline (SSE) — same as benchmark but without skill content
|
|
2727
2751
|
router.post("/api/skills/:plugin/:skill/baseline", async (req, res, params) => {
|
|
2752
|
+
const root = getRoot();
|
|
2728
2753
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
2729
2754
|
let aborted = false;
|
|
2730
2755
|
res.on("close", () => { aborted = true; });
|
|
@@ -2748,6 +2773,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
2748
2773
|
});
|
|
2749
2774
|
// Run single case (SSE) — per-case endpoint with semaphore
|
|
2750
2775
|
router.post("/api/skills/:plugin/:skill/benchmark/case/:evalId", async (req, res, params) => {
|
|
2776
|
+
const root = getRoot();
|
|
2751
2777
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
2752
2778
|
const evalId = parseInt(params.evalId, 10);
|
|
2753
2779
|
if (isNaN(evalId)) {
|
|
@@ -2823,6 +2849,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
2823
2849
|
});
|
|
2824
2850
|
// Bulk save — client assembles result from per-case runs and saves as one history entry
|
|
2825
2851
|
router.post("/api/skills/:plugin/:skill/benchmark/bulk-save", async (req, res, params) => {
|
|
2852
|
+
const root = getRoot();
|
|
2826
2853
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
2827
2854
|
try {
|
|
2828
2855
|
const body = await readBody(req);
|
|
@@ -2841,6 +2868,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
2841
2868
|
});
|
|
2842
2869
|
// Run comparison (SSE)
|
|
2843
2870
|
router.post("/api/skills/:plugin/:skill/compare", async (req, res, params) => {
|
|
2871
|
+
const root = getRoot();
|
|
2844
2872
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
2845
2873
|
let aborted = false;
|
|
2846
2874
|
res.on("close", () => { aborted = true; });
|
|
@@ -3047,6 +3075,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
3047
3075
|
});
|
|
3048
3076
|
// List benchmark history (with optional filters)
|
|
3049
3077
|
router.get("/api/skills/:plugin/:skill/history", async (req, res, params) => {
|
|
3078
|
+
const root = getRoot();
|
|
3050
3079
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
3051
3080
|
const url = new URL(req.url, `http://localhost`);
|
|
3052
3081
|
const filter = {};
|
|
@@ -3069,6 +3098,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
3069
3098
|
});
|
|
3070
3099
|
// Compare two history runs
|
|
3071
3100
|
router.get("/api/skills/:plugin/:skill/history-compare", async (req, res, params) => {
|
|
3101
|
+
const root = getRoot();
|
|
3072
3102
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
3073
3103
|
const url = new URL(req.url, `http://localhost`);
|
|
3074
3104
|
const tsA = url.searchParams.get("a");
|
|
@@ -3132,6 +3162,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
3132
3162
|
});
|
|
3133
3163
|
// Per-case history
|
|
3134
3164
|
router.get("/api/skills/:plugin/:skill/history/case/:evalId", async (req, res, params) => {
|
|
3165
|
+
const root = getRoot();
|
|
3135
3166
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
3136
3167
|
const evalId = parseInt(params.evalId, 10);
|
|
3137
3168
|
if (isNaN(evalId)) {
|
|
@@ -3145,6 +3176,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
3145
3176
|
});
|
|
3146
3177
|
// Get specific history entry
|
|
3147
3178
|
router.get("/api/skills/:plugin/:skill/history/:timestamp", async (req, res, params) => {
|
|
3179
|
+
const root = getRoot();
|
|
3148
3180
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
3149
3181
|
const entry = await readHistoryEntry(skillDir, params.timestamp);
|
|
3150
3182
|
if (!entry) {
|
|
@@ -3155,6 +3187,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
3155
3187
|
});
|
|
3156
3188
|
// Delete history entry
|
|
3157
3189
|
router.delete("/api/skills/:plugin/:skill/history/:timestamp", async (req, res, params) => {
|
|
3190
|
+
const root = getRoot();
|
|
3158
3191
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
3159
3192
|
const deleted = await deleteHistoryEntry(skillDir, params.timestamp);
|
|
3160
3193
|
if (!deleted) {
|
|
@@ -3165,6 +3198,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
3165
3198
|
});
|
|
3166
3199
|
// Get aggregated stats
|
|
3167
3200
|
router.get("/api/skills/:plugin/:skill/stats", async (req, res, params) => {
|
|
3201
|
+
const root = getRoot();
|
|
3168
3202
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
3169
3203
|
const stats = await computeStats(skillDir);
|
|
3170
3204
|
sendJson(res, stats, 200, req);
|
|
@@ -3179,6 +3213,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
3179
3213
|
// Works for any plugin slug (including dashes like `google-workspace`)
|
|
3180
3214
|
// because routing uses `[^/]+` groups (see router.ts T-020).
|
|
3181
3215
|
router.get("/api/skills/:plugin/:skill/benchmark/latest", async (req, res, params) => {
|
|
3216
|
+
const root = getRoot();
|
|
3182
3217
|
// 0704: always 200; body null = no benchmark persisted yet.
|
|
3183
3218
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
3184
3219
|
const benchmark = await readBenchmark(skillDir);
|
|
@@ -3186,6 +3221,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
3186
3221
|
});
|
|
3187
3222
|
// Run activation test (SSE)
|
|
3188
3223
|
router.post("/api/skills/:plugin/:skill/activation-test", async (req, res, params) => {
|
|
3224
|
+
const root = getRoot();
|
|
3189
3225
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
3190
3226
|
let aborted = false;
|
|
3191
3227
|
res.on("close", () => { aborted = true; });
|
|
@@ -3256,6 +3292,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
3256
3292
|
});
|
|
3257
3293
|
// GET parsed `## Test Cases` block from SKILL.md (increment 0776)
|
|
3258
3294
|
router.get("/api/skills/:plugin/:skill/test-cases", (req, res, params) => {
|
|
3295
|
+
const root = getRoot();
|
|
3259
3296
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
3260
3297
|
const skillMdPath = join(skillDir, "SKILL.md");
|
|
3261
3298
|
const content = existsSync(skillMdPath) ? readFileSync(skillMdPath, "utf-8") : "";
|
|
@@ -3266,6 +3303,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
3266
3303
|
// Empty prompts array removes the section. Frontmatter and other body
|
|
3267
3304
|
// sections are preserved verbatim.
|
|
3268
3305
|
router.put("/api/skills/:plugin/:skill/test-cases", async (req, res, params) => {
|
|
3306
|
+
const root = getRoot();
|
|
3269
3307
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
3270
3308
|
const skillMdPath = join(skillDir, "SKILL.md");
|
|
3271
3309
|
const body = (await readBody(req));
|
|
@@ -3296,6 +3334,7 @@ export function registerRoutes(router, root, projectName) {
|
|
|
3296
3334
|
});
|
|
3297
3335
|
// AI-generate activation test prompts (SSE)
|
|
3298
3336
|
router.post("/api/skills/:plugin/:skill/activation-prompts", async (req, res, params) => {
|
|
3337
|
+
const root = getRoot();
|
|
3299
3338
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
3300
3339
|
let aborted = false;
|
|
3301
3340
|
res.on("close", () => { aborted = true; });
|
|
@@ -3361,6 +3400,7 @@ Return ONLY the JSON lines, no other text.`;
|
|
|
3361
3400
|
// 500 { error } only for unexpected I/O failures
|
|
3362
3401
|
// (ENOENT is explicitly not one)
|
|
3363
3402
|
router.get("/api/skills/:plugin/:skill/activation-history", async (req, res, params) => {
|
|
3403
|
+
const root = getRoot();
|
|
3364
3404
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
3365
3405
|
try {
|
|
3366
3406
|
const runs = await listActivationRuns(skillDir);
|
|
@@ -3379,6 +3419,7 @@ Return ONLY the JSON lines, no other text.`;
|
|
|
3379
3419
|
});
|
|
3380
3420
|
// Get full activation test run by ID
|
|
3381
3421
|
router.get("/api/skills/:plugin/:skill/activation-history/:runId", async (req, res, params) => {
|
|
3422
|
+
const root = getRoot();
|
|
3382
3423
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
3383
3424
|
const run = await getActivationRun(skillDir, params.runId);
|
|
3384
3425
|
if (!run) {
|
|
@@ -3389,6 +3430,7 @@ Return ONLY the JSON lines, no other text.`;
|
|
|
3389
3430
|
});
|
|
3390
3431
|
// Get skill dependencies (MCP + skill-to-skill)
|
|
3391
3432
|
router.get("/api/skills/:plugin/:skill/dependencies", async (req, res, params) => {
|
|
3433
|
+
const root = getRoot();
|
|
3392
3434
|
const skillDir = resolveSkillDir(root, params.plugin, params.skill);
|
|
3393
3435
|
const skillMdPath = join(skillDir, "SKILL.md");
|
|
3394
3436
|
if (!existsSync(skillMdPath)) {
|
|
@@ -3406,6 +3448,7 @@ Return ONLY the JSON lines, no other text.`;
|
|
|
3406
3448
|
// server-side via scanSkillInstallLocations and rejects any non-basename
|
|
3407
3449
|
// `file`. Spawn is detached + unref'd so the response returns immediately.
|
|
3408
3450
|
router.post("/api/skills/reveal-in-editor", async (req, res) => {
|
|
3451
|
+
const root = getRoot();
|
|
3409
3452
|
let body;
|
|
3410
3453
|
try {
|
|
3411
3454
|
body = (await readBody(req));
|
|
@@ -3536,6 +3579,7 @@ Return ONLY the JSON lines, no other text.`;
|
|
|
3536
3579
|
// 500 { error: "clone_failed", message: <stderr tail>, exitCode }
|
|
3537
3580
|
// -------------------------------------------------------------------------
|
|
3538
3581
|
router.post("/api/skills/clone", async (req, res) => {
|
|
3582
|
+
const root = getRoot();
|
|
3539
3583
|
let body;
|
|
3540
3584
|
try {
|
|
3541
3585
|
body = (await readBody(req));
|
|
@@ -3654,14 +3698,23 @@ Return ONLY the JSON lines, no other text.`;
|
|
|
3654
3698
|
stdout: stdout.trim().split("\n").slice(-8).join("\n"),
|
|
3655
3699
|
}, 200, req);
|
|
3656
3700
|
});
|
|
3657
|
-
// Handle CORS preflight
|
|
3701
|
+
// Handle CORS preflight.
|
|
3702
|
+
//
|
|
3703
|
+
// 0836 US-002 (F-017 follow-up): every authenticated /api/* request now
|
|
3704
|
+
// carries the `X-Studio-Token` custom header. Browser-initiated cross-origin
|
|
3705
|
+
// fetches issue a CORS preflight; the response MUST list `X-Studio-Token`
|
|
3706
|
+
// in `Access-Control-Allow-Headers` or the browser blocks the actual
|
|
3707
|
+
// request. The desktop hot path is same-origin (WebView is navigated to
|
|
3708
|
+
// `http://127.0.0.1:{port}/`) and skips preflight, but CLI users opening
|
|
3709
|
+
// the studio in a regular browser tab — and any future cross-origin
|
|
3710
|
+
// tooling — depend on this echo.
|
|
3658
3711
|
router.options = (req, res) => {
|
|
3659
3712
|
const origin = req.headers.origin;
|
|
3660
3713
|
if (origin && /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(origin)) {
|
|
3661
3714
|
res.writeHead(204, {
|
|
3662
3715
|
"Access-Control-Allow-Origin": origin,
|
|
3663
3716
|
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
3664
|
-
"Access-Control-Allow-Headers": "Content-Type",
|
|
3717
|
+
"Access-Control-Allow-Headers": "Content-Type, X-Studio-Token",
|
|
3665
3718
|
"Access-Control-Max-Age": "3600",
|
|
3666
3719
|
});
|
|
3667
3720
|
}
|