Files
skills-here-run-place/dist/server/chunks/models_DPfuEi7q.mjs
Alejandro Martinez f09af719cf Initial commit
2026-02-12 02:04:10 +01:00

321 lines
20 KiB
JavaScript

import { useSSRContext, defineComponent, ref, computed, watch, mergeProps } from 'vue';
import { marked } from 'marked';
import { ssrRenderAttrs, ssrRenderAttr, ssrInterpolate, ssrRenderList, ssrRenderClass, ssrIncludeBooleanAttr, ssrLooseContain, ssrLooseEqual } from 'vue/server-renderer';
import { _ as _export_sfc } from './_plugin-vue_export-helper_B1lnwsE2.mjs';
const _sfc_main = /* @__PURE__ */ defineComponent({
__name: "SkillEditor",
props: {
mode: {},
slug: {},
initialName: {},
initialDescription: {},
initialAllowedTools: {},
initialArgumentHint: {},
initialModel: {},
initialUserInvocable: { type: Boolean },
initialDisableModelInvocation: { type: Boolean },
initialContext: {},
initialAgent: {},
initialHooks: {},
initialBody: {},
availableTools: {},
availableModels: {}
},
setup(__props, { expose: __expose }) {
__expose();
const props = __props;
const AVAILABLE_TOOLS = props.availableTools ?? [
"Bash",
"Read",
"Write",
"Edit",
"Glob",
"Grep",
"WebFetch",
"WebSearch",
"Task",
"NotebookEdit"
];
const AVAILABLE_MODELS = props.availableModels ?? [
{ id: "claude-opus-4-6", display_name: "Claude Opus 4.6" },
{ id: "claude-sonnet-4-5-20250929", display_name: "Claude Sonnet 4.5" },
{ id: "claude-haiku-4-5-20251001", display_name: "Claude Haiku 4.5" }
];
const name = ref(props.initialName || "");
const description = ref(props.initialDescription || "");
const argumentHint = ref(props.initialArgumentHint || "");
const model = ref(props.initialModel || "");
const userInvocable = ref(props.initialUserInvocable ?? true);
const disableModelInvocation = ref(props.initialDisableModelInvocation ?? false);
const context = ref(props.initialContext || "");
const agent = ref(props.initialAgent || "");
const hooksJson = ref(props.initialHooks || "");
const body = ref(props.initialBody || "");
const saving = ref(false);
const error = ref("");
const selectedTools = ref(new Set(
props.initialAllowedTools ? props.initialAllowedTools.split(",").map((t) => t.trim()).filter(Boolean) : []
));
function toggleTool(tool) {
if (selectedTools.value.has(tool)) {
selectedTools.value.delete(tool);
} else {
selectedTools.value.add(tool);
}
selectedTools.value = new Set(selectedTools.value);
}
const computedSlug = computed(() => {
if (props.mode === "edit" && props.slug) return props.slug;
return name.value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "my-skill";
});
let previewHtml = ref("");
let debounceTimer;
watch(body, (val) => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(async () => {
previewHtml.value = await marked(val || "");
}, 300);
}, { immediate: true });
function buildContent() {
const tools = [...selectedTools.value];
const lines = ["---"];
lines.push(`name: ${name.value}`);
if (description.value) lines.push(`description: ${description.value}`);
if (argumentHint.value) lines.push(`argument-hint: ${argumentHint.value}`);
if (tools.length > 0) lines.push(`allowed-tools: ${tools.join(", ")}`);
if (model.value) lines.push(`model: ${model.value}`);
if (userInvocable.value === false) lines.push("user-invocable: false");
if (disableModelInvocation.value) lines.push("disable-model-invocation: true");
if (context.value) lines.push(`context: ${context.value}`);
if (agent.value) lines.push(`agent: ${agent.value}`);
if (hooksJson.value.trim()) {
try {
const parsed = JSON.parse(hooksJson.value.trim());
lines.push(`hooks: ${JSON.stringify(parsed)}`);
} catch {
}
}
lines.push("---");
return lines.join("\n") + "\n\n" + body.value.trim() + "\n";
}
async function save() {
saving.value = true;
error.value = "";
try {
const content = buildContent();
if (props.mode === "create") {
const res = await fetch("/api/skills", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ slug: computedSlug.value, content })
});
if (!res.ok) {
const data = await res.json();
throw new Error(data.error || "Failed to create skill");
}
window.location.href = `/${computedSlug.value}`;
} else {
const res = await fetch(`/api/skills/${props.slug}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ content })
});
if (!res.ok) {
const data = await res.json();
throw new Error(data.error || "Failed to update skill");
}
window.location.href = `/${props.slug}`;
}
} catch (err) {
error.value = err instanceof Error ? err.message : "Something went wrong";
} finally {
saving.value = false;
}
}
const __returned__ = { props, AVAILABLE_TOOLS, AVAILABLE_MODELS, name, description, argumentHint, model, userInvocable, disableModelInvocation, context, agent, hooksJson, body, saving, error, selectedTools, toggleTool, computedSlug, get previewHtml() {
return previewHtml;
}, set previewHtml(v) {
previewHtml = v;
}, get debounceTimer() {
return debounceTimer;
}, set debounceTimer(v) {
debounceTimer = v;
}, buildContent, save };
Object.defineProperty(__returned__, "__isScriptSetup", { enumerable: false, value: true });
return __returned__;
}
});
function _sfc_ssrRender(_ctx, _push, _parent, _attrs, $props, $setup, $data, $options) {
_push(`<form${ssrRenderAttrs(mergeProps({ class: "space-y-6" }, _attrs))}><div class="grid gap-4 sm:grid-cols-2"><div><label class="block text-xs font-medium uppercase tracking-wider text-gray-500 mb-1.5">Name</label><input${ssrRenderAttr("value", $setup.name)} type="text" required placeholder="My Awesome Skill" class="w-full rounded-xl border border-white/[0.06] bg-[var(--color-surface-100)] px-4 py-2.5 text-sm text-white placeholder-gray-600 focus:border-[var(--color-accent-500)]/50 focus:outline-none focus:ring-1 focus:ring-[var(--color-accent-500)]/20 transition-all"><p class="mt-1.5 text-xs text-gray-600"> Slug: <code class="text-gray-500 font-mono">${ssrInterpolate($setup.computedSlug)}</code></p></div><div><label class="block text-xs font-medium uppercase tracking-wider text-gray-500 mb-1.5">Description</label><input${ssrRenderAttr("value", $setup.description)} type="text" placeholder="Brief description of what this skill does" class="w-full rounded-xl border border-white/[0.06] bg-[var(--color-surface-100)] px-4 py-2.5 text-sm text-white placeholder-gray-600 focus:border-[var(--color-accent-500)]/50 focus:outline-none focus:ring-1 focus:ring-[var(--color-accent-500)]/20 transition-all"></div></div><div><label class="block text-xs font-medium uppercase tracking-wider text-gray-500 mb-1.5">Allowed Tools</label><div class="flex flex-wrap gap-1.5 rounded-xl border border-white/[0.06] bg-[var(--color-surface-100)] px-3 py-2.5 min-h-[42px]"><!--[-->`);
ssrRenderList($setup.AVAILABLE_TOOLS, (tool) => {
_push(`<button type="button" class="${ssrRenderClass([
"rounded-md px-2.5 py-1 text-xs font-medium transition-all",
$setup.selectedTools.has(tool) ? "bg-[var(--color-accent-500)] text-white shadow-sm" : "bg-white/[0.04] border border-white/[0.06] text-gray-500 hover:text-gray-300 hover:bg-white/[0.08]"
])}">${ssrInterpolate(tool)}</button>`);
});
_push(`<!--]--></div></div><div class="grid gap-4 sm:grid-cols-3"><div><label class="block text-xs font-medium uppercase tracking-wider text-gray-500 mb-1.5">Model</label><select class="w-full rounded-xl border border-white/[0.06] bg-[var(--color-surface-100)] px-4 py-2.5 text-sm text-white focus:border-[var(--color-accent-500)]/50 focus:outline-none focus:ring-1 focus:ring-[var(--color-accent-500)]/20 transition-all"><option value=""${ssrIncludeBooleanAttr(Array.isArray($setup.model) ? ssrLooseContain($setup.model, "") : ssrLooseEqual($setup.model, "")) ? " selected" : ""}>Default</option><!--[-->`);
ssrRenderList($setup.AVAILABLE_MODELS, (m) => {
_push(`<option${ssrRenderAttr("value", m.id)}${ssrIncludeBooleanAttr(Array.isArray($setup.model) ? ssrLooseContain($setup.model, m.id) : ssrLooseEqual($setup.model, m.id)) ? " selected" : ""}>${ssrInterpolate(m.display_name)}</option>`);
});
_push(`<!--]--></select></div><div><label class="block text-xs font-medium uppercase tracking-wider text-gray-500 mb-1.5">Argument Hint</label><input${ssrRenderAttr("value", $setup.argumentHint)} type="text" placeholder="e.g. &lt;file-path&gt;" class="w-full rounded-xl border border-white/[0.06] bg-[var(--color-surface-100)] px-4 py-2.5 text-sm text-white placeholder-gray-600 focus:border-[var(--color-accent-500)]/50 focus:outline-none focus:ring-1 focus:ring-[var(--color-accent-500)]/20 transition-all"></div><div><label class="block text-xs font-medium uppercase tracking-wider text-gray-500 mb-1.5">Agent</label><select class="w-full rounded-xl border border-white/[0.06] bg-[var(--color-surface-100)] px-4 py-2.5 text-sm text-white focus:border-[var(--color-accent-500)]/50 focus:outline-none focus:ring-1 focus:ring-[var(--color-accent-500)]/20 transition-all"><option value=""${ssrIncludeBooleanAttr(Array.isArray($setup.agent) ? ssrLooseContain($setup.agent, "") : ssrLooseEqual($setup.agent, "")) ? " selected" : ""}>general-purpose (default)</option><option value="Explore"${ssrIncludeBooleanAttr(Array.isArray($setup.agent) ? ssrLooseContain($setup.agent, "Explore") : ssrLooseEqual($setup.agent, "Explore")) ? " selected" : ""}>Explore</option><option value="Plan"${ssrIncludeBooleanAttr(Array.isArray($setup.agent) ? ssrLooseContain($setup.agent, "Plan") : ssrLooseEqual($setup.agent, "Plan")) ? " selected" : ""}>Plan</option></select></div></div><div class="flex flex-wrap gap-6"><label class="flex items-center gap-2.5 cursor-pointer group"><input type="checkbox"${ssrIncludeBooleanAttr(Array.isArray($setup.userInvocable) ? ssrLooseContain($setup.userInvocable, null) : $setup.userInvocable) ? " checked" : ""} class="sr-only peer"><div class="h-5 w-9 rounded-full bg-white/[0.06] border border-white/[0.06] peer-checked:bg-[var(--color-accent-500)] peer-checked:border-[var(--color-accent-500)] relative transition-all after:content-[&#39;&#39;] after:absolute after:top-0.5 after:left-0.5 after:h-4 after:w-4 after:rounded-full after:bg-gray-400 after:transition-all peer-checked:after:translate-x-4 peer-checked:after:bg-white"></div><span class="text-xs text-gray-500 group-hover:text-gray-300 transition-colors">User Invocable <span class="text-gray-600">(show in /menu)</span></span></label><label class="flex items-center gap-2.5 cursor-pointer group"><input type="checkbox"${ssrIncludeBooleanAttr(Array.isArray($setup.disableModelInvocation) ? ssrLooseContain($setup.disableModelInvocation, null) : $setup.disableModelInvocation) ? " checked" : ""} class="sr-only peer"><div class="h-5 w-9 rounded-full bg-white/[0.06] border border-white/[0.06] peer-checked:bg-[var(--color-accent-500)] peer-checked:border-[var(--color-accent-500)] relative transition-all after:content-[&#39;&#39;] after:absolute after:top-0.5 after:left-0.5 after:h-4 after:w-4 after:rounded-full after:bg-gray-400 after:transition-all peer-checked:after:translate-x-4 peer-checked:after:bg-white"></div><span class="text-xs text-gray-500 group-hover:text-gray-300 transition-colors">Disable Model Invocation <span class="text-gray-600">(manual only)</span></span></label></div><div><label class="block text-xs font-medium uppercase tracking-wider text-gray-500 mb-1.5">Context</label><select class="w-full rounded-xl border border-white/[0.06] bg-[var(--color-surface-100)] px-4 py-2.5 text-sm text-white focus:border-[var(--color-accent-500)]/50 focus:outline-none focus:ring-1 focus:ring-[var(--color-accent-500)]/20 transition-all"><option value=""${ssrIncludeBooleanAttr(Array.isArray($setup.context) ? ssrLooseContain($setup.context, "") : ssrLooseEqual($setup.context, "")) ? " selected" : ""}>Inline (default)</option><option value="fork"${ssrIncludeBooleanAttr(Array.isArray($setup.context) ? ssrLooseContain($setup.context, "fork") : ssrLooseEqual($setup.context, "fork")) ? " selected" : ""}>Fork (run in subagent)</option></select><p class="mt-1.5 text-xs text-gray-600">Fork runs the skill in an isolated subagent context</p></div><details class="group"><summary class="text-xs font-medium uppercase tracking-wider text-gray-500 cursor-pointer hover:text-gray-400 transition-colors">Hooks (advanced)</summary><div class="mt-3"><textarea rows="4" placeholder="{ &quot;preToolExecution&quot;: [{ &quot;matcher&quot;: &quot;Bash&quot;, &quot;hooks&quot;: [{ &quot;type&quot;: &quot;command&quot;, &quot;command&quot;: &quot;echo pre&quot; }] }] }" class="w-full rounded-xl border border-white/[0.06] bg-[var(--color-surface-100)] px-4 py-3 font-mono text-xs text-white placeholder-gray-700 focus:border-[var(--color-accent-500)]/50 focus:outline-none focus:ring-1 focus:ring-[var(--color-accent-500)]/20 transition-all resize-y">${ssrInterpolate($setup.hooksJson)}</textarea><p class="mt-1.5 text-xs text-gray-600">JSON object. Leave empty to omit.</p></div></details><div class="grid gap-4 lg:grid-cols-2"><div><label class="block text-xs font-medium uppercase tracking-wider text-gray-500 mb-1.5">Skill Body</label><textarea rows="20" placeholder="# My Skill
Instructions for Claude..." class="w-full rounded-xl border border-white/[0.06] bg-[var(--color-surface-100)] px-4 py-3 font-mono text-sm text-white placeholder-gray-600 focus:border-[var(--color-accent-500)]/50 focus:outline-none focus:ring-1 focus:ring-[var(--color-accent-500)]/20 transition-all resize-y leading-relaxed">${ssrInterpolate($setup.body)}</textarea></div><div><p class="block text-xs font-medium uppercase tracking-wider text-gray-500 mb-1.5">Preview</p><div class="skill-prose rounded-xl border border-white/[0.06] bg-[var(--color-surface-100)] p-5 min-h-[20rem] overflow-auto">${$setup.previewHtml ?? ""}</div></div></div><div class="flex items-center gap-4 pt-2"><button type="submit"${ssrIncludeBooleanAttr($setup.saving) ? " disabled" : ""} class="inline-flex items-center gap-2 rounded-xl bg-[var(--color-accent-500)] px-6 py-2.5 text-sm font-semibold text-white shadow-lg shadow-[var(--color-accent-500)]/20 hover:bg-[var(--color-accent-600)] hover:shadow-[var(--color-accent-500)]/30 disabled:opacity-50 active:scale-[0.97] transition-all">`);
if ($setup.saving) {
_push(`<svg class="h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg>`);
} else {
_push(`<!---->`);
}
_push(` ${ssrInterpolate($setup.saving ? "Saving..." : $props.mode === "create" ? "Create Skill" : "Save Changes")}</button><a href="/" class="text-sm text-gray-600 hover:text-gray-300 transition-colors">Cancel</a>`);
if ($setup.error) {
_push(`<p class="text-sm text-red-400">${ssrInterpolate($setup.error)}</p>`);
} else {
_push(`<!---->`);
}
_push(`</div></form>`);
}
const _sfc_setup = _sfc_main.setup;
_sfc_main.setup = (props, ctx) => {
const ssrContext = useSSRContext();
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/components/SkillEditor.vue");
return _sfc_setup ? _sfc_setup(props, ctx) : void 0;
};
const SkillEditor = /* @__PURE__ */ _export_sfc(_sfc_main, [["ssrRender", _sfc_ssrRender]]);
const FALLBACK_TOOLS = [
"Bash",
"Read",
"Write",
"Edit",
"MultiEdit",
"Glob",
"Grep",
"WebFetch",
"WebSearch",
"Task",
"NotebookEdit",
"NotebookRead",
"TodoRead",
"TodoWrite"
];
const GIST_URL = "https://gist.githubusercontent.com/wong2/e0f34aac66caf890a332f7b6f9e2ba8f/raw";
const IGNORED = /* @__PURE__ */ new Set(["LS", "exit_plan_mode", "Agent", "BashOutput", "KillShell", "SlashCommand", "ExitPlanMode"]);
let cached$1 = null;
let lastFetch$1 = 0;
const CACHE_TTL$1 = 1e3 * 60 * 60;
async function getAvailableTools() {
if (cached$1 && Date.now() - lastFetch$1 < CACHE_TTL$1) {
return cached$1;
}
try {
const res = await fetch(GIST_URL, { signal: AbortSignal.timeout(5e3) });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const text = await res.text();
const matches = text.matchAll(/"name":\s*"([A-Z][a-zA-Z]+)"/g);
const tools = /* @__PURE__ */ new Set();
for (const m of matches) {
if (!IGNORED.has(m[1])) {
tools.add(m[1]);
}
}
if (tools.size >= 5) {
cached$1 = [...tools].sort();
lastFetch$1 = Date.now();
return cached$1;
}
} catch {
}
cached$1 = FALLBACK_TOOLS;
lastFetch$1 = Date.now();
return cached$1;
}
const FALLBACK_MODELS = [
{ id: "claude-opus-4-6", display_name: "Claude Opus 4.6" },
{ id: "claude-sonnet-4-5-20250929", display_name: "Claude Sonnet 4.5" },
{ id: "claude-haiku-4-5-20251001", display_name: "Claude Haiku 4.5" }
];
const DOCS_URL = "https://platform.claude.com/docs/en/about-claude/models/overview";
const MODEL_ID_RE = /claude-(?:opus|sonnet|haiku|3-\d+-(?:opus|sonnet|haiku)|3-(?:opus|sonnet|haiku))-[\w-]*\d+(?:-\d{8})?/g;
let cached = null;
let lastFetch = 0;
const CACHE_TTL = 1e3 * 60 * 60 * 24;
function displayName(id) {
const clean = id.replace(/-\d{8}$/, "").replace(/^claude-/, "");
const parts = clean.split("-");
const words = parts.map((p, i) => {
if (/^\d+$/.test(p) && i === parts.length - 1 && parts.length > 1) {
const prev = parts[i - 1];
if (/^\d+$/.test(prev)) return null;
return p;
}
return p.charAt(0).toUpperCase() + p.slice(1);
}).filter(Boolean);
const result = [];
for (const w of words) {
if (/^\d+$/.test(w) && result.length > 0 && /^\d+$/.test(result[result.length - 1])) {
result[result.length - 1] += "." + w;
} else {
result.push(w);
}
}
return "Claude " + result.join(" ");
}
async function getAvailableModels() {
if (cached && Date.now() - lastFetch < CACHE_TTL) {
return cached;
}
const apiKey = process.env.ANTHROPIC_API_KEY;
if (apiKey) {
try {
const res = await fetch("https://api.anthropic.com/v1/models?limit=100", {
headers: { "x-api-key": apiKey, "anthropic-version": "2023-06-01" },
signal: AbortSignal.timeout(5e3)
});
if (res.ok) {
const body = await res.json();
const models = body.data.filter((m) => m.id.startsWith("claude-")).map((m) => ({ id: m.id, display_name: m.display_name }));
if (models.length > 0) {
cached = models;
lastFetch = Date.now();
return cached;
}
}
} catch {
}
}
try {
const res = await fetch(DOCS_URL, { signal: AbortSignal.timeout(5e3) });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const html = await res.text();
const seen = /* @__PURE__ */ new Set();
const models = [];
for (const match of html.matchAll(MODEL_ID_RE)) {
const id = match[0];
if (id.endsWith("-v1") || id.includes("-v1:")) continue;
if (seen.has(id)) continue;
seen.add(id);
models.push({ id, display_name: displayName(id) });
}
const deduped = /* @__PURE__ */ new Map();
for (const m of models) {
const base = m.id.replace(/-\d{8}$/, "").replace(/-0$/, "");
if (!deduped.has(base) || m.id.length < deduped.get(base).id.length) {
deduped.set(base, m);
}
}
const result = [...deduped.values()];
if (result.length > 0) {
cached = result;
lastFetch = Date.now();
return cached;
}
} catch {
}
cached = FALLBACK_MODELS;
lastFetch = Date.now();
return cached;
}
export { SkillEditor as S, getAvailableModels as a, getAvailableTools as g };