vskill 1.0.15 → 1.0.18
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 +84 -9
- package/agents.json +3 -1
- package/dist/agents/agents-registry.d.ts +69 -3
- 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/commands/add-lockfile.d.ts +6 -0
- package/dist/commands/add-lockfile.js +10 -0
- package/dist/commands/add-lockfile.js.map +1 -1
- package/dist/commands/add.d.ts +7 -0
- package/dist/commands/add.js +110 -2
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/auth.d.ts +23 -0
- package/dist/commands/auth.js +105 -11
- 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/discovery/github-tree.d.ts +23 -3
- package/dist/discovery/github-tree.js +172 -24
- package/dist/discovery/github-tree.js.map +1 -1
- 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-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.js +206 -6
- package/dist/eval-server/api-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/eval-server.js +90 -6
- 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 +1 -0
- package/dist/eval-server/git-routes.js +101 -4
- package/dist/eval-server/git-routes.js.map +1 -1
- package/dist/eval-server/install-engine-routes.d.ts +3 -16
- package/dist/eval-server/install-engine-routes.js +9 -124
- 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 +506 -79
- package/dist/eval-server/install-skill-routes.js.map +1 -1
- package/dist/eval-server/install-state-routes.d.ts +25 -0
- package/dist/eval-server/install-state-routes.js +125 -0
- package/dist/eval-server/install-state-routes.js.map +1 -0
- 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 +17 -1
- package/dist/eval-server/platform-proxy.js +125 -13
- package/dist/eval-server/platform-proxy.js.map +1 -1
- package/dist/eval-server/plugin-cli-routes.js +9 -9
- 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 +145 -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/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/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-ui/assets/AdvancedTab-D8zbE5fH.js +1 -0
- package/dist/eval-ui/assets/{CreateSkillPage-BmbvQEzE.js → CreateSkillPage-DOBhKdgr.js} +5 -5
- package/dist/eval-ui/assets/FindSkillsPalette-CyMmNPr-.js +2 -0
- package/dist/eval-ui/assets/GeneralTab-DYR9NWC4.js +1 -0
- package/dist/eval-ui/assets/PrivacyTab-CXIqQokl.js +1 -0
- package/dist/eval-ui/assets/SearchPaletteCore-Dn5gQJS_.js +14 -0
- package/dist/eval-ui/assets/SkillDetailPanel-DTrRnyyJ.js +1 -0
- package/dist/eval-ui/assets/UpdateDropdown-Cvr2fe0z.js +1 -0
- package/dist/eval-ui/assets/UpdatesTab-DwJIUDPX.js +1 -0
- package/dist/eval-ui/assets/core-DZAvsxlC.js +1 -0
- package/dist/eval-ui/assets/event-CDYWU2X3.js +1 -0
- package/dist/eval-ui/assets/globals-BRZwPAPF.js +49 -0
- package/dist/eval-ui/assets/globals-C3oEdsJh.css +1 -0
- package/dist/eval-ui/assets/index-D7M0Jdss.js +1 -0
- package/dist/eval-ui/assets/lifecycle-DSleOV-l.js +1 -0
- package/dist/eval-ui/assets/lifecycle-d1Sm9Hts.css +1 -0
- package/dist/eval-ui/assets/main-D2shn1dH.js +87 -0
- package/dist/eval-ui/assets/preferences-BHZXB5dL.css +1 -0
- package/dist/eval-ui/assets/preferences-BKv6X7fK.js +2 -0
- package/dist/eval-ui/assets/useDesktopBridge-DxVWbYqK.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 +50 -23
- 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/frontmatter.js +1 -1
- package/dist/installer/frontmatter.js.map +1 -1
- 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/github-fetch.d.ts +1 -0
- package/dist/lib/github-fetch.js +11 -1
- package/dist/lib/github-fetch.js.map +1 -1
- package/dist/lib/keychain.d.ts +15 -2
- package/dist/lib/keychain.js +156 -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/lockfile/types.d.ts +8 -0
- package/dist/sidecar/eval-ui-manifest.json +1 -0
- package/dist/sidecar/sea-config.json +57 -0
- package/dist/sidecar/sea-prep.blob +0 -0
- package/dist/sidecar/server.cjs +141627 -0
- package/dist/sidecar/vskill-version.txt +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/provenance.js +3 -2
- package/dist/studio/lib/provenance.js.map +1 -1
- package/dist/studio/lib/query.d.ts +1 -0
- package/dist/studio/lib/query.js +7 -0
- package/dist/studio/lib/query.js.map +1 -0
- package/dist/studio/lib/scope-transfer.d.ts +16 -0
- package/dist/studio/lib/scope-transfer.js +52 -25
- package/dist/studio/lib/scope-transfer.js.map +1 -1
- package/dist/studio/routes/index.js +13 -1
- package/dist/studio/routes/index.js.map +1 -1
- package/dist/studio/routes/ops.js +31 -9
- package/dist/studio/routes/ops.js.map +1 -1
- package/dist/studio/routes/promote.js +16 -11
- package/dist/studio/routes/promote.js.map +1 -1
- package/dist/studio/routes/revert.js +14 -18
- package/dist/studio/routes/revert.js.map +1 -1
- package/dist/studio/routes/test-install.js +14 -11
- 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/dist/updater/source-fetcher.js +2 -2
- package/dist/updater/source-fetcher.js.map +1 -1
- package/dist/utils/skill-builder-detection.d.ts +14 -1
- package/dist/utils/skill-builder-detection.js +20 -8
- package/dist/utils/skill-builder-detection.js.map +1 -1
- package/dist/utils/skill-creator-detection.d.ts +10 -2
- package/dist/utils/skill-creator-detection.js +12 -43
- package/dist/utils/skill-creator-detection.js.map +1 -1
- package/package.json +17 -1
- package/dist/eval-ui/assets/FindSkillsPalette-D0Zjhm31.js +0 -2
- package/dist/eval-ui/assets/SearchPaletteCore-EhcN1xEa.js +0 -14
- package/dist/eval-ui/assets/SkillDetailPanel-B5J60ffv.js +0 -1
- package/dist/eval-ui/assets/UpdateDropdown-Celf0_Cr.js +0 -1
- package/dist/eval-ui/assets/index-BV7k6fdk.js +0 -124
- package/dist/eval-ui/assets/index-CKLqBL52.css +0 -1
- package/dist/eval-ui/assets/skill-studio-logo-CRyKgIrg.png +0 -0
|
@@ -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
|
|
@@ -2425,6 +2431,47 @@ export function registerRoutes(router, root, projectName) {
|
|
|
2425
2431
|
sendJson(res, { error: "Invalid skill path", code: "invalid-path" }, 400, req);
|
|
2426
2432
|
return;
|
|
2427
2433
|
}
|
|
2434
|
+
// 0786 AC-US3-03: lockfile-first gate. The previous flow attempted the
|
|
2435
|
+
// lockfile delete first, then the dir trash, and only returned a generic
|
|
2436
|
+
// 404 not-installed when both fell through — which surfaced as a
|
|
2437
|
+
// cryptic "Couldn't uninstall <skill>: api…" toast on source-authored
|
|
2438
|
+
// skills. Now: read the lockfile up front, and if the skill is not a
|
|
2439
|
+
// lockfile-tracked installed plugin skill, return HTTP 422 with the
|
|
2440
|
+
// structured `not-installed` code so the UI can render a friendly
|
|
2441
|
+
// "use Delete instead" message (see App.tsx pendingUninstall onFailure).
|
|
2442
|
+
//
|
|
2443
|
+
// Response contract:
|
|
2444
|
+
// 400 invalid-skill-name — bad kebab regex
|
|
2445
|
+
// 400 invalid-path — path-traversal attempt
|
|
2446
|
+
// 422 not-installed — skill not in lockfile (this gate)
|
|
2447
|
+
// 500 lockfile-read-failed — readLockfile threw
|
|
2448
|
+
// 500 lockfile-write-failed — writeLockfile threw
|
|
2449
|
+
// 500 trash-failed — trash threw
|
|
2450
|
+
// 200 ok — entry removed from lockfile, dir trashed
|
|
2451
|
+
//
|
|
2452
|
+
// 0786 review F-003: lockfile key is currently the bare skill name
|
|
2453
|
+
// (`lock.skills[params.skill]`) — params.plugin is intentionally NOT part
|
|
2454
|
+
// of the key because the on-disk layout `~/.claude/skills/<skill>/` is
|
|
2455
|
+
// unscoped. If two installed skills ever share the same name across
|
|
2456
|
+
// different plugins, this gate disambiguates the FIRST uninstall correctly
|
|
2457
|
+
// (entry removed) and 422s the SECOND (no entry left). The pre-0786 flow
|
|
2458
|
+
// had the same shape; documenting the limitation here so future contributors
|
|
2459
|
+
// don't accidentally introduce a plugin-scoped check on one side without
|
|
2460
|
+
// updating writeLockfile / skill-list (api-routes.ts:1940 same shape).
|
|
2461
|
+
let lockfileEntryExists = false;
|
|
2462
|
+
try {
|
|
2463
|
+
const lockBefore = readLockfile(root);
|
|
2464
|
+
lockfileEntryExists = !!(lockBefore?.skills &&
|
|
2465
|
+
Object.prototype.hasOwnProperty.call(lockBefore.skills, params.skill));
|
|
2466
|
+
}
|
|
2467
|
+
catch (err) {
|
|
2468
|
+
sendJson(res, { error: `Failed to read lockfile: ${err.message}`, code: "lockfile-read-failed" }, 500, req);
|
|
2469
|
+
return;
|
|
2470
|
+
}
|
|
2471
|
+
if (!lockfileEntryExists) {
|
|
2472
|
+
sendJson(res, { error: `${params.skill} is not installed`, code: "not-installed" }, 422, req);
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2428
2475
|
let removedFromLockfile = false;
|
|
2429
2476
|
try {
|
|
2430
2477
|
const lock = readLockfile(root);
|
|
@@ -2454,10 +2501,6 @@ export function registerRoutes(router, root, projectName) {
|
|
|
2454
2501
|
return;
|
|
2455
2502
|
}
|
|
2456
2503
|
}
|
|
2457
|
-
if (!removedFromLockfile && trashedDir === null) {
|
|
2458
|
-
sendJson(res, { error: "Skill is not installed", code: "not-installed" }, 404, req);
|
|
2459
|
-
return;
|
|
2460
|
-
}
|
|
2461
2504
|
sendJson(res, { ok: true, removedFromLockfile, trashedDir }, 200, req);
|
|
2462
2505
|
});
|
|
2463
2506
|
// Get skill description (for activation testing preview)
|
|
@@ -3469,14 +3512,171 @@ Return ONLY the JSON lines, no other text.`;
|
|
|
3469
3512
|
}
|
|
3470
3513
|
sendJson(res, { ok: true, command: launch.command, args: launch.args }, 200, req);
|
|
3471
3514
|
});
|
|
3472
|
-
//
|
|
3515
|
+
// -------------------------------------------------------------------------
|
|
3516
|
+
// 0828 — POST /api/skills/clone
|
|
3517
|
+
//
|
|
3518
|
+
// Closes the gap left by 0822 (vskill-clone-skill-fork) which shipped the
|
|
3519
|
+
// CLI but explicitly deferred Studio UI ("This increment is CLI-only" —
|
|
3520
|
+
// 0822/spec.md:149). Forks an installed skill into the authoring scope by
|
|
3521
|
+
// spawning the `vskill clone` binary with --target standalone --path
|
|
3522
|
+
// <root>/skills/<skill-name>-fork --yes (auto-confirm, non-interactive).
|
|
3523
|
+
//
|
|
3524
|
+
// We shell out rather than calling runCloneOnce in-process because the
|
|
3525
|
+
// CLI orchestrator uses `process.exit()` on errors (cloneCommand:580) and
|
|
3526
|
+
// because spawning the same binary the user would run from terminal keeps
|
|
3527
|
+
// a single source of truth — if the CLI gains new flags, this route
|
|
3528
|
+
// automatically benefits.
|
|
3529
|
+
//
|
|
3530
|
+
// Body schema:
|
|
3531
|
+
// { source: string, // skill name to clone
|
|
3532
|
+
// sourcePlugin?: string, // plugin context (informational)
|
|
3533
|
+
// target?: "standalone", // only standalone supported in v1
|
|
3534
|
+
// path?: string, // optional override; defaults to <root>/skills/<name>-fork
|
|
3535
|
+
// author?: string, // defaults to git config user.name
|
|
3536
|
+
// namespace?: string, // defaults to slugified author
|
|
3537
|
+
// force?: boolean } // overwrite existing target
|
|
3538
|
+
//
|
|
3539
|
+
// Returns:
|
|
3540
|
+
// 200 { ok: true, target: <abs path>, files: <count>, stdout: <tail> }
|
|
3541
|
+
// 400 { error: "invalid_body" | "invalid_source" }
|
|
3542
|
+
// 500 { error: "clone_failed", message: <stderr tail>, exitCode }
|
|
3543
|
+
// -------------------------------------------------------------------------
|
|
3544
|
+
router.post("/api/skills/clone", async (req, res) => {
|
|
3545
|
+
let body;
|
|
3546
|
+
try {
|
|
3547
|
+
body = (await readBody(req));
|
|
3548
|
+
}
|
|
3549
|
+
catch {
|
|
3550
|
+
sendJson(res, { error: "invalid_body" }, 400, req);
|
|
3551
|
+
return;
|
|
3552
|
+
}
|
|
3553
|
+
const source = typeof body.source === "string" ? body.source.trim() : "";
|
|
3554
|
+
if (!source || !/^[a-z0-9][a-z0-9-]*$/i.test(source) || source.length > 64) {
|
|
3555
|
+
sendJson(res, { error: "invalid_source" }, 400, req);
|
|
3556
|
+
return;
|
|
3557
|
+
}
|
|
3558
|
+
const validTargets = ["standalone", "plugin", "new-plugin"];
|
|
3559
|
+
const target = validTargets.includes(body.target)
|
|
3560
|
+
? body.target
|
|
3561
|
+
: "standalone";
|
|
3562
|
+
// Resolve the path inside `root`, with a path-traversal guard for any
|
|
3563
|
+
// client-supplied path. We compute `targetPath` for the response payload;
|
|
3564
|
+
// for plugin / new-plugin targets the CLI computes the actual skill dir
|
|
3565
|
+
// from --plugin / --plugin-name.
|
|
3566
|
+
const rootAbs = resolve(root);
|
|
3567
|
+
function ensureInsideRoot(p) {
|
|
3568
|
+
const abs = resolve(rootAbs, p);
|
|
3569
|
+
if (abs !== rootAbs && !abs.startsWith(rootAbs + "/"))
|
|
3570
|
+
return null;
|
|
3571
|
+
return abs;
|
|
3572
|
+
}
|
|
3573
|
+
const slug = source.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3574
|
+
let targetPath = "";
|
|
3575
|
+
// Build CLI args. We always pass --yes to avoid hanging on a missing TTY.
|
|
3576
|
+
const cliArgs = ["clone", source, "--yes", "--target", target];
|
|
3577
|
+
if (target === "standalone") {
|
|
3578
|
+
const defaultDir = join(rootAbs, "skills", `${slug}-fork`);
|
|
3579
|
+
targetPath = defaultDir;
|
|
3580
|
+
if (typeof body.path === "string" && body.path.trim()) {
|
|
3581
|
+
const candidate = ensureInsideRoot(body.path);
|
|
3582
|
+
if (!candidate) {
|
|
3583
|
+
sendJson(res, { error: "path_outside_root" }, 400, req);
|
|
3584
|
+
return;
|
|
3585
|
+
}
|
|
3586
|
+
targetPath = candidate;
|
|
3587
|
+
}
|
|
3588
|
+
cliArgs.push("--path", targetPath);
|
|
3589
|
+
}
|
|
3590
|
+
else if (target === "plugin") {
|
|
3591
|
+
// Existing plugin — body.plugin is the plugin name OR an absolute path.
|
|
3592
|
+
const plugin = typeof body.plugin === "string" ? body.plugin.trim() : "";
|
|
3593
|
+
if (!plugin) {
|
|
3594
|
+
sendJson(res, { error: "missing_plugin" }, 400, req);
|
|
3595
|
+
return;
|
|
3596
|
+
}
|
|
3597
|
+
// If it looks like a path, validate it's inside root. Otherwise let the
|
|
3598
|
+
// CLI resolve the plugin name from its registry.
|
|
3599
|
+
if (plugin.startsWith("/") || plugin.startsWith(".") || plugin.includes("/")) {
|
|
3600
|
+
const candidate = ensureInsideRoot(plugin);
|
|
3601
|
+
if (!candidate) {
|
|
3602
|
+
sendJson(res, { error: "path_outside_root" }, 400, req);
|
|
3603
|
+
return;
|
|
3604
|
+
}
|
|
3605
|
+
cliArgs.push("--plugin", candidate);
|
|
3606
|
+
targetPath = join(candidate, "skills", slug);
|
|
3607
|
+
}
|
|
3608
|
+
else {
|
|
3609
|
+
cliArgs.push("--plugin", plugin);
|
|
3610
|
+
targetPath = `<plugin:${plugin}>/skills/${slug}`; // informational
|
|
3611
|
+
}
|
|
3612
|
+
}
|
|
3613
|
+
else {
|
|
3614
|
+
// new-plugin — scaffold a fresh plugin folder.
|
|
3615
|
+
const pluginName = typeof body.pluginName === "string" ? body.pluginName.trim() : "";
|
|
3616
|
+
if (!pluginName || !/^[a-z][a-z0-9-]{0,62}[a-z0-9]$/.test(pluginName)) {
|
|
3617
|
+
sendJson(res, { error: "invalid_plugin_name" }, 400, req);
|
|
3618
|
+
return;
|
|
3619
|
+
}
|
|
3620
|
+
const pluginRoot = join(rootAbs, pluginName);
|
|
3621
|
+
cliArgs.push("--plugin-name", pluginName);
|
|
3622
|
+
cliArgs.push("--path", pluginRoot);
|
|
3623
|
+
targetPath = join(pluginRoot, "skills", slug);
|
|
3624
|
+
}
|
|
3625
|
+
if (typeof body.author === "string" && body.author.trim()) {
|
|
3626
|
+
cliArgs.push("--author", body.author.trim());
|
|
3627
|
+
}
|
|
3628
|
+
if (typeof body.namespace === "string" && body.namespace.trim()) {
|
|
3629
|
+
cliArgs.push("--namespace", body.namespace.trim());
|
|
3630
|
+
}
|
|
3631
|
+
if (body.force === true)
|
|
3632
|
+
cliArgs.push("--force");
|
|
3633
|
+
// Spawn the `vskill` binary on PATH — same pattern used by
|
|
3634
|
+
// install-skill-routes.ts:74 for `vskill install`. This keeps a single
|
|
3635
|
+
// source of truth (whatever the user runs from terminal also runs here).
|
|
3636
|
+
let stdout = "";
|
|
3637
|
+
let stderr = "";
|
|
3638
|
+
const exitCode = await new Promise((resolveCode) => {
|
|
3639
|
+
const child = spawn("vskill", cliArgs, {
|
|
3640
|
+
cwd: root,
|
|
3641
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3642
|
+
});
|
|
3643
|
+
child.stdout?.on("data", (c) => { stdout += c.toString(); });
|
|
3644
|
+
child.stderr?.on("data", (c) => { stderr += c.toString(); });
|
|
3645
|
+
child.on("error", () => resolveCode(127));
|
|
3646
|
+
child.on("close", (code) => resolveCode(code));
|
|
3647
|
+
});
|
|
3648
|
+
if (exitCode !== 0) {
|
|
3649
|
+
const tail = (stderr || stdout).trim().split("\n").slice(-6).join("\n");
|
|
3650
|
+
sendJson(res, { error: "clone_failed", message: tail || "non-zero exit", exitCode }, 500, req);
|
|
3651
|
+
return;
|
|
3652
|
+
}
|
|
3653
|
+
// Parse "files: N" from stdout for the response.
|
|
3654
|
+
const filesMatch = stdout.match(/files:\s+(\d+)/);
|
|
3655
|
+
const files = filesMatch ? parseInt(filesMatch[1], 10) : null;
|
|
3656
|
+
sendJson(res, {
|
|
3657
|
+
ok: true,
|
|
3658
|
+
target: targetPath,
|
|
3659
|
+
files,
|
|
3660
|
+
stdout: stdout.trim().split("\n").slice(-8).join("\n"),
|
|
3661
|
+
}, 200, req);
|
|
3662
|
+
});
|
|
3663
|
+
// Handle CORS preflight.
|
|
3664
|
+
//
|
|
3665
|
+
// 0836 US-002 (F-017 follow-up): every authenticated /api/* request now
|
|
3666
|
+
// carries the `X-Studio-Token` custom header. Browser-initiated cross-origin
|
|
3667
|
+
// fetches issue a CORS preflight; the response MUST list `X-Studio-Token`
|
|
3668
|
+
// in `Access-Control-Allow-Headers` or the browser blocks the actual
|
|
3669
|
+
// request. The desktop hot path is same-origin (WebView is navigated to
|
|
3670
|
+
// `http://127.0.0.1:{port}/`) and skips preflight, but CLI users opening
|
|
3671
|
+
// the studio in a regular browser tab — and any future cross-origin
|
|
3672
|
+
// tooling — depend on this echo.
|
|
3473
3673
|
router.options = (req, res) => {
|
|
3474
3674
|
const origin = req.headers.origin;
|
|
3475
3675
|
if (origin && /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(origin)) {
|
|
3476
3676
|
res.writeHead(204, {
|
|
3477
3677
|
"Access-Control-Allow-Origin": origin,
|
|
3478
3678
|
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
3479
|
-
"Access-Control-Allow-Headers": "Content-Type",
|
|
3679
|
+
"Access-Control-Allow-Headers": "Content-Type, X-Studio-Token",
|
|
3480
3680
|
"Access-Control-Max-Age": "3600",
|
|
3481
3681
|
});
|
|
3482
3682
|
}
|