@hasna/skills 0.1.57 → 0.1.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -50
- package/bin/index.js +3640 -3188
- package/bin/mcp.js +2621 -2428
- package/bin/migrate.js +70 -0
- package/bin/server.js +33666 -0
- package/bin/worker.js +30939 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3725 -3388
- package/dist/lib/config.d.ts +2 -2
- package/dist/lib/content-scan.d.ts +69 -0
- package/dist/lib/discovery.d.ts +1 -0
- package/dist/lib/hosted-availability.d.ts +2 -0
- package/dist/lib/packlist.d.ts +13 -0
- package/dist/lib/portable-skills.d.ts +46 -1
- package/dist/lib/project-state.d.ts +4 -1
- package/dist/lib/public-boundary.d.ts +31 -0
- package/dist/lib/registry-types.d.ts +20 -1
- package/dist/lib/remote-registry.d.ts +2 -2
- package/dist/lib/skill-validation.d.ts +5 -1
- package/dist/lib/skillinfo.d.ts +15 -0
- package/dist/storage.js +18 -6
- package/docs/skill-standard.md +34 -4
- package/migrations/0001_open_skills_self_hosted.sql +129 -0
- package/package.json +11 -3
- package/skills/browse/README.md +1 -1
- package/skills/browse/SKILL.md +1 -1
- package/skills/deepresearch/README.md +1 -1
- package/skills/deepresearch/SKILL.md +1 -1
- package/skills/image/README.md +1 -1
- package/skills/tmux-session/SKILL.md +2 -2
- package/skills/transcript/SKILL.md +1 -1
- package/skills/webcrawling/README.md +1 -1
- package/skills/apidocs/.claude/settings.json +0 -5
package/dist/lib/config.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Config file support for
|
|
2
|
+
* Config file support for Hasna Skills
|
|
3
3
|
*
|
|
4
4
|
* Loads configuration from:
|
|
5
5
|
* 1. Project-local: ./skills.config.json (highest priority)
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Values from the project config override global config.
|
|
10
10
|
*/
|
|
11
11
|
export interface SkillsConfig {
|
|
12
|
-
mode?: "local" | "hosted";
|
|
12
|
+
mode?: "local" | "self-hosted";
|
|
13
13
|
defaultAgent?: "claude" | "codex" | "gemini" | "pi" | "opencode" | "all";
|
|
14
14
|
defaultScope?: "global" | "project";
|
|
15
15
|
format?: "compact" | "json" | "csv";
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* content-scan.ts — scans skill BODIES (not just package.json/README) for content
|
|
3
|
+
* that must never ship in the public @hasna/skills package.
|
|
4
|
+
*
|
|
5
|
+
* Three finding categories:
|
|
6
|
+
* - secret-value: credential-shaped values (API keys, tokens, private keys)
|
|
7
|
+
* - pii-contact: personal contact PII (E.164 phone numbers)
|
|
8
|
+
* - private-context: internal/operational context that leaks the private fleet
|
|
9
|
+
* (fleet hostnames, home-directory paths with a real user,
|
|
10
|
+
* internal CLI invocations, internal infra/product names)
|
|
11
|
+
*
|
|
12
|
+
* All output is REDACTED: secret values are never emitted, phone numbers are masked
|
|
13
|
+
* to their country code, and only the rule id + a safe redacted marker are reported.
|
|
14
|
+
*/
|
|
15
|
+
export type ScanCategory = "secret-value" | "pii-contact" | "private-context";
|
|
16
|
+
export interface ScanRule {
|
|
17
|
+
category: ScanCategory;
|
|
18
|
+
/** Stable, human-readable rule id (safe to print — never a secret value). */
|
|
19
|
+
id: string;
|
|
20
|
+
description: string;
|
|
21
|
+
pattern: RegExp;
|
|
22
|
+
/** Optional predicate to drop known-safe example matches (e.g. 555 phone numbers). */
|
|
23
|
+
isExample?: (match: string) => boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface ScanFinding {
|
|
26
|
+
file: string;
|
|
27
|
+
line: number;
|
|
28
|
+
column: number;
|
|
29
|
+
category: ScanCategory;
|
|
30
|
+
ruleId: string;
|
|
31
|
+
/** Redacted marker — never contains a raw secret value. */
|
|
32
|
+
redacted: string;
|
|
33
|
+
}
|
|
34
|
+
export declare const SCAN_RULES: ScanRule[];
|
|
35
|
+
/**
|
|
36
|
+
* Produce a redacted marker for a matched value. Secret values are NEVER emitted;
|
|
37
|
+
* phone numbers are reduced to their leading country context; private-context
|
|
38
|
+
* matches (hostnames, paths, CLI names — not credentials) are shown verbatim so a
|
|
39
|
+
* maintainer can locate and remove them.
|
|
40
|
+
*/
|
|
41
|
+
export declare function redactMatch(category: ScanCategory, ruleId: string, match: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* Scan a block of text and return redacted findings. `file` is used only for
|
|
44
|
+
* reporting; it is never read from disk here.
|
|
45
|
+
*/
|
|
46
|
+
export declare function scanText(text: string, file?: string): ScanFinding[];
|
|
47
|
+
/** Read a file and scan its contents. Binary files are skipped and yield no findings. */
|
|
48
|
+
export declare function scanFile(path: string, reportedName?: string): ScanFinding[];
|
|
49
|
+
/**
|
|
50
|
+
* Scan a list of files. `nameFor` maps an absolute path to the name reported in
|
|
51
|
+
* findings (e.g. a repo-relative or package-relative path).
|
|
52
|
+
*/
|
|
53
|
+
export declare function scanFiles(paths: string[], nameFor?: (path: string) => string): ScanFinding[];
|
|
54
|
+
export interface ScanAllowlistEntry {
|
|
55
|
+
/** Package-relative file path the exception applies to. */
|
|
56
|
+
file: string;
|
|
57
|
+
/** Rule id the exception applies to. */
|
|
58
|
+
ruleId: string;
|
|
59
|
+
/** Human-readable justification (shown in audits). */
|
|
60
|
+
reason: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Drop findings that match an explicit, documented allowlist entry. Matching is
|
|
64
|
+
* exact on (file, ruleId) so an exception can never silently suppress a different
|
|
65
|
+
* file or a different rule. Returns the surviving findings.
|
|
66
|
+
*/
|
|
67
|
+
export declare function applyAllowlist(findings: ScanFinding[], allowlist: ScanAllowlistEntry[]): ScanFinding[];
|
|
68
|
+
/** Serialize findings as redacted JSON. Safe to print — contains no secret values. */
|
|
69
|
+
export declare function toRedactedJson(findings: ScanFinding[]): string;
|
package/dist/lib/discovery.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { type PublicSkillPricing } from "./pricing.js";
|
|
|
3
3
|
export interface CompactSkillDiscovery {
|
|
4
4
|
name: string;
|
|
5
5
|
category: string;
|
|
6
|
+
description: string;
|
|
6
7
|
pricing: PublicSkillPricing;
|
|
7
8
|
}
|
|
8
9
|
export type PublicSkillDiscovery<T extends SkillMeta = SkillMeta> = Omit<T, "description" | "tags"> & {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SkillAvailabilityMetadata } from "./registry-types.js";
|
|
1
2
|
export interface HostedRunUnavailable {
|
|
2
3
|
ok: false;
|
|
3
4
|
status: 503;
|
|
@@ -9,3 +10,4 @@ export type HostedRunAvailability = {
|
|
|
9
10
|
ok: true;
|
|
10
11
|
} | HostedRunUnavailable;
|
|
11
12
|
export declare function getHostedRunAvailability(slug: string): HostedRunAvailability;
|
|
13
|
+
export declare function getHostedAvailabilityMetadata(slug: string): SkillAvailabilityMetadata;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* packlist.ts — derive the REAL set of files that would be published to npm.
|
|
3
|
+
*
|
|
4
|
+
* Always resolve the package-visible file set from the packager itself
|
|
5
|
+
* (`npm pack --dry-run` / `bun pm pack --dry-run`), never from a hand-maintained
|
|
6
|
+
* copy of the `files` array. This guarantees the safety guards scan exactly what
|
|
7
|
+
* ships, including the effect of the `files` allow/deny globs.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Return the sorted list of package-relative file paths that would be published.
|
|
11
|
+
* Prefers `npm pack` (authoritative for npm) and falls back to `bun pm pack`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getPackedFiles(cwd?: string): string[];
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import type { SkillMeta } from "./registry-types.js";
|
|
1
|
+
import type { SkillKind, SkillMeta } from "./registry-types.js";
|
|
2
2
|
import { type SkillValidationResult } from "./skill-validation.js";
|
|
3
3
|
export declare const PORTABLE_SKILL_STANDARD = "hasna.skill.v1";
|
|
4
4
|
export declare const PORTABLE_SKILL_SCHEMA = "https://hasna.dev/schemas/skill.v1.json";
|
|
5
5
|
export declare const PORTABLE_SKILL_DEFAULT_VERSION = "0.1.0";
|
|
6
|
+
/**
|
|
7
|
+
* Artifact class of a portable skill.
|
|
8
|
+
* - `executable`: a runnable skill folder (package.json + bin + src entry).
|
|
9
|
+
* - `instruction`: a prose-only agent skill (SKILL.md primary, optional skill.json).
|
|
10
|
+
*
|
|
11
|
+
* Re-exports the canonical SkillKind (defined in registry-types) for existing consumers.
|
|
12
|
+
*/
|
|
13
|
+
export type { SkillKind };
|
|
6
14
|
export interface PortableSkillInput {
|
|
7
15
|
name: string;
|
|
8
16
|
type: string;
|
|
@@ -25,6 +33,7 @@ export interface PortableSkillManifest {
|
|
|
25
33
|
displayName?: string;
|
|
26
34
|
category?: string;
|
|
27
35
|
tags?: string[];
|
|
36
|
+
kind?: SkillKind;
|
|
28
37
|
inputs: PortableSkillInput[];
|
|
29
38
|
commands: PortableSkillCommand[];
|
|
30
39
|
}
|
|
@@ -45,10 +54,39 @@ export interface PortableSkillOptions {
|
|
|
45
54
|
export interface ScaffoldPortableSkillOptions extends PortableSkillOptions {
|
|
46
55
|
description?: string;
|
|
47
56
|
overwrite?: boolean;
|
|
57
|
+
kind?: SkillKind;
|
|
48
58
|
}
|
|
49
59
|
export interface PortPortableSkillOptions extends PortableSkillOptions {
|
|
50
60
|
name?: string;
|
|
51
61
|
overwrite?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Permit an imported skill name that shadows a bundled official skill.
|
|
64
|
+
* Without this, `port` refuses to silently override the official corpus.
|
|
65
|
+
*/
|
|
66
|
+
allowShadow?: boolean;
|
|
67
|
+
}
|
|
68
|
+
export interface BulkPortPortableSkillOptions extends PortableSkillOptions {
|
|
69
|
+
overwrite?: boolean;
|
|
70
|
+
/** When false, the first failure is rethrown. Defaults to true (skip-on-error). */
|
|
71
|
+
continueOnError?: boolean;
|
|
72
|
+
}
|
|
73
|
+
export interface BulkPortImportedEntry {
|
|
74
|
+
name: string;
|
|
75
|
+
path: string;
|
|
76
|
+
sourcePath: string;
|
|
77
|
+
}
|
|
78
|
+
export interface BulkPortSkippedEntry {
|
|
79
|
+
sourcePath: string;
|
|
80
|
+
name?: string;
|
|
81
|
+
reason: string;
|
|
82
|
+
}
|
|
83
|
+
export interface BulkPortResult {
|
|
84
|
+
root: string;
|
|
85
|
+
total: number;
|
|
86
|
+
succeeded: number;
|
|
87
|
+
failed: number;
|
|
88
|
+
imported: BulkPortImportedEntry[];
|
|
89
|
+
skipped: BulkPortSkippedEntry[];
|
|
52
90
|
}
|
|
53
91
|
export interface PortableSkillWriteResult {
|
|
54
92
|
name: string;
|
|
@@ -73,7 +111,14 @@ export declare function findPortableSkill(name: string, options?: PortableSkillO
|
|
|
73
111
|
export declare function listPortableSkills(options?: PortableSkillOptions): PortableSkillSummary[];
|
|
74
112
|
export declare function listPortableSkillMetas(options?: PortableSkillOptions): SkillMeta[];
|
|
75
113
|
export declare function readPortableSkillManifest(skillPath: string, fallbackName?: string): PortableSkillManifest;
|
|
114
|
+
export declare function isOfficialSkillName(name: string): boolean;
|
|
76
115
|
export declare function scaffoldPortableSkill(name: string, options?: ScaffoldPortableSkillOptions): PortableSkillWriteResult;
|
|
116
|
+
/**
|
|
117
|
+
* Import every immediate subfolder of a directory as a portable skill.
|
|
118
|
+
* Skip-on-error by default: non-skill folders and per-skill failures are recorded
|
|
119
|
+
* in the summary instead of aborting the whole run.
|
|
120
|
+
*/
|
|
121
|
+
export declare function portPortableSkillDirectory(sourceDir: string, options?: BulkPortPortableSkillOptions): BulkPortResult;
|
|
77
122
|
export declare function portPortableSkill(sourcePath: string, options?: PortPortableSkillOptions): PortableSkillWriteResult;
|
|
78
123
|
export declare function validatePortableSkillDirectory(name: string, skillPath: string): SkillValidationResult;
|
|
79
124
|
export declare function runPortableSkill(name: string, args: string[], options?: PortableSkillRunOptions): Promise<PortableSkillRunResult>;
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* `.skills/` is runtime/output state plus optional project preferences. It is
|
|
5
5
|
* not a skill source directory and must never contain copied skill definitions.
|
|
6
6
|
*/
|
|
7
|
+
import type { SkillSource } from "./registry-types.js";
|
|
8
|
+
/** Valid provenance sources for a project pin: skill sources plus project-local. */
|
|
9
|
+
export type ProjectPinSource = SkillSource | "local";
|
|
7
10
|
export declare const SKILLS_PROJECT_DIR = ".skills";
|
|
8
11
|
export declare const PROJECT_CONFIG_FILE = "project.json";
|
|
9
12
|
export declare const DEFAULT_EXPORT_DIR = ".skills/exports";
|
|
@@ -11,7 +14,7 @@ export interface ProjectSkillPin {
|
|
|
11
14
|
name: string;
|
|
12
15
|
pinnedAt: string;
|
|
13
16
|
version: string;
|
|
14
|
-
source:
|
|
17
|
+
source: ProjectPinSource;
|
|
15
18
|
}
|
|
16
19
|
export interface SkillsProjectConfig {
|
|
17
20
|
version: 1;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* public-boundary.ts — the public/private boundary for the published corpus.
|
|
3
|
+
*
|
|
4
|
+
* Some skills are PUBLIC (they ship in the open-source `@hasna/skills` package).
|
|
5
|
+
* Others are PRIVATE/TEAM (fleet-operational, PII-bearing, internal) and must
|
|
6
|
+
* NEVER enter the published npm files-list. This module lets a skill declare its
|
|
7
|
+
* visibility and provides enforcement that detects any private skill leaking into
|
|
8
|
+
* the real package file list.
|
|
9
|
+
*
|
|
10
|
+
* A skill is treated as PRIVATE (fully excluded from publish) when ANY of:
|
|
11
|
+
* - `package.json` -> `skills.visibility` is one of team|private|internal
|
|
12
|
+
* - `package.json` -> `skills.publish` is `false`
|
|
13
|
+
* - a marker file `.private` exists in the skill directory
|
|
14
|
+
* - `SKILL.md` frontmatter declares `visibility: team|private|internal`
|
|
15
|
+
*
|
|
16
|
+
* Note: `hosted` / `remote` / `private-hosted` skills are a DIFFERENT concept —
|
|
17
|
+
* they ship metadata (package.json) with their `src/` stripped. Those are premium
|
|
18
|
+
* hosted skills and are handled by the existing hosted-metadata boundary, not here.
|
|
19
|
+
*/
|
|
20
|
+
export type SkillVisibility = "public" | "team" | "private" | "internal";
|
|
21
|
+
export declare function isPrivateVisibility(value: string | undefined): value is SkillVisibility;
|
|
22
|
+
/** Determine whether a skill directory is private (must never be published). */
|
|
23
|
+
export declare function isPrivateSkillDir(skillDir: string): boolean;
|
|
24
|
+
/** List the slugs of every private skill under a `skills/` root, sorted. */
|
|
25
|
+
export declare function listPrivateSkillSlugs(skillsRoot: string): string[];
|
|
26
|
+
/**
|
|
27
|
+
* Given the real package file list and the set of private slugs, return every
|
|
28
|
+
* packed path that belongs to a private skill. A non-empty result means a private
|
|
29
|
+
* skill leaked into the publishable package.
|
|
30
|
+
*/
|
|
31
|
+
export declare function findPrivatePacklistLeaks(packedPaths: string[], privateSlugs: Iterable<string>): string[];
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export type SkillKind = "executable" | "instruction";
|
|
2
|
+
/**
|
|
3
|
+
* Provenance sources for a skill. Kept in sync with VALID_PROVENANCE_SOURCES in
|
|
4
|
+
* skill-validation.ts (plus "extension" for private extension overlays).
|
|
5
|
+
*/
|
|
6
|
+
export type SkillSource = "official" | "custom" | "remote" | "private" | "private-hosted" | "upstream" | "extension";
|
|
1
7
|
export interface SkillMeta {
|
|
2
8
|
name: string;
|
|
3
9
|
displayName: string;
|
|
@@ -6,8 +12,15 @@ export interface SkillMeta {
|
|
|
6
12
|
tags: string[];
|
|
7
13
|
dependencies?: string[];
|
|
8
14
|
version?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Artifact class of the skill. "executable" skills carry a runnable
|
|
17
|
+
* package.json/bin/src; "instruction" skills are SKILL.md-primary prose for
|
|
18
|
+
* agents. Missing kind defaults to "executable" during migration.
|
|
19
|
+
*/
|
|
20
|
+
kind?: SkillKind;
|
|
9
21
|
pricing?: SkillPricingMetadata;
|
|
10
|
-
|
|
22
|
+
availability?: SkillAvailabilityMetadata;
|
|
23
|
+
source?: SkillSource;
|
|
11
24
|
}
|
|
12
25
|
export interface SkillPricingMetadata {
|
|
13
26
|
tier?: "free" | "premium" | string;
|
|
@@ -21,6 +34,12 @@ export interface SkillPricingMetadata {
|
|
|
21
34
|
quoteRequired?: boolean;
|
|
22
35
|
description?: string;
|
|
23
36
|
}
|
|
37
|
+
export interface SkillAvailabilityMetadata {
|
|
38
|
+
status: "available" | "unavailable";
|
|
39
|
+
code?: string;
|
|
40
|
+
message?: string;
|
|
41
|
+
details?: string[];
|
|
42
|
+
}
|
|
24
43
|
export declare const CATEGORIES: readonly ["Development Tools", "Business & Marketing", "Productivity & Organization", "Project Management", "Content Generation", "Finance & Compliance", "Data & Analysis", "Media Processing", "Design & Branding", "Web & Browser", "Research & Writing", "Science & Academic", "Education & Learning", "Communication", "Health & Wellness", "Travel & Lifestyle", "Event Management"];
|
|
25
44
|
export type Category = (typeof CATEGORIES)[number];
|
|
26
45
|
export declare const BASIC_SKILL_NAMES: readonly ["image", "video", "audio", "music", "transcript", "audio-extract", "read-image", "read-pdf", "pdf-read", "pdf-to-markdown", "doc-read", "pdf-generate", "doc-generate", "read-csv", "read-excel", "excel", "convert"];
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Remote registry client.
|
|
3
3
|
*
|
|
4
4
|
* Local registry behavior remains the default. These helpers are opt-in and
|
|
5
|
-
* read from SKILLS_API_URL or config.apiUrl so
|
|
6
|
-
* a compatible registry API without hard-coding
|
|
5
|
+
* read from SKILLS_API_URL or config.apiUrl so self-hosted services can expose
|
|
6
|
+
* a compatible registry API without hard-coding deployment details upstream.
|
|
7
7
|
*/
|
|
8
8
|
import { type SkillsConfig } from "./config.js";
|
|
9
9
|
import type { SkillMeta } from "./registry.js";
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { SkillMeta } from "./registry.js";
|
|
2
|
+
import type { SkillKind } from "./registry-types.js";
|
|
2
3
|
import type { PortableSkillManifest } from "./portable-skills.js";
|
|
4
|
+
export declare const VALID_SKILL_KINDS: readonly SkillKind[];
|
|
3
5
|
export interface SkillValidationMessage {
|
|
4
6
|
code: string;
|
|
5
7
|
message: string;
|
|
@@ -18,7 +20,8 @@ export interface SkillValidationResult {
|
|
|
18
20
|
skillMdFrontmatter?: SkillFrontmatter;
|
|
19
21
|
portableManifest?: PortableSkillManifest;
|
|
20
22
|
provenance?: SkillValidationProvenance;
|
|
21
|
-
runtime?: "local" | "hosted";
|
|
23
|
+
runtime?: "local" | "hosted" | "none";
|
|
24
|
+
kind?: SkillKind;
|
|
22
25
|
};
|
|
23
26
|
}
|
|
24
27
|
export interface RegistryConsistencyResult {
|
|
@@ -35,6 +38,7 @@ export interface SkillFrontmatter {
|
|
|
35
38
|
tags?: string[];
|
|
36
39
|
version?: string;
|
|
37
40
|
source?: string;
|
|
41
|
+
kind?: string;
|
|
38
42
|
}
|
|
39
43
|
export interface SkillValidationProvenance {
|
|
40
44
|
directoryName: string;
|
package/dist/lib/skillinfo.d.ts
CHANGED
|
@@ -25,6 +25,21 @@ export declare function getSkillBestDoc(name: string): string | null;
|
|
|
25
25
|
* Extract requirements from a skill's source files
|
|
26
26
|
*/
|
|
27
27
|
export declare function getSkillRequirements(name: string): SkillRequirements | null;
|
|
28
|
+
export interface SkillDependencyStatus {
|
|
29
|
+
name: string;
|
|
30
|
+
version: string;
|
|
31
|
+
installed: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolve npm dependency install status for a skill.
|
|
35
|
+
*
|
|
36
|
+
* A dependency counts as installed only when it is resolvable from the skill
|
|
37
|
+
* directory (the skill's own node_modules or any ancestor node_modules),
|
|
38
|
+
* matching how the skill imports it when it runs. This keeps the doctor/test
|
|
39
|
+
* readiness signal truthful: a runnable skill that imports `xlsx` is not
|
|
40
|
+
* "ready" until `xlsx` is actually installed, even if every env var is set.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getSkillDependencyStatus(name: string): SkillDependencyStatus[];
|
|
28
43
|
/**
|
|
29
44
|
* Run a skill by name with given arguments
|
|
30
45
|
*/
|
package/dist/storage.js
CHANGED
|
@@ -36,14 +36,16 @@ var ENUM_KEYS = {
|
|
|
36
36
|
format: ["compact", "json", "csv"]
|
|
37
37
|
};
|
|
38
38
|
var STRING_KEYS = ["apiUrl"];
|
|
39
|
-
var MODE_VALUES = ["local", "hosted"];
|
|
39
|
+
var MODE_VALUES = ["local", "self-hosted"];
|
|
40
40
|
var MODE_ALIASES = {
|
|
41
41
|
local: "local",
|
|
42
42
|
offline: "local",
|
|
43
|
-
hosted: "hosted",
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
"self-hosted": "self-hosted",
|
|
44
|
+
selfhosted: "self-hosted",
|
|
45
|
+
self_hosted: "self-hosted",
|
|
46
|
+
hosted: "self-hosted",
|
|
47
|
+
"skills.md": "self-hosted",
|
|
48
|
+
skillsmd: "self-hosted"
|
|
47
49
|
};
|
|
48
50
|
function validKeys() {
|
|
49
51
|
return ["mode", ...Object.keys(ENUM_KEYS), ...STRING_KEYS];
|
|
@@ -178,6 +180,16 @@ function normalizeSkillName(name) {
|
|
|
178
180
|
}
|
|
179
181
|
|
|
180
182
|
// src/lib/project-state.ts
|
|
183
|
+
var VALID_PIN_SOURCES = [
|
|
184
|
+
"official",
|
|
185
|
+
"custom",
|
|
186
|
+
"remote",
|
|
187
|
+
"private",
|
|
188
|
+
"private-hosted",
|
|
189
|
+
"upstream",
|
|
190
|
+
"extension",
|
|
191
|
+
"local"
|
|
192
|
+
];
|
|
181
193
|
var SKILLS_PROJECT_DIR = ".skills";
|
|
182
194
|
var PROJECT_CONFIG_FILE = "project.json";
|
|
183
195
|
var DEFAULT_EXPORT_DIR = ".skills/exports";
|
|
@@ -302,7 +314,7 @@ function normalizeProjectConfig(raw) {
|
|
|
302
314
|
};
|
|
303
315
|
}
|
|
304
316
|
function isPinSource(value) {
|
|
305
|
-
return
|
|
317
|
+
return typeof value === "string" && VALID_PIN_SOURCES.includes(value);
|
|
306
318
|
}
|
|
307
319
|
|
|
308
320
|
// src/lib/native-storage.ts
|
package/docs/skill-standard.md
CHANGED
|
@@ -17,6 +17,34 @@ Portable skills live in one folder each:
|
|
|
17
17
|
`skills port <path>` and `skills add <path>` copy an existing skill folder into
|
|
18
18
|
this layout and add missing standard files.
|
|
19
19
|
|
|
20
|
+
## Skill Kinds
|
|
21
|
+
|
|
22
|
+
A skill declares its artifact class with the `kind` frontmatter field:
|
|
23
|
+
|
|
24
|
+
- `kind: executable` (default when omitted) — a runnable skill folder with
|
|
25
|
+
`package.json`, a non-empty `bin`, and `src/index.ts`. `skills run` executes it.
|
|
26
|
+
- `kind: instruction` — a `SKILL.md`-primary prose skill for coding agents.
|
|
27
|
+
`package.json`, `bin`, and `src/` are all optional. Instruction skills may still
|
|
28
|
+
bundle optional helper scripts (a `bin`/`src` is permitted, not forbidden), but
|
|
29
|
+
they are consumed by agents via `SKILL.md`, not executed. `skills run` on an
|
|
30
|
+
instruction skill returns a clear "not runnable — instruction skill" error
|
|
31
|
+
instead of executing a stub.
|
|
32
|
+
|
|
33
|
+
Missing `kind` defaults to `executable` so the bundled corpus is unaffected.
|
|
34
|
+
Migrated operational (prose) skills should set `kind: instruction` explicitly.
|
|
35
|
+
|
|
36
|
+
Minimum `SKILL.md` frontmatter for an instruction skill:
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
---
|
|
40
|
+
name: skill-project
|
|
41
|
+
description: Open or resume an existing Hasna repo project using the projects CLI.
|
|
42
|
+
kind: instruction
|
|
43
|
+
version: 0.1.0
|
|
44
|
+
source: private
|
|
45
|
+
---
|
|
46
|
+
```
|
|
47
|
+
|
|
20
48
|
## Naming
|
|
21
49
|
|
|
22
50
|
Skill names are lowercase slugs: letters, numbers, dots, underscores, and
|
|
@@ -107,10 +135,12 @@ skills validate my-skill --json
|
|
|
107
135
|
Validation checks:
|
|
108
136
|
|
|
109
137
|
- folder and name safety;
|
|
110
|
-
- `SKILL.md` frontmatter compatibility;
|
|
111
|
-
- `skill.json` standard, version, inputs, and commands
|
|
112
|
-
|
|
113
|
-
- `
|
|
138
|
+
- `SKILL.md` frontmatter compatibility (including a valid `kind`);
|
|
139
|
+
- `skill.json` standard, version, inputs, and commands (relaxed for
|
|
140
|
+
`kind: instruction`, which needs neither `commands`, `inputs`, nor `AGENTS.md`);
|
|
141
|
+
- `AGENTS.md` presence (executable skills only);
|
|
142
|
+
- `package.json` and command entrypoint safety (`package.json`/`bin`/`src` are
|
|
143
|
+
optional for `kind: instruction`);
|
|
114
144
|
- no reserved files such as `.env` or symlinks.
|
|
115
145
|
|
|
116
146
|
## Porting Existing Skills
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
2
|
+
|
|
3
|
+
CREATE TABLE IF NOT EXISTS organizations (
|
|
4
|
+
id text PRIMARY KEY,
|
|
5
|
+
slug text NOT NULL UNIQUE,
|
|
6
|
+
name text NOT NULL,
|
|
7
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
11
|
+
id text PRIMARY KEY,
|
|
12
|
+
email text NOT NULL UNIQUE,
|
|
13
|
+
name text,
|
|
14
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
CREATE TABLE IF NOT EXISTS organization_members (
|
|
18
|
+
org_id text NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
19
|
+
user_id text NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
20
|
+
role text NOT NULL DEFAULT 'owner',
|
|
21
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
22
|
+
PRIMARY KEY (org_id, user_id)
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
CREATE TABLE IF NOT EXISTS api_keys (
|
|
26
|
+
id text PRIMARY KEY,
|
|
27
|
+
org_id text NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
28
|
+
user_id text NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
29
|
+
name text NOT NULL,
|
|
30
|
+
key_hash text NOT NULL UNIQUE,
|
|
31
|
+
scopes_json jsonb NOT NULL DEFAULT '["skills:read","runs:write"]'::jsonb,
|
|
32
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
33
|
+
last_used_at timestamptz,
|
|
34
|
+
revoked_at timestamptz
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
CREATE TABLE IF NOT EXISTS skills_registry (
|
|
38
|
+
slug text PRIMARY KEY,
|
|
39
|
+
display_name text NOT NULL,
|
|
40
|
+
description text NOT NULL DEFAULT '',
|
|
41
|
+
category text NOT NULL DEFAULT 'Remote',
|
|
42
|
+
tags_json jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
43
|
+
source text NOT NULL DEFAULT 'bundled',
|
|
44
|
+
version text,
|
|
45
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
CREATE TABLE IF NOT EXISTS skills_runs (
|
|
49
|
+
id text PRIMARY KEY,
|
|
50
|
+
org_id text NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
51
|
+
user_id text NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
52
|
+
skill_slug text NOT NULL,
|
|
53
|
+
requested_slug text NOT NULL,
|
|
54
|
+
status text NOT NULL,
|
|
55
|
+
input_json jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
56
|
+
args_json jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
57
|
+
idempotency_key text,
|
|
58
|
+
correlation_id text NOT NULL,
|
|
59
|
+
cost_cents integer NOT NULL DEFAULT 0,
|
|
60
|
+
output_type text,
|
|
61
|
+
output_preview text,
|
|
62
|
+
error_code text,
|
|
63
|
+
error_message text,
|
|
64
|
+
locked_by text,
|
|
65
|
+
locked_at timestamptz,
|
|
66
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
67
|
+
started_at timestamptz,
|
|
68
|
+
completed_at timestamptz,
|
|
69
|
+
CHECK (status IN ('queued','waiting_for_approval','running','succeeded','failed','cancel_requested','cancelled','retrying','expired','refunded'))
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
CREATE UNIQUE INDEX IF NOT EXISTS skills_runs_org_idempotency_idx
|
|
73
|
+
ON skills_runs (org_id, idempotency_key)
|
|
74
|
+
WHERE idempotency_key IS NOT NULL;
|
|
75
|
+
|
|
76
|
+
CREATE INDEX IF NOT EXISTS skills_runs_org_created_idx ON skills_runs (org_id, created_at DESC);
|
|
77
|
+
CREATE INDEX IF NOT EXISTS skills_runs_status_created_idx ON skills_runs (status, created_at ASC);
|
|
78
|
+
|
|
79
|
+
CREATE TABLE IF NOT EXISTS skills_run_logs (
|
|
80
|
+
id bigserial PRIMARY KEY,
|
|
81
|
+
run_id text NOT NULL REFERENCES skills_runs(id) ON DELETE CASCADE,
|
|
82
|
+
org_id text NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
83
|
+
sequence integer NOT NULL,
|
|
84
|
+
level text NOT NULL DEFAULT 'info',
|
|
85
|
+
message text NOT NULL,
|
|
86
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
87
|
+
UNIQUE (run_id, sequence)
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
CREATE TABLE IF NOT EXISTS skills_artifacts (
|
|
91
|
+
id text PRIMARY KEY,
|
|
92
|
+
run_id text NOT NULL REFERENCES skills_runs(id) ON DELETE CASCADE,
|
|
93
|
+
org_id text NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
94
|
+
file_name text NOT NULL,
|
|
95
|
+
relative_path text NOT NULL,
|
|
96
|
+
content_type text NOT NULL,
|
|
97
|
+
byte_size integer NOT NULL,
|
|
98
|
+
sha256 text NOT NULL,
|
|
99
|
+
storage_kind text NOT NULL DEFAULT 'db',
|
|
100
|
+
storage_key text,
|
|
101
|
+
body_text text,
|
|
102
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
CREATE INDEX IF NOT EXISTS skills_artifacts_run_idx ON skills_artifacts (run_id);
|
|
106
|
+
|
|
107
|
+
CREATE TABLE IF NOT EXISTS skills_approvals (
|
|
108
|
+
id text PRIMARY KEY,
|
|
109
|
+
org_id text NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
110
|
+
run_id text REFERENCES skills_runs(id) ON DELETE CASCADE,
|
|
111
|
+
approved_by_user_id text REFERENCES users(id) ON DELETE SET NULL,
|
|
112
|
+
policy_digest text NOT NULL,
|
|
113
|
+
status text NOT NULL,
|
|
114
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
115
|
+
decided_at timestamptz,
|
|
116
|
+
CHECK (status IN ('pending','approved','rejected','expired'))
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
CREATE TABLE IF NOT EXISTS skills_audit_events (
|
|
120
|
+
id bigserial PRIMARY KEY,
|
|
121
|
+
org_id text REFERENCES organizations(id) ON DELETE SET NULL,
|
|
122
|
+
user_id text REFERENCES users(id) ON DELETE SET NULL,
|
|
123
|
+
api_key_id text REFERENCES api_keys(id) ON DELETE SET NULL,
|
|
124
|
+
action text NOT NULL,
|
|
125
|
+
target_type text NOT NULL,
|
|
126
|
+
target_id text,
|
|
127
|
+
metadata_json jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
128
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
129
|
+
);
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/skills",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.59",
|
|
4
4
|
"description": "Skills library for AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"skills": "bin/index.js",
|
|
8
|
-
"skills-mcp": "bin/mcp.js"
|
|
8
|
+
"skills-mcp": "bin/mcp.js",
|
|
9
|
+
"skills-server": "bin/server.js",
|
|
10
|
+
"skills-worker": "bin/worker.js",
|
|
11
|
+
"skills-migrate": "bin/migrate.js"
|
|
9
12
|
},
|
|
10
13
|
"exports": {
|
|
11
14
|
".": {
|
|
@@ -23,6 +26,7 @@
|
|
|
23
26
|
"!dist/platform",
|
|
24
27
|
"!dist/server",
|
|
25
28
|
"bin/",
|
|
29
|
+
"migrations/",
|
|
26
30
|
"docs/skill-standard.md",
|
|
27
31
|
"skills/",
|
|
28
32
|
"!skills/**/node_modules",
|
|
@@ -35,11 +39,14 @@
|
|
|
35
39
|
"types": "./dist/index.d.ts",
|
|
36
40
|
"scripts": {
|
|
37
41
|
"clean": "rm -rf bin/ dist/",
|
|
38
|
-
"build": "bun run clean && bun build ./src/cli/index.tsx --outdir ./bin --target bun && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/index.ts ./src/storage.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
42
|
+
"build": "bun run clean && bun build ./src/cli/index.tsx --outdir ./bin --target bun && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/server/index.ts --outfile ./bin/server.js --target bun && bun build ./src/server/worker.ts --outfile ./bin/worker.js --target bun && bun build ./src/server/migrate.ts --outfile ./bin/migrate.js --target bun && bun build ./src/index.ts ./src/storage.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
39
43
|
"test": "bun test",
|
|
40
44
|
"dev": "bun run ./src/cli/index.tsx",
|
|
41
45
|
"dev:watch": "bun --watch run ./src/cli/index.tsx",
|
|
42
46
|
"dev:mcp": "bun --watch run ./src/mcp/index.ts",
|
|
47
|
+
"dev:server": "bun --watch run ./src/server/index.ts",
|
|
48
|
+
"dev:worker": "bun --watch run ./src/server/worker.ts",
|
|
49
|
+
"migrate": "bun run ./src/server/migrate.ts",
|
|
43
50
|
"typecheck": "tsc --noEmit",
|
|
44
51
|
"verify:release": "bun run scripts/release-guard.ts",
|
|
45
52
|
"prepack": "bun run build && bun run verify:release",
|
|
@@ -71,6 +78,7 @@
|
|
|
71
78
|
"typescript": "^5"
|
|
72
79
|
},
|
|
73
80
|
"dependencies": {
|
|
81
|
+
"@aws-sdk/client-s3": "^3.1079.0",
|
|
74
82
|
"@hasna/events": "^0.1.7",
|
|
75
83
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
76
84
|
"chalk": "^5.3.0",
|
package/skills/browse/README.md
CHANGED
package/skills/browse/SKILL.md
CHANGED
|
@@ -6,7 +6,7 @@ citations, and downloadable artifacts.
|
|
|
6
6
|
## Usage
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
|
-
skills setup --mode hosted
|
|
9
|
+
skills setup --mode self-hosted
|
|
10
10
|
skills auth login
|
|
11
11
|
skills mcp --register
|
|
12
12
|
skills run deepresearch "Compare React Server Components with traditional SSR" --depth deep
|