vskill 1.0.14 → 1.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -0
- package/agents.json +1 -1
- package/dist/agents/agents-registry.d.ts +8 -3
- package/dist/agents/agents-registry.js.map +1 -1
- package/dist/bin.js +0 -0
- package/dist/clone/github-scaffold.d.ts +38 -0
- package/dist/clone/github-scaffold.js +108 -0
- package/dist/clone/github-scaffold.js.map +1 -0
- package/dist/clone/provenance-fork.d.ts +34 -0
- package/dist/clone/provenance-fork.js +97 -0
- package/dist/clone/provenance-fork.js.map +1 -0
- package/dist/clone/reference-scanner.d.ts +19 -0
- package/dist/clone/reference-scanner.js +144 -0
- package/dist/clone/reference-scanner.js.map +1 -0
- package/dist/clone/skill-locator.d.ts +26 -0
- package/dist/clone/skill-locator.js +248 -0
- package/dist/clone/skill-locator.js.map +1 -0
- package/dist/clone/target-router.d.ts +73 -0
- package/dist/clone/target-router.js +200 -0
- package/dist/clone/target-router.js.map +1 -0
- package/dist/clone/types.d.ts +82 -0
- package/dist/clone/types.js +11 -0
- package/dist/clone/types.js.map +1 -0
- package/dist/commands/add.js +96 -32
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/auth.d.ts +23 -0
- package/dist/commands/auth.js +273 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/clone-prompts.d.ts +13 -0
- package/dist/commands/clone-prompts.js +67 -0
- package/dist/commands/clone-prompts.js.map +1 -0
- package/dist/commands/clone.d.ts +70 -0
- package/dist/commands/clone.js +649 -0
- package/dist/commands/clone.js.map +1 -0
- package/dist/commands/eval/serve.js +8 -1
- package/dist/commands/eval/serve.js.map +1 -1
- package/dist/commands/keys.js +54 -2
- package/dist/commands/keys.js.map +1 -1
- package/dist/eval/skill-scanner.d.ts +2 -12
- package/dist/eval/skill-scanner.js +27 -5
- package/dist/eval/skill-scanner.js.map +1 -1
- package/dist/eval-server/api-routes.js +527 -35
- package/dist/eval-server/api-routes.js.map +1 -1
- package/dist/eval-server/data-events.d.ts +1 -1
- package/dist/eval-server/data-events.js.map +1 -1
- package/dist/eval-server/eval-server.js +8 -1
- package/dist/eval-server/eval-server.js.map +1 -1
- package/dist/eval-server/install-engine-routes-helpers.d.ts +1 -3
- package/dist/eval-server/install-engine-routes-helpers.js +6 -14
- package/dist/eval-server/install-engine-routes-helpers.js.map +1 -1
- package/dist/eval-server/install-state-routes.d.ts +21 -0
- package/dist/eval-server/install-state-routes.js +111 -0
- package/dist/eval-server/install-state-routes.js.map +1 -0
- package/dist/eval-server/origin-resolver.d.ts +42 -0
- package/dist/eval-server/origin-resolver.js +168 -0
- package/dist/eval-server/origin-resolver.js.map +1 -0
- package/dist/eval-server/platform-proxy.d.ts +10 -0
- package/dist/eval-server/platform-proxy.js +58 -2
- package/dist/eval-server/platform-proxy.js.map +1 -1
- package/dist/eval-server/skill-resolver.js +40 -0
- package/dist/eval-server/skill-resolver.js.map +1 -1
- package/dist/eval-server/utils/resolve-editor.d.ts +6 -1
- package/dist/eval-server/utils/resolve-editor.js +11 -26
- package/dist/eval-server/utils/resolve-editor.js.map +1 -1
- package/dist/eval-server/utils/scan-install-locations.d.ts +7 -0
- package/dist/eval-server/utils/scan-install-locations.js +20 -0
- package/dist/eval-server/utils/scan-install-locations.js.map +1 -1
- package/dist/eval-server/utils/which.d.ts +15 -0
- package/dist/eval-server/utils/which.js +76 -0
- package/dist/eval-server/utils/which.js.map +1 -0
- package/dist/eval-ui/assets/{CreateSkillPage-CKvqAya0.js → CreateSkillPage-CvdYq8Rr.js} +5 -5
- package/dist/eval-ui/assets/{FindSkillsPalette-B8pTa5NP.js → FindSkillsPalette-DsSgotS9.js} +2 -2
- package/dist/eval-ui/assets/{SearchPaletteCore-CkVRvaZk.js → SearchPaletteCore-Bf3PBC64.js} +2 -2
- package/dist/eval-ui/assets/SkillDetailPanel-DAD2yJO-.js +1 -0
- package/dist/eval-ui/assets/{UpdateDropdown-DA7OktXO.js → UpdateDropdown-h5Hg3h7Z.js} +1 -1
- package/dist/eval-ui/assets/{index-BKAvJDDF.css → index-CKLqBL52.css} +1 -1
- package/dist/eval-ui/assets/index-JaDg6FlU.js +124 -0
- package/dist/eval-ui/index.html +2 -2
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -1
- package/dist/installer/canonical.js +2 -11
- package/dist/installer/canonical.js.map +1 -1
- package/dist/installer/frontmatter.d.ts +26 -0
- package/dist/installer/frontmatter.js +91 -1
- package/dist/installer/frontmatter.js.map +1 -1
- package/dist/lib/github-fetch.d.ts +22 -0
- package/dist/lib/github-fetch.js +152 -0
- package/dist/lib/github-fetch.js.map +1 -0
- package/dist/lib/keychain.d.ts +41 -0
- package/dist/lib/keychain.js +232 -0
- package/dist/lib/keychain.js.map +1 -0
- package/dist/studio/lib/provenance.js +3 -2
- package/dist/studio/lib/provenance.js.map +1 -1
- package/dist/studio/lib/query.d.ts +1 -0
- package/dist/studio/lib/query.js +7 -0
- package/dist/studio/lib/query.js.map +1 -0
- package/dist/studio/lib/scope-transfer.d.ts +6 -0
- package/dist/studio/lib/scope-transfer.js +5 -2
- package/dist/studio/lib/scope-transfer.js.map +1 -1
- package/dist/studio/routes/index.js +3 -0
- package/dist/studio/routes/index.js.map +1 -1
- package/dist/studio/routes/ops.js +1 -3
- package/dist/studio/routes/ops.js.map +1 -1
- package/dist/studio/routes/promote.js +1 -3
- package/dist/studio/routes/promote.js.map +1 -1
- package/dist/studio/routes/revert.js +2 -17
- package/dist/studio/routes/revert.js.map +1 -1
- package/dist/studio/routes/test-install.js +1 -3
- package/dist/studio/routes/test-install.js.map +1 -1
- package/dist/studio/types.d.ts +13 -0
- package/dist/utils/claude-plugin.d.ts +26 -0
- package/dist/utils/claude-plugin.js +60 -0
- package/dist/utils/claude-plugin.js.map +1 -1
- package/dist/utils/skill-builder-detection.d.ts +14 -1
- package/dist/utils/skill-builder-detection.js +20 -8
- package/dist/utils/skill-builder-detection.js.map +1 -1
- package/dist/utils/skill-creator-detection.d.ts +10 -2
- package/dist/utils/skill-creator-detection.js +12 -43
- package/dist/utils/skill-creator-detection.js.map +1 -1
- package/package.json +2 -1
- package/dist/eval-ui/assets/SkillDetailPanel-d4_LquVH.js +0 -1
- package/dist/eval-ui/assets/index-DCbohW6l.js +0 -122
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// vskill clone — target-router
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Three target writers, all `.tmp`-staged so the orchestrator can atomically
|
|
5
|
+
// rename the staged copy into place once every other step (frontmatter
|
|
6
|
+
// rewrite, provenance write, validation) has succeeded.
|
|
7
|
+
//
|
|
8
|
+
// writeStandalone → drop the cloned skill at <dir>
|
|
9
|
+
// writeToPlugin → drop into <plugin>/skills/<skill> and append to manifest
|
|
10
|
+
// writeNewPlugin → scaffold a fresh plugin with .claude-plugin/plugin.json + skills/<skill>
|
|
11
|
+
//
|
|
12
|
+
// All three return the absolute path of the *staging* `.tmp` directory so the
|
|
13
|
+
// orchestrator can run subsequent in-place edits (frontmatter, sidecar) before
|
|
14
|
+
// committing with `fs.rename`. The ONLY exception is `writeToPlugin` which
|
|
15
|
+
// also returns a separate `manifestTmpPath` so the orchestrator can rename
|
|
16
|
+
// both `.tmp` paths in sequence (two-phase commit).
|
|
17
|
+
//
|
|
18
|
+
// Filtering: the recursive copy reuses the public `shouldSkipFromCommands`
|
|
19
|
+
// helper from src/shared/copy-plugin-filtered.ts plus a root-level filter for
|
|
20
|
+
// the vskill-internal sidecars `.vskill-meta.json` and `.vskill-source.json`
|
|
21
|
+
// (matching the OWN-scope copy semantics in src/studio/lib/scope-transfer.ts).
|
|
22
|
+
//
|
|
23
|
+
// See spec.md AC-US1-03 / AC-US2-01..03 / AC-US3-01, plan.md §5–§6.
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
import { promises as fs, existsSync, readdirSync, statSync, copyFileSync, mkdirSync, rmSync, } from "node:fs";
|
|
26
|
+
import { join, dirname, basename } from "node:path";
|
|
27
|
+
import { shouldSkipFromCommands } from "../shared/copy-plugin-filtered.js";
|
|
28
|
+
/**
|
|
29
|
+
* Recursive skill-dir copy with sidecar filtering at the root and
|
|
30
|
+
* shouldSkipFromCommands filtering at every depth. Mirrors the OWN-scope
|
|
31
|
+
* helper in src/studio/lib/scope-transfer.ts (lines 101–119) — that helper is
|
|
32
|
+
* not exported, so we re-implement the same behavior rather than duplicate
|
|
33
|
+
* its file.
|
|
34
|
+
*/
|
|
35
|
+
function copySkillDirFiltered(sourceDir, targetDir, relBase = "") {
|
|
36
|
+
mkdirSync(targetDir, { recursive: true });
|
|
37
|
+
let filesCopied = 0;
|
|
38
|
+
for (const entry of readdirSync(sourceDir)) {
|
|
39
|
+
// Filter vskill internal sidecars at the skill root only — same asymmetric
|
|
40
|
+
// contract as scope-transfer.ts: OWN scope is a single skill dir, so
|
|
41
|
+
// sidecars only ever exist at the root.
|
|
42
|
+
if (!relBase && (entry === ".vskill-meta.json" || entry === ".vskill-source.json"))
|
|
43
|
+
continue;
|
|
44
|
+
const relPath = relBase ? `${relBase}/${entry}` : entry;
|
|
45
|
+
const sourcePath = join(sourceDir, entry);
|
|
46
|
+
const st = statSync(sourcePath);
|
|
47
|
+
if (st.isDirectory()) {
|
|
48
|
+
filesCopied += copySkillDirFiltered(sourcePath, join(targetDir, entry), relPath);
|
|
49
|
+
}
|
|
50
|
+
else if (st.isFile() && !shouldSkipFromCommands(relPath)) {
|
|
51
|
+
copyFileSync(sourcePath, join(targetDir, entry));
|
|
52
|
+
filesCopied += 1;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return filesCopied;
|
|
56
|
+
}
|
|
57
|
+
/** Compute the `.tmp` sibling of a final destination path. */
|
|
58
|
+
export function tmpSibling(finalPath) {
|
|
59
|
+
return `${finalPath}.tmp`;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Best-effort cleanup of a `.tmp` path. Mirrors the rollback shape used by
|
|
63
|
+
* src/commands/add.ts:rollbackInstall — swallows per-call errors so a partial
|
|
64
|
+
* filesystem state doesn't suppress the underlying diagnostic the orchestrator
|
|
65
|
+
* is about to surface to the user.
|
|
66
|
+
*/
|
|
67
|
+
export function bestEffortRm(path) {
|
|
68
|
+
try {
|
|
69
|
+
if (existsSync(path))
|
|
70
|
+
rmSync(path, { recursive: true, force: true });
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// best-effort
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Copy a source skill into a fresh standalone target dir, staged at
|
|
78
|
+
* `<finalDir>.tmp`. The orchestrator is responsible for the atomic
|
|
79
|
+
* rename and any pre-checks for collisions / `--force`.
|
|
80
|
+
*/
|
|
81
|
+
export async function writeStandalone(args) {
|
|
82
|
+
const stagingDir = tmpSibling(args.finalDir);
|
|
83
|
+
// Clear any leftover staging directory from a prior failed run before copying.
|
|
84
|
+
bestEffortRm(stagingDir);
|
|
85
|
+
await fs.mkdir(dirname(args.finalDir), { recursive: true });
|
|
86
|
+
const filesCopied = copySkillDirFiltered(args.sourceSkillDir, stagingDir);
|
|
87
|
+
return { stagingDir, filesCopied, finalDir: args.finalDir };
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Copy a source skill into an existing user-owned plugin's skills/ directory
|
|
91
|
+
* AND stage an updated `plugin.json` at `<plugin.json>.tmp` so the
|
|
92
|
+
* orchestrator can rename both in sequence (two-phase commit).
|
|
93
|
+
*
|
|
94
|
+
* Pre-flight: detects malformed plugin.json BEFORE any copy is staged
|
|
95
|
+
* (AC-US2-03). Caller aborts on the thrown error; no skill files are written.
|
|
96
|
+
*/
|
|
97
|
+
export async function writeToPlugin(args) {
|
|
98
|
+
const manifestFinalPath = join(args.pluginRoot, ".claude-plugin", "plugin.json");
|
|
99
|
+
if (!existsSync(manifestFinalPath)) {
|
|
100
|
+
throw new Error(`target plugin manifest not found at ${manifestFinalPath} — pass a plugin root that contains .claude-plugin/plugin.json`);
|
|
101
|
+
}
|
|
102
|
+
// Pre-flight: parse the manifest BEFORE any copy is staged.
|
|
103
|
+
let manifestRaw;
|
|
104
|
+
try {
|
|
105
|
+
manifestRaw = await fs.readFile(manifestFinalPath, "utf-8");
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
throw new Error(`failed to read plugin manifest: ${err.message}`);
|
|
109
|
+
}
|
|
110
|
+
let manifest;
|
|
111
|
+
try {
|
|
112
|
+
manifest = JSON.parse(manifestRaw);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
throw new Error(`target plugin manifest is malformed JSON (${err.message}) — refusing to clone into ${args.pluginRoot}`);
|
|
116
|
+
}
|
|
117
|
+
// The "register the new skill" step appends to a `skills` array (creating
|
|
118
|
+
// one if absent). Existing entries are preserved; duplicate entries are
|
|
119
|
+
// collapsed.
|
|
120
|
+
const skills = Array.isArray(manifest.skills) ? [...manifest.skills] : [];
|
|
121
|
+
if (!skills.includes(args.newSkillName))
|
|
122
|
+
skills.push(args.newSkillName);
|
|
123
|
+
const updatedManifest = { ...manifest, skills };
|
|
124
|
+
const finalDir = join(args.pluginRoot, "skills", args.newSkillName);
|
|
125
|
+
const stagingDir = tmpSibling(finalDir);
|
|
126
|
+
bestEffortRm(stagingDir);
|
|
127
|
+
await fs.mkdir(dirname(finalDir), { recursive: true });
|
|
128
|
+
let filesCopied = 0;
|
|
129
|
+
try {
|
|
130
|
+
filesCopied = copySkillDirFiltered(args.sourceSkillDir, stagingDir);
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
bestEffortRm(stagingDir);
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
// Stage the updated manifest. The orchestrator commits both renames.
|
|
137
|
+
const manifestTmpPath = `${manifestFinalPath}.tmp`;
|
|
138
|
+
try {
|
|
139
|
+
await fs.writeFile(manifestTmpPath, JSON.stringify(updatedManifest, null, 2) + "\n", "utf-8");
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
bestEffortRm(stagingDir);
|
|
143
|
+
bestEffortRm(manifestTmpPath);
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
stagingDir,
|
|
148
|
+
filesCopied,
|
|
149
|
+
finalDir,
|
|
150
|
+
manifestTmpPath,
|
|
151
|
+
manifestFinalPath,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Scaffold a fresh new plugin at `<pluginRoot>` containing
|
|
156
|
+
* `.claude-plugin/plugin.json` (with the requested name) plus
|
|
157
|
+
* `skills/<skill>/` subtree containing the cloned skill.
|
|
158
|
+
*
|
|
159
|
+
* The whole plugin tree is staged at `<pluginRoot>.tmp` and atomically
|
|
160
|
+
* renamed by the caller — there is no two-phase commit because the manifest
|
|
161
|
+
* and the skill files live under a common root (one `rename` is enough).
|
|
162
|
+
*/
|
|
163
|
+
export async function writeNewPlugin(args) {
|
|
164
|
+
const stagingDir = tmpSibling(args.pluginRoot);
|
|
165
|
+
bestEffortRm(stagingDir);
|
|
166
|
+
// Build the new plugin's manifest.
|
|
167
|
+
await fs.mkdir(join(stagingDir, ".claude-plugin"), { recursive: true });
|
|
168
|
+
const manifest = {
|
|
169
|
+
name: args.pluginName,
|
|
170
|
+
description: args.description ?? `Plugin scaffolded by 'vskill clone' containing forked skills.`,
|
|
171
|
+
skills: [args.newSkillName],
|
|
172
|
+
};
|
|
173
|
+
if (args.author) {
|
|
174
|
+
manifest.author = { name: args.author };
|
|
175
|
+
}
|
|
176
|
+
await fs.writeFile(join(stagingDir, ".claude-plugin", "plugin.json"), JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
177
|
+
// Copy the skill into <stagingDir>/skills/<newSkillName>/.
|
|
178
|
+
const skillTargetDir = join(stagingDir, "skills", args.newSkillName);
|
|
179
|
+
let filesCopied = 0;
|
|
180
|
+
try {
|
|
181
|
+
filesCopied = copySkillDirFiltered(args.sourceSkillDir, skillTargetDir);
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
bestEffortRm(stagingDir);
|
|
185
|
+
throw err;
|
|
186
|
+
}
|
|
187
|
+
return { stagingDir, filesCopied, finalDir: args.pluginRoot };
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* The `bareSkillName` helper extracts the unqualified skill name from a
|
|
191
|
+
* fully-qualified name like `anton/ado-mapper` → `ado-mapper`. Used by the
|
|
192
|
+
* orchestrator and by callers that need the directory-safe segment.
|
|
193
|
+
*/
|
|
194
|
+
export function bareSkillName(fullyQualified) {
|
|
195
|
+
const idx = fullyQualified.indexOf("/");
|
|
196
|
+
return idx >= 0 ? fullyQualified.slice(idx + 1) : fullyQualified;
|
|
197
|
+
}
|
|
198
|
+
/** Re-export for callers that want to validate basenames. */
|
|
199
|
+
export { basename };
|
|
200
|
+
//# sourceMappingURL=target-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"target-router.js","sourceRoot":"","sources":["../../src/clone/target-router.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Provenance } from "../studio/types.js";
|
|
2
|
+
/** Where a skill lives on disk. */
|
|
3
|
+
export type SkillSourceLocation = "project" | "personal" | "cache";
|
|
4
|
+
/** A discovered source skill, with absolute path and parsed identity. */
|
|
5
|
+
export interface SkillSource {
|
|
6
|
+
location: SkillSourceLocation;
|
|
7
|
+
/** Absolute path to the skill directory (the dir containing SKILL.md). */
|
|
8
|
+
skillDir: string;
|
|
9
|
+
/** Skill name as parsed from frontmatter (or directory basename if frontmatter lacks `name`). */
|
|
10
|
+
skillName: string;
|
|
11
|
+
/** Original namespace if discoverable (e.g., "sw" for plugin-cache sources). undefined for personal/project. */
|
|
12
|
+
namespace?: string;
|
|
13
|
+
/** Original version from frontmatter (or "0.0.0" if absent). */
|
|
14
|
+
version: string;
|
|
15
|
+
/** Plugin context — present only for cache-located skills. */
|
|
16
|
+
plugin?: {
|
|
17
|
+
pluginNamespace: string;
|
|
18
|
+
pluginName: string;
|
|
19
|
+
pluginVersion: string;
|
|
20
|
+
pluginRoot: string;
|
|
21
|
+
};
|
|
22
|
+
/** Existing provenance read from .vskill-meta.json (null if no sidecar). */
|
|
23
|
+
existingProvenance: Provenance | null;
|
|
24
|
+
}
|
|
25
|
+
/** Where the cloned skill should be written. */
|
|
26
|
+
export type CloneTargetKind = "standalone" | "plugin" | "new-plugin";
|
|
27
|
+
export interface CloneTarget {
|
|
28
|
+
kind: CloneTargetKind;
|
|
29
|
+
/** Absolute path to the final skill directory (where files end up after atomic rename). */
|
|
30
|
+
targetSkillDir: string;
|
|
31
|
+
/** For "plugin" kind: absolute path to the existing plugin's .claude-plugin/plugin.json. */
|
|
32
|
+
existingPluginManifestPath?: string;
|
|
33
|
+
/** For "new-plugin" kind: absolute path to the plugin root being scaffolded. */
|
|
34
|
+
newPluginRoot?: string;
|
|
35
|
+
/** For "new-plugin" kind: name to use in the new plugin manifest. */
|
|
36
|
+
newPluginName?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface CloneOptions {
|
|
39
|
+
/** New namespace for the cloned skill (e.g., "anton"). Required. */
|
|
40
|
+
namespace: string;
|
|
41
|
+
/** New author name. Required. */
|
|
42
|
+
author: string;
|
|
43
|
+
/** When true, overwrite existing target without confirmation. */
|
|
44
|
+
force: boolean;
|
|
45
|
+
/** When true, after files land, scaffold a GitHub repo via gh. */
|
|
46
|
+
github: boolean;
|
|
47
|
+
/** When true, do not write any files — just print the planned actions. */
|
|
48
|
+
dryRun: boolean;
|
|
49
|
+
}
|
|
50
|
+
/** Result returned by the clone orchestrator. */
|
|
51
|
+
export interface CloneResult {
|
|
52
|
+
source: SkillSource;
|
|
53
|
+
target: CloneTarget;
|
|
54
|
+
/** Final skill name written (e.g., "anton/ado-mapper"). */
|
|
55
|
+
finalSkillName: string;
|
|
56
|
+
/** Number of files copied. */
|
|
57
|
+
filesCopied: number;
|
|
58
|
+
/** Cross-skill references found in the body (informational, not auto-rewritten). */
|
|
59
|
+
referenceReport: ReferenceMatch[];
|
|
60
|
+
/** Self-name occurrences in prose (informational). */
|
|
61
|
+
selfNameMatches: ReferenceMatch[];
|
|
62
|
+
/** GitHub repo URL if --github was used and succeeded; null if skipped. */
|
|
63
|
+
githubRepoUrl: string | null;
|
|
64
|
+
/** Provenance written to the cloned skill. */
|
|
65
|
+
provenance: Provenance;
|
|
66
|
+
}
|
|
67
|
+
export interface ReferenceMatch {
|
|
68
|
+
/** Path of the file the match was found in (relative to skill root). */
|
|
69
|
+
file: string;
|
|
70
|
+
/** Line number (1-indexed). */
|
|
71
|
+
line: number;
|
|
72
|
+
/** The matched text (e.g., "sw:ado-mapper"). */
|
|
73
|
+
match: string;
|
|
74
|
+
/** Pattern category. */
|
|
75
|
+
kind: "backtick" | "skill-call" | "slash-command" | "self-name";
|
|
76
|
+
}
|
|
77
|
+
/** Fork provenance metadata written to the cloned skill's .vskill-meta.json. */
|
|
78
|
+
export interface ForkProvenance {
|
|
79
|
+
source: string;
|
|
80
|
+
version: string;
|
|
81
|
+
clonedAt: string;
|
|
82
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// vskill clone — shared type contracts
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Source of truth for the `vskill clone` command. Consumed by skill-locator,
|
|
5
|
+
// frontmatter rewriter, provenance-fork, target-router, github-scaffold, and
|
|
6
|
+
// the orchestrator in src/commands/clone.ts.
|
|
7
|
+
//
|
|
8
|
+
// See .specweave/increments/0822-vskill-clone-skill-fork/spec.md and plan.md.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/clone/types.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,uCAAuC;AACvC,8EAA8E;AAC9E,6EAA6E;AAC7E,6EAA6E;AAC7E,6CAA6C;AAC7C,EAAE;AACF,8EAA8E;AAC9E,8EAA8E"}
|
package/dist/commands/add.js
CHANGED
|
@@ -17,6 +17,7 @@ import { checkInstallSafety } from "../blocklist/blocklist.js";
|
|
|
17
17
|
import { getSkill, searchSkills } from "../api/client.js";
|
|
18
18
|
import { checkPlatformSecurity } from "../security/index.js";
|
|
19
19
|
import { discoverSkills, getDefaultBranch, checkRepoExists, warnRateLimitOnce } from "../discovery/github-tree.js";
|
|
20
|
+
import { githubFetch } from "../lib/github-fetch.js";
|
|
20
21
|
import { parseGitHubSource } from "../utils/validation.js";
|
|
21
22
|
import { parseSkillsShUrl, isCompleteParsed, isIncompleteParsed, } from "../resolvers/url-resolver.js";
|
|
22
23
|
import { bold, green, red, yellow, dim, cyan, spinner, link, formatInstalls, } from "../utils/output.js";
|
|
@@ -32,7 +33,7 @@ import { extractFrontmatterVersion } from "../utils/version.js";
|
|
|
32
33
|
// mutation to the claude CLI (ADR 0724-01), and we honour the --no-enable
|
|
33
34
|
// flag to skip it. Uninstall is imported for F-003 rollback of earlier
|
|
34
35
|
// already-enabled plugins on a partway failure.
|
|
35
|
-
import { claudePluginInstall, claudePluginUninstall } from "../utils/claude-plugin.js";
|
|
36
|
+
import { claudePluginInstall, claudePluginUninstall, claudePluginMarketplaceAdd, claudePluginMarketplaceList, } from "../utils/claude-plugin.js";
|
|
36
37
|
import { buildPerAgentReport, resolvePluginId, } from "../lib/skill-lifecycle.js";
|
|
37
38
|
// ---------------------------------------------------------------------------
|
|
38
39
|
/** Validate that a download_url from GitHub Contents API points to a trusted GitHub domain. */
|
|
@@ -48,7 +49,7 @@ function isGitHubDownloadUrl(url) {
|
|
|
48
49
|
async function parseManifestFromContentsApi(data) {
|
|
49
50
|
// Prefer download_url for raw content — validate URL before fetching (SSRF prevention)
|
|
50
51
|
if (data.download_url && isGitHubDownloadUrl(data.download_url)) {
|
|
51
|
-
const rawRes = await
|
|
52
|
+
const rawRes = await githubFetch(data.download_url);
|
|
52
53
|
if (rawRes.ok) {
|
|
53
54
|
const content = await rawRes.text();
|
|
54
55
|
if (getAvailablePlugins(content).length > 0)
|
|
@@ -75,7 +76,7 @@ export async function detectMarketplaceRepo(owner, repo) {
|
|
|
75
76
|
const headers = { Accept: "application/vnd.github.v3+json", "User-Agent": "vskill-cli" };
|
|
76
77
|
// Attempt 1: Contents API
|
|
77
78
|
try {
|
|
78
|
-
const res = await
|
|
79
|
+
const res = await githubFetch(contentsUrl, { headers });
|
|
79
80
|
if (res.status === 404)
|
|
80
81
|
return { isMarketplace: false };
|
|
81
82
|
if (res.status === 403)
|
|
@@ -93,7 +94,7 @@ export async function detectMarketplaceRepo(owner, repo) {
|
|
|
93
94
|
// Attempt 2: Retry Contents API after 1s delay
|
|
94
95
|
try {
|
|
95
96
|
await new Promise((r) => setTimeout(r, 1000));
|
|
96
|
-
const res = await
|
|
97
|
+
const res = await githubFetch(contentsUrl, { headers });
|
|
97
98
|
if (res.status === 404)
|
|
98
99
|
return { isMarketplace: false };
|
|
99
100
|
if (res.status === 403)
|
|
@@ -112,7 +113,7 @@ export async function detectMarketplaceRepo(owner, repo) {
|
|
|
112
113
|
try {
|
|
113
114
|
const branch = await getDefaultBranch(owner, repo);
|
|
114
115
|
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/.claude-plugin/marketplace.json`;
|
|
115
|
-
const res = await
|
|
116
|
+
const res = await githubFetch(rawUrl);
|
|
116
117
|
if (res.ok) {
|
|
117
118
|
const content = await res.text();
|
|
118
119
|
if (getAvailablePlugins(content).length > 0) {
|
|
@@ -191,7 +192,7 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
|
|
|
191
192
|
// Not in marketplace or unregistered list — probe plugins/<name>/ folder directly
|
|
192
193
|
let probeSource = null;
|
|
193
194
|
try {
|
|
194
|
-
const probeRes = await
|
|
195
|
+
const probeRes = await githubFetch(`https://api.github.com/repos/${owner}/${repo}/contents/plugins/${preSelected[0]}`, { headers: { "User-Agent": "vskill-cli" } });
|
|
195
196
|
if (probeRes.ok) {
|
|
196
197
|
const data = await probeRes.json();
|
|
197
198
|
if (Array.isArray(data)) {
|
|
@@ -351,7 +352,7 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
|
|
|
351
352
|
// Discover skills: try nested {pluginPath}/skills/ first, fall back to flat {pluginPath}/SKILL.md
|
|
352
353
|
const skillsToSubmit = [];
|
|
353
354
|
const skillsUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${pluginPath}/skills`;
|
|
354
|
-
const skillsRes = await
|
|
355
|
+
const skillsRes = await githubFetch(skillsUrl, { headers: { "User-Agent": "vskill-cli" } });
|
|
355
356
|
if (skillsRes.ok) {
|
|
356
357
|
const skillDirs = (await skillsRes.json())
|
|
357
358
|
.filter((e) => e.type === "dir");
|
|
@@ -450,24 +451,27 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
|
|
|
450
451
|
})),
|
|
451
452
|
];
|
|
452
453
|
for (const plugin of allPluginsToInstall) {
|
|
453
|
-
|
|
454
|
-
if (!pluginPath)
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
454
|
+
// 0826: a marketplace plugin whose `source` is `"./"` lives at the repo
|
|
455
|
+
// root — the previous guard (`if (!pluginPath) … failed`) short-circuited
|
|
456
|
+
// those installs. Keep the path normalization but allow an empty value to
|
|
457
|
+
// mean "repo root" instead of treating it as a hard failure. Build URL
|
|
458
|
+
// segments with a leading-slash prefix only when there's a real path so
|
|
459
|
+
// we don't emit `//` between the contents-API base and `skills`.
|
|
460
|
+
const pluginPath = plugin.source.replace(/^\.\//, "").replace(/\/$/, "");
|
|
461
|
+
const pathSegment = pluginPath ? `/${pluginPath}` : "";
|
|
458
462
|
const installSpin = spinner(`Installing skills: ${bold(plugin.name)}`);
|
|
459
463
|
try {
|
|
460
464
|
// Discover skills: try {pluginPath}/skills/ first, then fall back to {pluginPath}/SKILL.md
|
|
461
465
|
const installedSkillNames = [];
|
|
462
|
-
const skillsUrl = `https://api.github.com/repos/${owner}/${repo}/contents
|
|
463
|
-
const skillsRes = await
|
|
466
|
+
const skillsUrl = `https://api.github.com/repos/${owner}/${repo}/contents${pathSegment}/skills`;
|
|
467
|
+
const skillsRes = await githubFetch(skillsUrl, { headers: { "User-Agent": "vskill-cli" } });
|
|
464
468
|
if (skillsRes.ok) {
|
|
465
469
|
// Nested layout: {pluginPath}/skills/{skillName}/SKILL.md
|
|
466
470
|
const skillDirs = (await skillsRes.json())
|
|
467
471
|
.filter((e) => e.type === "dir");
|
|
468
472
|
for (const sd of skillDirs) {
|
|
469
|
-
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}
|
|
470
|
-
const contentRes = await
|
|
473
|
+
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}${pathSegment}/skills/${sd.name}/SKILL.md`;
|
|
474
|
+
const contentRes = await githubFetch(rawUrl);
|
|
471
475
|
if (!contentRes.ok)
|
|
472
476
|
continue;
|
|
473
477
|
const content = await contentRes.text();
|
|
@@ -488,8 +492,8 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
|
|
|
488
492
|
}
|
|
489
493
|
else {
|
|
490
494
|
// Flat layout: {pluginPath}/SKILL.md directly in the plugin root
|
|
491
|
-
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}
|
|
492
|
-
const contentRes = await
|
|
495
|
+
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}${pathSegment}/SKILL.md`;
|
|
496
|
+
const contentRes = await githubFetch(rawUrl);
|
|
493
497
|
if (contentRes.ok) {
|
|
494
498
|
const content = await contentRes.text();
|
|
495
499
|
const processedContent = ensureFrontmatter(content, plugin.name);
|
|
@@ -565,7 +569,14 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
|
|
|
565
569
|
// `enabledSoFar` so that a later failure rolls back all earlier
|
|
566
570
|
// already-enabled plugins (otherwise we'd leave exactly the stale
|
|
567
571
|
// `enabledPlugins` entries that `vskill cleanup` is meant to fix).
|
|
572
|
+
// 0826: include `opts.global` here. Previously a Studio "Global" install
|
|
573
|
+
// (or CLI `vskill install … --global`) wrote the SKILL.md stub to disk
|
|
574
|
+
// but never invoked `claude plugin install` — leaving the plugin
|
|
575
|
+
// unenabled. The user saw the skill on disk but Claude Code didn't load
|
|
576
|
+
// it. Treat --global as opt-in to the enable hook (scope = user, since
|
|
577
|
+
// --global is per-machine, not per-repo).
|
|
568
578
|
const userOptedIn = opts.scope !== undefined ||
|
|
579
|
+
opts.global === true ||
|
|
569
580
|
opts.dryRun === true ||
|
|
570
581
|
opts.enable === false;
|
|
571
582
|
if (userOptedIn) {
|
|
@@ -692,9 +703,62 @@ export function enableAfterInstall(skillName, entry, opts) {
|
|
|
692
703
|
console.log(dim(`Dry-run: would invoke ${cyan(`claude plugin install --scope ${scope} -- ${pluginId}`)}`));
|
|
693
704
|
return { invoked: false, pluginId, scope };
|
|
694
705
|
}
|
|
706
|
+
// 0826: when the source repo is a Claude Code plugin marketplace that
|
|
707
|
+
// hasn't been registered yet, `claude plugin install foo@bar` fails with
|
|
708
|
+
// "Plugin foo not found in marketplace bar" — and the rollback then
|
|
709
|
+
// wipes the on-disk extraction we just succeeded at. Detect that case
|
|
710
|
+
// up-front from the lockfile entry's marketplace metadata and register
|
|
711
|
+
// the marketplace via `claude plugin marketplace add` before delegating
|
|
712
|
+
// the install. Best-effort — if the add fails (network, invalid source)
|
|
713
|
+
// we still attempt the install so the original error is surfaced.
|
|
714
|
+
ensureMarketplaceRegistered(entry, pluginId, scope);
|
|
695
715
|
claudePluginInstall(pluginId, scope, scope === "project" ? { cwd: process.cwd() } : undefined);
|
|
696
716
|
return { invoked: true, pluginId, scope };
|
|
697
717
|
}
|
|
718
|
+
/**
|
|
719
|
+
* 0826: Ensure the marketplace referenced by `entry` is registered with the
|
|
720
|
+
* claude CLI before we try to enable the plugin. The lockfile entry shape
|
|
721
|
+
* stores the marketplace name (e.g. `postiz-agent`) plus the originating
|
|
722
|
+
* `sourceRepoUrl` — we derive `<owner>/<repo>` from the URL and run
|
|
723
|
+
* `claude plugin marketplace add` if the name is missing from the current
|
|
724
|
+
* `claude plugin marketplace list`.
|
|
725
|
+
*
|
|
726
|
+
* Pure best-effort: any failure (network, parse) falls through silently and
|
|
727
|
+
* lets the subsequent `claudePluginInstall` surface the real error.
|
|
728
|
+
*/
|
|
729
|
+
function ensureMarketplaceRegistered(entry, pluginId, scope) {
|
|
730
|
+
const marketplaceName = entry.marketplace;
|
|
731
|
+
if (!marketplaceName)
|
|
732
|
+
return;
|
|
733
|
+
// Already registered — nothing to do.
|
|
734
|
+
let registered = [];
|
|
735
|
+
try {
|
|
736
|
+
registered = claudePluginMarketplaceList();
|
|
737
|
+
}
|
|
738
|
+
catch {
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
if (registered.includes(marketplaceName))
|
|
742
|
+
return;
|
|
743
|
+
// Derive a `claude plugin marketplace add` source from the lockfile.
|
|
744
|
+
// GitHub URL → `owner/repo`; falls back to `marketplace@<id>` form which
|
|
745
|
+
// claude rejects with a clear message.
|
|
746
|
+
const repoSource = entry.sourceRepoUrl?.match(/github\.com\/([^/]+\/[^/]+?)(?:\.git)?\/?$/);
|
|
747
|
+
const source = repoSource ? repoSource[1] : "";
|
|
748
|
+
if (!source) {
|
|
749
|
+
console.error(dim(` marketplace "${marketplaceName}" not registered and no source repo URL available — skipping auto-add (claude plugin install will likely fail).`));
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
console.log(dim(` Registering marketplace "${marketplaceName}" (${source}) — required for ${pluginId}`));
|
|
753
|
+
try {
|
|
754
|
+
claudePluginMarketplaceAdd(source, scope);
|
|
755
|
+
}
|
|
756
|
+
catch (err) {
|
|
757
|
+
// Surface the underlying error but keep going — claudePluginInstall
|
|
758
|
+
// will still throw a clearer "not found in marketplace" if needed.
|
|
759
|
+
console.error(dim(` Failed to auto-register marketplace "${marketplaceName}": ${err.message.split("\n")[0]}`));
|
|
760
|
+
}
|
|
761
|
+
}
|
|
698
762
|
/**
|
|
699
763
|
* 0724 T-006: rollback the on-disk extraction + lockfile entry when
|
|
700
764
|
* `claudePluginInstall` throws (AC-US1-05). Best-effort — wraps each rm in
|
|
@@ -871,7 +935,7 @@ async function promptInstallOptions(agents, opts) {
|
|
|
871
935
|
async function fetchSkillContent(url) {
|
|
872
936
|
const spin = spinner("Fetching skill");
|
|
873
937
|
try {
|
|
874
|
-
const res = await
|
|
938
|
+
const res = await githubFetch(url);
|
|
875
939
|
if (!res.ok) {
|
|
876
940
|
spin.stop();
|
|
877
941
|
if (res.status === 404) {
|
|
@@ -1173,7 +1237,7 @@ sourceSkillPath) {
|
|
|
1173
1237
|
// Fetch content (non-exiting for multi-skill support)
|
|
1174
1238
|
let content;
|
|
1175
1239
|
try {
|
|
1176
|
-
const res = await
|
|
1240
|
+
const res = await githubFetch(rawUrl);
|
|
1177
1241
|
if (!res.ok) {
|
|
1178
1242
|
return { skillName, installed: false, verdict: "FETCH_FAILED" };
|
|
1179
1243
|
}
|
|
@@ -1221,7 +1285,7 @@ sourceSkillPath) {
|
|
|
1221
1285
|
agentFiles = {};
|
|
1222
1286
|
const fetches = Object.entries(agentRawUrls).map(async ([relPath, url]) => {
|
|
1223
1287
|
try {
|
|
1224
|
-
const res = await
|
|
1288
|
+
const res = await githubFetch(url);
|
|
1225
1289
|
if (res.ok)
|
|
1226
1290
|
agentFiles[relPath] = await res.text();
|
|
1227
1291
|
}
|
|
@@ -1264,7 +1328,7 @@ async function installAllRepoPlugins(ownerRepo, opts) {
|
|
|
1264
1328
|
const manifestSpin = spinner("Fetching marketplace.json");
|
|
1265
1329
|
let manifestContent;
|
|
1266
1330
|
try {
|
|
1267
|
-
const res = await
|
|
1331
|
+
const res = await githubFetch(manifestUrl);
|
|
1268
1332
|
if (!res.ok) {
|
|
1269
1333
|
manifestSpin.stop();
|
|
1270
1334
|
console.error(red(`marketplace.json not found at ${owner}/${repo}\n`) +
|
|
@@ -1326,7 +1390,7 @@ async function installRepoPlugin(ownerRepo, pluginName, opts, overrideSource) {
|
|
|
1326
1390
|
const manifestSpin = spinner("Fetching marketplace.json");
|
|
1327
1391
|
let manifestContent;
|
|
1328
1392
|
try {
|
|
1329
|
-
const res = await
|
|
1393
|
+
const res = await githubFetch(manifestUrl);
|
|
1330
1394
|
if (!res.ok) {
|
|
1331
1395
|
manifestSpin.stop();
|
|
1332
1396
|
throw new Error(`marketplace.json not found at ${owner}/${repo}. ` +
|
|
@@ -1347,7 +1411,7 @@ async function installRepoPlugin(ownerRepo, pluginName, opts, overrideSource) {
|
|
|
1347
1411
|
// Not in marketplace.json — probe plugins/<name>/ folder in the repo
|
|
1348
1412
|
const probeSpin = spinner(`Looking for "${pluginName}" folder in repo`);
|
|
1349
1413
|
try {
|
|
1350
|
-
const probeRes = await
|
|
1414
|
+
const probeRes = await githubFetch(`https://api.github.com/repos/${owner}/${repo}/contents/plugins/${pluginName}`, { headers: { "User-Agent": "vskill-cli" } });
|
|
1351
1415
|
if (probeRes.ok) {
|
|
1352
1416
|
const data = await probeRes.json();
|
|
1353
1417
|
if (Array.isArray(data)) {
|
|
@@ -1387,7 +1451,7 @@ async function installRepoPlugin(ownerRepo, pluginName, opts, overrideSource) {
|
|
|
1387
1451
|
let skillEntries = [];
|
|
1388
1452
|
let flatLayout = false;
|
|
1389
1453
|
try {
|
|
1390
|
-
const res = await
|
|
1454
|
+
const res = await githubFetch(`https://api.github.com/repos/${owner}/${repo}/contents/${pluginPath}/skills`, { headers: { "User-Agent": "vskill-cli" } });
|
|
1391
1455
|
if (res.ok) {
|
|
1392
1456
|
skillEntries = (await res.json()).filter((e) => e.type === "dir");
|
|
1393
1457
|
}
|
|
@@ -1396,7 +1460,7 @@ async function installRepoPlugin(ownerRepo, pluginName, opts, overrideSource) {
|
|
|
1396
1460
|
// Flat layout fallback: check for SKILL.md directly at plugin root
|
|
1397
1461
|
if (skillEntries.length === 0) {
|
|
1398
1462
|
try {
|
|
1399
|
-
const res = await
|
|
1463
|
+
const res = await githubFetch(`https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${pluginPath}/SKILL.md`);
|
|
1400
1464
|
if (res.ok) {
|
|
1401
1465
|
flatLayout = true;
|
|
1402
1466
|
skillEntries = [{ name: pluginName, type: "dir" }];
|
|
@@ -1406,7 +1470,7 @@ async function installRepoPlugin(ownerRepo, pluginName, opts, overrideSource) {
|
|
|
1406
1470
|
}
|
|
1407
1471
|
let cmdEntries = [];
|
|
1408
1472
|
try {
|
|
1409
|
-
const res = await
|
|
1473
|
+
const res = await githubFetch(`https://api.github.com/repos/${owner}/${repo}/contents/${pluginPath}/commands`, { headers: { "User-Agent": "vskill-cli" } });
|
|
1410
1474
|
if (res.ok) {
|
|
1411
1475
|
cmdEntries = (await res.json()).filter((e) => e.name.endsWith(".md"));
|
|
1412
1476
|
}
|
|
@@ -1433,7 +1497,7 @@ async function installRepoPlugin(ownerRepo, pluginName, opts, overrideSource) {
|
|
|
1433
1497
|
? `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${pluginPath}/SKILL.md`
|
|
1434
1498
|
: `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${pluginPath}/skills/${entry.name}/SKILL.md`;
|
|
1435
1499
|
try {
|
|
1436
|
-
const res = await
|
|
1500
|
+
const res = await githubFetch(rawUrl);
|
|
1437
1501
|
if (res.ok) {
|
|
1438
1502
|
const content = await res.text();
|
|
1439
1503
|
skills.push({ name: entry.name, content });
|
|
@@ -1446,7 +1510,7 @@ async function installRepoPlugin(ownerRepo, pluginName, opts, overrideSource) {
|
|
|
1446
1510
|
for (const entry of cmdEntries) {
|
|
1447
1511
|
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${pluginPath}/commands/${entry.name}`;
|
|
1448
1512
|
try {
|
|
1449
|
-
const res = await
|
|
1513
|
+
const res = await githubFetch(rawUrl);
|
|
1450
1514
|
if (res.ok) {
|
|
1451
1515
|
const content = await res.text();
|
|
1452
1516
|
commands.push({ name: entry.name, content });
|
|
@@ -1630,7 +1694,7 @@ export async function addCommand(source, opts) {
|
|
|
1630
1694
|
const pluginPath = plugin.source.replace(/^\.\//, "");
|
|
1631
1695
|
const subpath = `${pluginPath}/skills/${threeSkill}/SKILL.md`;
|
|
1632
1696
|
const probeUrl = `https://raw.githubusercontent.com/${threeOwner}/${threeRepo}/${branch}/${subpath}`;
|
|
1633
|
-
const probeRes = await
|
|
1697
|
+
const probeRes = await githubFetch(probeUrl);
|
|
1634
1698
|
if (probeRes.ok) {
|
|
1635
1699
|
return installSingleSkillLegacy(threeOwner, threeRepo, threeSkill, opts, subpath, plugin.name);
|
|
1636
1700
|
}
|
|
@@ -2157,7 +2221,7 @@ async function installSingleSkillLegacy(owner, repo, skill, opts, skillSubpathOv
|
|
|
2157
2221
|
? skillSubpathOverride.replace(/\/SKILL\.md$/, "/agents")
|
|
2158
2222
|
: `skills/${skill}/agents`;
|
|
2159
2223
|
const agentsDirUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${agentsBasePath}`;
|
|
2160
|
-
const dirRes = await
|
|
2224
|
+
const dirRes = await githubFetch(agentsDirUrl, { headers: { Accept: "application/vnd.github.v3+json" } });
|
|
2161
2225
|
if (dirRes.ok) {
|
|
2162
2226
|
const entries = (await dirRes.json());
|
|
2163
2227
|
const mdEntries = entries.filter((e) => e.name.endsWith(".md") && e.download_url && isGitHubDownloadUrl(e.download_url));
|
|
@@ -2165,7 +2229,7 @@ async function installSingleSkillLegacy(owner, repo, skill, opts, skillSubpathOv
|
|
|
2165
2229
|
legacyAgentFiles = {};
|
|
2166
2230
|
const fetches = mdEntries.map(async (entry) => {
|
|
2167
2231
|
try {
|
|
2168
|
-
const res = await
|
|
2232
|
+
const res = await githubFetch(entry.download_url);
|
|
2169
2233
|
if (res.ok)
|
|
2170
2234
|
legacyAgentFiles[`agents/${entry.name}`] = await res.text();
|
|
2171
2235
|
}
|