vskill 0.5.88 → 0.5.89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +28 -0
  2. package/agents.json +124 -0
  3. package/dist/agents/agents-registry.d.ts +29 -5
  4. package/dist/agents/agents-registry.js +133 -12
  5. package/dist/agents/agents-registry.js.map +1 -1
  6. package/dist/commands/add.d.ts +2 -16
  7. package/dist/commands/add.js +3 -89
  8. package/dist/commands/add.js.map +1 -1
  9. package/dist/eval/plugin-scanner.d.ts +14 -0
  10. package/dist/eval/plugin-scanner.js +237 -0
  11. package/dist/eval/plugin-scanner.js.map +1 -0
  12. package/dist/eval/skill-scanner.d.ts +18 -0
  13. package/dist/eval/skill-scanner.js +82 -9
  14. package/dist/eval/skill-scanner.js.map +1 -1
  15. package/dist/eval/standalone-skill-scanner.d.ts +7 -0
  16. package/dist/eval/standalone-skill-scanner.js +76 -0
  17. package/dist/eval/standalone-skill-scanner.js.map +1 -0
  18. package/dist/eval-server/api-routes.d.ts +1 -0
  19. package/dist/eval-server/api-routes.js +12 -1
  20. package/dist/eval-server/api-routes.js.map +1 -1
  21. package/dist/eval-server/eval-server.d.ts +9 -1
  22. package/dist/eval-server/eval-server.js +51 -1
  23. package/dist/eval-server/eval-server.js.map +1 -1
  24. package/dist/eval-server/workspace-routes.d.ts +14 -0
  25. package/dist/eval-server/workspace-routes.js +108 -0
  26. package/dist/eval-server/workspace-routes.js.map +1 -0
  27. package/dist/eval-server/workspace-store.d.ts +36 -0
  28. package/dist/eval-server/workspace-store.js +144 -0
  29. package/dist/eval-server/workspace-store.js.map +1 -0
  30. package/dist/eval-ui/assets/{CommandPalette-B75Qr7kR.js → CommandPalette-DrPj2MN0.js} +1 -1
  31. package/dist/eval-ui/assets/{UpdateDropdown-C9KlPZnX.js → UpdateDropdown-CXwYZ5VE.js} +1 -1
  32. package/dist/eval-ui/assets/index-D5A8Jbqy.css +1 -0
  33. package/dist/eval-ui/assets/index-Dg6Nwzn_.js +90 -0
  34. package/dist/eval-ui/index.html +2 -2
  35. package/dist/shared/copy-plugin-filtered.d.ts +13 -0
  36. package/dist/shared/copy-plugin-filtered.js +97 -0
  37. package/dist/shared/copy-plugin-filtered.js.map +1 -0
  38. package/dist/studio/lib/ops-log.d.ts +10 -0
  39. package/dist/studio/lib/ops-log.js +99 -0
  40. package/dist/studio/lib/ops-log.js.map +1 -0
  41. package/dist/studio/lib/provenance.d.ts +4 -0
  42. package/dist/studio/lib/provenance.js +41 -0
  43. package/dist/studio/lib/provenance.js.map +1 -0
  44. package/dist/studio/lib/scope-transfer.d.ts +28 -0
  45. package/dist/studio/lib/scope-transfer.js +112 -0
  46. package/dist/studio/lib/scope-transfer.js.map +1 -0
  47. package/dist/studio/routes/index.d.ts +2 -0
  48. package/dist/studio/routes/index.js +19 -0
  49. package/dist/studio/routes/index.js.map +1 -0
  50. package/dist/studio/routes/ops.d.ts +2 -0
  51. package/dist/studio/routes/ops.js +63 -0
  52. package/dist/studio/routes/ops.js.map +1 -0
  53. package/dist/studio/routes/promote.d.ts +2 -0
  54. package/dist/studio/routes/promote.js +118 -0
  55. package/dist/studio/routes/promote.js.map +1 -0
  56. package/dist/studio/routes/revert.d.ts +2 -0
  57. package/dist/studio/routes/revert.js +97 -0
  58. package/dist/studio/routes/revert.js.map +1 -0
  59. package/dist/studio/routes/test-install.d.ts +2 -0
  60. package/dist/studio/routes/test-install.js +102 -0
  61. package/dist/studio/routes/test-install.js.map +1 -0
  62. package/dist/studio/types.d.ts +48 -0
  63. package/dist/studio/types.js +11 -0
  64. package/dist/studio/types.js.map +1 -0
  65. package/dist/utils/agent-filter.js +9 -1
  66. package/dist/utils/agent-filter.js.map +1 -1
  67. package/package.json +4 -1
  68. package/scripts/preuninstall.cjs +9 -2
  69. package/dist/eval-ui/assets/index-C4XKAX1s.css +0 -1
  70. package/dist/eval-ui/assets/index-D6QZl0bI.js +0 -89
@@ -46,8 +46,8 @@
46
46
  }
47
47
  })();
48
48
  </script>
49
- <script type="module" crossorigin src="/assets/index-D6QZl0bI.js"></script>
50
- <link rel="stylesheet" crossorigin href="/assets/index-C4XKAX1s.css">
49
+ <script type="module" crossorigin src="/assets/index-Dg6Nwzn_.js"></script>
50
+ <link rel="stylesheet" crossorigin href="/assets/index-D5A8Jbqy.css">
51
51
  <link rel="stylesheet" crossorigin href="/assets/fonts-i7Lkz2zN.css">
52
52
  </head>
53
53
  <body>
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Returns true for .md files that should NOT be installed into an agent's
3
+ * commands directory. Claude Code (and similar agents) register every .md
4
+ * file they find recursively as a slash command, so plugin-internal files
5
+ * must be excluded.
6
+ */
7
+ export declare function shouldSkipFromCommands(relPath: string): boolean;
8
+ /**
9
+ * Check if a .md file inside a plugin should be promoted to SKILL.md.
10
+ * Returns true when the file is likely skill content (not documentation, not inside agents/).
11
+ */
12
+ export declare function isSkillMdCandidate(entry: string, relPath: string): boolean;
13
+ export declare function copyPluginFiltered(sourceDir: string, targetDir: string, relBase?: string): void;
@@ -0,0 +1,97 @@
1
+ // ---------------------------------------------------------------------------
2
+ // copyPluginFiltered — shared plugin-to-target copy helper
3
+ // ---------------------------------------------------------------------------
4
+ // Lifted from src/commands/add.ts so both the `vskill add` command and the
5
+ // studio scope-transfer routes (src/studio/routes/*) can consume one
6
+ // implementation without creating a commands/ → studio/ dependency.
7
+ // ---------------------------------------------------------------------------
8
+ import { mkdirSync, writeFileSync, readFileSync, copyFileSync, statSync, readdirSync, } from "node:fs";
9
+ import { join, basename } from "node:path";
10
+ import { ensureFrontmatter } from "../installer/frontmatter.js";
11
+ /**
12
+ * Returns true for .md files that should NOT be installed into an agent's
13
+ * commands directory. Claude Code (and similar agents) register every .md
14
+ * file they find recursively as a slash command, so plugin-internal files
15
+ * must be excluded.
16
+ */
17
+ export function shouldSkipFromCommands(relPath) {
18
+ const normalized = relPath.replace(/\\/g, "/");
19
+ const parts = normalized.split("/");
20
+ const filename = parts[parts.length - 1];
21
+ if (!filename.endsWith(".md"))
22
+ return false;
23
+ if (parts.length === 1 && filename === "PLUGIN.md")
24
+ return true;
25
+ if (filename === "README.md")
26
+ return true;
27
+ if (filename === "FRESHNESS.md")
28
+ return true;
29
+ if (parts.length > 1 && parts[0].startsWith("."))
30
+ return true;
31
+ const internalRootDirs = new Set(["knowledge-base", "lib", "templates", "scripts", "hooks"]);
32
+ if (parts.length > 1 && internalRootDirs.has(parts[0]))
33
+ return true;
34
+ if (parts[0] === "skills" && parts.length > 2 && filename !== "SKILL.md") {
35
+ // Allow agents/*.md files inside skill directories (skills/{name}/agents/*.md)
36
+ if (parts[2] === "agents" && filename.endsWith(".md"))
37
+ return false;
38
+ return true;
39
+ }
40
+ return false;
41
+ }
42
+ /** Skip-list of .md files that are documentation, not skill content. */
43
+ const COPY_SKIP_MD = new Set(["README.md", "CHANGELOG.md", "LICENSE.md", "FRESHNESS.md", "PLUGIN.md"]);
44
+ /**
45
+ * Check if a .md file inside a plugin should be promoted to SKILL.md.
46
+ * Returns true when the file is likely skill content (not documentation, not inside agents/).
47
+ */
48
+ export function isSkillMdCandidate(entry, relPath) {
49
+ if (!entry.endsWith(".md"))
50
+ return false;
51
+ if (entry === "SKILL.md")
52
+ return false;
53
+ if (COPY_SKIP_MD.has(entry))
54
+ return false;
55
+ const parts = relPath.split("/");
56
+ // Don't promote files inside agents/ subdirectories — those are agent templates
57
+ if (parts.some((p) => p === "agents"))
58
+ return false;
59
+ // Don't promote files from commands/ — those are slash commands, not skill content
60
+ if (parts.some((p) => p === "commands"))
61
+ return false;
62
+ return true;
63
+ }
64
+ export function copyPluginFiltered(sourceDir, targetDir, relBase = "") {
65
+ mkdirSync(targetDir, { recursive: true });
66
+ const entries = readdirSync(sourceDir);
67
+ for (const entry of entries) {
68
+ const relPath = relBase ? `${relBase}/${entry}` : entry;
69
+ const sourcePath = join(sourceDir, entry);
70
+ const stat = statSync(sourcePath);
71
+ if (stat.isDirectory()) {
72
+ // Flatten: root-level commands/ and skills/ merge into the parent target dir
73
+ const isFlattened = !relBase && (entry === "commands" || entry === "skills");
74
+ let nextTargetDir = isFlattened ? targetDir : join(targetDir, entry);
75
+ // Prevent double-nesting: when inside a flattened skills/ directory and
76
+ // a skill subdirectory has the same name as the target directory
77
+ // (e.g., single-skill plugin where pluginName == skillName),
78
+ // merge into the target instead of creating a redundant nested directory.
79
+ const inFlattenedSkills = relBase === "skills";
80
+ if (inFlattenedSkills && entry === basename(targetDir)) {
81
+ nextTargetDir = targetDir;
82
+ }
83
+ copyPluginFiltered(sourcePath, nextTargetDir, relPath);
84
+ }
85
+ else if (stat.isFile() && !shouldSkipFromCommands(relPath)) {
86
+ if (entry === "SKILL.md" || isSkillMdCandidate(entry, relPath)) {
87
+ const raw = readFileSync(sourcePath, "utf-8");
88
+ const skillName = basename(relBase || sourceDir);
89
+ writeFileSync(join(targetDir, "SKILL.md"), ensureFrontmatter(raw, skillName), "utf-8");
90
+ }
91
+ else {
92
+ copyFileSync(sourcePath, join(targetDir, entry));
93
+ }
94
+ }
95
+ }
96
+ }
97
+ //# sourceMappingURL=copy-plugin-filtered.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy-plugin-filtered.js","sourceRoot":"","sources":["../../src/shared/copy-plugin-filtered.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { StudioOp } from "../types.js";
2
+ export declare function getLogPath(): string;
3
+ export declare function appendOp(op: StudioOp): Promise<void>;
4
+ export interface ListOpsOptions {
5
+ before?: number;
6
+ limit?: number;
7
+ }
8
+ export declare function listOps(opts?: ListOpsOptions): Promise<StudioOp[]>;
9
+ export declare function subscribe(fn: (op: StudioOp) => void): () => void;
10
+ export declare function deleteOp(id: string): Promise<void>;
@@ -0,0 +1,99 @@
1
+ // ---------------------------------------------------------------------------
2
+ // ops-log — append-only JSONL log of studio file-changing operations
3
+ // ---------------------------------------------------------------------------
4
+ // Plan.md §2.2 / AC-US4-01 / AC-US4-04 / AC-US4-05.
5
+ //
6
+ // File: ~/.vskill/studio-ops.jsonl (override via VSKILL_OPS_LOG_PATH env).
7
+ // Atomicity: openSync(O_APPEND | O_WRONLY | O_CREAT) + single writeSync of a
8
+ // newline-terminated line. POSIX guarantees writes < PIPE_BUF (4 KiB) are
9
+ // atomic with O_APPEND — our ops are well under.
10
+ //
11
+ // In-process subscribe()/listOps()/deleteOp() (tombstone) — no cross-process IPC.
12
+ // ---------------------------------------------------------------------------
13
+ import { openSync, writeSync, closeSync, mkdirSync, existsSync, readFileSync } from "node:fs";
14
+ import { dirname, join } from "node:path";
15
+ import { homedir } from "node:os";
16
+ import { EventEmitter } from "node:events";
17
+ const APPEND_FLAGS = "a";
18
+ const emitter = new EventEmitter();
19
+ emitter.setMaxListeners(0);
20
+ export function getLogPath() {
21
+ return process.env.VSKILL_OPS_LOG_PATH || join(homedir(), ".vskill", "studio-ops.jsonl");
22
+ }
23
+ function ensureDir(path) {
24
+ const dir = dirname(path);
25
+ if (!existsSync(dir))
26
+ mkdirSync(dir, { recursive: true });
27
+ }
28
+ export async function appendOp(op) {
29
+ const path = getLogPath();
30
+ ensureDir(path);
31
+ const line = JSON.stringify(op) + "\n";
32
+ // Single openSync + writeSync + closeSync — atomic for small writes under POSIX O_APPEND.
33
+ const fd = openSync(path, APPEND_FLAGS);
34
+ try {
35
+ writeSync(fd, line);
36
+ }
37
+ finally {
38
+ closeSync(fd);
39
+ }
40
+ emitter.emit("op", op);
41
+ }
42
+ function readAllLines(path) {
43
+ if (!existsSync(path))
44
+ return [];
45
+ const raw = readFileSync(path, "utf-8");
46
+ const out = [];
47
+ for (const line of raw.split("\n")) {
48
+ const trimmed = line.trim();
49
+ if (!trimmed)
50
+ continue;
51
+ try {
52
+ out.push(JSON.parse(trimmed));
53
+ }
54
+ catch {
55
+ // Skip malformed line — log corruption shouldn't crash listOps.
56
+ }
57
+ }
58
+ return out;
59
+ }
60
+ export async function listOps(opts = {}) {
61
+ const path = getLogPath();
62
+ const all = readAllLines(path);
63
+ const tombstoned = new Set();
64
+ for (const entry of all) {
65
+ if (entry.tombstone === true && entry.id) {
66
+ tombstoned.add(entry.id);
67
+ }
68
+ }
69
+ let ops = all
70
+ .filter((e) => !e.tombstone && typeof e.ts === "number")
71
+ .filter((op) => !tombstoned.has(op.id));
72
+ // Newest first.
73
+ ops.sort((a, b) => b.ts - a.ts);
74
+ if (opts.before != null) {
75
+ ops = ops.filter((op) => op.ts < opts.before);
76
+ }
77
+ if (opts.limit != null) {
78
+ ops = ops.slice(0, opts.limit);
79
+ }
80
+ return ops;
81
+ }
82
+ export function subscribe(fn) {
83
+ emitter.on("op", fn);
84
+ return () => emitter.off("op", fn);
85
+ }
86
+ export async function deleteOp(id) {
87
+ const path = getLogPath();
88
+ ensureDir(path);
89
+ const line = JSON.stringify({ id, tombstone: true }) + "\n";
90
+ const fd = openSync(path, APPEND_FLAGS);
91
+ try {
92
+ writeSync(fd, line);
93
+ }
94
+ finally {
95
+ closeSync(fd);
96
+ }
97
+ emitter.emit("delete", id);
98
+ }
99
+ //# sourceMappingURL=ops-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ops-log.js","sourceRoot":"","sources":["../../../src/studio/lib/ops-log.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { Provenance } from "../types.js";
2
+ export declare function writeProvenance(skillDir: string, p: Provenance): Promise<void>;
3
+ export declare function readProvenance(skillDir: string): Promise<Provenance | null>;
4
+ export declare function removeProvenance(skillDir: string): Promise<void>;
@@ -0,0 +1,41 @@
1
+ // ---------------------------------------------------------------------------
2
+ // provenance — read/write/remove .vskill-meta.json sidecar in skill dirs
3
+ // ---------------------------------------------------------------------------
4
+ // See ADR 0688-02 (sidecar vs frontmatter). The sidecar enriches OWN rows
5
+ // for UI purposes only; it does NOT change scope classification.
6
+ //
7
+ // Read is best-effort: ENOENT or JSON parse error → returns null, never
8
+ // throws. This keeps the scanner robust against stale/corrupt files.
9
+ // ---------------------------------------------------------------------------
10
+ import { promises as fs } from "node:fs";
11
+ import { join } from "node:path";
12
+ const SIDECAR_NAME = ".vskill-meta.json";
13
+ export async function writeProvenance(skillDir, p) {
14
+ await fs.mkdir(skillDir, { recursive: true });
15
+ await fs.writeFile(join(skillDir, SIDECAR_NAME), JSON.stringify(p, null, 2), "utf-8");
16
+ }
17
+ export async function readProvenance(skillDir) {
18
+ try {
19
+ const raw = await fs.readFile(join(skillDir, SIDECAR_NAME), "utf-8");
20
+ return JSON.parse(raw);
21
+ }
22
+ catch (err) {
23
+ const code = err?.code;
24
+ if (code !== "ENOENT") {
25
+ // Parse error or transient IO — log at debug, do not throw.
26
+ console.error(`[provenance] readProvenance(${skillDir}) failed: ${err.message}`);
27
+ }
28
+ return null;
29
+ }
30
+ }
31
+ export async function removeProvenance(skillDir) {
32
+ try {
33
+ await fs.unlink(join(skillDir, SIDECAR_NAME));
34
+ }
35
+ catch (err) {
36
+ const code = err?.code;
37
+ if (code !== "ENOENT")
38
+ throw err;
39
+ }
40
+ }
41
+ //# sourceMappingURL=provenance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provenance.js","sourceRoot":"","sources":["../../../src/studio/lib/provenance.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,yEAAyE;AACzE,8EAA8E;AAC9E,0EAA0E;AAC1E,iEAAiE;AACjE,EAAE;AACF,wEAAwE;AACxE,qEAAqE;AACrE,8EAA8E;AAE9E,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAIjC,MAAM,YAAY,GAAG,mBAAmB,CAAC;AAEzC,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAAgB,EAAE,CAAa;IACnE,MAAM,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AACxF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,QAAgB;IACnD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;QACrE,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAe,CAAC;IACvC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,GAAI,GAA6B,EAAE,IAAI,CAAC;QAClD,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtB,4DAA4D;YAC5D,OAAO,CAAC,KAAK,CAAC,+BAA+B,QAAQ,aAAc,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9F,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,QAAgB;IACrD,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC;IAChD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,GAAI,GAA6B,EAAE,IAAI,CAAC;QAClD,IAAI,IAAI,KAAK,QAAQ;YAAE,MAAM,GAAG,CAAC;IACnC,CAAC;AACH,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { SkillScope, TransferEvent } from "../types.js";
2
+ export interface TransferRequest {
3
+ plugin: string;
4
+ skill: string;
5
+ fromScope: SkillScope;
6
+ toScope: SkillScope;
7
+ root: string;
8
+ home: string;
9
+ overwrite?: boolean;
10
+ }
11
+ export interface TransferResult {
12
+ sourcePath: string;
13
+ destPath: string;
14
+ filesWritten: number;
15
+ }
16
+ export type SSEEmit = (e: TransferEvent) => void;
17
+ export declare class CollisionError extends Error {
18
+ readonly path: string;
19
+ readonly code: "collision";
20
+ constructor(path: string);
21
+ }
22
+ export declare class MissingSourceError extends Error {
23
+ readonly path: string;
24
+ readonly code: "missing-source";
25
+ constructor(path: string);
26
+ }
27
+ export declare function resolveScopePath(scope: SkillScope, root: string, skill: string, home: string): string;
28
+ export declare function transfer(req: TransferRequest, emit: SSEEmit): Promise<TransferResult>;
@@ -0,0 +1,112 @@
1
+ // ---------------------------------------------------------------------------
2
+ // scope-transfer — copy a skill between OWN / INSTALLED / GLOBAL scopes
3
+ // ---------------------------------------------------------------------------
4
+ // Used by /api/skills/:plugin/:skill/promote | test-install | revert routes.
5
+ // Reuses src/shared/copy-plugin-filtered.ts for file copy + filtering so the
6
+ // logic is shared with `vskill add`.
7
+ //
8
+ // Path contract (plan.md §2.3):
9
+ // OWN → <root>/skills/<skill>/
10
+ // INSTALLED → <root>/.claude/skills/<skill>/ (Claude-default; non-Claude agents deferred)
11
+ // GLOBAL → <home>/.claude/skills/<skill>/
12
+ //
13
+ // Provenance sidecar (.vskill-meta.json) must NEVER leak out of OWN scope —
14
+ // filtered inside transfer before invoking copy.
15
+ // ---------------------------------------------------------------------------
16
+ import { existsSync, rmSync, readdirSync, statSync, copyFileSync, mkdirSync } from "node:fs";
17
+ import { join, basename } from "node:path";
18
+ import { copyPluginFiltered, isSkillMdCandidate, shouldSkipFromCommands, } from "../../shared/copy-plugin-filtered.js";
19
+ export class CollisionError extends Error {
20
+ path;
21
+ code = "collision";
22
+ constructor(path) {
23
+ super(`destination already exists: ${path}`);
24
+ this.name = "CollisionError";
25
+ this.path = path;
26
+ }
27
+ }
28
+ export class MissingSourceError extends Error {
29
+ path;
30
+ code = "missing-source";
31
+ constructor(path) {
32
+ super(`source path missing: ${path}`);
33
+ this.name = "MissingSourceError";
34
+ this.path = path;
35
+ }
36
+ }
37
+ export function resolveScopePath(scope, root, skill, home) {
38
+ switch (scope) {
39
+ case "own":
40
+ return join(root, "skills", skill);
41
+ case "installed":
42
+ return join(root, ".claude", "skills", skill);
43
+ case "global":
44
+ return join(home, ".claude", "skills", skill);
45
+ }
46
+ }
47
+ /**
48
+ * Count files written recursively into a directory — used for the SSE
49
+ * `copied` event payload.
50
+ */
51
+ function countFiles(dir) {
52
+ let n = 0;
53
+ for (const entry of readdirSync(dir)) {
54
+ const full = join(dir, entry);
55
+ const st = statSync(full);
56
+ if (st.isDirectory())
57
+ n += countFiles(full);
58
+ else if (st.isFile())
59
+ n += 1;
60
+ }
61
+ return n;
62
+ }
63
+ /**
64
+ * Copy a OWN skill directory filtering out `.vskill-meta.json`. Unlike
65
+ * copyPluginFiltered this does not do the plugin-root flattening, because
66
+ * OWN → INSTALLED|GLOBAL is a straight skill-dir copy with one exclusion.
67
+ */
68
+ function copyOwnSkillFiltered(sourceDir, targetDir, relBase = "") {
69
+ mkdirSync(targetDir, { recursive: true });
70
+ for (const entry of readdirSync(sourceDir)) {
71
+ if (!relBase && entry === ".vskill-meta.json")
72
+ continue;
73
+ const relPath = relBase ? `${relBase}/${entry}` : entry;
74
+ const sourcePath = join(sourceDir, entry);
75
+ const st = statSync(sourcePath);
76
+ if (st.isDirectory()) {
77
+ copyOwnSkillFiltered(sourcePath, join(targetDir, entry), relPath);
78
+ }
79
+ else if (st.isFile() && !shouldSkipFromCommands(relPath)) {
80
+ copyFileSync(sourcePath, join(targetDir, entry));
81
+ }
82
+ }
83
+ void isSkillMdCandidate;
84
+ void basename;
85
+ }
86
+ export async function transfer(req, emit) {
87
+ const sourcePath = resolveScopePath(req.fromScope, req.root, req.skill, req.home);
88
+ const destPath = resolveScopePath(req.toScope, req.root, req.skill, req.home);
89
+ if (!existsSync(sourcePath)) {
90
+ throw new MissingSourceError(sourcePath);
91
+ }
92
+ if (existsSync(destPath) && !req.overwrite) {
93
+ throw new CollisionError(destPath);
94
+ }
95
+ // If overwrite, clear the destination first so stale files aren't left behind.
96
+ if (existsSync(destPath) && req.overwrite) {
97
+ rmSync(destPath, { recursive: true, force: true });
98
+ }
99
+ // Branch on direction — OWN → * uses the sidecar-filtering copier,
100
+ // * → OWN uses the plugin-root copier (which already ignores PLUGIN.md etc
101
+ // and promotes SKILL.md candidates).
102
+ if (req.fromScope === "own") {
103
+ copyOwnSkillFiltered(sourcePath, destPath);
104
+ }
105
+ else {
106
+ copyPluginFiltered(sourcePath, destPath);
107
+ }
108
+ const filesWritten = countFiles(destPath);
109
+ emit({ type: "copied", filesWritten });
110
+ return { sourcePath, destPath, filesWritten };
111
+ }
112
+ //# sourceMappingURL=scope-transfer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope-transfer.js","sourceRoot":"","sources":["../../../src/studio/lib/scope-transfer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Router } from "../../eval-server/router.js";
2
+ export declare function registerScopeTransferRoutes(router: Router, root: string): void;
@@ -0,0 +1,19 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Studio scope-transfer routes — aggregate registration
3
+ // ---------------------------------------------------------------------------
4
+ // Registered from src/eval-server/eval-server.ts alongside the existing
5
+ // registerSkillCreateRoutes() etc. Keeps all scope-transfer HTTP surface in
6
+ // one module so cross-route concerns (SSE headers, error shape) stay
7
+ // consistent.
8
+ // ---------------------------------------------------------------------------
9
+ import { registerPromoteRoute } from "./promote.js";
10
+ import { registerTestInstallRoute } from "./test-install.js";
11
+ import { registerRevertRoute } from "./revert.js";
12
+ import { registerOpsRoutes } from "./ops.js";
13
+ export function registerScopeTransferRoutes(router, root) {
14
+ registerPromoteRoute(router, root);
15
+ registerTestInstallRoute(router, root);
16
+ registerRevertRoute(router, root);
17
+ registerOpsRoutes(router);
18
+ }
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/studio/routes/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,wDAAwD;AACxD,8EAA8E;AAC9E,wEAAwE;AACxE,4EAA4E;AAC5E,qEAAqE;AACrE,cAAc;AACd,8EAA8E;AAG9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE7C,MAAM,UAAU,2BAA2B,CAAC,MAAc,EAAE,IAAY;IACtE,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACnC,wBAAwB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACvC,mBAAmB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAClC,iBAAiB,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Router } from "../../eval-server/router.js";
2
+ export declare function registerOpsRoutes(router: Router): void;
@@ -0,0 +1,63 @@
1
+ // ---------------------------------------------------------------------------
2
+ // ops routes — studio ops-log REST + SSE
3
+ // ---------------------------------------------------------------------------
4
+ // GET /api/studio/ops — paginated JSON (AC-US4-05)
5
+ // GET /api/studio/ops/stream — long-lived SSE op stream (AC-US4-04)
6
+ // DELETE /api/studio/ops/:id — tombstone (soft delete)
7
+ // ---------------------------------------------------------------------------
8
+ import { sendJson } from "../../eval-server/router.js";
9
+ import { initSSE, sendSSE } from "../../eval-server/sse-helpers.js";
10
+ import { listOps, subscribe, deleteOp } from "../lib/ops-log.js";
11
+ function parseQuery(url) {
12
+ return new URL(url || "/", "http://localhost").searchParams;
13
+ }
14
+ export function registerOpsRoutes(router) {
15
+ // GET /api/studio/ops?limit=50&before=<ts>
16
+ router.get("/api/studio/ops", async (req, res) => {
17
+ const q = parseQuery(req.url);
18
+ const limitRaw = q.get("limit");
19
+ const beforeRaw = q.get("before");
20
+ const limit = limitRaw != null ? Number(limitRaw) : 50;
21
+ const before = beforeRaw != null ? Number(beforeRaw) : undefined;
22
+ const ops = await listOps({
23
+ limit: Number.isFinite(limit) ? limit : 50,
24
+ before: before != null && Number.isFinite(before) ? before : undefined,
25
+ });
26
+ sendJson(res, { ok: true, ops }, 200, req);
27
+ });
28
+ // GET /api/studio/ops/stream — long-lived SSE of new op events + heartbeat.
29
+ router.get("/api/studio/ops/stream", async (req, res) => {
30
+ initSSE(res, req);
31
+ const unsub = subscribe((op) => {
32
+ try {
33
+ sendSSE(res, "op", op);
34
+ }
35
+ catch {
36
+ // Writing after stream close — safe to swallow.
37
+ }
38
+ });
39
+ const heartbeat = setInterval(() => {
40
+ try {
41
+ sendSSE(res, "heartbeat", { ts: Date.now() });
42
+ }
43
+ catch { }
44
+ }, 3000);
45
+ const cleanup = () => {
46
+ clearInterval(heartbeat);
47
+ unsub();
48
+ };
49
+ req.on("close", cleanup);
50
+ req.on("aborted", cleanup);
51
+ });
52
+ // DELETE /api/studio/ops/:id — tombstone.
53
+ router.delete("/api/studio/ops/:id", async (req, res, params) => {
54
+ const { id } = params;
55
+ if (!id) {
56
+ sendJson(res, { ok: false, error: "missing id" }, 400, req);
57
+ return;
58
+ }
59
+ await deleteOp(id);
60
+ sendJson(res, { ok: true, id }, 200, req);
61
+ });
62
+ }
63
+ //# sourceMappingURL=ops.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ops.js","sourceRoot":"","sources":["../../../src/studio/routes/ops.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Router } from "../../eval-server/router.js";
2
+ export declare function registerPromoteRoute(router: Router, root: string, home?: string): void;