Add author auth, forking, tags, and stats tracking
Introduce token-based author authentication (register/verify API), skill forking with EditGate protection, tag metadata on skills, and download/push stats. Enhanced push scripts with token auth and per-skill filtering. Updated UI with stats, tags, and author info on skill cards.
This commit is contained in:
2
dist/server/_@astrojs-ssr-adapter.mjs
vendored
2
dist/server/_@astrojs-ssr-adapter.mjs
vendored
@@ -1 +1 @@
|
||||
export { c as createExports, a as start } from './chunks/_@astrojs-ssr-adapter_DIu76Dvd.mjs';
|
||||
export { c as createExports, a as start } from './chunks/_@astrojs-ssr-adapter_BeL8VyJ8.mjs';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { q as decryptString, v as createSlotValueFromString, w as isAstroComponentFactory, k as renderComponent, r as renderTemplate, R as ROUTE_TYPE_HEADER, x as REROUTE_DIRECTIVE_HEADER, A as AstroError, y as i18nNoLocaleFoundInPath, z as ResponseSentError, B as ActionNotFoundError, C as MiddlewareNoDataOrNextCalled, D as MiddlewareNotAResponse, G as originPathnameSymbol, H as RewriteWithBodyUsed, J as GetStaticPathsRequired, K as InvalidGetStaticPathsReturn, O as InvalidGetStaticPathsEntry, P as GetStaticPathsExpectedParams, Q as GetStaticPathsInvalidRouteParam, S as PageNumberParamNotFound, T as DEFAULT_404_COMPONENT, V as NoMatchingStaticPathFound, W as PrerenderDynamicEndpointPathCollide, X as ReservedSlotName, Y as renderSlotToString, Z as renderJSX, _ as chunkToString, $ as isRenderInstruction, a0 as ForbiddenRewrite, a1 as SessionStorageInitError, a2 as SessionStorageSaveError, a3 as ASTRO_VERSION, a4 as CspNotEnabled, a5 as LocalsReassigned, a6 as generateCspDigest, a7 as PrerenderClientAddressNotAvailable, a8 as clientAddressSymbol, a9 as ClientAddressNotAvailable, aa as StaticClientAddressNotAvailable, ab as AstroResponseHeadersReassigned, ac as responseSentSymbol$1, ad as renderPage, ae as REWRITE_DIRECTIVE_HEADER_KEY, af as REWRITE_DIRECTIVE_HEADER_VALUE, ag as renderEndpoint, ah as LocalsNotAnObject, ai as FailedToFindPageMapSSR, aj as REROUTABLE_STATUS_CODES, ak as nodeRequestAbortControllerCleanupSymbol } from './astro/server_B-2LxKLH.mjs';
|
||||
import { q as decryptString, v as createSlotValueFromString, w as isAstroComponentFactory, k as renderComponent, r as renderTemplate, R as ROUTE_TYPE_HEADER, x as REROUTE_DIRECTIVE_HEADER, A as AstroError, y as i18nNoLocaleFoundInPath, z as ResponseSentError, B as ActionNotFoundError, C as MiddlewareNoDataOrNextCalled, D as MiddlewareNotAResponse, G as originPathnameSymbol, H as RewriteWithBodyUsed, J as GetStaticPathsRequired, K as InvalidGetStaticPathsReturn, O as InvalidGetStaticPathsEntry, P as GetStaticPathsExpectedParams, Q as GetStaticPathsInvalidRouteParam, S as PageNumberParamNotFound, T as DEFAULT_404_COMPONENT, V as NoMatchingStaticPathFound, W as PrerenderDynamicEndpointPathCollide, X as ReservedSlotName, Y as renderSlotToString, Z as renderJSX, _ as chunkToString, $ as isRenderInstruction, a0 as ForbiddenRewrite, a1 as SessionStorageInitError, a2 as SessionStorageSaveError, a3 as ASTRO_VERSION, a4 as CspNotEnabled, a5 as LocalsReassigned, a6 as generateCspDigest, a7 as PrerenderClientAddressNotAvailable, a8 as clientAddressSymbol, a9 as ClientAddressNotAvailable, aa as StaticClientAddressNotAvailable, ab as AstroResponseHeadersReassigned, ac as responseSentSymbol$1, ad as renderPage, ae as REWRITE_DIRECTIVE_HEADER_KEY, af as REWRITE_DIRECTIVE_HEADER_VALUE, ag as renderEndpoint, ah as LocalsNotAnObject, ai as FailedToFindPageMapSSR, aj as REROUTABLE_STATUS_CODES, ak as nodeRequestAbortControllerCleanupSymbol } from './astro/server_CF97kUu8.mjs';
|
||||
import colors from 'piccolore';
|
||||
import 'clsx';
|
||||
import { A as ActionError, d as deserializeActionResult, s as serializeActionResult, a as ACTION_RPC_ROUTE_PATTERN, b as ACTION_QUERY_PARAMS, g as getActionQueryString, D as DEFAULT_404_ROUTE, c as default404Instance, N as NOOP_MIDDLEWARE_FN, e as ensure404Route } from './astro-designed-error-pages_B_BAqCrl.mjs';
|
||||
import { A as ActionError, d as deserializeActionResult, s as serializeActionResult, a as ACTION_RPC_ROUTE_PATTERN, b as ACTION_QUERY_PARAMS, g as getActionQueryString, D as DEFAULT_404_ROUTE, c as default404Instance, N as NOOP_MIDDLEWARE_FN, e as ensure404Route } from './astro-designed-error-pages_DSexancP.mjs';
|
||||
import 'es-module-lexer';
|
||||
import buffer from 'node:buffer';
|
||||
import crypto$1 from 'node:crypto';
|
||||
@@ -1,24 +0,0 @@
|
||||
import { e as createComponent, n as renderHead, o as renderSlot, r as renderTemplate, h as createAstro } from './astro/server_B-2LxKLH.mjs';
|
||||
import 'piccolore';
|
||||
import 'clsx';
|
||||
/* empty css */
|
||||
|
||||
const $$Astro = createAstro();
|
||||
const $$Base = createComponent(($$result, $$props, $$slots) => {
|
||||
const Astro2 = $$result.createAstro($$Astro, $$props, $$slots);
|
||||
Astro2.self = $$Base;
|
||||
const { title = "Skillit" } = Astro2.props;
|
||||
return renderTemplate`<html lang="en"> <head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"><title>${title}</title>${renderHead()}</head> <body class="min-h-screen font-sans text-gray-300 antialiased"> <!-- Subtle gradient glow --> <div class="pointer-events-none fixed inset-0 overflow-hidden"> <div class="absolute -top-40 left-1/2 -translate-x-1/2 h-80 w-[600px] rounded-full bg-accent-500/[0.07] blur-[120px]"></div> </div> <nav class="relative z-50 border-b border-white/[0.06] bg-surface-50/80 backdrop-blur-xl"> <div class="mx-auto max-w-5xl flex items-center justify-between px-6 py-4"> <a href="/" class="group flex items-center gap-2.5"> <div class="flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-accent-500 to-accent-600 shadow-lg shadow-accent-500/20"> <svg class="h-4 w-4 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"> <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5"></path> </svg> </div> <span class="text-lg font-bold tracking-tight text-white group-hover:text-accent-400 transition-colors">skillit</span> </a> <a href="/new" class="inline-flex items-center gap-1.5 rounded-lg bg-accent-500 px-4 py-2 text-sm font-semibold text-white shadow-lg shadow-accent-500/20 hover:bg-accent-600 hover:shadow-accent-500/30 active:scale-[0.97] transition-all"> <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"> <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"></path> </svg>
|
||||
New Skill
|
||||
</a> </div> </nav> <main class="relative mx-auto max-w-5xl px-6 py-10"> ${renderSlot($$result, $$slots["default"])} </main> </body></html>`;
|
||||
}, "/Users/alex/projects/skillit/src/layouts/Base.astro", void 0);
|
||||
|
||||
const _export_sfc = (sfc, props) => {
|
||||
const target = sfc.__vccOpts || sfc;
|
||||
for (const [key, val] of props) {
|
||||
target[key] = val;
|
||||
}
|
||||
return target;
|
||||
};
|
||||
|
||||
export { $$Base as $, _export_sfc as _ };
|
||||
24
dist/server/chunks/_plugin-vue_export-helper_CEgY73aA.mjs
vendored
Normal file
24
dist/server/chunks/_plugin-vue_export-helper_CEgY73aA.mjs
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import { e as createAstro, f as createComponent, n as renderHead, o as renderSlot, r as renderTemplate } from './astro/server_CF97kUu8.mjs';
|
||||
import 'piccolore';
|
||||
import 'clsx';
|
||||
/* empty css */
|
||||
|
||||
const $$Astro = createAstro("https://skills.here.run.place");
|
||||
const $$Base = createComponent(($$result, $$props, $$slots) => {
|
||||
const Astro2 = $$result.createAstro($$Astro, $$props, $$slots);
|
||||
Astro2.self = $$Base;
|
||||
const { title = "Skills Here" } = Astro2.props;
|
||||
return renderTemplate`<html lang="en"> <head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><title>${title}</title>${renderHead()}</head> <body class="min-h-screen font-sans text-gray-300 antialiased"> <!-- Subtle gradient glow --> <div class="pointer-events-none fixed inset-0 overflow-hidden"> <div class="absolute -top-40 left-1/2 -translate-x-1/2 h-80 w-[600px] rounded-full bg-accent-500/[0.07] blur-[120px]"></div> </div> <nav class="relative z-50 border-b border-white/[0.06] bg-surface-50/80 backdrop-blur-xl"> <div class="mx-auto max-w-6xl flex items-center justify-between px-6 py-4"> <a href="/" class="group flex items-center gap-2.5"> <img src="/favicon.svg" alt="Skills Here" class="h-8 w-8"> <span class="text-lg font-bold tracking-tight text-white group-hover:text-accent-400 transition-colors">Skills Here</span> </a> <a href="/new" class="inline-flex items-center gap-1.5 rounded-lg bg-accent-500 px-4 py-2 text-sm font-semibold text-white shadow-lg shadow-accent-500/20 hover:bg-accent-600 hover:shadow-accent-500/30 active:scale-[0.97] transition-all"> <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"> <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"></path> </svg>
|
||||
New Skill
|
||||
</a> </div> </nav> <main class="relative mx-auto max-w-6xl px-6 py-10"> ${renderSlot($$result, $$slots["default"])} </main> </body></html>`;
|
||||
}, "/Users/alex/projects/skillit/src/layouts/Base.astro", void 0);
|
||||
|
||||
const _export_sfc = (sfc, props) => {
|
||||
const target = sfc.__vccOpts || sfc;
|
||||
for (const [key, val] of props) {
|
||||
target[key] = val;
|
||||
}
|
||||
return target;
|
||||
};
|
||||
|
||||
export { $$Base as $, _export_sfc as _ };
|
||||
@@ -1,4 +1,4 @@
|
||||
import { al as NOOP_MIDDLEWARE_HEADER, am as REDIRECT_STATUS_CODES, A as AstroError, an as ActionsReturnedInvalidDataError, T as DEFAULT_404_COMPONENT } from './astro/server_B-2LxKLH.mjs';
|
||||
import { al as NOOP_MIDDLEWARE_HEADER, am as REDIRECT_STATUS_CODES, A as AstroError, an as ActionsReturnedInvalidDataError, T as DEFAULT_404_COMPONENT } from './astro/server_CF97kUu8.mjs';
|
||||
import { parse, stringify } from 'devalue';
|
||||
import { escape } from 'html-escaper';
|
||||
|
||||
@@ -12,7 +12,7 @@ const ACTION_QUERY_PARAMS$1 = {
|
||||
actionName: "_action"};
|
||||
const ACTION_RPC_ROUTE_PATTERN = "/_actions/[...path]";
|
||||
|
||||
const __vite_import_meta_env__ = {"ASSETS_PREFIX": undefined, "BASE_URL": "/", "DEV": false, "MODE": "production", "PROD": true, "SITE": undefined, "SSR": true};
|
||||
const __vite_import_meta_env__ = {"ASSETS_PREFIX": undefined, "BASE_URL": "/", "DEV": false, "MODE": "production", "PROD": true, "SITE": "https://skills.here.run.place", "SSR": true};
|
||||
const ACTION_QUERY_PARAMS = ACTION_QUERY_PARAMS$1;
|
||||
const codeToStatusMap = {
|
||||
// Implemented from IANA HTTP Status Code Registry
|
||||
@@ -455,7 +455,7 @@ Use import.meta.glob instead: https://vitejs.dev/guide/features.html#glob-import
|
||||
}
|
||||
function createAstro(site) {
|
||||
return {
|
||||
site: void 0,
|
||||
site: new URL(site) ,
|
||||
generator: `Astro v${ASTRO_VERSION}`,
|
||||
glob: createAstroGlobFn()
|
||||
};
|
||||
@@ -2837,4 +2837,4 @@ function spreadAttributes(values = {}, _name, { class: scopedClassName } = {}) {
|
||||
return markHTMLString(output);
|
||||
}
|
||||
|
||||
export { isRenderInstruction as $, AstroError as A, ActionNotFoundError as B, MiddlewareNoDataOrNextCalled as C, MiddlewareNotAResponse as D, ExpectedImage as E, FailedToFetchRemoteImageDimensions as F, originPathnameSymbol as G, RewriteWithBodyUsed as H, IncompatibleDescriptorOptions as I, GetStaticPathsRequired as J, InvalidGetStaticPathsReturn as K, LocalImageUsedWrongly as L, MissingImageDimension as M, NoImageMetadata as N, InvalidGetStaticPathsEntry as O, GetStaticPathsExpectedParams as P, GetStaticPathsInvalidRouteParam as Q, ROUTE_TYPE_HEADER as R, PageNumberParamNotFound as S, DEFAULT_404_COMPONENT as T, UnsupportedImageFormat as U, NoMatchingStaticPathFound as V, PrerenderDynamicEndpointPathCollide as W, ReservedSlotName as X, renderSlotToString as Y, renderJSX as Z, chunkToString as _, UnsupportedImageConversion as a, ForbiddenRewrite as a0, SessionStorageInitError as a1, SessionStorageSaveError as a2, ASTRO_VERSION as a3, CspNotEnabled as a4, LocalsReassigned as a5, generateCspDigest as a6, PrerenderClientAddressNotAvailable as a7, clientAddressSymbol as a8, ClientAddressNotAvailable as a9, StaticClientAddressNotAvailable as aa, AstroResponseHeadersReassigned as ab, responseSentSymbol as ac, renderPage as ad, REWRITE_DIRECTIVE_HEADER_KEY as ae, REWRITE_DIRECTIVE_HEADER_VALUE as af, renderEndpoint as ag, LocalsNotAnObject as ah, FailedToFindPageMapSSR as ai, REROUTABLE_STATUS_CODES as aj, nodeRequestAbortControllerCleanupSymbol as ak, NOOP_MIDDLEWARE_HEADER as al, REDIRECT_STATUS_CODES as am, ActionsReturnedInvalidDataError as an, MissingSharp as ao, ExpectedImageOptions as b, ExpectedNotESMImage as c, InvalidImageService as d, createComponent as e, ImageMissingAlt as f, addAttribute as g, createAstro as h, ExperimentalFontsNotEnabled as i, FontFamilyNotFound as j, renderComponent as k, renderScript as l, maybeRenderHead as m, renderHead as n, renderSlot as o, decodeKey as p, decryptString as q, renderTemplate as r, spreadAttributes as s, toStyleString as t, unescapeHTML as u, createSlotValueFromString as v, isAstroComponentFactory as w, REROUTE_DIRECTIVE_HEADER as x, i18nNoLocaleFoundInPath as y, ResponseSentError as z };
|
||||
export { isRenderInstruction as $, AstroError as A, ActionNotFoundError as B, MiddlewareNoDataOrNextCalled as C, MiddlewareNotAResponse as D, ExpectedImage as E, FailedToFetchRemoteImageDimensions as F, originPathnameSymbol as G, RewriteWithBodyUsed as H, IncompatibleDescriptorOptions as I, GetStaticPathsRequired as J, InvalidGetStaticPathsReturn as K, LocalImageUsedWrongly as L, MissingImageDimension as M, NoImageMetadata as N, InvalidGetStaticPathsEntry as O, GetStaticPathsExpectedParams as P, GetStaticPathsInvalidRouteParam as Q, ROUTE_TYPE_HEADER as R, PageNumberParamNotFound as S, DEFAULT_404_COMPONENT as T, UnsupportedImageFormat as U, NoMatchingStaticPathFound as V, PrerenderDynamicEndpointPathCollide as W, ReservedSlotName as X, renderSlotToString as Y, renderJSX as Z, chunkToString as _, UnsupportedImageConversion as a, ForbiddenRewrite as a0, SessionStorageInitError as a1, SessionStorageSaveError as a2, ASTRO_VERSION as a3, CspNotEnabled as a4, LocalsReassigned as a5, generateCspDigest as a6, PrerenderClientAddressNotAvailable as a7, clientAddressSymbol as a8, ClientAddressNotAvailable as a9, StaticClientAddressNotAvailable as aa, AstroResponseHeadersReassigned as ab, responseSentSymbol as ac, renderPage as ad, REWRITE_DIRECTIVE_HEADER_KEY as ae, REWRITE_DIRECTIVE_HEADER_VALUE as af, renderEndpoint as ag, LocalsNotAnObject as ah, FailedToFindPageMapSSR as ai, REROUTABLE_STATUS_CODES as aj, nodeRequestAbortControllerCleanupSymbol as ak, NOOP_MIDDLEWARE_HEADER as al, REDIRECT_STATUS_CODES as am, ActionsReturnedInvalidDataError as an, MissingSharp as ao, ExpectedImageOptions as b, ExpectedNotESMImage as c, InvalidImageService as d, createAstro as e, createComponent as f, ImageMissingAlt as g, addAttribute as h, ExperimentalFontsNotEnabled as i, FontFamilyNotFound as j, renderComponent as k, renderScript as l, maybeRenderHead as m, renderHead as n, renderSlot as o, decodeKey as p, decryptString as q, renderTemplate as r, spreadAttributes as s, toStyleString as t, unescapeHTML as u, createSlotValueFromString as v, isAstroComponentFactory as w, REROUTE_DIRECTIVE_HEADER as x, i18nNoLocaleFoundInPath as y, ResponseSentError as z };
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
import { i as isRemoteAllowed, j as joinPaths, a as isRemotePath, r as removeQueryString, b as isParentDirectory } from './remote_B3W5fv4r.mjs';
|
||||
import { A as AstroError, E as ExpectedImage, L as LocalImageUsedWrongly, M as MissingImageDimension, U as UnsupportedImageFormat, I as IncompatibleDescriptorOptions, a as UnsupportedImageConversion, t as toStyleString, N as NoImageMetadata, F as FailedToFetchRemoteImageDimensions, b as ExpectedImageOptions, c as ExpectedNotESMImage, d as InvalidImageService, e as createComponent, f as ImageMissingAlt, m as maybeRenderHead, g as addAttribute, s as spreadAttributes, r as renderTemplate, h as createAstro, i as ExperimentalFontsNotEnabled, j as FontFamilyNotFound, u as unescapeHTML } from './astro/server_B-2LxKLH.mjs';
|
||||
import { A as AstroError, E as ExpectedImage, L as LocalImageUsedWrongly, M as MissingImageDimension, U as UnsupportedImageFormat, I as IncompatibleDescriptorOptions, a as UnsupportedImageConversion, t as toStyleString, N as NoImageMetadata, F as FailedToFetchRemoteImageDimensions, b as ExpectedImageOptions, c as ExpectedNotESMImage, d as InvalidImageService, e as createAstro, f as createComponent, g as ImageMissingAlt, m as maybeRenderHead, h as addAttribute, s as spreadAttributes, r as renderTemplate, i as ExperimentalFontsNotEnabled, j as FontFamilyNotFound, u as unescapeHTML } from './astro/server_CF97kUu8.mjs';
|
||||
import 'clsx';
|
||||
import * as mime from 'mrmime';
|
||||
import 'piccolore';
|
||||
@@ -1434,7 +1434,7 @@ async function getConfiguredImageService() {
|
||||
if (!globalThis?.astroAsset?.imageService) {
|
||||
const { default: service } = await import(
|
||||
// @ts-expect-error
|
||||
'./sharp_CRCimLOL.mjs'
|
||||
'./sharp_D9uxjd11.mjs'
|
||||
).catch((e) => {
|
||||
const error = new AstroError(InvalidImageService);
|
||||
error.cause = e;
|
||||
@@ -1589,7 +1589,7 @@ async function getImage$1(options, imageConfig) {
|
||||
};
|
||||
}
|
||||
|
||||
const $$Astro$2 = createAstro();
|
||||
const $$Astro$2 = createAstro("https://skills.here.run.place");
|
||||
const $$Image = createComponent(async ($$result, $$props, $$slots) => {
|
||||
const Astro2 = $$result.createAstro($$Astro$2, $$props, $$slots);
|
||||
Astro2.self = $$Image;
|
||||
@@ -1618,7 +1618,7 @@ const $$Image = createComponent(async ($$result, $$props, $$slots) => {
|
||||
return renderTemplate`${maybeRenderHead()}<img${addAttribute(image.src, "src")}${spreadAttributes(attributes)}${addAttribute(className, "class")}>`;
|
||||
}, "/Users/alex/projects/skillit/node_modules/astro/components/Image.astro", void 0);
|
||||
|
||||
const $$Astro$1 = createAstro();
|
||||
const $$Astro$1 = createAstro("https://skills.here.run.place");
|
||||
const $$Picture = createComponent(async ($$result, $$props, $$slots) => {
|
||||
const Astro2 = $$result.createAstro($$Astro$1, $$props, $$slots);
|
||||
Astro2.self = $$Picture;
|
||||
@@ -1728,7 +1728,7 @@ function checkWeight(input, target) {
|
||||
return input === target;
|
||||
}
|
||||
|
||||
const $$Astro = createAstro();
|
||||
const $$Astro = createAstro("https://skills.here.run.place");
|
||||
const $$Font = createComponent(($$result, $$props, $$slots) => {
|
||||
const Astro2 = $$result.createAstro($$Astro, $$props, $$slots);
|
||||
Astro2.self = $$Font;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { A as AstroError, ao as MissingSharp } from './astro/server_B-2LxKLH.mjs';
|
||||
import { b as baseService, p as parseQuality } from './node_WXNYuHqd.mjs';
|
||||
import { A as AstroError, ao as MissingSharp } from './astro/server_CF97kUu8.mjs';
|
||||
import { b as baseService, p as parseQuality } from './node_HH9e2ntY.mjs';
|
||||
|
||||
let sharp;
|
||||
const qualityTable = {
|
||||
@@ -31,6 +31,10 @@ function parseSkill(slug, raw) {
|
||||
"disable-model-invocation": Boolean(data["disable-model-invocation"]),
|
||||
context: data.context || "",
|
||||
agent: data.agent || "",
|
||||
author: data.author || "",
|
||||
"author-email": data["author-email"] || "",
|
||||
"fork-of": data["fork-of"] || "",
|
||||
tags: parseTools(data.tags),
|
||||
hooks: typeof data.hooks === "object" && data.hooks !== null ? data.hooks : null,
|
||||
content: content.trim(),
|
||||
raw
|
||||
@@ -49,6 +53,14 @@ async function listSkills() {
|
||||
}
|
||||
return skills.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
async function getAllTags() {
|
||||
const all = await listSkills();
|
||||
return [...new Set(all.flatMap((s) => s.tags))].sort();
|
||||
}
|
||||
async function getForksOf(slug) {
|
||||
const all = await listSkills();
|
||||
return all.filter((s) => s["fork-of"] === slug);
|
||||
}
|
||||
async function getSkill(slug) {
|
||||
try {
|
||||
const raw = await fs.readFile(skillPath(slug), "utf-8");
|
||||
@@ -94,4 +106,4 @@ async function deleteSkill(slug) {
|
||||
await fs.unlink(dest);
|
||||
}
|
||||
|
||||
export { createSkill as c, deleteSkill as d, getSkill as g, isValidSlug as i, listSkills as l, updateSkill as u };
|
||||
export { getAllTags as a, getForksOf as b, createSkill as c, deleteSkill as d, getSkill as g, isValidSlug as i, listSkills as l, updateSkill as u };
|
||||
46
dist/server/chunks/stats_CaDi9y9J.mjs
vendored
Normal file
46
dist/server/chunks/stats_CaDi9y9J.mjs
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
const STATS_FILE = path.resolve(process.env.STATS_FILE || "data/stats.json");
|
||||
async function readStore() {
|
||||
try {
|
||||
const raw = await fs.readFile(STATS_FILE, "utf-8");
|
||||
return JSON.parse(raw);
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
async function writeStore(store) {
|
||||
const dir = path.dirname(STATS_FILE);
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
const tmp = STATS_FILE + ".tmp";
|
||||
await fs.writeFile(tmp, JSON.stringify(store, null, 2), "utf-8");
|
||||
await fs.rename(tmp, STATS_FILE);
|
||||
}
|
||||
function ensure(store, slug) {
|
||||
if (!store[slug]) {
|
||||
store[slug] = { downloads: 0, pushes: 0, lastPushedAt: null };
|
||||
}
|
||||
return store[slug];
|
||||
}
|
||||
async function recordDownload(slug) {
|
||||
const store = await readStore();
|
||||
ensure(store, slug).downloads++;
|
||||
await writeStore(store);
|
||||
}
|
||||
async function recordPush(slug) {
|
||||
const store = await readStore();
|
||||
const entry = ensure(store, slug);
|
||||
entry.pushes++;
|
||||
entry.lastPushedAt = (/* @__PURE__ */ new Date()).toISOString();
|
||||
await writeStore(store);
|
||||
}
|
||||
async function getStatsForSlug(slug) {
|
||||
const store = await readStore();
|
||||
return store[slug] || { downloads: 0, pushes: 0, lastPushedAt: null };
|
||||
}
|
||||
async function getAllStats() {
|
||||
return readStore();
|
||||
}
|
||||
|
||||
export { recordDownload as a, getAllStats as b, getStatsForSlug as g, recordPush as r };
|
||||
309
dist/server/chunks/sync_BEq_wzpT.mjs
vendored
Normal file
309
dist/server/chunks/sync_BEq_wzpT.mjs
vendored
Normal file
@@ -0,0 +1,309 @@
|
||||
import { l as listSkills } from './skills_BacVQUiS.mjs';
|
||||
|
||||
function isPowerShell(request) {
|
||||
const ua = request.headers.get("user-agent") || "";
|
||||
return /PowerShell/i.test(ua);
|
||||
}
|
||||
async function buildPushScript(baseUrl, skillsDir) {
|
||||
const lines = [
|
||||
"#!/usr/bin/env bash",
|
||||
"set -euo pipefail",
|
||||
"",
|
||||
`SKILLS_DIR="${skillsDir}"`,
|
||||
`BASE_URL="${baseUrl}"`,
|
||||
'FILTER="${1:-}"',
|
||||
'TOKEN_FILE="$HOME/.claude/skills.here-token"',
|
||||
"",
|
||||
"# Get git author if available",
|
||||
'AUTHOR_NAME=$(git config user.name 2>/dev/null || echo "")',
|
||||
'AUTHOR_EMAIL=$(git config user.email 2>/dev/null || echo "")',
|
||||
"",
|
||||
"# Load or register token",
|
||||
'TOKEN=""',
|
||||
'if [ -f "$TOKEN_FILE" ]; then',
|
||||
' TOKEN=$(cat "$TOKEN_FILE")',
|
||||
'elif [ -n "$AUTHOR_EMAIL" ]; then',
|
||||
' echo "No token found. Registering with $AUTHOR_EMAIL..."',
|
||||
" REGISTER_RESPONSE=$(curl -sS -X POST \\",
|
||||
' -H "Content-Type: application/json" \\',
|
||||
' -d "{\\"email\\": \\"$AUTHOR_EMAIL\\", \\"name\\": \\"$AUTHOR_NAME\\"}" \\',
|
||||
' -w "\\n%{http_code}" \\',
|
||||
' "$BASE_URL/api/auth/register")',
|
||||
` REGISTER_BODY=$(echo "$REGISTER_RESPONSE" | sed '$d')`,
|
||||
' REGISTER_STATUS=$(echo "$REGISTER_RESPONSE" | tail -1)',
|
||||
' if [ "$REGISTER_STATUS" = "201" ]; then',
|
||||
' TOKEN=$(echo "$REGISTER_BODY" | jq -r .token)',
|
||||
' mkdir -p "$(dirname "$TOKEN_FILE")"',
|
||||
' echo "$TOKEN" > "$TOKEN_FILE"',
|
||||
' chmod 600 "$TOKEN_FILE"',
|
||||
' echo " Token saved to $TOKEN_FILE"',
|
||||
' elif [ "$REGISTER_STATUS" = "409" ]; then',
|
||||
' echo " Email already registered. Place your token in $TOKEN_FILE"',
|
||||
' echo " Continuing without token (unprotected skills only)..."',
|
||||
" else",
|
||||
' echo " Registration failed ($REGISTER_STATUS): $REGISTER_BODY"',
|
||||
' echo " Continuing without token (unprotected skills only)..."',
|
||||
" fi",
|
||||
"fi",
|
||||
"",
|
||||
'if [ ! -d "$SKILLS_DIR" ]; then',
|
||||
' echo "No skills directory found at $SKILLS_DIR"',
|
||||
" exit 1",
|
||||
"fi",
|
||||
"",
|
||||
'AUTH_HEADER=""',
|
||||
'if [ -n "$TOKEN" ]; then',
|
||||
' AUTH_HEADER="Authorization: Bearer $TOKEN"',
|
||||
"fi",
|
||||
"",
|
||||
"push_skill() {",
|
||||
' local file="$1"',
|
||||
' local slug=$(basename "$file" .md)',
|
||||
' local content=$(cat "$file")',
|
||||
"",
|
||||
" # Inject author into frontmatter if available and not already present",
|
||||
' if [ -n "$AUTHOR_EMAIL" ] && ! echo "$content" | grep -q "^author-email:"; then',
|
||||
` content=$(echo "$content" | awk -v name="$AUTHOR_NAME" -v email="$AUTHOR_EMAIL" 'NR==1 && /^---$/{print; if (name) print "author: " name; print "author-email: " email; next} {print}')`,
|
||||
" fi",
|
||||
"",
|
||||
" # Build curl auth args",
|
||||
' local auth_args=""',
|
||||
' if [ -n "$AUTH_HEADER" ]; then',
|
||||
' auth_args="-H \\"$AUTH_HEADER\\""',
|
||||
" fi",
|
||||
"",
|
||||
" # Try PUT (update), fallback to POST (create)",
|
||||
" local response",
|
||||
' if [ -n "$TOKEN" ]; then',
|
||||
' response=$(curl -sS -o /dev/null -w "%{http_code}" -X PUT \\',
|
||||
' -H "Content-Type: application/json" \\',
|
||||
' -H "Authorization: Bearer $TOKEN" \\',
|
||||
' -d "{\\"content\\": $(echo "$content" | jq -Rs .)}" \\',
|
||||
' "$BASE_URL/api/skills/$slug")',
|
||||
" else",
|
||||
' response=$(curl -sS -o /dev/null -w "%{http_code}" -X PUT \\',
|
||||
' -H "Content-Type: application/json" \\',
|
||||
' -d "{\\"content\\": $(echo "$content" | jq -Rs .)}" \\',
|
||||
' "$BASE_URL/api/skills/$slug")',
|
||||
" fi",
|
||||
"",
|
||||
' if [ "$response" = "403" ]; then',
|
||||
' echo " ✗ $slug (permission denied — token missing or invalid)"',
|
||||
" return 1",
|
||||
" fi",
|
||||
"",
|
||||
' if [ "$response" = "404" ]; then',
|
||||
' if [ -n "$TOKEN" ]; then',
|
||||
' local post_status=$(curl -sS -o /dev/null -w "%{http_code}" -X POST \\',
|
||||
' -H "Content-Type: application/json" \\',
|
||||
' -H "Authorization: Bearer $TOKEN" \\',
|
||||
' -d "{\\"slug\\": \\"$slug\\", \\"content\\": $(echo "$content" | jq -Rs .)}" \\',
|
||||
' "$BASE_URL/api/skills")',
|
||||
" else",
|
||||
' local post_status=$(curl -sS -o /dev/null -w "%{http_code}" -X POST \\',
|
||||
' -H "Content-Type: application/json" \\',
|
||||
' -d "{\\"slug\\": \\"$slug\\", \\"content\\": $(echo "$content" | jq -Rs .)}" \\',
|
||||
' "$BASE_URL/api/skills")',
|
||||
" fi",
|
||||
' if [ "$post_status" = "403" ]; then',
|
||||
' echo " ✗ $slug (permission denied — token missing or invalid)"',
|
||||
" return 1",
|
||||
" fi",
|
||||
" fi",
|
||||
"",
|
||||
' echo " ✓ $slug"',
|
||||
"}",
|
||||
"",
|
||||
"count=0",
|
||||
"failed=0",
|
||||
'if [ -n "$FILTER" ]; then',
|
||||
" # Push a specific skill",
|
||||
' file="$SKILLS_DIR/${FILTER%.md}.md"',
|
||||
' if [ ! -f "$file" ]; then',
|
||||
' echo "Skill not found: $file"',
|
||||
" exit 1",
|
||||
" fi",
|
||||
' if push_skill "$file"; then',
|
||||
" count=1",
|
||||
" else",
|
||||
" failed=1",
|
||||
" fi",
|
||||
"else",
|
||||
" # Push all skills",
|
||||
' for file in "$SKILLS_DIR"/*.md; do',
|
||||
' [ -f "$file" ] || continue',
|
||||
' if push_skill "$file"; then',
|
||||
" count=$((count + 1))",
|
||||
" else",
|
||||
" failed=$((failed + 1))",
|
||||
" fi",
|
||||
" done",
|
||||
"fi",
|
||||
"",
|
||||
'echo "Pushed $count skill(s) to $BASE_URL"',
|
||||
'if [ "$failed" -gt 0 ]; then',
|
||||
' echo "$failed skill(s) failed (permission denied)"',
|
||||
"fi",
|
||||
""
|
||||
];
|
||||
return lines.join("\n");
|
||||
}
|
||||
async function buildSyncScript(baseUrl, skillsDir) {
|
||||
const skills = await listSkills();
|
||||
const lines = [
|
||||
"#!/usr/bin/env bash",
|
||||
"set -euo pipefail",
|
||||
"",
|
||||
`SKILLS_DIR="${skillsDir}"`,
|
||||
'mkdir -p "$SKILLS_DIR"',
|
||||
""
|
||||
];
|
||||
if (skills.length === 0) {
|
||||
lines.push('echo "No skills available to sync."');
|
||||
} else {
|
||||
lines.push(`echo "Syncing ${skills.length} skill(s) from ${baseUrl}..."`);
|
||||
lines.push("");
|
||||
for (const skill of skills) {
|
||||
const skillUrl = `${baseUrl}/${skill.slug}`;
|
||||
lines.push(`curl -fsSL "${skillUrl}" -o "$SKILLS_DIR/${skill.slug}.md"`);
|
||||
lines.push(`echo " ✓ ${skill.name}"`);
|
||||
}
|
||||
lines.push("");
|
||||
lines.push('echo "Done! Skills synced to $SKILLS_DIR"');
|
||||
}
|
||||
lines.push("");
|
||||
return lines.join("\n");
|
||||
}
|
||||
async function buildSyncScriptPS(baseUrl, skillsDir) {
|
||||
const skills = await listSkills();
|
||||
const lines = [
|
||||
'$ErrorActionPreference = "Stop"',
|
||||
"",
|
||||
`$SkillsDir = "${skillsDir}"`,
|
||||
"New-Item -ItemType Directory -Force -Path $SkillsDir | Out-Null",
|
||||
""
|
||||
];
|
||||
if (skills.length === 0) {
|
||||
lines.push('Write-Host "No skills available to sync."');
|
||||
} else {
|
||||
lines.push(`Write-Host "Syncing ${skills.length} skill(s) from ${baseUrl}..."`);
|
||||
lines.push("");
|
||||
for (const skill of skills) {
|
||||
const skillUrl = `${baseUrl}/${skill.slug}`;
|
||||
lines.push(`Invoke-WebRequest -Uri "${skillUrl}" -OutFile (Join-Path $SkillsDir "${skill.slug}.md")`);
|
||||
lines.push(`Write-Host " ✓ ${skill.name}"`);
|
||||
}
|
||||
lines.push("");
|
||||
lines.push('Write-Host "Done! Skills synced to $SkillsDir"');
|
||||
}
|
||||
lines.push("");
|
||||
return lines.join("\n");
|
||||
}
|
||||
async function buildPushScriptPS(baseUrl, skillsDir) {
|
||||
const lines = [
|
||||
'$ErrorActionPreference = "Stop"',
|
||||
"",
|
||||
`$SkillsDir = "${skillsDir}"`,
|
||||
`$BaseUrl = "${baseUrl}"`,
|
||||
'$Filter = if ($args.Count -gt 0) { $args[0] } else { "" }',
|
||||
'$TokenFile = Join-Path $HOME ".claude\\skills.here-token"',
|
||||
"",
|
||||
"# Get git author if available",
|
||||
'$AuthorName = try { git config user.name 2>$null } catch { "" }',
|
||||
'$AuthorEmail = try { git config user.email 2>$null } catch { "" }',
|
||||
"",
|
||||
"# Load or register token",
|
||||
'$Token = ""',
|
||||
"if (Test-Path $TokenFile) {",
|
||||
" $Token = (Get-Content $TokenFile -Raw).Trim()",
|
||||
"} elseif ($AuthorEmail) {",
|
||||
' Write-Host "No token found. Registering with $AuthorEmail..."',
|
||||
" try {",
|
||||
" $body = @{ email = $AuthorEmail; name = $AuthorName } | ConvertTo-Json",
|
||||
' $resp = Invoke-WebRequest -Uri "$BaseUrl/api/auth/register" -Method POST -ContentType "application/json" -Body $body',
|
||||
" if ($resp.StatusCode -eq 201) {",
|
||||
" $Token = ($resp.Content | ConvertFrom-Json).token",
|
||||
" New-Item -ItemType Directory -Force -Path (Split-Path $TokenFile) | Out-Null",
|
||||
" Set-Content -Path $TokenFile -Value $Token",
|
||||
' Write-Host " Token saved to $TokenFile"',
|
||||
" }",
|
||||
" } catch {",
|
||||
" $code = $_.Exception.Response.StatusCode.value__",
|
||||
" if ($code -eq 409) {",
|
||||
' Write-Host " Email already registered. Place your token in $TokenFile"',
|
||||
" } else {",
|
||||
' Write-Host " Registration failed: $_"',
|
||||
" }",
|
||||
' Write-Host " Continuing without token (unprotected skills only)..."',
|
||||
" }",
|
||||
"}",
|
||||
"",
|
||||
"if (-not (Test-Path $SkillsDir)) {",
|
||||
' Write-Host "No skills directory found at $SkillsDir"',
|
||||
" exit 1",
|
||||
"}",
|
||||
"",
|
||||
'$headers = @{ "Content-Type" = "application/json" }',
|
||||
'if ($Token) { $headers["Authorization"] = "Bearer $Token" }',
|
||||
"",
|
||||
"function Push-Skill($file) {",
|
||||
" $slug = [IO.Path]::GetFileNameWithoutExtension($file)",
|
||||
" $content = Get-Content $file -Raw",
|
||||
"",
|
||||
" # Inject author if available and not present",
|
||||
' if ($AuthorEmail -and $content -notmatch "(?m)^author-email:") {',
|
||||
' $inject = ""',
|
||||
' if ($AuthorName) { $inject += "author: $AuthorName`n" }',
|
||||
' $inject += "author-email: $AuthorEmail"',
|
||||
' $content = $content -replace "^---$", "---`n$inject" -replace "^---`n", "---`n"',
|
||||
" }",
|
||||
"",
|
||||
" $body = @{ content = $content } | ConvertTo-Json",
|
||||
" try {",
|
||||
' Invoke-WebRequest -Uri "$BaseUrl/api/skills/$slug" -Method PUT -Headers $headers -Body $body | Out-Null',
|
||||
' Write-Host " ✓ $slug"',
|
||||
" return $true",
|
||||
" } catch {",
|
||||
" $code = $_.Exception.Response.StatusCode.value__",
|
||||
" if ($code -eq 403) {",
|
||||
' Write-Host " ✗ $slug (permission denied)"',
|
||||
" return $false",
|
||||
" }",
|
||||
" if ($code -eq 404) {",
|
||||
" $postBody = @{ slug = $slug; content = $content } | ConvertTo-Json",
|
||||
" try {",
|
||||
' Invoke-WebRequest -Uri "$BaseUrl/api/skills" -Method POST -Headers $headers -Body $postBody | Out-Null',
|
||||
' Write-Host " ✓ $slug"',
|
||||
" return $true",
|
||||
" } catch {",
|
||||
" $postCode = $_.Exception.Response.StatusCode.value__",
|
||||
" if ($postCode -eq 403) {",
|
||||
' Write-Host " ✗ $slug (permission denied)"',
|
||||
" return $false",
|
||||
" }",
|
||||
" }",
|
||||
" }",
|
||||
" }",
|
||||
' Write-Host " ✓ $slug"',
|
||||
" return $true",
|
||||
"}",
|
||||
"",
|
||||
"$count = 0; $failed = 0",
|
||||
"if ($Filter) {",
|
||||
` $file = Join-Path $SkillsDir "$($Filter -replace '\\.md$','').md"`,
|
||||
' if (-not (Test-Path $file)) { Write-Host "Skill not found: $file"; exit 1 }',
|
||||
" if (Push-Skill $file) { $count++ } else { $failed++ }",
|
||||
"} else {",
|
||||
' Get-ChildItem -Path $SkillsDir -Filter "*.md" | ForEach-Object {',
|
||||
" if (Push-Skill $_.FullName) { $count++ } else { $failed++ }",
|
||||
" }",
|
||||
"}",
|
||||
"",
|
||||
'Write-Host "Pushed $count skill(s) to $BaseUrl"',
|
||||
'if ($failed -gt 0) { Write-Host "$failed skill(s) failed (permission denied)" }',
|
||||
""
|
||||
];
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
export { buildSyncScriptPS as a, buildSyncScript as b, buildPushScript as c, buildPushScriptPS as d, isPowerShell as i };
|
||||
71
dist/server/chunks/sync_B_Og9xl3.mjs
vendored
71
dist/server/chunks/sync_B_Og9xl3.mjs
vendored
@@ -1,71 +0,0 @@
|
||||
import { l as listSkills } from './skills_COWfD5oy.mjs';
|
||||
|
||||
async function buildPushScript(baseUrl, skillsDir) {
|
||||
const lines = [
|
||||
"#!/usr/bin/env bash",
|
||||
"set -euo pipefail",
|
||||
"",
|
||||
`SKILLS_DIR="${skillsDir}"`,
|
||||
`BASE_URL="${baseUrl}"`,
|
||||
"",
|
||||
'if [ ! -d "$SKILLS_DIR" ]; then',
|
||||
' echo "No skills directory found at $SKILLS_DIR"',
|
||||
" exit 1",
|
||||
"fi",
|
||||
"",
|
||||
"count=0",
|
||||
'for file in "$SKILLS_DIR"/*.md; do',
|
||||
' [ -f "$file" ] || continue',
|
||||
' slug=$(basename "$file" .md)',
|
||||
' content=$(cat "$file")',
|
||||
"",
|
||||
" # Try PUT (update), fallback to POST (create)",
|
||||
' status=$(curl -s -o /dev/null -w "%{http_code}" -X PUT \\',
|
||||
' -H "Content-Type: application/json" \\',
|
||||
' -d "{\\"content\\": $(echo "$content" | jq -Rs .)}" \\',
|
||||
' "$BASE_URL/api/skills/$slug")',
|
||||
"",
|
||||
' if [ "$status" = "404" ]; then',
|
||||
" curl -fsS -X POST \\",
|
||||
' -H "Content-Type: application/json" \\',
|
||||
' -d "{\\"slug\\": \\"$slug\\", \\"content\\": $(echo "$content" | jq -Rs .)}" \\',
|
||||
' "$BASE_URL/api/skills" > /dev/null',
|
||||
" fi",
|
||||
"",
|
||||
' echo " ✓ $slug"',
|
||||
" count=$((count + 1))",
|
||||
"done",
|
||||
"",
|
||||
'echo "Pushed $count skill(s) to $BASE_URL"',
|
||||
""
|
||||
];
|
||||
return lines.join("\n");
|
||||
}
|
||||
async function buildSyncScript(baseUrl, skillsDir) {
|
||||
const skills = await listSkills();
|
||||
const lines = [
|
||||
"#!/usr/bin/env bash",
|
||||
"set -euo pipefail",
|
||||
"",
|
||||
`SKILLS_DIR="${skillsDir}"`,
|
||||
'mkdir -p "$SKILLS_DIR"',
|
||||
""
|
||||
];
|
||||
if (skills.length === 0) {
|
||||
lines.push('echo "No skills available to sync."');
|
||||
} else {
|
||||
lines.push(`echo "Syncing ${skills.length} skill(s) from ${baseUrl}..."`);
|
||||
lines.push("");
|
||||
for (const skill of skills) {
|
||||
const skillUrl = `${baseUrl}/${skill.slug}`;
|
||||
lines.push(`curl -fsSL "${skillUrl}" -o "$SKILLS_DIR/${skill.slug}.md"`);
|
||||
lines.push(`echo " ✓ ${skill.name}"`);
|
||||
}
|
||||
lines.push("");
|
||||
lines.push('echo "Done! Skills synced to $SKILLS_DIR"');
|
||||
}
|
||||
lines.push("");
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
export { buildPushScript as a, buildSyncScript as b };
|
||||
55
dist/server/chunks/tokens_CAzj9Aj8.mjs
vendored
Normal file
55
dist/server/chunks/tokens_CAzj9Aj8.mjs
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import crypto from 'node:crypto';
|
||||
|
||||
const TOKENS_FILE = path.resolve(process.env.TOKENS_FILE || "data/tokens.json");
|
||||
async function readStore() {
|
||||
try {
|
||||
const raw = await fs.readFile(TOKENS_FILE, "utf-8");
|
||||
return JSON.parse(raw);
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
async function writeStore(store) {
|
||||
const dir = path.dirname(TOKENS_FILE);
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
const tmp = TOKENS_FILE + ".tmp";
|
||||
await fs.writeFile(tmp, JSON.stringify(store, null, 2), "utf-8");
|
||||
await fs.rename(tmp, TOKENS_FILE);
|
||||
}
|
||||
function hashToken(token) {
|
||||
return crypto.createHash("sha256").update(token).digest("hex");
|
||||
}
|
||||
async function generateToken(email, name) {
|
||||
const store = await readStore();
|
||||
if (store[email]) {
|
||||
throw new Error("Email already registered");
|
||||
}
|
||||
const token = crypto.randomBytes(32).toString("hex");
|
||||
store[email] = {
|
||||
hash: hashToken(token),
|
||||
name,
|
||||
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
||||
};
|
||||
await writeStore(store);
|
||||
return token;
|
||||
}
|
||||
async function verifyToken(email, token) {
|
||||
if (!email || !token) return false;
|
||||
const store = await readStore();
|
||||
const entry = store[email];
|
||||
if (!entry) return false;
|
||||
return entry.hash === hashToken(token);
|
||||
}
|
||||
async function hasToken(email) {
|
||||
const store = await readStore();
|
||||
return Boolean(store[email]);
|
||||
}
|
||||
function extractBearerToken(request) {
|
||||
const auth = request.headers.get("Authorization") || "";
|
||||
const match = auth.match(/^Bearer\s+(\S+)$/i);
|
||||
return match ? match[1] : "";
|
||||
}
|
||||
|
||||
export { extractBearerToken as e, generateToken as g, hasToken as h, verifyToken as v };
|
||||
60
dist/server/entry.mjs
vendored
60
dist/server/entry.mjs
vendored
@@ -1,36 +1,44 @@
|
||||
import { renderers } from './renderers.mjs';
|
||||
import { c as createExports, s as serverEntrypointModule } from './chunks/_@astrojs-ssr-adapter_DIu76Dvd.mjs';
|
||||
import { manifest } from './manifest_BJPuFUv4.mjs';
|
||||
import { c as createExports, s as serverEntrypointModule } from './chunks/_@astrojs-ssr-adapter_BeL8VyJ8.mjs';
|
||||
import { manifest } from './manifest_Bz0Ba_R4.mjs';
|
||||
|
||||
const serverIslandMap = new Map();;
|
||||
|
||||
const _page0 = () => import('./pages/_image.astro.mjs');
|
||||
const _page1 = () => import('./pages/api/skills/_slug_.astro.mjs');
|
||||
const _page2 = () => import('./pages/api/skills.astro.mjs');
|
||||
const _page3 = () => import('./pages/api/sync/project.astro.mjs');
|
||||
const _page4 = () => import('./pages/api/sync.astro.mjs');
|
||||
const _page5 = () => import('./pages/gi.astro.mjs');
|
||||
const _page6 = () => import('./pages/gp.astro.mjs');
|
||||
const _page7 = () => import('./pages/i.astro.mjs');
|
||||
const _page8 = () => import('./pages/new.astro.mjs');
|
||||
const _page9 = () => import('./pages/p.astro.mjs');
|
||||
const _page10 = () => import('./pages/_slug_/edit.astro.mjs');
|
||||
const _page11 = () => import('./pages/_slug_.astro.mjs');
|
||||
const _page12 = () => import('./pages/index.astro.mjs');
|
||||
const _page1 = () => import('./pages/api/auth/register.astro.mjs');
|
||||
const _page2 = () => import('./pages/api/auth/verify.astro.mjs');
|
||||
const _page3 = () => import('./pages/api/skills/_slug_.astro.mjs');
|
||||
const _page4 = () => import('./pages/api/skills.astro.mjs');
|
||||
const _page5 = () => import('./pages/api/sync/project.astro.mjs');
|
||||
const _page6 = () => import('./pages/api/sync.astro.mjs');
|
||||
const _page7 = () => import('./pages/gi.astro.mjs');
|
||||
const _page8 = () => import('./pages/gp.astro.mjs');
|
||||
const _page9 = () => import('./pages/i.astro.mjs');
|
||||
const _page10 = () => import('./pages/new.astro.mjs');
|
||||
const _page11 = () => import('./pages/p.astro.mjs');
|
||||
const _page12 = () => import('./pages/_slug_/edit.astro.mjs');
|
||||
const _page13 = () => import('./pages/_slug_/gi.astro.mjs');
|
||||
const _page14 = () => import('./pages/_slug_/i.astro.mjs');
|
||||
const _page15 = () => import('./pages/_slug_.astro.mjs');
|
||||
const _page16 = () => import('./pages/index.astro.mjs');
|
||||
const pageMap = new Map([
|
||||
["node_modules/astro/dist/assets/endpoint/node.js", _page0],
|
||||
["src/pages/api/skills/[slug].ts", _page1],
|
||||
["src/pages/api/skills/index.ts", _page2],
|
||||
["src/pages/api/sync/project.ts", _page3],
|
||||
["src/pages/api/sync/index.ts", _page4],
|
||||
["src/pages/gi.ts", _page5],
|
||||
["src/pages/gp.ts", _page6],
|
||||
["src/pages/i.ts", _page7],
|
||||
["src/pages/new.astro", _page8],
|
||||
["src/pages/p.ts", _page9],
|
||||
["src/pages/[slug]/edit.astro", _page10],
|
||||
["src/pages/[slug].astro", _page11],
|
||||
["src/pages/index.astro", _page12]
|
||||
["src/pages/api/auth/register.ts", _page1],
|
||||
["src/pages/api/auth/verify.ts", _page2],
|
||||
["src/pages/api/skills/[slug].ts", _page3],
|
||||
["src/pages/api/skills/index.ts", _page4],
|
||||
["src/pages/api/sync/project.ts", _page5],
|
||||
["src/pages/api/sync/index.ts", _page6],
|
||||
["src/pages/gi.ts", _page7],
|
||||
["src/pages/gp.ts", _page8],
|
||||
["src/pages/i.ts", _page9],
|
||||
["src/pages/new.astro", _page10],
|
||||
["src/pages/p.ts", _page11],
|
||||
["src/pages/[slug]/edit.astro", _page12],
|
||||
["src/pages/[slug]/gi.ts", _page13],
|
||||
["src/pages/[slug]/i.ts", _page14],
|
||||
["src/pages/[slug].astro", _page15],
|
||||
["src/pages/index.astro", _page16]
|
||||
]);
|
||||
|
||||
const _manifest = Object.assign(manifest, {
|
||||
|
||||
101
dist/server/manifest_BJPuFUv4.mjs
vendored
101
dist/server/manifest_BJPuFUv4.mjs
vendored
File diff suppressed because one or more lines are too long
101
dist/server/manifest_Bz0Ba_R4.mjs
vendored
Normal file
101
dist/server/manifest_Bz0Ba_R4.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
2
dist/server/pages/_image.astro.mjs
vendored
2
dist/server/pages/_image.astro.mjs
vendored
@@ -1,2 +1,2 @@
|
||||
export { a as page } from '../chunks/node_WXNYuHqd.mjs';
|
||||
export { a as page } from '../chunks/node_HH9e2ntY.mjs';
|
||||
export { renderers } from '../renderers.mjs';
|
||||
|
||||
252
dist/server/pages/_slug_.astro.mjs
vendored
252
dist/server/pages/_slug_.astro.mjs
vendored
@@ -1,45 +1,240 @@
|
||||
import { e as createComponent, k as renderComponent, l as renderScript, r as renderTemplate, h as createAstro, m as maybeRenderHead, g as addAttribute, u as unescapeHTML } from '../chunks/astro/server_B-2LxKLH.mjs';
|
||||
import { e as createAstro, f as createComponent, k as renderComponent, l as renderScript, r as renderTemplate, m as maybeRenderHead, h as addAttribute, u as unescapeHTML } from '../chunks/astro/server_CF97kUu8.mjs';
|
||||
import 'piccolore';
|
||||
import { _ as _export_sfc, $ as $$Base } from '../chunks/_plugin-vue_export-helper_B1lnwsE2.mjs';
|
||||
import { useSSRContext, defineComponent, ref, mergeProps } from 'vue';
|
||||
import { ssrRenderAttrs, ssrInterpolate } from 'vue/server-renderer';
|
||||
import { g as getSkill } from '../chunks/skills_COWfD5oy.mjs';
|
||||
import { _ as _export_sfc, $ as $$Base } from '../chunks/_plugin-vue_export-helper_CEgY73aA.mjs';
|
||||
import { useSSRContext, defineComponent, ref, nextTick, mergeProps } from 'vue';
|
||||
import { ssrRenderTeleport, ssrInterpolate, ssrRenderAttr, ssrIncludeBooleanAttr, ssrRenderAttrs } from 'vue/server-renderer';
|
||||
import { g as getSkill, b as getForksOf } from '../chunks/skills_BacVQUiS.mjs';
|
||||
import { h as hasToken } from '../chunks/tokens_CAzj9Aj8.mjs';
|
||||
import { a as recordDownload, g as getStatsForSlug } from '../chunks/stats_CaDi9y9J.mjs';
|
||||
import { marked } from 'marked';
|
||||
/* empty css */
|
||||
export { renderers } from '../renderers.mjs';
|
||||
|
||||
const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
||||
__name: "EditGate",
|
||||
props: {
|
||||
slug: {},
|
||||
authorEmail: {},
|
||||
authorName: {},
|
||||
authorHasToken: { type: Boolean }
|
||||
},
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
const props = __props;
|
||||
const showModal = ref(false);
|
||||
const token = ref("");
|
||||
const error = ref("");
|
||||
const verifying = ref(false);
|
||||
const tokenInput = ref();
|
||||
async function handleClick() {
|
||||
if (!props.authorEmail || !props.authorHasToken) {
|
||||
window.location.href = `/${props.slug}/edit`;
|
||||
return;
|
||||
}
|
||||
const saved = localStorage.getItem("skillshere-token") || "";
|
||||
if (saved) {
|
||||
try {
|
||||
const res = await fetch("/api/auth/verify", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email: props.authorEmail, token: saved })
|
||||
});
|
||||
if (res.ok) {
|
||||
localStorage.setItem("skillshere-token", saved);
|
||||
window.location.href = `/${props.slug}/edit`;
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
showModal.value = true;
|
||||
error.value = "";
|
||||
token.value = "";
|
||||
nextTick(() => tokenInput.value?.focus());
|
||||
}
|
||||
async function verify() {
|
||||
verifying.value = true;
|
||||
error.value = "";
|
||||
try {
|
||||
const res = await fetch("/api/auth/verify", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email: props.authorEmail, token: token.value })
|
||||
});
|
||||
if (!res.ok) {
|
||||
const data = await res.json();
|
||||
error.value = data.error || "Invalid token";
|
||||
return;
|
||||
}
|
||||
localStorage.setItem("skillshere-token", token.value);
|
||||
window.location.href = `/${props.slug}/edit`;
|
||||
} catch {
|
||||
error.value = "Could not verify token";
|
||||
} finally {
|
||||
verifying.value = false;
|
||||
}
|
||||
}
|
||||
function forkSkill() {
|
||||
showModal.value = false;
|
||||
window.location.href = `/new?from=${encodeURIComponent(props.slug)}`;
|
||||
}
|
||||
const __returned__ = { props, showModal, token, error, verifying, tokenInput, handleClick, verify, forkSkill };
|
||||
Object.defineProperty(__returned__, "__isScriptSetup", { enumerable: false, value: true });
|
||||
return __returned__;
|
||||
}
|
||||
});
|
||||
function _sfc_ssrRender$1(_ctx, _push, _parent, _attrs, $props, $setup, $data, $options) {
|
||||
_push(`<!--[--><button class="inline-flex items-center gap-1.5 rounded-lg border border-white/[0.08] bg-surface-200 px-3.5 py-2 text-sm font-medium text-gray-300 hover:border-white/[0.15] hover:text-white transition-all"><svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"></path></svg> Edit </button>`);
|
||||
ssrRenderTeleport(_push, (_push2) => {
|
||||
if ($setup.showModal) {
|
||||
_push2(`<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm"><div class="w-full max-w-md rounded-2xl border border-white/[0.08] bg-[var(--color-surface-200)] p-6 shadow-2xl"><h3 class="text-lg font-semibold text-white mb-1">Author Verification</h3><p class="text-sm text-gray-500 mb-4"> This skill is owned by <strong class="text-gray-300">${ssrInterpolate($props.authorName || $props.authorEmail)}</strong>. Enter your token to edit. </p><form><input${ssrRenderAttr("value", $setup.token)} type="password" placeholder="Paste your author token..." 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 font-mono focus:border-[var(--color-accent-500)]/50 focus:outline-none focus:ring-1 focus:ring-[var(--color-accent-500)]/20 transition-all">`);
|
||||
if ($setup.error) {
|
||||
_push2(`<p class="mt-2 text-sm text-red-400">${ssrInterpolate($setup.error)}</p>`);
|
||||
} else {
|
||||
_push2(`<!---->`);
|
||||
}
|
||||
_push2(`<div class="mt-4 flex items-center gap-3"><button type="submit"${ssrIncludeBooleanAttr($setup.verifying || !$setup.token) ? " disabled" : ""} class="inline-flex items-center gap-2 rounded-xl bg-[var(--color-accent-500)] px-5 py-2 text-sm font-semibold text-white shadow-lg shadow-[var(--color-accent-500)]/20 hover:bg-[var(--color-accent-600)] disabled:opacity-50 active:scale-[0.97] transition-all">`);
|
||||
if ($setup.verifying) {
|
||||
_push2(`<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 {
|
||||
_push2(`<!---->`);
|
||||
}
|
||||
_push2(` ${ssrInterpolate($setup.verifying ? "Verifying..." : "Continue to Edit")}</button><button type="button" class="text-sm text-[var(--color-accent-400)] hover:text-[var(--color-accent-300)] transition-colors"> Fork instead </button><button type="button" class="ml-auto text-sm text-gray-600 hover:text-gray-300 transition-colors"> Cancel </button></div></form></div></div>`);
|
||||
} else {
|
||||
_push2(`<!---->`);
|
||||
}
|
||||
}, "body", false, _parent);
|
||||
_push(`<!--]-->`);
|
||||
}
|
||||
const _sfc_setup$1 = _sfc_main$1.setup;
|
||||
_sfc_main$1.setup = (props, ctx) => {
|
||||
const ssrContext = useSSRContext();
|
||||
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/components/EditGate.vue");
|
||||
return _sfc_setup$1 ? _sfc_setup$1(props, ctx) : void 0;
|
||||
};
|
||||
const EditGate = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["ssrRender", _sfc_ssrRender$1]]);
|
||||
|
||||
const _sfc_main = /* @__PURE__ */ defineComponent({
|
||||
__name: "DeleteButton",
|
||||
props: {
|
||||
slug: {}
|
||||
slug: {},
|
||||
authorEmail: {},
|
||||
authorName: {},
|
||||
authorHasToken: { type: Boolean }
|
||||
},
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
const props = __props;
|
||||
const deleting = ref(false);
|
||||
async function handleDelete() {
|
||||
if (!confirm(`Delete "${props.slug}"? This cannot be undone.`)) return;
|
||||
const showModal = ref(false);
|
||||
const token = ref("");
|
||||
const error = ref("");
|
||||
const tokenInput = ref();
|
||||
async function handleClick() {
|
||||
if (props.authorEmail && props.authorHasToken) {
|
||||
const saved = localStorage.getItem("skillshere-token") || "";
|
||||
if (saved) {
|
||||
try {
|
||||
const res = await fetch("/api/auth/verify", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email: props.authorEmail, token: saved })
|
||||
});
|
||||
if (res.ok) {
|
||||
doDelete(saved);
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
showModal.value = true;
|
||||
error.value = "";
|
||||
token.value = "";
|
||||
nextTick(() => tokenInput.value?.focus());
|
||||
} else {
|
||||
doDelete("");
|
||||
}
|
||||
}
|
||||
async function verifyAndDelete() {
|
||||
error.value = "";
|
||||
deleting.value = true;
|
||||
try {
|
||||
const res = await fetch(`/api/skills/${props.slug}`, { method: "DELETE" });
|
||||
const verifyRes = await fetch("/api/auth/verify", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email: props.authorEmail, token: token.value })
|
||||
});
|
||||
if (!verifyRes.ok) {
|
||||
const data = await verifyRes.json();
|
||||
error.value = data.error || "Invalid token";
|
||||
deleting.value = false;
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
error.value = "Could not verify token";
|
||||
deleting.value = false;
|
||||
return;
|
||||
}
|
||||
localStorage.setItem("skillshere-token", token.value);
|
||||
doDelete(token.value);
|
||||
}
|
||||
async function doDelete(authToken) {
|
||||
if (!confirm(`Delete "${props.slug}"? This cannot be undone.`)) {
|
||||
deleting.value = false;
|
||||
return;
|
||||
}
|
||||
deleting.value = true;
|
||||
error.value = "";
|
||||
try {
|
||||
const headers = {};
|
||||
if (authToken) {
|
||||
headers["Authorization"] = `Bearer ${authToken}`;
|
||||
}
|
||||
const res = await fetch(`/api/skills/${props.slug}`, { method: "DELETE", headers });
|
||||
if (res.status === 403) {
|
||||
const data = await res.json();
|
||||
error.value = data.error || "Permission denied";
|
||||
showModal.value = true;
|
||||
deleting.value = false;
|
||||
return;
|
||||
}
|
||||
if (!res.ok && res.status !== 204) {
|
||||
throw new Error("Failed to delete");
|
||||
const data = await res.json().catch(() => ({ error: "Failed to delete" }));
|
||||
throw new Error(data.error || "Failed to delete");
|
||||
}
|
||||
window.location.href = "/";
|
||||
} catch {
|
||||
alert("Failed to delete skill.");
|
||||
} catch (err) {
|
||||
error.value = err instanceof Error ? err.message : "Failed to delete skill.";
|
||||
deleting.value = false;
|
||||
}
|
||||
}
|
||||
const __returned__ = { props, deleting, handleDelete };
|
||||
const __returned__ = { props, deleting, showModal, token, error, tokenInput, handleClick, verifyAndDelete, doDelete };
|
||||
Object.defineProperty(__returned__, "__isScriptSetup", { enumerable: false, value: true });
|
||||
return __returned__;
|
||||
}
|
||||
});
|
||||
function _sfc_ssrRender(_ctx, _push, _parent, _attrs, $props, $setup, $data, $options) {
|
||||
_push(`<button${ssrRenderAttrs(mergeProps({
|
||||
disabled: $setup.deleting,
|
||||
class: "inline-flex items-center gap-1.5 rounded-lg border border-red-500/20 bg-red-500/5 px-3.5 py-2 text-sm font-medium text-red-400 hover:bg-red-500/10 hover:border-red-500/30 disabled:opacity-50 active:scale-[0.97] transition-all"
|
||||
}, _attrs))}><svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"></path></svg> ${ssrInterpolate($setup.deleting ? "Deleting..." : "Delete")}</button>`);
|
||||
_push(`<div${ssrRenderAttrs(mergeProps({ class: "inline-flex" }, _attrs))}><button${ssrIncludeBooleanAttr($setup.deleting) ? " disabled" : ""} class="inline-flex items-center gap-1.5 rounded-lg border border-red-500/20 bg-red-500/5 px-3.5 py-2 text-sm font-medium text-red-400 hover:bg-red-500/10 hover:border-red-500/30 disabled:opacity-50 active:scale-[0.97] transition-all"><svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"></path></svg> ${ssrInterpolate($setup.deleting ? "Deleting..." : "Delete")}</button>`);
|
||||
ssrRenderTeleport(_push, (_push2) => {
|
||||
if ($setup.showModal) {
|
||||
_push2(`<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm"><div class="w-full max-w-md rounded-2xl border border-white/[0.08] bg-[var(--color-surface-200)] p-6 shadow-2xl"><h3 class="text-lg font-semibold text-red-400 mb-1">Delete Skill</h3><p class="text-sm text-gray-500 mb-4"> This skill is owned by <strong class="text-gray-300">${ssrInterpolate($props.authorName || $props.authorEmail)}</strong>. Enter your token to delete it. </p><form><input${ssrRenderAttr("value", $setup.token)} type="password" placeholder="Paste your author token..." 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 font-mono focus:border-red-500/50 focus:outline-none focus:ring-1 focus:ring-red-500/20 transition-all">`);
|
||||
if ($setup.error) {
|
||||
_push2(`<p class="mt-2 text-sm text-red-400">${ssrInterpolate($setup.error)}</p>`);
|
||||
} else {
|
||||
_push2(`<!---->`);
|
||||
}
|
||||
_push2(`<div class="mt-4 flex items-center gap-3"><button type="submit"${ssrIncludeBooleanAttr($setup.deleting || !$setup.token) ? " disabled" : ""} class="inline-flex items-center gap-2 rounded-xl bg-red-500 px-5 py-2 text-sm font-semibold text-white hover:bg-red-600 disabled:opacity-50 active:scale-[0.97] transition-all">`);
|
||||
if ($setup.deleting) {
|
||||
_push2(`<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 {
|
||||
_push2(`<!---->`);
|
||||
}
|
||||
_push2(` ${ssrInterpolate($setup.deleting ? "Deleting..." : "Delete Permanently")}</button><button type="button" class="ml-auto text-sm text-gray-600 hover:text-gray-300 transition-colors"> Cancel </button></div></form></div></div>`);
|
||||
} else {
|
||||
_push2(`<!---->`);
|
||||
}
|
||||
}, "body", false, _parent);
|
||||
_push(`</div>`);
|
||||
}
|
||||
const _sfc_setup = _sfc_main.setup;
|
||||
_sfc_main.setup = (props, ctx) => {
|
||||
@@ -49,7 +244,7 @@ _sfc_main.setup = (props, ctx) => {
|
||||
};
|
||||
const DeleteButton = /* @__PURE__ */ _export_sfc(_sfc_main, [["ssrRender", _sfc_ssrRender]]);
|
||||
|
||||
const $$Astro = createAstro();
|
||||
const $$Astro = createAstro("https://skills.here.run.place");
|
||||
const $$slug = createComponent(async ($$result, $$props, $$slots) => {
|
||||
const Astro2 = $$result.createAstro($$Astro, $$props, $$slots);
|
||||
Astro2.self = $$slug;
|
||||
@@ -60,19 +255,26 @@ const $$slug = createComponent(async ($$result, $$props, $$slots) => {
|
||||
}
|
||||
const accept = Astro2.request.headers.get("accept") || "";
|
||||
if (!accept.includes("text/html")) {
|
||||
recordDownload(slug);
|
||||
return new Response(skill.raw, {
|
||||
headers: { "Content-Type": "text/markdown; charset=utf-8" }
|
||||
});
|
||||
}
|
||||
const authorHasToken = skill["author-email"] ? await hasToken(skill["author-email"]) : false;
|
||||
const forks = await getForksOf(slug);
|
||||
const stats = await getStatsForSlug(slug);
|
||||
const html = await marked(skill.content);
|
||||
const installCmd = `curl -fsSL ${Astro2.url.origin}/${slug} -o .claude/skills/${slug}.md`;
|
||||
return renderTemplate`${renderComponent($$result, "Base", $$Base, { "title": `${skill.name} \u2014 Skillit` }, { "default": async ($$result2) => renderTemplate` ${maybeRenderHead()}<div class="mb-8"> <a href="/" class="inline-flex items-center gap-1 text-sm text-gray-600 hover:text-gray-300 transition-colors mb-4"> <svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5"></path> </svg>
|
||||
const origin = Astro2.url.origin;
|
||||
const cmds = {
|
||||
unix: `curl -fsSL ${origin}/${slug}/i | bash`,
|
||||
unixGlobal: `curl -fsSL ${origin}/${slug}/gi | bash`,
|
||||
win: `irm ${origin}/${slug}/i | iex`,
|
||||
winGlobal: `irm ${origin}/${slug}/gi | iex`
|
||||
};
|
||||
return renderTemplate`${renderComponent($$result, "Base", $$Base, { "title": `${skill.name} \u2014 Skills Here`, "data-astro-cid-yvbahnfj": true }, { "default": async ($$result2) => renderTemplate` ${maybeRenderHead()}<a href="/" class="inline-flex items-center gap-1 text-sm text-gray-600 hover:text-gray-300 transition-colors mb-4" data-astro-cid-yvbahnfj> <svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" data-astro-cid-yvbahnfj> <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" data-astro-cid-yvbahnfj></path> </svg>
|
||||
Back
|
||||
</a> <div class="flex items-start justify-between gap-4"> <div> <h1 class="text-2xl font-bold tracking-tight text-white">${skill.name}</h1> ${skill.description && renderTemplate`<p class="text-gray-500 mt-1.5 leading-relaxed">${skill.description}</p>`} </div> <div class="flex items-center gap-2 shrink-0"> <a${addAttribute(`/${slug}/edit`, "href")} class="inline-flex items-center gap-1.5 rounded-lg border border-white/[0.08] bg-surface-200 px-3.5 py-2 text-sm font-medium text-gray-300 hover:border-white/[0.15] hover:text-white transition-all"> <svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"></path> </svg>
|
||||
Edit
|
||||
</a> ${renderComponent($$result2, "DeleteButton", DeleteButton, { "slug": slug, "client:load": true, "client:component-hydration": "load", "client:component-path": "/Users/alex/projects/skillit/src/components/DeleteButton.vue", "client:component-export": "default" })} </div> </div> </div> ${skill["allowed-tools"].length > 0 && renderTemplate`<div class="flex flex-wrap gap-1.5 mb-8"> ${skill["allowed-tools"].map((tool) => renderTemplate`<span class="rounded-md bg-white/[0.04] border border-white/[0.06] px-2.5 py-1 text-xs font-medium text-gray-400"> ${tool} </span>`)} </div>`} <div class="rounded-2xl border border-white/[0.06] bg-surface-100 p-6 mb-8 max-w-2xl space-y-4"> <h2 class="text-sm font-semibold text-white">Install this skill</h2> <p class="text-xs text-gray-500 leading-relaxed">Run this in your project root. The skill file will be saved to <code class="text-gray-400 font-mono bg-white/[0.04] px-1 py-0.5 rounded">.claude/skills/${slug}.md</code> and Claude Code will load it automatically.</p> <div class="flex items-center gap-3 rounded-xl bg-surface-50 border border-white/[0.06] px-4 py-3"> <code class="flex-1 text-xs font-mono text-gray-500 select-all truncate">${installCmd}</code> <button id="copy-install" class="shrink-0 rounded-md bg-white/[0.06] border border-white/[0.06] px-2.5 py-1 text-xs font-medium text-gray-400 hover:text-white hover:bg-white/[0.1] transition-all">
|
||||
Copy
|
||||
</button> </div> ${skill["allowed-tools"].length > 0 && renderTemplate`<p class="text-xs text-gray-600 leading-relaxed">This skill uses: ${skill["allowed-tools"].join(", ")}. Claude will have access to these tools when this skill is active.</p>`} </div> <article class="skill-prose rounded-2xl border border-white/[0.06] bg-surface-100 p-8">${unescapeHTML(html)}</article> ` })} ${renderScript($$result, "/Users/alex/projects/skillit/src/pages/[slug].astro?astro&type=script&index=0&lang.ts")}`;
|
||||
</a> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-bottom: 2rem;" data-astro-cid-yvbahnfj> <div class="rounded-2xl border border-white/[0.06] bg-surface-100 p-6" style="min-width: 0;" data-astro-cid-yvbahnfj> <h1 class="text-2xl font-bold tracking-tight text-white mb-1" data-astro-cid-yvbahnfj>${skill.name}</h1> ${skill.description && renderTemplate`<p class="text-gray-500 leading-relaxed mb-3" data-astro-cid-yvbahnfj>${skill.description}</p>`} ${skill["allowed-tools"].length > 0 && renderTemplate`<div class="flex flex-wrap gap-1.5" data-astro-cid-yvbahnfj> ${skill["allowed-tools"].map((tool) => renderTemplate`<span class="rounded-md bg-white/[0.04] border border-white/[0.06] px-2.5 py-1 text-xs font-medium text-gray-400" data-astro-cid-yvbahnfj> ${tool} </span>`)} </div>`} ${skill.tags.length > 0 && renderTemplate`<div class="flex flex-wrap gap-1.5 mt-3" data-astro-cid-yvbahnfj> ${skill.tags.map((tag) => renderTemplate`<span class="rounded-full bg-[var(--color-accent-500)]/10 px-2.5 py-0.5 text-xs font-medium text-[var(--color-accent-400)]" data-astro-cid-yvbahnfj> ${tag} </span>`)} </div>`} ${skill.author && renderTemplate`<p class="text-xs text-gray-600 mt-3" data-astro-cid-yvbahnfj>by ${skill.author}</p>`} ${skill["fork-of"] && renderTemplate`<p class="text-xs text-gray-600 mt-1" data-astro-cid-yvbahnfj>forked from <a${addAttribute(`/${skill["fork-of"]}`, "href")} class="text-[var(--color-accent-500)] hover:text-[var(--color-accent-400)] transition-colors" data-astro-cid-yvbahnfj>${skill["fork-of"]}</a></p>`} ${forks.length > 0 && renderTemplate`<details class="mt-3" data-astro-cid-yvbahnfj> <summary class="text-xs text-gray-500 cursor-pointer hover:text-gray-300 transition-colors select-none" data-astro-cid-yvbahnfj> ${forks.length} fork${forks.length !== 1 ? "s" : ""} </summary> <ul class="mt-1.5 space-y-1 pl-3 border-l border-white/[0.06]" data-astro-cid-yvbahnfj> ${forks.map((f) => renderTemplate`<li data-astro-cid-yvbahnfj> <a${addAttribute(`/${f.slug}`, "href")} class="text-xs text-[var(--color-accent-500)] hover:text-[var(--color-accent-400)] transition-colors" data-astro-cid-yvbahnfj> ${f.name} ${f.author && renderTemplate`<span class="text-gray-600" data-astro-cid-yvbahnfj> by ${f.author}</span>`} </a> </li>`)} </ul> </details>`} <div class="flex flex-wrap items-center gap-4 mt-4 pt-3 border-t border-white/[0.06]" data-astro-cid-yvbahnfj> <span class="inline-flex items-center gap-1.5 text-xs text-gray-500" data-astro-cid-yvbahnfj> <svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" data-astro-cid-yvbahnfj> <path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" data-astro-cid-yvbahnfj></path> </svg> ${stats.downloads} download${stats.downloads !== 1 ? "s" : ""} </span> <span class="inline-flex items-center gap-1.5 text-xs text-gray-500" data-astro-cid-yvbahnfj> <svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" data-astro-cid-yvbahnfj> <path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5m-13.5-9L12 3m0 0 4.5 4.5M12 3v13.5" data-astro-cid-yvbahnfj></path> </svg> ${stats.pushes} push${stats.pushes !== 1 ? "es" : ""} </span> ${stats.lastPushedAt && renderTemplate`<span class="text-xs text-gray-600" data-astro-cid-yvbahnfj>
|
||||
Last updated ${new Date(stats.lastPushedAt).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })} </span>`} </div> </div> <div class="rounded-2xl border border-white/[0.06] bg-surface-100 p-6 space-y-4" style="min-width: 0;" data-astro-cid-yvbahnfj> <div class="flex items-center justify-between" data-astro-cid-yvbahnfj> <h2 class="text-sm font-semibold text-white" data-astro-cid-yvbahnfj>Install this skill</h2> <div class="flex rounded-lg border border-white/[0.06] overflow-hidden" id="os-tabs" data-astro-cid-yvbahnfj> <button data-os="unix" class="os-tab px-2.5 py-1 text-[11px] font-medium transition-all" data-astro-cid-yvbahnfj>macOS / Linux</button> <button data-os="win" class="os-tab px-2.5 py-1 text-[11px] font-medium transition-all" data-astro-cid-yvbahnfj>Windows</button> </div> </div> <p class="text-xs text-gray-500 leading-relaxed" data-astro-cid-yvbahnfj>Run in your project root to add this skill.</p> <div class="flex items-center gap-3 rounded-xl bg-surface-50 border border-white/[0.06] px-4 py-3" data-astro-cid-yvbahnfj> <code data-cmd="unix" class="flex-1 text-xs font-mono text-gray-500 select-all truncate" data-astro-cid-yvbahnfj>${cmds.unix}</code> <code data-cmd="win" class="flex-1 text-xs font-mono text-gray-500 select-all truncate hidden" data-astro-cid-yvbahnfj>${cmds.win}</code> <button data-copy class="shrink-0 rounded-md bg-white/[0.06] border border-white/[0.06] px-2.5 py-1 text-xs font-medium text-gray-400 hover:text-white hover:bg-white/[0.1] transition-all" data-astro-cid-yvbahnfj>Copy</button> </div> <details class="group" data-astro-cid-yvbahnfj> <summary class="text-xs text-gray-600 cursor-pointer hover:text-gray-400 transition-colors" data-astro-cid-yvbahnfj>More options</summary> <div class="mt-3 space-y-3 text-xs text-gray-500" data-astro-cid-yvbahnfj> <div data-astro-cid-yvbahnfj> <p class="mb-1.5" data-astro-cid-yvbahnfj>Install globally (available in all projects):</p> <div class="flex items-center gap-3 rounded-lg bg-surface-50 border border-white/[0.06] px-3 py-2" data-astro-cid-yvbahnfj> <code data-cmd="unix" class="flex-1 font-mono text-gray-500 select-all truncate" data-astro-cid-yvbahnfj>${cmds.unixGlobal}</code> <code data-cmd="win" class="flex-1 font-mono text-gray-500 select-all truncate hidden" data-astro-cid-yvbahnfj>${cmds.winGlobal}</code> <button data-copy class="shrink-0 rounded bg-white/[0.06] border border-white/[0.06] px-2 py-0.5 font-medium text-gray-500 hover:text-white hover:bg-white/[0.1] transition-all" data-astro-cid-yvbahnfj>Copy</button> </div> </div> </div> </details> </div> </div> <div class="relative rounded-2xl border border-white/[0.06] bg-surface-100 p-8" data-astro-cid-yvbahnfj> <div class="absolute top-4 right-4 flex items-center gap-2" data-astro-cid-yvbahnfj> ${renderComponent($$result2, "EditGate", EditGate, { "slug": slug, "authorEmail": skill["author-email"], "authorName": skill.author, "authorHasToken": authorHasToken, "client:load": true, "client:component-hydration": "load", "client:component-path": "/Users/alex/projects/skillit/src/components/EditGate.vue", "client:component-export": "default", "data-astro-cid-yvbahnfj": true })} ${renderComponent($$result2, "DeleteButton", DeleteButton, { "slug": slug, "authorEmail": skill["author-email"], "authorName": skill.author, "authorHasToken": authorHasToken, "client:load": true, "client:component-hydration": "load", "client:component-path": "/Users/alex/projects/skillit/src/components/DeleteButton.vue", "client:component-export": "default", "data-astro-cid-yvbahnfj": true })} </div> <article class="skill-prose" data-astro-cid-yvbahnfj>${unescapeHTML(html)}</article> </div> ` })} ${renderScript($$result, "/Users/alex/projects/skillit/src/pages/[slug].astro?astro&type=script&index=0&lang.ts")}`;
|
||||
}, "/Users/alex/projects/skillit/src/pages/[slug].astro", void 0);
|
||||
|
||||
const $$file = "/Users/alex/projects/skillit/src/pages/[slug].astro";
|
||||
|
||||
15
dist/server/pages/_slug_/edit.astro.mjs
vendored
15
dist/server/pages/_slug_/edit.astro.mjs
vendored
@@ -1,11 +1,11 @@
|
||||
import { e as createComponent, k as renderComponent, r as renderTemplate, h as createAstro, m as maybeRenderHead, g as addAttribute } from '../../chunks/astro/server_B-2LxKLH.mjs';
|
||||
import { e as createAstro, f as createComponent, k as renderComponent, r as renderTemplate, m as maybeRenderHead, h as addAttribute } from '../../chunks/astro/server_CF97kUu8.mjs';
|
||||
import 'piccolore';
|
||||
import { $ as $$Base } from '../../chunks/_plugin-vue_export-helper_B1lnwsE2.mjs';
|
||||
import { g as getAvailableTools, a as getAvailableModels, S as SkillEditor } from '../../chunks/models_DPfuEi7q.mjs';
|
||||
import { g as getSkill } from '../../chunks/skills_COWfD5oy.mjs';
|
||||
import { $ as $$Base } from '../../chunks/_plugin-vue_export-helper_CEgY73aA.mjs';
|
||||
import { g as getAvailableTools, a as getAvailableModels, S as SkillEditor } from '../../chunks/models_BK7lP4G3.mjs';
|
||||
import { g as getSkill, a as getAllTags } from '../../chunks/skills_BacVQUiS.mjs';
|
||||
export { renderers } from '../../renderers.mjs';
|
||||
|
||||
const $$Astro = createAstro();
|
||||
const $$Astro = createAstro("https://skills.here.run.place");
|
||||
const $$Edit = createComponent(async ($$result, $$props, $$slots) => {
|
||||
const Astro2 = $$result.createAstro($$Astro, $$props, $$slots);
|
||||
Astro2.self = $$Edit;
|
||||
@@ -18,8 +18,9 @@ const $$Edit = createComponent(async ($$result, $$props, $$slots) => {
|
||||
const availableModels = await getAvailableModels();
|
||||
const allowedTools = skill["allowed-tools"].join(", ");
|
||||
const hooksJson = skill.hooks ? JSON.stringify(skill.hooks, null, 2) : "";
|
||||
return renderTemplate`${renderComponent($$result, "Base", $$Base, { "title": `Edit ${skill.name} \u2014 Skillit` }, { "default": async ($$result2) => renderTemplate` ${maybeRenderHead()}<a${addAttribute(`/${slug}`, "href")} class="inline-flex items-center gap-1 text-sm text-gray-600 hover:text-gray-300 transition-colors mb-4"> <svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5"></path> </svg>
|
||||
Back to ${skill.name} </a> <h1 class="text-2xl font-bold tracking-tight text-white mb-2">Edit Skill</h1> <p class="text-sm text-gray-500 mb-8">Editing <strong class="text-gray-400">${skill.name}</strong>. Users who already installed this skill will get the updated version on their next sync.</p> ${renderComponent($$result2, "SkillEditor", SkillEditor, { "mode": "edit", "slug": slug, "initialName": skill.name, "initialDescription": skill.description, "initialAllowedTools": allowedTools, "initialArgumentHint": skill["argument-hint"], "initialModel": skill.model, "initialUserInvocable": skill["user-invocable"], "initialDisableModelInvocation": skill["disable-model-invocation"], "initialContext": skill.context, "initialAgent": skill.agent, "initialHooks": hooksJson, "initialBody": skill.content, ":availableTools": availableTools, ":availableModels": availableModels, "client:load": true, "client:component-hydration": "load", "client:component-path": "/Users/alex/projects/skillit/src/components/SkillEditor.vue", "client:component-export": "default" })} ` })}`;
|
||||
const availableTags = await getAllTags();
|
||||
return renderTemplate`${renderComponent($$result, "Base", $$Base, { "title": `Edit ${skill.name} \u2014 Skills Here` }, { "default": async ($$result2) => renderTemplate` ${maybeRenderHead()}<a${addAttribute(`/${slug}`, "href")} class="inline-flex items-center gap-1 text-sm text-gray-600 hover:text-gray-300 transition-colors mb-4"> <svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5"></path> </svg>
|
||||
Back to ${skill.name} </a> <h1 class="text-2xl font-bold tracking-tight text-white mb-2">Edit Skill</h1> <p class="text-sm text-gray-500 mb-8">Editing <strong class="text-gray-400">${skill.name}</strong>. Users who already installed this skill will get the updated version on their next sync.</p> ${renderComponent($$result2, "SkillEditor", SkillEditor, { "mode": "edit", "slug": slug, "initialName": skill.name, "initialDescription": skill.description, "initialAllowedTools": allowedTools, "initialArgumentHint": skill["argument-hint"], "initialModel": skill.model, "initialUserInvocable": skill["user-invocable"], "initialDisableModelInvocation": skill["disable-model-invocation"], "initialContext": skill.context, "initialAgent": skill.agent, "initialHooks": hooksJson, "initialBody": skill.content, "initialAuthor": skill.author, "initialAuthorEmail": skill["author-email"], "initialTags": skill.tags.join(", "), ":availableTools": availableTools, ":availableModels": availableModels, "availableTags": availableTags.join(","), "client:load": true, "client:component-hydration": "load", "client:component-path": "/Users/alex/projects/skillit/src/components/SkillEditor.vue", "client:component-export": "default" })} ` })}`;
|
||||
}, "/Users/alex/projects/skillit/src/pages/[slug]/edit.astro", void 0);
|
||||
|
||||
const $$file = "/Users/alex/projects/skillit/src/pages/[slug]/edit.astro";
|
||||
|
||||
40
dist/server/pages/_slug_/gi.astro.mjs
vendored
Normal file
40
dist/server/pages/_slug_/gi.astro.mjs
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
import { g as getSkill } from '../../chunks/skills_BacVQUiS.mjs';
|
||||
import { i as isPowerShell } from '../../chunks/sync_BEq_wzpT.mjs';
|
||||
export { renderers } from '../../renderers.mjs';
|
||||
|
||||
const GET = async ({ params, url, request }) => {
|
||||
const { slug } = params;
|
||||
const skill = await getSkill(slug);
|
||||
if (!skill) {
|
||||
return new Response("Skill not found", { status: 404 });
|
||||
}
|
||||
const origin = url.origin;
|
||||
const ps = isPowerShell(request);
|
||||
const script = ps ? [
|
||||
'$ErrorActionPreference = "Stop"',
|
||||
'$Dir = Join-Path $HOME ".claude\\skills"',
|
||||
"New-Item -ItemType Directory -Force -Path $Dir | Out-Null",
|
||||
`Invoke-WebRequest -Uri "${origin}/${slug}" -OutFile (Join-Path $Dir "${slug}.md")`,
|
||||
`Write-Host "✓ Installed ${skill.name} globally to $Dir\\${slug}.md"`,
|
||||
""
|
||||
].join("\n") : [
|
||||
"#!/usr/bin/env bash",
|
||||
"set -euo pipefail",
|
||||
"mkdir -p ~/.claude/skills",
|
||||
`curl -fsSL "${origin}/${slug}" -o ~/.claude/skills/${slug}.md`,
|
||||
`echo "✓ Installed ${skill.name} globally to ~/.claude/skills/${slug}.md"`,
|
||||
""
|
||||
].join("\n");
|
||||
return new Response(script, {
|
||||
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
||||
});
|
||||
};
|
||||
|
||||
const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
||||
__proto__: null,
|
||||
GET
|
||||
}, Symbol.toStringTag, { value: 'Module' }));
|
||||
|
||||
const page = () => _page;
|
||||
|
||||
export { page };
|
||||
40
dist/server/pages/_slug_/i.astro.mjs
vendored
Normal file
40
dist/server/pages/_slug_/i.astro.mjs
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
import { g as getSkill } from '../../chunks/skills_BacVQUiS.mjs';
|
||||
import { i as isPowerShell } from '../../chunks/sync_BEq_wzpT.mjs';
|
||||
export { renderers } from '../../renderers.mjs';
|
||||
|
||||
const GET = async ({ params, url, request }) => {
|
||||
const { slug } = params;
|
||||
const skill = await getSkill(slug);
|
||||
if (!skill) {
|
||||
return new Response("Skill not found", { status: 404 });
|
||||
}
|
||||
const origin = url.origin;
|
||||
const ps = isPowerShell(request);
|
||||
const script = ps ? [
|
||||
'$ErrorActionPreference = "Stop"',
|
||||
'$Dir = ".claude\\skills"',
|
||||
"New-Item -ItemType Directory -Force -Path $Dir | Out-Null",
|
||||
`Invoke-WebRequest -Uri "${origin}/${slug}" -OutFile (Join-Path $Dir "${slug}.md")`,
|
||||
`Write-Host "✓ Installed ${skill.name} to $Dir\\${slug}.md"`,
|
||||
""
|
||||
].join("\n") : [
|
||||
"#!/usr/bin/env bash",
|
||||
"set -euo pipefail",
|
||||
"mkdir -p .claude/skills",
|
||||
`curl -fsSL "${origin}/${slug}" -o ".claude/skills/${slug}.md"`,
|
||||
`echo "✓ Installed ${skill.name} to .claude/skills/${slug}.md"`,
|
||||
""
|
||||
].join("\n");
|
||||
return new Response(script, {
|
||||
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
||||
});
|
||||
};
|
||||
|
||||
const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
||||
__proto__: null,
|
||||
GET
|
||||
}, Symbol.toStringTag, { value: 'Module' }));
|
||||
|
||||
const page = () => _page;
|
||||
|
||||
export { page };
|
||||
55
dist/server/pages/api/auth/register.astro.mjs
vendored
Normal file
55
dist/server/pages/api/auth/register.astro.mjs
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
import { h as hasToken, g as generateToken } from '../../../chunks/tokens_CAzj9Aj8.mjs';
|
||||
export { renderers } from '../../../renderers.mjs';
|
||||
|
||||
const POST = async ({ request }) => {
|
||||
let body;
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch {
|
||||
return new Response(JSON.stringify({ error: "Invalid JSON" }), {
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
const { email, name } = body;
|
||||
if (!email) {
|
||||
return new Response(JSON.stringify({ error: "email is required" }), {
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
if (await hasToken(email)) {
|
||||
return new Response(JSON.stringify({ error: "Email already registered" }), {
|
||||
status: 409,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
try {
|
||||
const token = await generateToken(email, name || "");
|
||||
return new Response(JSON.stringify({ token }), {
|
||||
status: 201,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : "Unknown error";
|
||||
if (message.includes("already registered")) {
|
||||
return new Response(JSON.stringify({ error: message }), {
|
||||
status: 409,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
return new Response(JSON.stringify({ error: message }), {
|
||||
status: 500,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
||||
__proto__: null,
|
||||
POST
|
||||
}, Symbol.toStringTag, { value: 'Module' }));
|
||||
|
||||
const page = () => _page;
|
||||
|
||||
export { page };
|
||||
40
dist/server/pages/api/auth/verify.astro.mjs
vendored
Normal file
40
dist/server/pages/api/auth/verify.astro.mjs
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
import { v as verifyToken } from '../../../chunks/tokens_CAzj9Aj8.mjs';
|
||||
export { renderers } from '../../../renderers.mjs';
|
||||
|
||||
const POST = async ({ request }) => {
|
||||
let body;
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch {
|
||||
return new Response(JSON.stringify({ error: "Invalid JSON" }), {
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
const { email, token } = body;
|
||||
if (!email || !token) {
|
||||
return new Response(JSON.stringify({ error: "email and token are required" }), {
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
const valid = await verifyToken(email, token);
|
||||
if (!valid) {
|
||||
return new Response(JSON.stringify({ error: "Invalid token" }), {
|
||||
status: 403,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
return new Response(JSON.stringify({ ok: true }), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
};
|
||||
|
||||
const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
||||
__proto__: null,
|
||||
POST
|
||||
}, Symbol.toStringTag, { value: 'Module' }));
|
||||
|
||||
const page = () => _page;
|
||||
|
||||
export { page };
|
||||
18
dist/server/pages/api/skills.astro.mjs
vendored
18
dist/server/pages/api/skills.astro.mjs
vendored
@@ -1,4 +1,7 @@
|
||||
import { l as listSkills, i as isValidSlug, c as createSkill } from '../../chunks/skills_COWfD5oy.mjs';
|
||||
import matter from 'gray-matter';
|
||||
import { l as listSkills, i as isValidSlug, c as createSkill } from '../../chunks/skills_BacVQUiS.mjs';
|
||||
import { h as hasToken, e as extractBearerToken, v as verifyToken } from '../../chunks/tokens_CAzj9Aj8.mjs';
|
||||
import { r as recordPush } from '../../chunks/stats_CaDi9y9J.mjs';
|
||||
export { renderers } from '../../renderers.mjs';
|
||||
|
||||
const GET = async () => {
|
||||
@@ -30,8 +33,21 @@ const POST = async ({ request }) => {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
const parsed = matter(content);
|
||||
const authorEmail = parsed.data["author-email"] || "";
|
||||
if (authorEmail && await hasToken(authorEmail)) {
|
||||
const token = extractBearerToken(request);
|
||||
const valid = await verifyToken(authorEmail, token);
|
||||
if (!valid) {
|
||||
return new Response(JSON.stringify({ error: "Valid token required to create a skill with author-email. Register first via POST /api/auth/register." }), {
|
||||
status: 403,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
}
|
||||
try {
|
||||
const skill = await createSkill(slug, content);
|
||||
recordPush(slug);
|
||||
return new Response(JSON.stringify(skill), {
|
||||
status: 201,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
|
||||
54
dist/server/pages/api/skills/_slug_.astro.mjs
vendored
54
dist/server/pages/api/skills/_slug_.astro.mjs
vendored
@@ -1,4 +1,7 @@
|
||||
import { d as deleteSkill, g as getSkill, u as updateSkill } from '../../../chunks/skills_COWfD5oy.mjs';
|
||||
import 'gray-matter';
|
||||
import { g as getSkill, d as deleteSkill, u as updateSkill } from '../../../chunks/skills_BacVQUiS.mjs';
|
||||
import { h as hasToken, e as extractBearerToken, v as verifyToken } from '../../../chunks/tokens_CAzj9Aj8.mjs';
|
||||
import { r as recordPush } from '../../../chunks/stats_CaDi9y9J.mjs';
|
||||
export { renderers } from '../../../renderers.mjs';
|
||||
|
||||
const GET = async ({ params }) => {
|
||||
@@ -27,36 +30,59 @@ const PUT = async ({ params, request }) => {
|
||||
});
|
||||
}
|
||||
try {
|
||||
const existing = await getSkill(params.slug);
|
||||
if (!existing) {
|
||||
return new Response(JSON.stringify({ error: "Skill not found" }), {
|
||||
status: 404,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
if (existing["author-email"] && await hasToken(existing["author-email"])) {
|
||||
const token = extractBearerToken(request);
|
||||
const valid = await verifyToken(existing["author-email"], token);
|
||||
if (!valid) {
|
||||
return new Response(JSON.stringify({ error: `Only ${existing.author || existing["author-email"]} can update this skill. Provide a valid token via Authorization: Bearer header.` }), {
|
||||
status: 403,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
}
|
||||
const skill = await updateSkill(params.slug, body.content);
|
||||
recordPush(params.slug);
|
||||
return new Response(JSON.stringify(skill), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : "Unknown error";
|
||||
if (message.includes("not found")) {
|
||||
return new Response(JSON.stringify({ error: message }), {
|
||||
status: 404,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
return new Response(JSON.stringify({ error: message }), {
|
||||
status: 500,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
};
|
||||
const DELETE = async ({ params }) => {
|
||||
const DELETE = async ({ params, request }) => {
|
||||
try {
|
||||
await deleteSkill(params.slug);
|
||||
return new Response(null, { status: 204 });
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : "Unknown error";
|
||||
if (message.includes("not found")) {
|
||||
return new Response(JSON.stringify({ error: message }), {
|
||||
const existing = await getSkill(params.slug);
|
||||
if (!existing) {
|
||||
return new Response(JSON.stringify({ error: "Skill not found" }), {
|
||||
status: 404,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
if (existing["author-email"] && await hasToken(existing["author-email"])) {
|
||||
const token = extractBearerToken(request);
|
||||
const valid = await verifyToken(existing["author-email"], token);
|
||||
if (!valid) {
|
||||
return new Response(JSON.stringify({ error: `Only ${existing.author || existing["author-email"]} can delete this skill. Provide a valid token via Authorization: Bearer header.` }), {
|
||||
status: 403,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
}
|
||||
await deleteSkill(params.slug);
|
||||
return new Response(null, { status: 204 });
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : "Unknown error";
|
||||
return new Response(JSON.stringify({ error: message }), {
|
||||
status: 500,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
|
||||
2
dist/server/pages/api/sync.astro.mjs
vendored
2
dist/server/pages/api/sync.astro.mjs
vendored
@@ -1,4 +1,4 @@
|
||||
import { b as buildSyncScript } from '../../chunks/sync_B_Og9xl3.mjs';
|
||||
import { b as buildSyncScript } from '../../chunks/sync_BEq_wzpT.mjs';
|
||||
export { renderers } from '../../renderers.mjs';
|
||||
|
||||
const GET = async ({ url }) => {
|
||||
|
||||
2
dist/server/pages/api/sync/project.astro.mjs
vendored
2
dist/server/pages/api/sync/project.astro.mjs
vendored
@@ -1,4 +1,4 @@
|
||||
import { b as buildSyncScript } from '../../../chunks/sync_B_Og9xl3.mjs';
|
||||
import { b as buildSyncScript } from '../../../chunks/sync_BEq_wzpT.mjs';
|
||||
export { renderers } from '../../../renderers.mjs';
|
||||
|
||||
const GET = async ({ url }) => {
|
||||
|
||||
7
dist/server/pages/gi.astro.mjs
vendored
7
dist/server/pages/gi.astro.mjs
vendored
@@ -1,8 +1,9 @@
|
||||
import { b as buildSyncScript } from '../chunks/sync_B_Og9xl3.mjs';
|
||||
import { i as isPowerShell, a as buildSyncScriptPS, b as buildSyncScript } from '../chunks/sync_BEq_wzpT.mjs';
|
||||
export { renderers } from '../renderers.mjs';
|
||||
|
||||
const GET = async ({ url }) => {
|
||||
const script = await buildSyncScript(url.origin, "$HOME/.claude/skills");
|
||||
const GET = async ({ url, request }) => {
|
||||
const ps = isPowerShell(request);
|
||||
const script = ps ? await buildSyncScriptPS(url.origin, "$HOME\\.claude\\skills") : await buildSyncScript(url.origin, "$HOME/.claude/skills");
|
||||
return new Response(script, {
|
||||
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
||||
});
|
||||
|
||||
2
dist/server/pages/gp.astro.mjs
vendored
2
dist/server/pages/gp.astro.mjs
vendored
@@ -1,4 +1,4 @@
|
||||
import { a as buildPushScript } from '../chunks/sync_B_Og9xl3.mjs';
|
||||
import { c as buildPushScript } from '../chunks/sync_BEq_wzpT.mjs';
|
||||
export { renderers } from '../renderers.mjs';
|
||||
|
||||
const GET = async ({ url }) => {
|
||||
|
||||
2
dist/server/pages/i.astro.mjs
vendored
2
dist/server/pages/i.astro.mjs
vendored
@@ -1,4 +1,4 @@
|
||||
import { b as buildSyncScript } from '../chunks/sync_B_Og9xl3.mjs';
|
||||
import { b as buildSyncScript } from '../chunks/sync_BEq_wzpT.mjs';
|
||||
export { renderers } from '../renderers.mjs';
|
||||
|
||||
const GET = async ({ url }) => {
|
||||
|
||||
297
dist/server/pages/index.astro.mjs
vendored
297
dist/server/pages/index.astro.mjs
vendored
File diff suppressed because one or more lines are too long
22
dist/server/pages/new.astro.mjs
vendored
22
dist/server/pages/new.astro.mjs
vendored
@@ -1,15 +1,25 @@
|
||||
import { e as createComponent, k as renderComponent, r as renderTemplate, m as maybeRenderHead } from '../chunks/astro/server_B-2LxKLH.mjs';
|
||||
import { e as createAstro, f as createComponent, k as renderComponent, r as renderTemplate, m as maybeRenderHead, h as addAttribute } from '../chunks/astro/server_CF97kUu8.mjs';
|
||||
import 'piccolore';
|
||||
import { $ as $$Base } from '../chunks/_plugin-vue_export-helper_B1lnwsE2.mjs';
|
||||
import { g as getAvailableTools, a as getAvailableModels, S as SkillEditor } from '../chunks/models_DPfuEi7q.mjs';
|
||||
import { $ as $$Base } from '../chunks/_plugin-vue_export-helper_CEgY73aA.mjs';
|
||||
import { g as getAvailableTools, a as getAvailableModels, S as SkillEditor } from '../chunks/models_BK7lP4G3.mjs';
|
||||
import { a as getAllTags, g as getSkill } from '../chunks/skills_BacVQUiS.mjs';
|
||||
export { renderers } from '../renderers.mjs';
|
||||
|
||||
const $$Astro = createAstro("https://skills.here.run.place");
|
||||
const $$New = createComponent(async ($$result, $$props, $$slots) => {
|
||||
const Astro2 = $$result.createAstro($$Astro, $$props, $$slots);
|
||||
Astro2.self = $$New;
|
||||
const availableTools = await getAvailableTools();
|
||||
const availableModels = await getAvailableModels();
|
||||
return renderTemplate`${renderComponent($$result, "Base", $$Base, { "title": "New Skill \u2014 Skillit" }, { "default": async ($$result2) => renderTemplate` ${maybeRenderHead()}<a href="/" class="inline-flex items-center gap-1 text-sm text-gray-600 hover:text-gray-300 transition-colors mb-4"> <svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5"></path> </svg>
|
||||
Back
|
||||
</a> <h1 class="text-2xl font-bold tracking-tight text-white mb-2">New Skill</h1> <p class="text-sm text-gray-500 mb-8 max-w-xl">Write a prompt in Markdown that tells Claude how to behave. The <strong class="text-gray-400">body</strong> is the instruction Claude receives. Use <strong class="text-gray-400">Allowed Tools</strong> to restrict which tools the skill can use.</p> ${renderComponent($$result2, "SkillEditor", SkillEditor, { "mode": "create", ":availableTools": availableTools, ":availableModels": availableModels, "client:load": true, "client:component-hydration": "load", "client:component-path": "/Users/alex/projects/skillit/src/components/SkillEditor.vue", "client:component-export": "default" })} ` })}`;
|
||||
const availableTags = await getAllTags();
|
||||
const fromSlug = Astro2.url.searchParams.get("from");
|
||||
let forkSource = null;
|
||||
if (fromSlug) {
|
||||
forkSource = await getSkill(fromSlug);
|
||||
}
|
||||
const isFork = Boolean(forkSource);
|
||||
const title = isFork ? `Fork ${forkSource.name} \u2014 Skills Here` : "New Skill \u2014 Skills Here";
|
||||
return renderTemplate`${renderComponent($$result, "Base", $$Base, { "title": title }, { "default": async ($$result2) => renderTemplate` ${maybeRenderHead()}<a${addAttribute(isFork ? `/${fromSlug}` : "/", "href")} class="inline-flex items-center gap-1 text-sm text-gray-600 hover:text-gray-300 transition-colors mb-4"> <svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5"></path> </svg> ${isFork ? `Back to ${forkSource.name}` : "Back"} </a> <h1 class="text-2xl font-bold tracking-tight text-white mb-2">${isFork ? "Fork Skill" : "New Skill"}</h1> ${isFork ? renderTemplate`<p class="text-sm text-gray-500 mb-8">Creating an independent copy of <strong class="text-gray-400">${forkSource.name}</strong>. Change the <strong class="text-gray-400">name</strong> to generate a new slug before saving.</p>` : renderTemplate`<p class="text-sm text-gray-500 mb-8 max-w-xl">Write a prompt in Markdown that tells Claude how to behave. The <strong class="text-gray-400">body</strong> is the instruction Claude receives. Use <strong class="text-gray-400">Allowed Tools</strong> to restrict which tools the skill can use.</p>`}${isFork ? renderTemplate`${renderComponent($$result2, "SkillEditor", SkillEditor, { "mode": "create", "forkOf": fromSlug, "initialName": forkSource.name, "initialDescription": forkSource.description, "initialAllowedTools": forkSource["allowed-tools"].join(", "), "initialArgumentHint": forkSource["argument-hint"], "initialModel": forkSource.model, "initialUserInvocable": forkSource["user-invocable"], "initialDisableModelInvocation": forkSource["disable-model-invocation"], "initialContext": forkSource.context, "initialAgent": forkSource.agent, "initialHooks": forkSource.hooks ? JSON.stringify(forkSource.hooks, null, 2) : "", "initialBody": forkSource.content, "initialTags": forkSource.tags.join(", "), ":availableTools": availableTools, ":availableModels": availableModels, "availableTags": availableTags.join(","), "client:load": true, "client:component-hydration": "load", "client:component-path": "/Users/alex/projects/skillit/src/components/SkillEditor.vue", "client:component-export": "default" })}` : renderTemplate`${renderComponent($$result2, "SkillEditor", SkillEditor, { "mode": "create", ":availableTools": availableTools, ":availableModels": availableModels, "availableTags": availableTags.join(","), "client:load": true, "client:component-hydration": "load", "client:component-path": "/Users/alex/projects/skillit/src/components/SkillEditor.vue", "client:component-export": "default" })}`}` })}`;
|
||||
}, "/Users/alex/projects/skillit/src/pages/new.astro", void 0);
|
||||
|
||||
const $$file = "/Users/alex/projects/skillit/src/pages/new.astro";
|
||||
|
||||
7
dist/server/pages/p.astro.mjs
vendored
7
dist/server/pages/p.astro.mjs
vendored
@@ -1,8 +1,9 @@
|
||||
import { a as buildPushScript } from '../chunks/sync_B_Og9xl3.mjs';
|
||||
import { i as isPowerShell, d as buildPushScriptPS, c as buildPushScript } from '../chunks/sync_BEq_wzpT.mjs';
|
||||
export { renderers } from '../renderers.mjs';
|
||||
|
||||
const GET = async ({ url }) => {
|
||||
const script = await buildPushScript(url.origin, ".claude/skills");
|
||||
const GET = async ({ url, request }) => {
|
||||
const ps = isPowerShell(request);
|
||||
const script = ps ? await buildPushScriptPS(url.origin, ".claude\\skills") : await buildPushScript(url.origin, ".claude/skills");
|
||||
return new Response(script, {
|
||||
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user