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
@@ -2,24 +2,16 @@
2
2
  // install-engine-routes-helpers.ts — extracted prerequisite-CLI lookup so it
3
3
  // can be mocked independently of node:child_process in unit tests.
4
4
  // Ref: 0734 AC-US5-02
5
+ //
6
+ // Now a thin re-export of the shared `whichSync` helper; the previous
7
+ // inline `spawnSync`-based implementation lives in utils/which.ts.
5
8
  // ---------------------------------------------------------------------------
6
- import { spawnSync } from "node:child_process";
9
+ import { whichSync } from "./utils/which.js";
7
10
  /**
8
11
  * Returns true when `cmd` resolves to an executable on PATH.
9
- *
10
- * Uses `which` (POSIX) / `where` (Windows). Pure best-effort: any failure to
11
- * spawn the lookup process is treated as "not available".
12
+ * Uses `which` / `where` under the hood with metacharacter guard + cache.
12
13
  */
13
14
  export function isCliAvailable(cmd) {
14
- if (!cmd || /[^a-zA-Z0-9_-]/.test(cmd))
15
- return false; // hard guard against shell metacharacters
16
- const lookup = process.platform === "win32" ? "where" : "which";
17
- try {
18
- const res = spawnSync(lookup, [cmd], { stdio: "ignore" });
19
- return res.status === 0;
20
- }
21
- catch {
22
- return false;
23
- }
15
+ return whichSync(cmd);
24
16
  }
25
17
  //# sourceMappingURL=install-engine-routes-helpers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"install-engine-routes-helpers.js","sourceRoot":"","sources":["../../src/eval-server/install-engine-routes-helpers.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,6EAA6E;AAC7E,mEAAmE;AACnE,sBAAsB;AACtB,8EAA8E;AAE9E,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,CAAC,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC,0CAA0C;IAEhG,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;IAChE,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC1D,OAAO,GAAG,CAAC,MAAM,KAAK,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"install-engine-routes-helpers.js","sourceRoot":"","sources":["../../src/eval-server/install-engine-routes-helpers.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,6EAA6E;AAC7E,mEAAmE;AACnE,sBAAsB;AACtB,EAAE;AACF,sEAAsE;AACtE,mEAAmE;AACnE,8EAA8E;AAE9E,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC;AACxB,CAAC"}
@@ -0,0 +1,42 @@
1
+ export type OriginSource = "platform" | "anthropic-registry" | "local";
2
+ export type OriginProvider = "vskill" | "anthropic" | "local";
3
+ export interface OriginEnvelope {
4
+ source: OriginSource;
5
+ owner: string | null;
6
+ repo: string | null;
7
+ provider: OriginProvider;
8
+ trackedForUpdates: boolean;
9
+ /** Lockfile dir that produced the hit (for debugging). */
10
+ lockfilePath?: string;
11
+ /** Frontmatter source string when Tier 3 hits (debug). */
12
+ frontmatterSource?: string;
13
+ /** Registry key when Tier 4 hits (debug). */
14
+ registryMatch?: string;
15
+ }
16
+ /**
17
+ * Well-known Anthropic-shipped skills. Mapped to their canonical
18
+ * `anthropics/skills/<name>` upstream (the verified-skill.com URL pattern
19
+ * for skills published from the `anthropics/skills` GitHub repo) so the
20
+ * Versions tab can fetch upstream history even when the skill arrived
21
+ * without a vskill lockfile entry.
22
+ *
23
+ * Verified live (2026-05-01): `https://verified-skill.com/api/v1/skills/anthropics/skills/pptx/versions`
24
+ * returns `{ versions, count, unversioned, currentVersion }` JSON. The
25
+ * `anthropic-skills/<name>` pattern (with hyphen) returns 404.
26
+ *
27
+ * Maintained as a hand-curated list. Add new names as Anthropic ships them.
28
+ */
29
+ export declare const ANTHROPIC_SKILL_REGISTRY: Record<string, {
30
+ owner: string;
31
+ repo: string;
32
+ }>;
33
+ /** Test-only: clear the in-process cache between cases. */
34
+ export declare function resetOriginResolverCache(): void;
35
+ /**
36
+ * Resolve a skill's full provenance envelope.
37
+ *
38
+ * @param skill — bare skill slug (e.g. "slack-messaging")
39
+ * @param plugin — the plugin/agent dir the skill is mounted under (e.g. ".claude")
40
+ * @param root — the studio process cwd (project root)
41
+ */
42
+ export declare function resolveSkillOrigin(skill: string, plugin: string | null, root: string): Promise<OriginEnvelope>;
@@ -0,0 +1,168 @@
1
+ // 0823 — Comprehensive Origin Resolver for Skill Provenance.
2
+ //
3
+ // Determines where a skill came from so the Versions tab + rescan can fetch
4
+ // upstream version history. Walks five tiers; first hit wins.
5
+ //
6
+ // (1) project lockfile `<root>/vskill.lock`
7
+ // (2) user-global lockfile `~/.agents/vskill.lock`
8
+ // (3) frontmatter `source:` field in SKILL.md (deferred — present in interface,
9
+ // wired in a follow-up; today this tier is reserved and falls through.)
10
+ // (4) Anthropic-skill registry (well-known names → anthropics/skills/<name>)
11
+ // — see ANTHROPIC_SKILL_REGISTRY below. Verified-live URL pattern.
12
+ // (5) bare-name fallback / unknown (provider="local", no upstream)
13
+ //
14
+ // IMPORTANT: cache resolved envelopes to avoid repeated lockfile reads, but
15
+ // DO NOT cache the "local" fallback — a future install would otherwise be
16
+ // invisible until studio restart.
17
+ import { homedir } from "node:os";
18
+ import { join } from "node:path";
19
+ import { readLockfile } from "../lockfile/lockfile.js";
20
+ import { parseSource } from "../resolvers/source-resolver.js";
21
+ /**
22
+ * Well-known Anthropic-shipped skills. Mapped to their canonical
23
+ * `anthropics/skills/<name>` upstream (the verified-skill.com URL pattern
24
+ * for skills published from the `anthropics/skills` GitHub repo) so the
25
+ * Versions tab can fetch upstream history even when the skill arrived
26
+ * without a vskill lockfile entry.
27
+ *
28
+ * Verified live (2026-05-01): `https://verified-skill.com/api/v1/skills/anthropics/skills/pptx/versions`
29
+ * returns `{ versions, count, unversioned, currentVersion }` JSON. The
30
+ * `anthropic-skills/<name>` pattern (with hyphen) returns 404.
31
+ *
32
+ * Maintained as a hand-curated list. Add new names as Anthropic ships them.
33
+ */
34
+ export const ANTHROPIC_SKILL_REGISTRY = {
35
+ "slack-messaging": { owner: "anthropics", repo: "skills" },
36
+ pptx: { owner: "anthropics", repo: "skills" },
37
+ "excalidraw-diagram-generator": { owner: "anthropics", repo: "skills" },
38
+ "excalidraw-skill": { owner: "anthropics", repo: "skills" },
39
+ "frontend-design": { owner: "anthropics", repo: "skills" },
40
+ gws: { owner: "anthropics", repo: "skills" },
41
+ "remotion-best-practices": { owner: "anthropics", repo: "skills" },
42
+ "social-media-posting": { owner: "anthropics", repo: "skills" },
43
+ "webapp-testing": { owner: "anthropics", repo: "skills" },
44
+ "obsidian-brain": { owner: "anthropics", repo: "skills" },
45
+ nanobanana: { owner: "anthropics", repo: "skills" },
46
+ pdf: { owner: "anthropics", repo: "skills" },
47
+ docx: { owner: "anthropics", repo: "skills" },
48
+ xlsx: { owner: "anthropics", repo: "skills" },
49
+ "skill-creator": { owner: "anthropics", repo: "skills" },
50
+ };
51
+ // 0823 simplify: cap the cache so a long-lived studio process polling many
52
+ // distinct (plugin, skill) combinations doesn't grow unboundedly. The Map's
53
+ // insertion-order semantics give us cheap LRU-ish eviction: when we exceed
54
+ // MAX_CACHE_ENTRIES, drop the oldest entries until under the soft limit.
55
+ const MAX_CACHE_ENTRIES = 1000;
56
+ const PRUNE_TARGET = 500;
57
+ const cache = new Map();
58
+ function setCacheEntry(key, value) {
59
+ cache.set(key, value);
60
+ if (cache.size > MAX_CACHE_ENTRIES) {
61
+ const dropCount = cache.size - PRUNE_TARGET;
62
+ let i = 0;
63
+ for (const k of cache.keys()) {
64
+ if (i++ >= dropCount)
65
+ break;
66
+ cache.delete(k);
67
+ }
68
+ }
69
+ }
70
+ /** Test-only: clear the in-process cache between cases. */
71
+ export function resetOriginResolverCache() {
72
+ cache.clear();
73
+ }
74
+ function tryLockfileTier(skill, dir) {
75
+ let lock;
76
+ try {
77
+ lock = readLockfile(dir);
78
+ }
79
+ catch {
80
+ return null;
81
+ }
82
+ const entry = lock?.skills?.[skill];
83
+ if (!entry?.source)
84
+ return null;
85
+ let parsed;
86
+ try {
87
+ parsed = parseSource(entry.source);
88
+ }
89
+ catch {
90
+ return null;
91
+ }
92
+ if ((parsed.type === "github" ||
93
+ parsed.type === "github-plugin" ||
94
+ parsed.type === "marketplace") &&
95
+ parsed.owner &&
96
+ parsed.repo) {
97
+ return { owner: parsed.owner, repo: parsed.repo };
98
+ }
99
+ return null;
100
+ }
101
+ /**
102
+ * Resolve a skill's full provenance envelope.
103
+ *
104
+ * @param skill — bare skill slug (e.g. "slack-messaging")
105
+ * @param plugin — the plugin/agent dir the skill is mounted under (e.g. ".claude")
106
+ * @param root — the studio process cwd (project root)
107
+ */
108
+ export async function resolveSkillOrigin(skill, plugin, root) {
109
+ const cacheKey = `${plugin ?? ""}::${skill}`;
110
+ const cached = cache.get(cacheKey);
111
+ if (cached)
112
+ return cached;
113
+ // Tier 1 — project lockfile
114
+ const projectHit = tryLockfileTier(skill, root);
115
+ if (projectHit) {
116
+ const env = {
117
+ source: "platform",
118
+ owner: projectHit.owner,
119
+ repo: projectHit.repo,
120
+ provider: "vskill",
121
+ trackedForUpdates: true,
122
+ lockfilePath: root,
123
+ };
124
+ setCacheEntry(cacheKey, env);
125
+ return env;
126
+ }
127
+ // Tier 2 — user-global lockfile (~/.agents/vskill.lock)
128
+ const globalDir = join(homedir(), ".agents");
129
+ const globalHit = tryLockfileTier(skill, globalDir);
130
+ if (globalHit) {
131
+ const env = {
132
+ source: "platform",
133
+ owner: globalHit.owner,
134
+ repo: globalHit.repo,
135
+ provider: "vskill",
136
+ trackedForUpdates: true,
137
+ lockfilePath: globalDir,
138
+ };
139
+ setCacheEntry(cacheKey, env);
140
+ return env;
141
+ }
142
+ // Tier 3 — frontmatter `source:` field. Reserved; not yet wired (no
143
+ // installed-skill writes a frontmatter origin today). Tier-skipped silently.
144
+ // Tier 4 — Anthropic-skill registry
145
+ const registryHit = ANTHROPIC_SKILL_REGISTRY[skill];
146
+ if (registryHit) {
147
+ const env = {
148
+ source: "anthropic-registry",
149
+ owner: registryHit.owner,
150
+ repo: registryHit.repo,
151
+ provider: "anthropic",
152
+ trackedForUpdates: true,
153
+ registryMatch: skill,
154
+ };
155
+ setCacheEntry(cacheKey, env);
156
+ return env;
157
+ }
158
+ // Tier 5 — local / unknown. NOT cached so a future install becomes visible
159
+ // without a studio restart.
160
+ return {
161
+ source: "local",
162
+ owner: null,
163
+ repo: null,
164
+ provider: "local",
165
+ trackedForUpdates: false,
166
+ };
167
+ }
168
+ //# sourceMappingURL=origin-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"origin-resolver.js","sourceRoot":"","sources":["../../src/eval-server/origin-resolver.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,16 @@
1
1
  import * as http from "node:http";
2
2
  export declare function getPlatformBaseUrl(): string;
3
3
  export declare function shouldProxyToPlatform(url: string | undefined): boolean;
4
+ export declare function shouldInjectAuth(url: string | undefined): boolean;
5
+ /** Test-only reset hook. */
6
+ export declare function _resetPlatformProxyAuthCacheForTests(): void;
7
+ export interface PickHeadersOptions {
8
+ /** Request path (e.g. "/api/v1/tenants/abc/skills"). */
9
+ path?: string;
10
+ /** Token provider override; defaults to in-process cached keychain read. */
11
+ tokenProvider?: () => string | null;
12
+ }
13
+ export declare function pickHeadersForUpstream(src: http.IncomingHttpHeaders, opts?: PickHeadersOptions): Record<string, string>;
4
14
  /**
5
15
  * Proxy a single HTTP request from the studio to the platform.
6
16
  * SSE-safe: streams the upstream body chunks directly to the response so
@@ -50,6 +50,7 @@
50
50
  import * as http from "node:http";
51
51
  import * as https from "node:https";
52
52
  import { randomUUID } from "node:crypto";
53
+ import { getDefaultKeychain } from "../lib/keychain.js";
53
54
  const DEFAULT_PLATFORM_URL = "https://verified-skill.com";
54
55
  // Hop-by-hop headers per RFC 2616 §13.5.1 — never forward these on a proxy.
55
56
  const HOP_BY_HOP = new Set([
@@ -88,24 +89,79 @@ const PROXY_PREFIXES = [
88
89
  "/api/v1/studio/search",
89
90
  "/api/v1/studio/telemetry/",
90
91
  "/api/v1/stats",
92
+ // Private (org-scoped) routes that must carry the user's GitHub bearer token
93
+ // to the platform. The browser never sees this token; injection happens here.
94
+ "/api/v1/private/",
95
+ "/api/v1/tenants/",
96
+ ];
97
+ /**
98
+ * Path prefixes that require an `Authorization: Bearer <github-token>` header
99
+ * to be injected at the proxy boundary. Public skill routes are intentionally
100
+ * excluded — they must remain anonymous so unauthenticated tabs continue to
101
+ * function in the public skill catalog.
102
+ */
103
+ const AUTH_REQUIRED_PREFIXES = [
104
+ "/api/v1/private/",
105
+ "/api/v1/tenants/",
91
106
  ];
92
107
  export function shouldProxyToPlatform(url) {
93
108
  if (!url)
94
109
  return false;
95
110
  return PROXY_PREFIXES.some((p) => url.startsWith(p));
96
111
  }
97
- function pickHeadersForUpstream(src) {
112
+ export function shouldInjectAuth(url) {
113
+ if (!url)
114
+ return false;
115
+ return AUTH_REQUIRED_PREFIXES.some((p) => url.startsWith(p));
116
+ }
117
+ // In-process token cache so a burst of proxy requests doesn't repeatedly hit
118
+ // the OS keychain. The keychain itself is fast on macOS but can be slower on
119
+ // Linux libsecret. 60s aligns with how stale a manual `vskill auth login`
120
+ // → `vskill studio refresh` loop would feel.
121
+ let _cachedToken = null;
122
+ const TOKEN_CACHE_MS = 60_000;
123
+ function readTokenForProxy(now = Date.now()) {
124
+ if (_cachedToken && _cachedToken.expiresAt > now)
125
+ return _cachedToken.value;
126
+ let token = null;
127
+ try {
128
+ token = getDefaultKeychain().getGitHubToken();
129
+ }
130
+ catch {
131
+ token = null;
132
+ }
133
+ _cachedToken = { value: token, expiresAt: now + TOKEN_CACHE_MS };
134
+ return token;
135
+ }
136
+ /** Test-only reset hook. */
137
+ export function _resetPlatformProxyAuthCacheForTests() {
138
+ _cachedToken = null;
139
+ }
140
+ export function pickHeadersForUpstream(src, opts = {}) {
98
141
  const out = {};
99
142
  for (const [k, v] of Object.entries(src)) {
100
143
  if (typeof v === "undefined")
101
144
  continue;
102
145
  if (HOP_BY_HOP.has(k.toLowerCase()))
103
146
  continue;
147
+ // Strip any client-supplied Authorization on private/tenant paths — we
148
+ // mint our own from the keychain. On other paths we pass through (the
149
+ // existing public proxy never carried Authorization, so this is a no-op).
150
+ if (k.toLowerCase() === "authorization" && opts.path && shouldInjectAuth(opts.path)) {
151
+ continue;
152
+ }
104
153
  out[k] = Array.isArray(v) ? v.join(", ") : String(v);
105
154
  }
106
155
  // Preserve client-IP intent for any future platform-side observability.
107
156
  const xff = out["x-forwarded-for"];
108
157
  out["x-forwarded-for"] = xff ? `${xff}, 127.0.0.1` : "127.0.0.1";
158
+ if (opts.path && shouldInjectAuth(opts.path)) {
159
+ const tokenProvider = opts.tokenProvider ?? (() => readTokenForProxy());
160
+ const token = tokenProvider();
161
+ if (token) {
162
+ out["authorization"] = `Bearer ${token}`;
163
+ }
164
+ }
109
165
  return out;
110
166
  }
111
167
  function pickHeadersForDownstream(src) {
@@ -138,7 +194,7 @@ export function proxyToPlatform(req, res, baseUrl = getPlatformBaseUrl()) {
138
194
  port: target.port || (target.protocol === "https:" ? 443 : 80),
139
195
  path: `${target.pathname}${target.search}`,
140
196
  method: req.method,
141
- headers: pickHeadersForUpstream(req.headers),
197
+ headers: pickHeadersForUpstream(req.headers, { path: target.pathname }),
142
198
  }, (upstreamRes) => {
143
199
  const status = upstreamRes.statusCode ?? 502;
144
200
  const headers = pickHeadersForDownstream(upstreamRes.headers);
@@ -1 +1 @@
1
- {"version":3,"file":"platform-proxy.js","sourceRoot":"","sources":["../../src/eval-server/platform-proxy.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"platform-proxy.js","sourceRoot":"","sources":["../../src/eval-server/platform-proxy.ts"],"names":[],"mappings":"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"}
@@ -126,6 +126,14 @@ export declare function interpretValidatorResult(skillPath: string, res: SpawnRe
126
126
  * helper trivially testable.
127
127
  */
128
128
  export declare function formatValidatorReport(outcome: ValidatorOutcome): string;
129
+ /** Compute target directory for a new skill based on layout */
130
+ export declare function resolveSafe(base: string, relPath: string): string;
131
+ /**
132
+ * Build a `.env.example` body from a list of secret env-var names. Each line
133
+ * is `NAME=` with no value — the file ships placeholders only, never real
134
+ * secrets. Caller is responsible for including a leading comment if desired.
135
+ */
136
+ export declare function buildEnvExample(secrets: string[]): string;
129
137
  /**
130
138
  * Check if a resolved path is strictly within a root directory.
131
139
  * Uses trailing separator to prevent prefix collision (e.g., /project vs /project-evil).
@@ -412,6 +412,55 @@ export function formatValidatorReport(outcome) {
412
412
  }
413
413
  }
414
414
  /** Compute target directory for a new skill based on layout */
415
+ // ---------------------------------------------------------------------------
416
+ // 0815: path safety + .env.example helpers for multi-file skill creation.
417
+ //
418
+ // resolveSafe rejects any path that would escape the skill directory: absolute
419
+ // paths, drive letters, '..' segments, and relative paths whose resolved form
420
+ // lands outside `base`. Used by POST /api/skills/create when looping the
421
+ // optional `files` map. Throws so the route returns 400.
422
+ // ---------------------------------------------------------------------------
423
+ export function resolveSafe(base, relPath) {
424
+ if (typeof relPath !== "string" || relPath.length === 0) {
425
+ throw new Error(`resolveSafe: empty path`);
426
+ }
427
+ // Reject absolute paths (POSIX) and Windows drive letters.
428
+ if (relPath.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(relPath)) {
429
+ throw new Error(`resolveSafe: refused absolute path '${relPath}'`);
430
+ }
431
+ // Reject any '..' segment — easier to reason about than relying on resolve()
432
+ // alone, since a benign-looking resolve result can still hide a traversal
433
+ // depending on the realpath of `base`.
434
+ const segments = relPath.split(/[/\\]+/);
435
+ if (segments.some((s) => s === "..")) {
436
+ throw new Error(`resolveSafe: refused traversal in '${relPath}'`);
437
+ }
438
+ const resolved = resolve(base, relPath);
439
+ const baseResolved = resolve(base);
440
+ // Defense in depth: ensure the resolved path is still inside base.
441
+ if (resolved !== baseResolved && !resolved.startsWith(baseResolved + sep)) {
442
+ throw new Error(`resolveSafe: path '${relPath}' escapes base`);
443
+ }
444
+ return resolved;
445
+ }
446
+ /**
447
+ * Build a `.env.example` body from a list of secret env-var names. Each line
448
+ * is `NAME=` with no value — the file ships placeholders only, never real
449
+ * secrets. Caller is responsible for including a leading comment if desired.
450
+ */
451
+ export function buildEnvExample(secrets) {
452
+ const lines = [
453
+ "# .env.example — populated by the skill author, never committed.",
454
+ "# Copy to .env.local in this directory and fill in real values.",
455
+ "",
456
+ ];
457
+ for (const name of secrets) {
458
+ if (typeof name !== "string" || name.length === 0)
459
+ continue;
460
+ lines.push(`${name}=`);
461
+ }
462
+ return lines.join("\n") + "\n";
463
+ }
415
464
  function computeSkillDir(root, layout, plugin, name) {
416
465
  switch (layout) {
417
466
  case 1: return join(root, plugin, "skills", name);
@@ -1018,6 +1067,53 @@ export function registerSkillCreateRoutes(router, root) {
1018
1067
  catch { /* history write failure should not break the main response */ }
1019
1068
  }
1020
1069
  }
1070
+ // -------------------------------------------------------------------
1071
+ // 0815: multi-file payload — write auxiliary files atomically.
1072
+ //
1073
+ // Each `files` entry is validated through resolveSafe() before any I/O.
1074
+ // On any failure mid-write, rollback by removing the skill directory
1075
+ // (unless this is an update — preserve existing user content). The
1076
+ // rollback is intentionally NOT inside a finally — the existing outer
1077
+ // try/catch (a few lines below) handles top-level failures and we want
1078
+ // the path-traversal case to surface as a 400 with the dir cleaned up.
1079
+ // -------------------------------------------------------------------
1080
+ if (body.files && typeof body.files === "object") {
1081
+ const written = [];
1082
+ try {
1083
+ for (const [relPath, contents] of Object.entries(body.files)) {
1084
+ if (typeof contents !== "string") {
1085
+ throw new Error(`files['${relPath}'] is not a string`);
1086
+ }
1087
+ const safePath = resolveSafe(targetDir, relPath);
1088
+ const safeDir = safePath.slice(0, safePath.lastIndexOf(sep));
1089
+ mkdirSync(safeDir, { recursive: true });
1090
+ writeFileSync(safePath, contents, "utf-8");
1091
+ written.push(safePath);
1092
+ }
1093
+ }
1094
+ catch (err) {
1095
+ // Rollback: clean up the partial directory unless we're updating.
1096
+ if (!isUpdateMode) {
1097
+ try {
1098
+ rmSync(targetDir, { recursive: true, force: true });
1099
+ }
1100
+ catch { /* best-effort */ }
1101
+ }
1102
+ sendJson(res, {
1103
+ error: `Failed to write multi-file payload: ${err.message}`,
1104
+ code: "multi-file-write-failed",
1105
+ }, 400, req);
1106
+ return;
1107
+ }
1108
+ }
1109
+ // 0815: emit .env.example from declared secrets (placeholders only).
1110
+ if (Array.isArray(body.secrets) && body.secrets.length > 0) {
1111
+ try {
1112
+ const envExamplePath = join(targetDir, ".env.example");
1113
+ writeFileSync(envExamplePath, buildEnvExample(body.secrets), "utf-8");
1114
+ }
1115
+ catch { /* non-blocking — secrets are advisory */ }
1116
+ }
1021
1117
  // Finalize: remove draft.json if it exists (draft → final)
1022
1118
  const draftPath = join(targetDir, "draft.json");
1023
1119
  if (existsSync(draftPath)) {