vskill 1.0.13 → 1.0.15

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 (102) hide show
  1. package/README.md +63 -2
  2. package/agents.json +1 -1
  3. package/dist/bin.js +0 -0
  4. package/dist/clone/github-scaffold.d.ts +38 -0
  5. package/dist/clone/github-scaffold.js +108 -0
  6. package/dist/clone/github-scaffold.js.map +1 -0
  7. package/dist/clone/provenance-fork.d.ts +34 -0
  8. package/dist/clone/provenance-fork.js +97 -0
  9. package/dist/clone/provenance-fork.js.map +1 -0
  10. package/dist/clone/reference-scanner.d.ts +19 -0
  11. package/dist/clone/reference-scanner.js +144 -0
  12. package/dist/clone/reference-scanner.js.map +1 -0
  13. package/dist/clone/skill-locator.d.ts +26 -0
  14. package/dist/clone/skill-locator.js +248 -0
  15. package/dist/clone/skill-locator.js.map +1 -0
  16. package/dist/clone/target-router.d.ts +73 -0
  17. package/dist/clone/target-router.js +200 -0
  18. package/dist/clone/target-router.js.map +1 -0
  19. package/dist/clone/types.d.ts +82 -0
  20. package/dist/clone/types.js +11 -0
  21. package/dist/clone/types.js.map +1 -0
  22. package/dist/commands/add.js +96 -32
  23. package/dist/commands/add.js.map +1 -1
  24. package/dist/commands/auth.d.ts +23 -0
  25. package/dist/commands/auth.js +273 -0
  26. package/dist/commands/auth.js.map +1 -0
  27. package/dist/commands/check.d.ts +55 -0
  28. package/dist/commands/check.js +279 -0
  29. package/dist/commands/check.js.map +1 -0
  30. package/dist/commands/clone-prompts.d.ts +13 -0
  31. package/dist/commands/clone-prompts.js +67 -0
  32. package/dist/commands/clone-prompts.js.map +1 -0
  33. package/dist/commands/clone.d.ts +70 -0
  34. package/dist/commands/clone.js +649 -0
  35. package/dist/commands/clone.js.map +1 -0
  36. package/dist/commands/eval/serve.js +8 -1
  37. package/dist/commands/eval/serve.js.map +1 -1
  38. package/dist/commands/keys.js +54 -2
  39. package/dist/commands/keys.js.map +1 -1
  40. package/dist/core/agent-prompts.d.ts +35 -0
  41. package/dist/core/agent-prompts.js +201 -0
  42. package/dist/core/agent-prompts.js.map +1 -0
  43. package/dist/core/skill-generator.d.ts +25 -3
  44. package/dist/core/skill-generator.js +131 -0
  45. package/dist/core/skill-generator.js.map +1 -1
  46. package/dist/eval/skill-scanner.d.ts +2 -12
  47. package/dist/eval/skill-scanner.js +27 -5
  48. package/dist/eval/skill-scanner.js.map +1 -1
  49. package/dist/eval-server/api-routes.d.ts +14 -0
  50. package/dist/eval-server/api-routes.js +376 -31
  51. package/dist/eval-server/api-routes.js.map +1 -1
  52. package/dist/eval-server/data-events.d.ts +1 -1
  53. package/dist/eval-server/data-events.js.map +1 -1
  54. package/dist/eval-server/install-engine-routes-helpers.d.ts +1 -3
  55. package/dist/eval-server/install-engine-routes-helpers.js +6 -14
  56. package/dist/eval-server/install-engine-routes-helpers.js.map +1 -1
  57. package/dist/eval-server/origin-resolver.d.ts +42 -0
  58. package/dist/eval-server/origin-resolver.js +168 -0
  59. package/dist/eval-server/origin-resolver.js.map +1 -0
  60. package/dist/eval-server/platform-proxy.d.ts +10 -0
  61. package/dist/eval-server/platform-proxy.js +58 -2
  62. package/dist/eval-server/platform-proxy.js.map +1 -1
  63. package/dist/eval-server/skill-create-routes.d.ts +8 -0
  64. package/dist/eval-server/skill-create-routes.js +96 -0
  65. package/dist/eval-server/skill-create-routes.js.map +1 -1
  66. package/dist/eval-server/skill-resolver.js +40 -0
  67. package/dist/eval-server/skill-resolver.js.map +1 -1
  68. package/dist/eval-server/utils/resolve-editor.d.ts +18 -0
  69. package/dist/eval-server/utils/resolve-editor.js +77 -0
  70. package/dist/eval-server/utils/resolve-editor.js.map +1 -0
  71. package/dist/eval-server/utils/scan-install-locations.d.ts +7 -0
  72. package/dist/eval-server/utils/scan-install-locations.js +20 -0
  73. package/dist/eval-server/utils/scan-install-locations.js.map +1 -1
  74. package/dist/eval-server/utils/which.d.ts +15 -0
  75. package/dist/eval-server/utils/which.js +76 -0
  76. package/dist/eval-server/utils/which.js.map +1 -0
  77. package/dist/eval-ui/assets/{CreateSkillPage-T0YWZWw-.js → CreateSkillPage-BmbvQEzE.js} +1 -1
  78. package/dist/eval-ui/assets/{FindSkillsPalette-KcFM32hZ.js → FindSkillsPalette-D0Zjhm31.js} +2 -2
  79. package/dist/eval-ui/assets/{SearchPaletteCore-EhBtr4Xx.js → SearchPaletteCore-EhcN1xEa.js} +1 -1
  80. package/dist/eval-ui/assets/SkillDetailPanel-B5J60ffv.js +1 -0
  81. package/dist/eval-ui/assets/{UpdateDropdown-pjFhHTi6.js → UpdateDropdown-Celf0_Cr.js} +1 -1
  82. package/dist/eval-ui/assets/index-BV7k6fdk.js +124 -0
  83. package/dist/eval-ui/assets/{index-BKAvJDDF.css → index-CKLqBL52.css} +1 -1
  84. package/dist/eval-ui/index.html +2 -2
  85. package/dist/index.js +47 -0
  86. package/dist/index.js.map +1 -1
  87. package/dist/installer/frontmatter.d.ts +26 -0
  88. package/dist/installer/frontmatter.js +90 -0
  89. package/dist/installer/frontmatter.js.map +1 -1
  90. package/dist/lib/github-fetch.d.ts +22 -0
  91. package/dist/lib/github-fetch.js +152 -0
  92. package/dist/lib/github-fetch.js.map +1 -0
  93. package/dist/lib/keychain.d.ts +41 -0
  94. package/dist/lib/keychain.js +232 -0
  95. package/dist/lib/keychain.js.map +1 -0
  96. package/dist/studio/types.d.ts +13 -0
  97. package/dist/utils/claude-plugin.d.ts +26 -0
  98. package/dist/utils/claude-plugin.js +60 -0
  99. package/dist/utils/claude-plugin.js.map +1 -1
  100. package/package.json +2 -1
  101. package/dist/eval-ui/assets/SkillDetailPanel-cyzLsLcK.js +0 -1
  102. package/dist/eval-ui/assets/index-C3S9iHnq.js +0 -122
@@ -0,0 +1,152 @@
1
+ // ---------------------------------------------------------------------------
2
+ // github-fetch.ts — central, authenticated fetch helper for any vskill code
3
+ // path that talks to GitHub.
4
+ //
5
+ // Responsibilities:
6
+ // 1. Inject `Authorization: Bearer <token>` from the OS keychain when one is
7
+ // available AND the URL targets an allow-listed GitHub host.
8
+ // 2. Stamp `User-Agent: vskill/<cli-version>` so GitHub's abuse heuristics
9
+ // don't 403 our requests.
10
+ // 3. Enforce SSRF allowlist: only api.github.com + raw.githubusercontent.com.
11
+ // 4. Retry on 429 / Retry-After (capped at 30s, max 3 retries).
12
+ // 5. Surface 401 with an actionable message ("Run `vskill auth login`").
13
+ // 6. Refuse `/search/code` URLs entirely — that endpoint has a 10/min cap
14
+ // and burns the whole installation budget if hit by accident.
15
+ //
16
+ // Designed for dependency injection (tokenProvider, fetchImpl, sleep) so tests
17
+ // never touch the real network or the OS keychain.
18
+ // ---------------------------------------------------------------------------
19
+ import { getDefaultKeychain } from "./keychain.js";
20
+ const ALLOWED_HOSTS = new Set([
21
+ "api.github.com",
22
+ "raw.githubusercontent.com",
23
+ ]);
24
+ const MAX_RETRIES = 3;
25
+ const MAX_RETRY_AFTER_SECONDS = 30;
26
+ export class GitHubFetchError extends Error {
27
+ status;
28
+ body;
29
+ constructor(status, body, message) {
30
+ super(message);
31
+ this.name = "GitHubFetchError";
32
+ this.status = status;
33
+ this.body = body;
34
+ }
35
+ }
36
+ function defaultSleep(ms) {
37
+ return new Promise((resolve) => setTimeout(resolve, ms));
38
+ }
39
+ function isAllowedHost(url, allowed) {
40
+ try {
41
+ const u = new URL(url);
42
+ if (u.protocol !== "https:")
43
+ return false;
44
+ return allowed.has(u.hostname);
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ }
50
+ function isSearchCode(url) {
51
+ try {
52
+ const u = new URL(url);
53
+ return u.pathname.startsWith("/search/code");
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ }
59
+ function mergeHeaders(base, init) {
60
+ const out = { ...base };
61
+ if (!init?.headers)
62
+ return out;
63
+ if (init.headers instanceof Headers) {
64
+ init.headers.forEach((v, k) => {
65
+ out[k] = v;
66
+ });
67
+ }
68
+ else if (Array.isArray(init.headers)) {
69
+ for (const [k, v] of init.headers)
70
+ out[k] = v;
71
+ }
72
+ else {
73
+ for (const [k, v] of Object.entries(init.headers)) {
74
+ out[k] = v;
75
+ }
76
+ }
77
+ return out;
78
+ }
79
+ export function createGitHubFetch(opts = {}) {
80
+ const tokenProvider = opts.tokenProvider ?? (() => getDefaultKeychain().getGitHubToken());
81
+ // Defer to globalThis.fetch on every call so test suites that replace
82
+ // `globalThis.fetch = vi.fn(...)` continue to intercept requests.
83
+ const fetchImpl = opts.fetchImpl ?? ((url, init) => globalThis.fetch(url, init));
84
+ const sleep = opts.sleep ?? defaultSleep;
85
+ const version = opts.version ?? "vskill";
86
+ const allowed = opts.allowedHosts ?? ALLOWED_HOSTS;
87
+ return async function githubFetch(url, init = {}) {
88
+ if (isSearchCode(url)) {
89
+ throw new Error("github-fetch: /search/code is not permitted (rate-limit guardrail; use a workflow-specific endpoint instead)");
90
+ }
91
+ if (!isAllowedHost(url, allowed)) {
92
+ throw new Error(`github-fetch: host not allowed for SSRF guard (got ${safeHost(url)}; allowed: ${[...allowed].join(", ")})`);
93
+ }
94
+ const token = tokenProvider();
95
+ const baseHeaders = {
96
+ "User-Agent": `vskill/${version}`,
97
+ };
98
+ if (token)
99
+ baseHeaders.Authorization = `Bearer ${token}`;
100
+ const headers = mergeHeaders(baseHeaders, init);
101
+ let attempt = 0;
102
+ let lastResponse = null;
103
+ while (attempt <= MAX_RETRIES) {
104
+ const res = await fetchImpl(url, { ...init, headers });
105
+ lastResponse = res;
106
+ if (res.status === 429 || (res.status >= 500 && res.status < 600)) {
107
+ const retryAfterRaw = res.headers.get("retry-after");
108
+ const retryAfter = retryAfterRaw
109
+ ? Math.min(MAX_RETRY_AFTER_SECONDS, Math.max(0, Number(retryAfterRaw)))
110
+ : Math.min(MAX_RETRY_AFTER_SECONDS, 1 + attempt);
111
+ attempt++;
112
+ if (attempt > MAX_RETRIES)
113
+ break;
114
+ await sleep(retryAfter * 1000);
115
+ continue;
116
+ }
117
+ if (res.status === 401) {
118
+ const body = await res.text().catch(() => "");
119
+ throw new GitHubFetchError(401, body, token
120
+ ? "GitHub returned 401: token expired or insufficient scope. Run `vskill auth login` to re-authenticate."
121
+ : "GitHub returned 401: this resource requires authentication. Run `vskill auth login` to sign in.");
122
+ }
123
+ return res;
124
+ }
125
+ // Exhausted retries on 429/5xx — surface the last response.
126
+ return lastResponse;
127
+ };
128
+ }
129
+ function safeHost(url) {
130
+ try {
131
+ return new URL(url).hostname || "<invalid>";
132
+ }
133
+ catch {
134
+ return "<invalid>";
135
+ }
136
+ }
137
+ /**
138
+ * Module-level default helper. Most call sites just want
139
+ * `await githubFetch(url)`
140
+ * without juggling option objects.
141
+ */
142
+ let _defaultFetch = null;
143
+ export function githubFetch(url, init) {
144
+ if (!_defaultFetch)
145
+ _defaultFetch = createGitHubFetch();
146
+ return _defaultFetch(url, init);
147
+ }
148
+ /** Test-only reset hook. */
149
+ export function _resetDefaultGitHubFetchForTests() {
150
+ _defaultFetch = null;
151
+ }
152
+ //# sourceMappingURL=github-fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-fetch.js","sourceRoot":"","sources":["../../src/lib/github-fetch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ export declare const SERVICE_NAME = "vskill-github";
2
+ export declare const GITHUB_TOKEN_KEY = "github_token";
3
+ export interface KeyringBackend {
4
+ setPassword(service: string, account: string, password: string): void;
5
+ getPassword(service: string, account: string): string | null;
6
+ deletePassword(service: string, account: string): boolean;
7
+ }
8
+ export interface FsAdapter {
9
+ readFileSync(path: string): string;
10
+ writeFileSync(path: string, content: string, mode: number): void;
11
+ chmodSync(path: string, mode: number): void;
12
+ existsSync(path: string): boolean;
13
+ mkdirSync(path: string): void;
14
+ unlinkSync(path: string): void;
15
+ }
16
+ export interface KeychainOptions {
17
+ /** Optional keyring backend; defaults to a real `@napi-rs/keyring` adapter. */
18
+ keyring?: KeyringBackend | null;
19
+ /** Filesystem adapter (override for tests). */
20
+ fs?: FsAdapter;
21
+ /** Absolute path to the fallback file. Defaults to `~/.vskill/keys.env`. */
22
+ fallbackPath?: string;
23
+ /** Warn channel — receives one message when fallback kicks in. */
24
+ warn?: (message: string) => void;
25
+ }
26
+ export interface Keychain {
27
+ setGitHubToken(token: string): void;
28
+ getGitHubToken(): string | null;
29
+ clearGitHubToken(): boolean;
30
+ /** True iff fallback storage is in use (introspection for `vskill auth status`). */
31
+ usingFallback(): boolean;
32
+ }
33
+ export declare function createKeychain(opts?: KeychainOptions): Keychain;
34
+ export declare function getDefaultKeychain(): Keychain;
35
+ /** Test-only reset hook — never call from production code. */
36
+ export declare function _resetDefaultKeychainForTests(): void;
37
+ export declare function getGitHubToken(): string | null;
38
+ export declare function setGitHubToken(token: string): void;
39
+ export declare function clearGitHubToken(): boolean;
40
+ /** Last-4 redaction for log-safe output. */
41
+ export declare function redactToken(token: string | null | undefined): string;
@@ -0,0 +1,232 @@
1
+ // ---------------------------------------------------------------------------
2
+ // keychain.ts — OS-level secure storage for the vskill CLI's GitHub token.
3
+ //
4
+ // Backend selection (in order):
5
+ // 1. @napi-rs/keyring (macOS Keychain / Windows DPAPI / libsecret on Linux)
6
+ // 2. ~/.vskill/keys.env file with mode 0600 (when keyring is unavailable —
7
+ // headless Linux without keyring daemon, sandboxed environments, etc.)
8
+ //
9
+ // Public surface:
10
+ // - getGitHubToken(): string | null
11
+ // - setGitHubToken(token: string): void
12
+ // - clearGitHubToken(): boolean /// true if a token was removed
13
+ //
14
+ // Tokens are NEVER logged. Callers should redact via `redactToken()` before
15
+ // any user-facing output.
16
+ //
17
+ // Service identity:
18
+ // - service: "vskill-github"
19
+ // - account: "github_token"
20
+ // ---------------------------------------------------------------------------
21
+ import * as nodeFs from "node:fs";
22
+ import * as nodePath from "node:path";
23
+ import * as nodeOs from "node:os";
24
+ export const SERVICE_NAME = "vskill-github";
25
+ export const GITHUB_TOKEN_KEY = "github_token";
26
+ const DEFAULT_FALLBACK = nodePath.join(nodeOs.homedir(), ".vskill", "keys.env");
27
+ const defaultFs = {
28
+ readFileSync: (p) => nodeFs.readFileSync(p, { encoding: "utf8" }),
29
+ writeFileSync: (p, c, mode) => nodeFs.writeFileSync(p, c, { encoding: "utf8", mode }),
30
+ chmodSync: (p, mode) => nodeFs.chmodSync(p, mode),
31
+ existsSync: (p) => nodeFs.existsSync(p),
32
+ mkdirSync: (p) => nodeFs.mkdirSync(p, { recursive: true, mode: 0o700 }),
33
+ unlinkSync: (p) => nodeFs.unlinkSync(p),
34
+ };
35
+ function loadDefaultKeyring() {
36
+ // The native module is loaded lazily so the CLI still boots in environments
37
+ // where the binary isn't available (rare, since @napi-rs/keyring ships
38
+ // prebuilds for the major platforms — but we never want a missing native to
39
+ // crash unrelated subcommands like `vskill --version`).
40
+ try {
41
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
42
+ const mod = require("@napi-rs/keyring");
43
+ return {
44
+ setPassword(service, account, password) {
45
+ const entry = new mod.Entry(service, account);
46
+ entry.setPassword(password);
47
+ },
48
+ getPassword(service, account) {
49
+ try {
50
+ const entry = new mod.Entry(service, account);
51
+ return entry.getPassword();
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ },
57
+ deletePassword(service, account) {
58
+ try {
59
+ const entry = new mod.Entry(service, account);
60
+ return entry.deletePassword();
61
+ }
62
+ catch {
63
+ return false;
64
+ }
65
+ },
66
+ };
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ }
72
+ function parseEnvFile(content) {
73
+ const map = new Map();
74
+ for (const rawLine of content.split(/\r?\n/)) {
75
+ const line = rawLine.trim();
76
+ if (!line || line.startsWith("#"))
77
+ continue;
78
+ const eq = line.indexOf("=");
79
+ if (eq <= 0)
80
+ continue;
81
+ const key = line.slice(0, eq).trim();
82
+ const val = line.slice(eq + 1).trim();
83
+ map.set(key, val);
84
+ }
85
+ return map;
86
+ }
87
+ function serializeEnvMap(map) {
88
+ const lines = [
89
+ "# vskill keys.env — fallback secret store (mode 0600).",
90
+ "# Managed by vskill; edit at your own risk.",
91
+ ];
92
+ for (const [k, v] of map)
93
+ lines.push(`${k}=${v}`);
94
+ return lines.join("\n") + "\n";
95
+ }
96
+ export function createKeychain(opts = {}) {
97
+ const fs = opts.fs ?? defaultFs;
98
+ const fallbackPath = opts.fallbackPath ?? DEFAULT_FALLBACK;
99
+ const warn = opts.warn ?? ((msg) => process.stderr.write(`${msg}\n`));
100
+ let keyring;
101
+ if (opts.keyring !== undefined)
102
+ keyring = opts.keyring;
103
+ else
104
+ keyring = loadDefaultKeyring();
105
+ let warnedFallback = false;
106
+ let fallbackInUse = false;
107
+ function tryKeyring(fn) {
108
+ if (!keyring)
109
+ return { ok: false };
110
+ try {
111
+ return { ok: true, value: fn(keyring) };
112
+ }
113
+ catch {
114
+ return { ok: false };
115
+ }
116
+ }
117
+ function ensureFallbackWarned() {
118
+ fallbackInUse = true;
119
+ if (warnedFallback)
120
+ return;
121
+ warnedFallback = true;
122
+ warn(`vskill: OS keychain unavailable; storing GitHub token at ${fallbackPath} (mode 0600).`);
123
+ }
124
+ function readFallback() {
125
+ if (!fs.existsSync(fallbackPath))
126
+ return new Map();
127
+ try {
128
+ return parseEnvFile(fs.readFileSync(fallbackPath));
129
+ }
130
+ catch {
131
+ return new Map();
132
+ }
133
+ }
134
+ function writeFallback(map) {
135
+ const dir = nodePath.dirname(fallbackPath);
136
+ if (!fs.existsSync(dir))
137
+ fs.mkdirSync(dir);
138
+ fs.writeFileSync(fallbackPath, serializeEnvMap(map), 0o600);
139
+ fs.chmodSync(fallbackPath, 0o600);
140
+ }
141
+ return {
142
+ setGitHubToken(token) {
143
+ if (!token || typeof token !== "string") {
144
+ throw new Error("setGitHubToken: token must be a non-empty string");
145
+ }
146
+ const r = tryKeyring((kr) => kr.setPassword(SERVICE_NAME, GITHUB_TOKEN_KEY, token));
147
+ if (r.ok)
148
+ return;
149
+ ensureFallbackWarned();
150
+ const map = readFallback();
151
+ map.set(GITHUB_TOKEN_KEY, token);
152
+ writeFallback(map);
153
+ },
154
+ getGitHubToken() {
155
+ const r = tryKeyring((kr) => kr.getPassword(SERVICE_NAME, GITHUB_TOKEN_KEY));
156
+ if (r.ok && r.value)
157
+ return r.value;
158
+ if (r.ok && r.value === null) {
159
+ // keyring works but slot is empty — also peek at fallback in case the
160
+ // user set a token before keyring became available, but DO NOT warn
161
+ // because keyring is fine.
162
+ const map = readFallback();
163
+ return map.get(GITHUB_TOKEN_KEY) ?? null;
164
+ }
165
+ ensureFallbackWarned();
166
+ const map = readFallback();
167
+ return map.get(GITHUB_TOKEN_KEY) ?? null;
168
+ },
169
+ clearGitHubToken() {
170
+ let removed = false;
171
+ const r = tryKeyring((kr) => kr.deletePassword(SERVICE_NAME, GITHUB_TOKEN_KEY));
172
+ if (r.ok && r.value)
173
+ removed = true;
174
+ // Always also clear the fallback — defense in depth in case both backends
175
+ // hold copies (e.g., keyring re-enabled after a fallback period).
176
+ if (fs.existsSync(fallbackPath)) {
177
+ const map = readFallback();
178
+ if (map.delete(GITHUB_TOKEN_KEY)) {
179
+ if (map.size === 0) {
180
+ try {
181
+ fs.unlinkSync(fallbackPath);
182
+ }
183
+ catch {
184
+ writeFallback(map);
185
+ }
186
+ }
187
+ else {
188
+ writeFallback(map);
189
+ }
190
+ removed = true;
191
+ }
192
+ }
193
+ return removed;
194
+ },
195
+ usingFallback() {
196
+ return fallbackInUse || keyring === null;
197
+ },
198
+ };
199
+ }
200
+ /**
201
+ * Convenience accessor used by every CLI surface that needs the GitHub token.
202
+ * Lazily constructs a default keychain on first call.
203
+ */
204
+ let _defaultKeychain = null;
205
+ export function getDefaultKeychain() {
206
+ if (_defaultKeychain)
207
+ return _defaultKeychain;
208
+ _defaultKeychain = createKeychain();
209
+ return _defaultKeychain;
210
+ }
211
+ /** Test-only reset hook — never call from production code. */
212
+ export function _resetDefaultKeychainForTests() {
213
+ _defaultKeychain = null;
214
+ }
215
+ export function getGitHubToken() {
216
+ return getDefaultKeychain().getGitHubToken();
217
+ }
218
+ export function setGitHubToken(token) {
219
+ getDefaultKeychain().setGitHubToken(token);
220
+ }
221
+ export function clearGitHubToken() {
222
+ return getDefaultKeychain().clearGitHubToken();
223
+ }
224
+ /** Last-4 redaction for log-safe output. */
225
+ export function redactToken(token) {
226
+ if (!token)
227
+ return "(none)";
228
+ if (token.length <= 8)
229
+ return "****";
230
+ return `****${token.slice(-4)}`;
231
+ }
232
+ //# sourceMappingURL=keychain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keychain.js","sourceRoot":"","sources":["../../src/lib/keychain.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,2EAA2E;AAC3E,EAAE;AACF,gCAAgC;AAChC,8EAA8E;AAC9E,6EAA6E;AAC7E,4EAA4E;AAC5E,EAAE;AACF,kBAAkB;AAClB,sCAAsC;AACtC,0CAA0C;AAC1C,oEAAoE;AACpE,EAAE;AACF,4EAA4E;AAC5E,0BAA0B;AAC1B,EAAE;AACF,oBAAoB;AACpB,gCAAgC;AAChC,+BAA+B;AAC/B,8EAA8E;AAE9E,OAAO,KAAK,MAAM,MAAM,SAAS,CAAC;AAClC,OAAO,KAAK,QAAQ,MAAM,WAAW,CAAC;AACtC,OAAO,KAAK,MAAM,MAAM,SAAS,CAAC;AAElC,MAAM,CAAC,MAAM,YAAY,GAAG,eAAe,CAAC;AAC5C,MAAM,CAAC,MAAM,gBAAgB,GAAG,cAAc,CAAC;AAoC/C,MAAM,gBAAgB,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;AAEhF,MAAM,SAAS,GAAc;IAC3B,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACjE,aAAa,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,CAC5B,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACxD,SAAS,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC;IACjD,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;IACvC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACvE,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;CACxC,CAAC;AAEF,SAAS,kBAAkB;IACzB,4EAA4E;IAC5E,uEAAuE;IACvE,4EAA4E;IAC5E,wDAAwD;IACxD,IAAI,CAAC;QACH,8DAA8D;QAC9D,MAAM,GAAG,GAAG,OAAO,CAAC,kBAAkB,CAMrC,CAAC;QACF,OAAO;YACL,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ;gBACpC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBAC9C,KAAK,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;YAC9B,CAAC;YACD,WAAW,CAAC,OAAO,EAAE,OAAO;gBAC1B,IAAI,CAAC;oBACH,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;oBAC9C,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC;gBAC7B,CAAC;gBAAC,MAAM,CAAC;oBACP,OAAO,IAAI,CAAC;gBACd,CAAC;YACH,CAAC;YACD,cAAc,CAAC,OAAO,EAAE,OAAO;gBAC7B,IAAI,CAAC;oBACH,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;oBAC9C,OAAO,KAAK,CAAC,cAAc,EAAE,CAAC;gBAChC,CAAC;gBAAC,MAAM,CAAC;oBACP,OAAO,KAAK,CAAC;gBACf,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,OAAe;IACnC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtC,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5C,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,EAAE,IAAI,CAAC;YAAE,SAAS;QACtB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACtC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACpB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,eAAe,CAAC,GAAwB;IAC/C,MAAM,KAAK,GAAa;QACtB,wDAAwD;QACxD,6CAA6C;KAC9C,CAAC;IACF,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,GAAG;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAwB,EAAE;IACvD,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,IAAI,SAAS,CAAC;IAChC,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,gBAAgB,CAAC;IAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC;IAEtE,IAAI,OAA8B,CAAC;IACnC,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;;QAClD,OAAO,GAAG,kBAAkB,EAAE,CAAC;IAEpC,IAAI,cAAc,GAAG,KAAK,CAAC;IAC3B,IAAI,aAAa,GAAG,KAAK,CAAC;IAE1B,SAAS,UAAU,CAAI,EAA6B;QAClD,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;QACnC,IAAI,CAAC;YACH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;QACvB,CAAC;IACH,CAAC;IAED,SAAS,oBAAoB;QAC3B,aAAa,GAAG,IAAI,CAAC;QACrB,IAAI,cAAc;YAAE,OAAO;QAC3B,cAAc,GAAG,IAAI,CAAC;QACtB,IAAI,CACF,4DAA4D,YAAY,eAAe,CACxF,CAAC;IACJ,CAAC;IAED,SAAS,YAAY;QACnB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC;YAAE,OAAO,IAAI,GAAG,EAAE,CAAC;QACnD,IAAI,CAAC;YACH,OAAO,YAAY,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,CAAC;QACrD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,GAAG,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;IAED,SAAS,aAAa,CAAC,GAAwB;QAC7C,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAC3C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAC3C,EAAE,CAAC,aAAa,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;QAC5D,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IACpC,CAAC;IAED,OAAO;QACL,cAAc,CAAC,KAAa;YAC1B,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBACxC,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;YACtE,CAAC;YACD,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,WAAW,CAAC,YAAY,EAAE,gBAAgB,EAAE,KAAK,CAAC,CAAC,CAAC;YACpF,IAAI,CAAC,CAAC,EAAE;gBAAE,OAAO;YACjB,oBAAoB,EAAE,CAAC;YACvB,MAAM,GAAG,GAAG,YAAY,EAAE,CAAC;YAC3B,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;YACjC,aAAa,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;QAED,cAAc;YACZ,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,WAAW,CAAC,YAAY,EAAE,gBAAgB,CAAC,CAAC,CAAC;YAC7E,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK;gBAAE,OAAO,CAAC,CAAC,KAAK,CAAC;YACpC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;gBAC7B,sEAAsE;gBACtE,oEAAoE;gBACpE,2BAA2B;gBAC3B,MAAM,GAAG,GAAG,YAAY,EAAE,CAAC;gBAC3B,OAAO,GAAG,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,IAAI,CAAC;YAC3C,CAAC;YACD,oBAAoB,EAAE,CAAC;YACvB,MAAM,GAAG,GAAG,YAAY,EAAE,CAAC;YAC3B,OAAO,GAAG,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,IAAI,CAAC;QAC3C,CAAC;QAED,gBAAgB;YACd,IAAI,OAAO,GAAG,KAAK,CAAC;YACpB,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,cAAc,CAAC,YAAY,EAAE,gBAAgB,CAAC,CAAC,CAAC;YAChF,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK;gBAAE,OAAO,GAAG,IAAI,CAAC;YACpC,0EAA0E;YAC1E,kEAAkE;YAClE,IAAI,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;gBAChC,MAAM,GAAG,GAAG,YAAY,EAAE,CAAC;gBAC3B,IAAI,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC;oBACjC,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;wBACnB,IAAI,CAAC;4BACH,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;wBAC9B,CAAC;wBAAC,MAAM,CAAC;4BACP,aAAa,CAAC,GAAG,CAAC,CAAC;wBACrB,CAAC;oBACH,CAAC;yBAAM,CAAC;wBACN,aAAa,CAAC,GAAG,CAAC,CAAC;oBACrB,CAAC;oBACD,OAAO,GAAG,IAAI,CAAC;gBACjB,CAAC;YACH,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC;QAED,aAAa;YACX,OAAO,aAAa,IAAI,OAAO,KAAK,IAAI,CAAC;QAC3C,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,IAAI,gBAAgB,GAAoB,IAAI,CAAC;AAC7C,MAAM,UAAU,kBAAkB;IAChC,IAAI,gBAAgB;QAAE,OAAO,gBAAgB,CAAC;IAC9C,gBAAgB,GAAG,cAAc,EAAE,CAAC;IACpC,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,6BAA6B;IAC3C,gBAAgB,GAAG,IAAI,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,kBAAkB,EAAE,CAAC,cAAc,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,kBAAkB,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,kBAAkB,EAAE,CAAC,gBAAgB,EAAE,CAAC;AACjD,CAAC;AAED,4CAA4C;AAC5C,MAAM,UAAU,WAAW,CAAC,KAAgC;IAC1D,IAAI,CAAC,KAAK;QAAE,OAAO,QAAQ,CAAC;IAC5B,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,MAAM,CAAC;IACrC,OAAO,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAClC,CAAC"}
@@ -19,6 +19,19 @@ export type Provenance = {
19
19
  sourcePath: string;
20
20
  promotedAt: number;
21
21
  sourceSkillVersion?: string;
22
+ /** Set when this skill was created via `vskill clone`. */
23
+ forkedFrom?: {
24
+ source: string;
25
+ version: string;
26
+ clonedAt: string;
27
+ };
28
+ /** Set when the source itself was a fork — points to the deepest known ancestor. */
29
+ originalSource?: {
30
+ repoUrl?: string;
31
+ skillPath?: string;
32
+ };
33
+ /** Chain of intermediate `forkedFrom.source` values when forking a fork. Oldest-first (`forkChain[0]` is the deepest ancestor; latest intermediate parent is appended at the end). */
34
+ forkChain?: string[];
22
35
  };
23
36
  export type TransferEventName = "started" | "copied" | "deleted" | "indexed" | "done" | "error";
24
37
  export type TransferEvent = {
@@ -1,3 +1,29 @@
1
+ /**
2
+ * 0826: Add a Claude Code plugin marketplace via the claude CLI.
3
+ *
4
+ * Delegates to: claude plugin marketplace add --scope <scope> -- <source>
5
+ *
6
+ * Required when installing plugins from a marketplace that hasn't been
7
+ * registered yet — `claude plugin install foo@bar` fails with "Plugin foo
8
+ * not found in marketplace bar" until the marketplace itself is added.
9
+ *
10
+ * Safe to call against an already-registered marketplace: claude prints a
11
+ * "marketplace already exists" message and exits 0.
12
+ *
13
+ * @throws if the claude binary is not found or the add fails (network,
14
+ * invalid source, etc.). Callers should `try/catch` and surface a
15
+ * user-friendly error.
16
+ */
17
+ export declare function claudePluginMarketplaceAdd(source: string, scope?: "user" | "project"): void;
18
+ /**
19
+ * 0826: List Claude Code marketplace names registered with the CLI.
20
+ *
21
+ * Cheap probe used by the install flow to decide whether to call
22
+ * `claudePluginMarketplaceAdd` before `claudePluginInstall`. Returns an
23
+ * empty list (instead of throwing) when claude isn't installed or the
24
+ * subcommand fails — callers fall through to "treat as not registered".
25
+ */
26
+ export declare function claudePluginMarketplaceList(): string[];
1
27
  /**
2
28
  * Install a marketplace plugin via the claude CLI.
3
29
  *
@@ -7,6 +7,66 @@
7
7
  import { execFileSync } from "node:child_process";
8
8
  import { resolveCliBinary } from "./resolve-binary.js";
9
9
  import { purgeStalePlugins } from "../settings/index.js";
10
+ /**
11
+ * 0826: Add a Claude Code plugin marketplace via the claude CLI.
12
+ *
13
+ * Delegates to: claude plugin marketplace add --scope <scope> -- <source>
14
+ *
15
+ * Required when installing plugins from a marketplace that hasn't been
16
+ * registered yet — `claude plugin install foo@bar` fails with "Plugin foo
17
+ * not found in marketplace bar" until the marketplace itself is added.
18
+ *
19
+ * Safe to call against an already-registered marketplace: claude prints a
20
+ * "marketplace already exists" message and exits 0.
21
+ *
22
+ * @throws if the claude binary is not found or the add fails (network,
23
+ * invalid source, etc.). Callers should `try/catch` and surface a
24
+ * user-friendly error.
25
+ */
26
+ export function claudePluginMarketplaceAdd(source, scope = "user") {
27
+ const claude = resolveCliBinary("claude");
28
+ execFileSync(claude, ["plugin", "marketplace", "add", "--scope", scope, "--", source], { stdio: "pipe", timeout: 180_000 });
29
+ }
30
+ /**
31
+ * 0826: List Claude Code marketplace names registered with the CLI.
32
+ *
33
+ * Cheap probe used by the install flow to decide whether to call
34
+ * `claudePluginMarketplaceAdd` before `claudePluginInstall`. Returns an
35
+ * empty list (instead of throwing) when claude isn't installed or the
36
+ * subcommand fails — callers fall through to "treat as not registered".
37
+ */
38
+ export function claudePluginMarketplaceList() {
39
+ let claude;
40
+ try {
41
+ claude = resolveCliBinary("claude");
42
+ }
43
+ catch {
44
+ return [];
45
+ }
46
+ let stdout = "";
47
+ try {
48
+ stdout = execFileSync(claude, ["plugin", "marketplace", "list"], {
49
+ stdio: ["ignore", "pipe", "pipe"],
50
+ timeout: 30_000,
51
+ }).toString("utf-8");
52
+ }
53
+ catch {
54
+ return [];
55
+ }
56
+ // Format: leading marker (e.g. "❯ "), then name, then "Source: …" line.
57
+ // The marker's exact glyph varies across terminals — match anything that
58
+ // isn't word/whitespace, followed by a name token.
59
+ const names = [];
60
+ for (const raw of stdout.split(/\r?\n/)) {
61
+ const line = raw.trim();
62
+ if (line.startsWith("Source:"))
63
+ continue;
64
+ const m = line.match(/^[^\w\s]+\s+([a-z0-9][\w./:-]*)\s*$/i);
65
+ if (m)
66
+ names.push(m[1]);
67
+ }
68
+ return names;
69
+ }
10
70
  /**
11
71
  * Install a marketplace plugin via the claude CLI.
12
72
  *
@@ -1 +1 @@
1
- {"version":3,"file":"claude-plugin.js","sourceRoot":"","sources":["../../src/utils/claude-plugin.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,0CAA0C;AAC1C,EAAE;AACF,2EAA2E;AAC3E,4EAA4E;AAC5E,8EAA8E;AAE9E,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEzD;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAAgB,EAChB,QAAsC,MAAM,EAC5C,IAAuB;IAEvB,MAAM,MAAM,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC1C,YAAY,CACV,MAAM,EACN,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,EACvD,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAC5E,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,qBAAqB,CACnC,QAAgB,EAChB,QAAsC,MAAM,EAC5C,IAAuB;IAEvB,MAAM,MAAM,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC1C,YAAY,CACV,MAAM,EACN,CAAC,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,EACzD,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAC3D,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CACnC,cAAwD,EACxD,IAAsC;IAEtC,MAAM,GAAG,GAAG,IAAI,EAAE,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACrC,MAAM,SAAS,GAAG,iBAAiB,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,cAAc,CAAC,CAAC;IACvE,MAAM,YAAY,GAAG,iBAAiB,CACpC,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,EAC/C,cAAc,CACf,CAAC;IACF,MAAM,QAAQ,GAAG;QACf,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAe,EAAE,CAAC,CAAC;QAC1D,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,SAAkB,EAAE,CAAC,CAAC;KACjE,CAAC;IAEF,MAAM,OAAO,GAAkE,EAAE,CAAC;IAClF,KAAK,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,QAAQ,EAAE,CAAC;QACrC,IAAI,CAAC;YACH,qBAAqB,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;YACjC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QACxC,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,CAAC,KAAK,EAAE,qCAAqC,CAAC,CAAC;YAClD,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
1
+ {"version":3,"file":"claude-plugin.js","sourceRoot":"","sources":["../../src/utils/claude-plugin.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vskill",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "type": "module",
5
5
  "description": "Secure multi-platform AI skill installer — scan before you install",
6
6
  "bin": {
@@ -83,6 +83,7 @@
83
83
  },
84
84
  "dependencies": {
85
85
  "@anthropic-ai/sdk": "^0.78.0",
86
+ "@napi-rs/keyring": "^1.3.0",
86
87
  "commander": "^14.0.3",
87
88
  "minimatch": "^9.0.9",
88
89
  "openai": "^6.34.0",
@@ -1 +0,0 @@
1
- import{j as e,r as d,T as X}from"./index-C3S9iHnq.js";import{s as B}from"./skill-url-C4ekwoGs.js";/* empty css */const M={T0:{label:"BLOCKED",cssVar:"var(--trust-t0)"},T1:{label:"UNSCANNED",cssVar:"var(--trust-t1)"},T2:{label:"BASIC",cssVar:"var(--trust-t2)"},T3:{label:"VERIFIED",cssVar:"var(--trust-t3)"},T4:{label:"CERTIFIED",cssVar:"var(--trust-t4)"}};function Y({tier:r}){const s=M[r]??M.T1;return e.jsxs("span",{"data-testid":"trust-badge","data-tier":r,style:{display:"inline-flex",alignItems:"center",gap:"0.3rem",padding:"0.15rem 0.5rem",borderRadius:"4px",fontFamily:"var(--font-geist-mono)",fontSize:"0.6875rem",fontWeight:600,letterSpacing:"0.03em",textTransform:"uppercase",whiteSpace:"nowrap",color:s.cssVar,backgroundColor:`color-mix(in srgb, ${s.cssVar} 10%, transparent)`,border:`1px solid color-mix(in srgb, ${s.cssVar} 25%, transparent)`,lineHeight:1,height:"22px"},children:[r," ",s.label]})}const Q=/^https?:\/\/github\.com\/([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+?)(\.git)?(\/.*)?$/;function ee(r){var g,x;const a=r.trim().match(Q);if(!a)return null;const l=(g=a[1])==null?void 0:g.toLowerCase();let n=(x=a[2])==null?void 0:x.toLowerCase();if(!l||!n)return null;n=n.replace(/\.git$/,"");const u=`https://github.com/${l}/${n}`;return{owner:l,name:n,url:u}}function te({repoUrl:r,mono:s="var(--font-geist-mono)",fontSize:a="0.75rem",showPlaceholder:l=!0}){if(!r)return l?e.jsx("span",{style:{color:"var(--text-faint)",fontSize:a},children:"--"}):null;const n=ee(r);return n?e.jsxs("a",{"data-testid":"repo-link",href:n.url,target:"_blank",rel:"noopener noreferrer",onClick:u=>u.stopPropagation(),style:{color:"#0D9488",textDecoration:"none",fontSize:a,fontFamily:s},children:[n.owner,"/",n.name]}):e.jsx("span",{style:{color:"var(--text-faint)",fontSize:a,fontFamily:s},children:r})}const re="var(--font-geist-mono)",ne={ONLINE:"var(--status-success-text)",OFFLINE:"var(--status-danger-text)",STALE:"var(--status-neutral-text)"},oe={ONLINE:"var(--status-success-bg)",OFFLINE:"var(--status-danger-bg)",STALE:"var(--status-neutral-bg)"};function se({skillName:r,repoUrl:s}){const[a,l]=d.useState(null),[n,u]=d.useState(!1);if(d.useEffect(()=>{s&&(u(!0),fetch(B(r,"repo-health")).then(b=>{if(!b.ok)throw new Error("fetch failed");return b.json()}).then(b=>{l(b.status),u(!1)}).catch(()=>{l(null),u(!1)}))},[r,s]),!s)return null;if(n)return e.jsx("span",{"data-testid":"repo-health-loading",style:{display:"inline-block",width:56,height:20,borderRadius:"4px",backgroundColor:"color-mix(in srgb, var(--text-faint) 20%, transparent)",animation:"pulse 1.5s ease-in-out infinite"}});if(!a||a==="UNKNOWN")return null;const g=ne[a]??"var(--status-neutral-text)",x=oe[a]??"var(--status-neutral-bg)";return e.jsx("span",{"data-testid":"repo-health-badge",style:{fontFamily:re,fontSize:"0.75rem",fontWeight:600,textTransform:"uppercase",letterSpacing:"0.03em",padding:"0.2rem 0.6rem",borderRadius:"999px",backgroundColor:x,color:g,whiteSpace:"nowrap"},children:a})}function ae({children:r,compact:s}){return e.jsx("pre",{"data-testid":"terminal-block",style:{background:"var(--bg-code, #161B22)",color:"#E6EDF3",fontFamily:"var(--font-geist-mono)",fontSize:s?"0.8rem":"0.875rem",lineHeight:1.6,padding:s?"1rem 1.25rem":"1.5rem 2rem",borderRadius:"6px",overflowX:"auto",margin:0},children:r})}const ie="/api/v1/studio/telemetry/install-copy",le=/^[a-zA-Z0-9._@/-]+$/,ce=/^[a-zA-Z0-9._-]+$/,de="https://verified-skill.com";function ue(r){return`${r.owner}/${r.repo}/${r.slug}`}function me(r,s){return r!=null&&r.ownerSlug&&(r!=null&&r.repoSlug)?`${r.ownerSlug}/${r.repoSlug}`:r!=null&&r.publisher?r.publisher:`${s.owner}/${s.repo}`}function pe(r){return r==="global"?" --global":` --scope ${r}`}function fe(r,s,a,l){const n=a?`${r}/${s}@${a}`:`${r}/${s}`;if(!le.test(`${r}/${s}`))return{ok:!1,reason:"Invalid skill identifier"};if(a&&!ce.test(a))return{ok:!1,reason:"Invalid version identifier"};const u=pe(l),g=`npx vskill@latest install ${n}${u}`,x=[{label:"npm",comment:"# npm",command:g},{label:"bun",comment:"# bun",command:`bunx vskill@latest install ${n}${u}`},{label:"pnpm",comment:"# pnpm",command:`pnpx vskill@latest install ${n}${u}`},{label:"yarn",comment:"# yarn",command:`yarn dlx vskill@latest install ${n}${u}`},{label:"alternative",comment:"# alternative (publisher + --skill flag)",command:a?`npx vskill@latest install ${r}@${a} --skill ${s}${u}`:`npx vskill@latest install ${r} --skill ${s}${u}`}];return{ok:!0,command:g,variants:x}}function F(r,s,a){typeof window>"u"||window.dispatchEvent(new CustomEvent("studio:toast",{detail:{message:r,kind:s,durationMs:a,severity:s==="error"?"error":"info"}}))}async function P(r){var s;if(typeof navigator<"u"&&((s=navigator.clipboard)!=null&&s.writeText))try{return await navigator.clipboard.writeText(r),!0}catch{}if(typeof document>"u")return!1;try{const a=document.createElement("textarea");a.value=r,a.setAttribute("readonly",""),a.style.position="fixed",a.style.left="-9999px",document.body.appendChild(a),a.select();const l=document.execCommand("copy");return document.body.removeChild(a),l}catch{return!1}}function U(r,s){try{fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s),keepalive:!0}).catch(()=>{})}catch{}}function xe({selectedSkill:r,onClose:s,telemetryInstallCopyUrl:a=ie,onToast:l}){const[n,u]=d.useState(null),[g,x]=d.useState([]),[b,T]=d.useState(!0),[I,D]=d.useState(null),[h,L]=d.useState(null),[k,W]=d.useState("project"),[H,K]=d.useState(0),z=d.useRef(null),N=d.useRef(null),O=d.useRef(null),v=ue(r);d.useEffect(()=>(O.current=document.activeElement??null,()=>{const t=O.current;if(t&&typeof t.focus=="function")try{t.focus()}catch{}}),[]),d.useEffect(()=>{const t=setTimeout(()=>{var o;return(o=N.current)==null?void 0:o.focus()},50);return()=>clearTimeout(t)},[]),d.useEffect(()=>{let t=!1;T(!0),D(null);const o=B(v),c=B(v,"versions");return Promise.all([fetch(o).then(async i=>{if(!i.ok)throw new Error(`metadata ${i.status}`);return i.json()}),fetch(c).then(async i=>{if(!i.ok)throw new Error(`versions ${i.status}`);const m=await i.json();return Array.isArray(m)?m:(m==null?void 0:m.versions)??[]})]).then(([i,m])=>{if(t)return;u(i),x(m);const f=m.find(y=>y.isLatest)??m[0]??null;L(f?f.version:null),T(!1)}).catch(i=>{t||(D(i instanceof Error?i.message:String(i)),T(!1))}),()=>{t=!0}},[v,H]),d.useEffect(()=>{function t(o){if(o.key==="Escape")o.stopPropagation(),R();else if(o.key==="Tab"){const c=z.current;if(!c)return;const i=c.querySelectorAll('a[href], button:not([disabled]), input:not([disabled]), [tabindex]:not([tabindex="-1"])');if(i.length===0)return;const m=i[0],f=i[i.length-1],y=document.activeElement;o.shiftKey&&y===m?(o.preventDefault(),f.focus()):!o.shiftKey&&y===f&&(o.preventDefault(),m.focus())}}return window.addEventListener("keydown",t,!0),()=>window.removeEventListener("keydown",t,!0)},[]);const R=d.useCallback(()=>{s();let t="";try{typeof window<"u"&&window.sessionStorage&&(t=window.sessionStorage.getItem("find-skills:last-query")??"")}catch{}typeof window<"u"&&window.dispatchEvent(new CustomEvent("openFindSkills",{detail:{query:t}}))},[s]),j=me(n,r),E=(n==null?void 0:n.skillSlug)??r.slug,_=(n==null?void 0:n.displayName)??r.displayName??E,q=(n==null?void 0:n.isBlocked)===!0,S=d.useMemo(()=>g.length===0?[]:[...g].sort((o,c)=>{const i=o.publishedAt?Date.parse(o.publishedAt):0;return(c.publishedAt?Date.parse(c.publishedAt):0)-i}).slice(0,5),[g]),A=d.useMemo(()=>!h||S.length===0?!0:S[0].version===h,[h,S]),p=d.useMemo(()=>h?fe(j,E,A?null:h,k):null,[j,E,h,A,k]),C=d.useCallback(async()=>{if(!p||!p.ok)return;const t=await P(p.command);U(a,{skillName:v,version:h??"",q:"",ts:Date.now()});const o=t?`Run ${p.command} in your terminal`:"Copy failed — please copy the command manually.";if(l)try{l(o,t?"success":"error")}catch{}else F(o,t?"success":"error",3500)},[p,a,v,h,j,E,A,l]),G=d.useCallback(async()=>{if(!p||!p.ok)return;const t=`${j}/${E}`;if(l)try{l(`Installing ${t}…`,"info")}catch{}else F(`Installing ${t}…`,"info",5e3);let o=null;try{const f=await fetch("/api/studio/install-skill",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({skill:t,scope:k})});if(f.status===404)return C();if(!f.ok){const $=(await f.json().catch(()=>({}))).error||`Install failed (HTTP ${f.status})`;if(l)try{l($,"error")}catch{}else F($,"error",6e3);return}const y=await f.json();o=(y==null?void 0:y.jobId)??null}catch{return C()}if(!o||typeof EventSource>"u")return;const c=new EventSource(`/api/studio/install-skill/${o}/stream`),m=setTimeout(()=>{try{c.close()}catch{}},2e5);c.addEventListener("done",f=>{var V;clearTimeout(m);try{c.close()}catch{}let y={};try{y=JSON.parse(f.data)}catch{}const w=y.success===!0,$=w?`Installed ${t} (${k})`:`Install failed: ${((V=y.stderr)==null?void 0:V.trim().split(/\r?\n/).slice(-1)[0])||"see terminal"}`;if(l)try{l($,w?"success":"error")}catch{}else F($,w?"success":"error",w?4e3:8e3)}),c.onerror=()=>{clearTimeout(m);try{c.close()}catch{}}},[p,j,E,k,l,C]),Z=(n==null?void 0:n.trustTier)??"T1",J=(n==null?void 0:n.certTier)==="CERTIFIED"||(n==null?void 0:n.certTier)==="VERIFIED"?n.certTier:"VERIFIED";return e.jsxs("div",{ref:z,"data-testid":"skill-detail-panel",role:"dialog","aria-modal":"true","aria-label":`Skill detail — ${_}`,style:{position:"fixed",inset:0,zIndex:9998,display:"flex",alignItems:"flex-start",justifyContent:"center",paddingTop:"min(10vh, 80px)"},onClick:t=>{t.target===t.currentTarget&&R()},children:[e.jsx("div",{style:{position:"fixed",inset:0,background:"rgba(0,0,0,0.4)",backdropFilter:"blur(4px)"}}),e.jsxs("div",{onClick:t=>t.stopPropagation(),style:{position:"relative",width:"100%",maxWidth:720,margin:"0 1rem",background:"var(--bg-surface, #FFFFFF)",color:"var(--text-primary, #191919)",borderRadius:"8px",border:"1px solid var(--color-rule, #E8E1D6)",boxShadow:"0 20px 60px rgba(0,0,0,0.15)",overflow:"hidden",maxHeight:"80vh",display:"flex",flexDirection:"column"},children:[e.jsxs("div",{style:{padding:"0.75rem 1rem",borderBottom:"1px solid var(--color-rule, #E8E1D6)",display:"flex",alignItems:"center",justifyContent:"space-between",flexShrink:0},children:[e.jsx("button",{ref:N,type:"button",onClick:R,"data-testid":"skill-detail-back",style:{background:"transparent",border:"none",padding:"4px 8px",borderRadius:4,cursor:"pointer",fontFamily:"var(--font-mono, monospace)",fontSize:12,color:"var(--text-secondary, #5A5651)"},children:"← Back to results"}),e.jsx("kbd",{style:{fontFamily:"var(--font-mono, monospace)",fontSize:11,color:"var(--text-secondary, #5A5651)",border:"1px solid var(--color-rule, #E8E1D6)",borderRadius:4,padding:"1px 5px"},children:"Esc"})]}),e.jsxs("div",{style:{overflowY:"auto",padding:"1rem",flex:1},children:[b&&e.jsx("div",{"data-testid":"skill-detail-loading",style:{padding:"2rem",textAlign:"center",color:"var(--text-secondary, #5A5651)"},children:"Loading…"}),I&&!b&&e.jsxs("div",{"data-testid":"skill-detail-error",style:{padding:"1.5rem",textAlign:"center",fontFamily:"var(--font-mono, monospace)",fontSize:"0.875rem",color:"var(--red, #b54444)"},children:[e.jsxs("div",{style:{marginBottom:"0.75rem"},children:["Failed to load skill: ",I]}),e.jsx("button",{"data-testid":"skill-detail-retry",onClick:()=>K(t=>t+1),style:{fontFamily:"var(--font-mono, monospace)",fontSize:"0.8125rem",padding:"0.4rem 1rem",borderRadius:4,border:"1px solid var(--color-rule, #E8E1D6)",background:"transparent",color:"var(--text-primary, #191919)",cursor:"pointer"},children:"Retry"})]}),!b&&!I&&e.jsxs(e.Fragment,{children:[e.jsxs("section",{"data-testid":"skill-detail-hero",style:{marginBottom:"1.25rem"},children:[e.jsx("h2",{style:{margin:"0 0 0.5rem",fontSize:"1.25rem",fontWeight:600},children:_}),(n==null?void 0:n.description)&&e.jsx("p",{style:{margin:"0 0 0.75rem",color:"var(--text-secondary, #5A5651)",fontSize:"0.875rem",lineHeight:1.5},children:n.description}),e.jsxs("div",{style:{display:"flex",flexWrap:"wrap",gap:"0.5rem",alignItems:"center"},children:[e.jsx(Y,{tier:Z}),e.jsx(X,{tier:J,isTainted:n==null?void 0:n.isTainted}),e.jsx(te,{repoUrl:(n==null?void 0:n.repoUrl)??null}),e.jsx(se,{skillName:v,repoUrl:n==null?void 0:n.repoUrl})]})]}),e.jsxs("section",{"data-testid":"skill-detail-versions",style:{marginBottom:"1.25rem"},children:[e.jsx("h3",{style:{margin:"0 0 0.5rem",fontSize:"0.8125rem",textTransform:"uppercase",letterSpacing:"0.06em",color:"var(--text-secondary, #5A5651)"},children:"Versions"}),S.length===0?e.jsx("div",{style:{color:"var(--text-secondary, #5A5651)",fontSize:"0.8125rem"},children:"No versions found."}):e.jsx("ul",{style:{listStyle:"none",margin:0,padding:0,display:"flex",flexDirection:"column",gap:4},children:S.map(t=>{const o=t.version===h,c=t.publishedAt?t.publishedAt.slice(0,10):"",i=t.authorEmail??t.author??"";return e.jsx("li",{children:e.jsxs("button",{type:"button","data-testid":"skill-detail-version-row","data-version":t.version,"data-selected":o?"true":"false","aria-pressed":o,onClick:()=>L(t.version),style:{width:"100%",textAlign:"left",padding:"0.5rem 0.75rem",borderRadius:4,border:o?"1px solid var(--color-action, #2F5B8E)":"1px solid var(--color-rule, #E8E1D6)",background:o?"color-mix(in srgb, var(--color-action, #2F5B8E) 8%, transparent)":"transparent",cursor:"pointer",fontFamily:"var(--font-mono, monospace)",fontSize:"0.8125rem",color:"var(--text-primary, #191919)",display:"flex",alignItems:"center",gap:"0.5rem"},children:[e.jsx("span",{"aria-hidden":"true",style:{width:14,display:"inline-flex",justifyContent:"center"},children:o?"●":"○"}),e.jsxs("span",{style:{fontWeight:600},children:["v",t.version]}),c&&e.jsxs("span",{style:{color:"var(--text-secondary, #5A5651)"},children:["· ",c]}),i&&e.jsxs("span",{style:{color:"var(--text-secondary, #5A5651)"},children:["· ",i]}),o&&e.jsx("span",{"data-testid":"skill-detail-version-selected-tag",style:{marginLeft:"auto",fontSize:"0.6875rem",color:"var(--color-action, #2F5B8E)"},children:"Selected"})]})},t.version)})}),e.jsx("div",{style:{marginTop:"0.5rem",textAlign:"right"},children:e.jsx("a",{"data-testid":"skill-detail-see-all-versions",href:`${de}/skills/${r.owner}/${r.repo}/${r.slug}/versions`,target:"_blank",rel:"noopener noreferrer",style:{fontFamily:"var(--font-mono, monospace)",fontSize:"0.75rem",color:"var(--color-action, #2F5B8E)",textDecoration:"none"},children:"see all versions →"})})]}),q?e.jsxs("section",{"data-testid":"skill-detail-blocked",style:{marginBottom:"0.5rem"},children:[e.jsx("h3",{style:{margin:"0 0 0.5rem",fontSize:"0.8125rem",textTransform:"uppercase",letterSpacing:"0.06em",color:"var(--red, #b54444)"},children:"This skill is blocked"}),e.jsx("div",{style:{padding:"1rem",borderRadius:6,border:"1px solid var(--red, #b54444)",background:"var(--red-muted, color-mix(in srgb, #b54444 18%, transparent))",color:"var(--red, #b54444)",fontSize:"0.875rem"},children:(n==null?void 0:n.blockReason)??"This skill has been blocked by platform moderators and cannot be installed."})]}):p&&!p.ok?e.jsxs("section",{"data-testid":"skill-detail-install-error",style:{marginBottom:"0.5rem"},children:[e.jsx("h3",{style:{margin:"0 0 0.5rem",fontSize:"0.8125rem",textTransform:"uppercase",letterSpacing:"0.06em",color:"var(--red, #b54444)"},children:"Install command unavailable"}),e.jsxs("div",{style:{padding:"1rem",borderRadius:6,border:"1px solid var(--red, #b54444)",background:"var(--red-muted, color-mix(in srgb, #b54444 18%, transparent))",color:"var(--red, #b54444)",fontSize:"0.875rem"},children:[p.reason," — refusing to render the install panel for safety."]})]}):p&&p.ok?e.jsxs("section",{"data-testid":"skill-detail-install",style:{marginBottom:"0.5rem"},children:[e.jsx("h3",{style:{margin:"0 0 0.5rem",fontSize:"0.8125rem",textTransform:"uppercase",letterSpacing:"0.06em",color:"var(--text-secondary, #5A5651)"},children:"Install"}),e.jsxs("div",{role:"radiogroup","aria-label":"Install scope","data-testid":"skill-detail-install-scope",style:{display:"flex",gap:"0.5rem",alignItems:"center",marginBottom:"0.75rem",fontFamily:"var(--font-mono, monospace)",fontSize:"0.75rem",color:"var(--text-secondary, #5A5651)"},children:[e.jsx("span",{children:"Scope:"}),["project","user","global"].map(t=>{const o=k===t,c=t==="project"?"Per-repo .claude/ — only this project":t==="user"?"~/.claude/ — every project on this account":"System agent dirs — every agent on this machine",i=t==="global"?"Global":t==="user"?"User":"Project";return e.jsx("button",{type:"button",role:"radio","aria-checked":o,"data-testid":`skill-detail-install-scope-${t}`,title:c,onClick:()=>W(t),style:{padding:"0.25rem 0.6rem",borderRadius:4,border:`1px solid ${o?"var(--text-primary, #191919)":"var(--color-rule, #E8E1D6)"}`,background:o?"var(--text-primary, #191919)":"transparent",color:o?"var(--bg-surface, #FFFFFF)":"var(--text-secondary, #5A5651)",cursor:"pointer",fontFamily:"var(--font-mono, monospace)",fontSize:"0.75rem",fontWeight:o?600:400},children:i},t)})]}),e.jsx("button",{type:"button",onClick:G,"data-testid":"skill-detail-install-primary","aria-label":"Install skill",style:{display:"inline-flex",alignItems:"center",marginBottom:"0.75rem",padding:"0.5rem 1rem",borderRadius:6,border:"1px solid var(--text-primary, #191919)",background:"var(--text-primary, #191919)",color:"var(--bg-surface, #FFFFFF)",cursor:"pointer",fontFamily:"var(--font-mono, monospace)",fontSize:"0.875rem",fontWeight:600},children:"Install"}),e.jsx("div",{"data-testid":"skill-detail-install-command",style:{display:"block"},children:e.jsx(ae,{compact:!0,children:p.variants.map((t,o)=>e.jsxs("div",{"data-testid":`skill-detail-install-variant-${t.label}`,style:{display:"flex",gap:"0.75rem",alignItems:"flex-start",marginTop:o===0?0:"0.75rem"},children:[e.jsxs("div",{style:{flex:1,minWidth:0},children:[e.jsx("div",{style:{color:"#8B949E",marginBottom:"0.125rem"},children:t.comment}),e.jsxs("div",{children:[e.jsx("span",{style:{color:"#8B949E",marginRight:"0.5rem"},children:"$"}),e.jsx("span",{"data-testid":`skill-detail-install-variant-cmd-${t.label}`,children:t.command})]})]}),e.jsx("button",{type:"button",onClick:async()=>{const c=await P(t.command),i=c?`Run ${t.command} in your terminal`:"Copy failed — please copy the command manually.";if(l)try{l(i,c?"success":"error")}catch{}else F(i,c?"success":"error",3500);c&&U(a,{skillName:v,version:h??"",q:"",ts:Date.now()})},"data-testid":`skill-detail-copy-${t.label}`,"aria-label":`Copy ${t.label} install command`,style:{flexShrink:0,padding:"0.25rem 0.6rem",borderRadius:4,border:"1px solid color-mix(in srgb, #E6EDF3 25%, transparent)",background:"color-mix(in srgb, #E6EDF3 8%, transparent)",color:"#E6EDF3",cursor:"pointer",fontFamily:"var(--font-mono, monospace)",fontSize:"0.7rem",alignSelf:"flex-end"},children:"Copy"})]},t.label))})})]}):null]})]})]})]})}export{xe as SkillDetailPanel,xe as default};