@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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Config file support for Open Skills
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;
@@ -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: "official" | "custom" | "remote" | "local";
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
- source?: "official" | "custom" | "remote";
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 private SaaS products can expose
6
- * a compatible registry API without hard-coding any SaaS details upstream.
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;
@@ -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
- remote: "hosted",
45
- "skills.md": "hosted",
46
- skillsmd: "hosted"
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 value === "official" || value === "custom" || value === "remote" || value === "local";
317
+ return typeof value === "string" && VALID_PIN_SOURCES.includes(value);
306
318
  }
307
319
 
308
320
  // src/lib/native-storage.ts
@@ -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
- - `AGENTS.md` presence;
113
- - `package.json` and command entrypoint safety;
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.57",
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",
@@ -6,7 +6,7 @@ data extraction.
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 run browse --task "Find the top three announcements on example.com"
12
12
  ```
@@ -13,7 +13,7 @@ logs stay server-side.
13
13
  ## Usage
14
14
 
15
15
  ```bash
16
- skills setup --mode hosted
16
+ skills setup --mode self-hosted
17
17
  skills auth login
18
18
  skills run browse --task "Open example.com and summarize the page"
19
19
  skills runs status <run-id>
@@ -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