Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ validation path:
2. Parse YAML frontmatter with `gray-matter`.
3. Validate schema with `zod`.
4. Scan content against the denylist in `scripts/security/patterns.json`.
5. Cross-check declared capabilities against observed behavior.
5. Cross-check declared capabilities against observed behavior. Skills may include `metadata.author` and `metadata.source` (see the Agent Skills spec at agentskills.io). AutoVault-curated skills declare `metadata.author: "AutoVault"` (and `source`) so that hosts like Grok can group and attribute them (similar to how Resend skills appear under "Resend").
6. Deduplicate exact, near-exact, and functionally similar proposals.
7. Write the skill, source sidecar, signed manifest, and Ed25519 signature.

Expand Down
15 changes: 11 additions & 4 deletions skills/autovault-skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ agents:
- autojack
category: meta
metadata:
author: AutoVault
version: "1.0.0"
source: https://github.com/autoworks-ai/autovault
capabilities:
network: false
filesystem: readonly
Expand Down Expand Up @@ -156,15 +158,20 @@ name: kebab-case-name
description: At least 20 characters describing what the skill does and when to use it.
agents: [claude-code, codex]
metadata:
author: YourOrg
version: "1.0.0"
source: https://github.com/yourorg/your-skill
---
```

Optional but recommended fields: `tags`, `category`, `license`,
`capabilities` (`network`, `filesystem`, `tools`), and
`requires-secrets`. If the bundle ships files beyond `SKILL.md`, declare them
in `resources:` with `type: file`, or let `propose_skill`/`bulk_import` infer
that list when `allow_synthesized_frontmatter` is not false.
`capabilities` (`network`, `filesystem`, `tools`), `requires-secrets`, and
`metadata` (with `author`, `source`, `version`). Use `metadata.author` (and
`metadata.source`) following the Agent Skills spec so host UIs (Grok, etc.)
can group AutoVault skills and show provenance. If the bundle ships files
beyond `SKILL.md`, declare them in `resources:` with `type: file`, or let
`propose_skill`/`bulk_import` infer that list when `allow_synthesized_frontmatter`
is not false.

## Security expectations

Expand Down
12 changes: 12 additions & 0 deletions skills/skill-author/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ tags: [authoring, skills, autovault, meta, demo]
agents: [claude-code, codex, autojack]
category: meta
metadata:
author: AutoVault
version: "1.0.0"
source: https://github.com/autoworks-ai/autovault
capabilities:
network: false
filesystem: readwrite
Expand Down Expand Up @@ -35,6 +37,9 @@ skill already exists, reuse or extend it instead of creating a duplicate.
name: kebab-case-name # letters, digits, hyphens, underscores
description: At least 20 characters explaining WHAT the skill does and WHEN to use it.
agents: [claude-code, codex] # at least one visible target profile
metadata:
author: YourOrg # for host UI grouping / attribution (Grok etc.)
source: https://github.com/yourorg/your-skill
---
```

Expand All @@ -45,6 +50,9 @@ agents: [claude-code, codex] # at least one visible target profile
- `agents` is required. A skill with no target profile would enter the vault
but be invisible to every generated skill directory, so AutoVault rejects it
instead of accepting a hidden install.
- `metadata.author` and `metadata.source` (optional but recommended for
published/curated skills) surface in host UIs for attribution and grouping
(see agentskills.io spec and how Grok displays "(user · Resend)" skills).

## Recommended frontmatter

Expand All @@ -53,7 +61,9 @@ license: MIT
tags: [topic, tool, domain]
category: <one-word-bucket>
metadata:
author: YourOrg
version: "1.0.0"
source: https://github.com/yourorg/your-skill
capabilities:
network: false | true
filesystem: readonly | readwrite
Expand Down Expand Up @@ -185,7 +195,9 @@ license: MIT
tags: [domain, tool]
category: general
metadata:
author: YourOrg
version: "1.0.0"
source: https://github.com/yourorg/your-skill
capabilities:
network: false
filesystem: readonly
Expand Down
12 changes: 9 additions & 3 deletions src/storage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,11 +323,13 @@ function buildSummary(
const capabilities = asCapabilities(frontmatter.capabilities);
const requiresSecrets = asSecretsArray(frontmatter["requires-secrets"]);
const frontmatterAgents = asStringArray(frontmatter.agents);
const author = Object.hasOwn(metadata, "author") && typeof metadata.author === "string" && metadata.author.length > 0 ? metadata.author : undefined;
const frontmatterSource = Object.hasOwn(metadata, "source") && typeof metadata.source === "string" && metadata.source.length > 0 ? metadata.source : undefined;
Comment on lines +326 to +327
return {
name: asString(frontmatter.name, name),
title: optionalString(frontmatter.title),
description: asString(frontmatter.description, ""),
version: asString(metadata.version, "0.0.0"),
version: Object.hasOwn(metadata, "version") && typeof metadata.version === "string" && metadata.version.length > 0 ? metadata.version : "0.0.0",
tags: asStringArray(frontmatter.tags),
category: typeof frontmatter.category === "string" ? frontmatter.category : undefined,
agents: frontmatterAgents.length > 0 ? frontmatterAgents : fallbackAgents,
Expand All @@ -337,7 +339,9 @@ function buildSummary(
capabilities,
requires_tools: capabilities.tools,
requires_secrets: requiresSecrets,
requiresSecrets
requiresSecrets,
author,
frontmatter_source: frontmatterSource
};
}

Expand Down Expand Up @@ -911,7 +915,9 @@ export async function readSkillSummary(name: string): Promise<SkillSummary | nul
capabilities: record.capabilities,
requires_tools: record.requires_tools,
requires_secrets: record.requires_secrets,
requiresSecrets: record.requiresSecrets
requiresSecrets: record.requiresSecrets,
author: record.author,
frontmatter_source: record.frontmatter_source
};
}

Expand Down
10 changes: 7 additions & 3 deletions src/tools/get-skill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { readSkill, readSkillSource } from "../storage/index.js";
import { renderSkillForAgent } from "../transforms/index.js";
import { assertSafeSkillName } from "../util/skill-name.js";
import { resourcePathsForSkill } from "../util/skill-resource-paths.js";
import { parseFrontmatter } from "../validation/frontmatter.js";
import { extractAuthor, extractSource, parseFrontmatter } from "../validation/frontmatter.js";
import { readSkillResources } from "./read-skill-resource.js";

export type GetSkillOptions = {
Expand Down Expand Up @@ -36,7 +36,9 @@ export async function getSkill(
bin: skill.bin,
requires_secrets: skill.requiresSecrets,
capabilities: parseRenderedCapabilities(rendered.skill_md),
source,
author: skill.author ?? extractAuthor(rendered.skill_md),
source, // AutoVault provenance object (distinct from frontmatter metadata.source)
frontmatter_source: skill.frontmatter_source ?? extractSource(rendered.skill_md),
agent,
applied_transforms: rendered.applied_transforms,
warnings: rendered.warnings,
Expand All @@ -54,7 +56,9 @@ export async function getSkill(
bin: skill.bin,
requires_secrets: skill.requiresSecrets,
capabilities: skill.capabilities,
source,
author: skill.author,
source, // AutoVault provenance object
frontmatter_source: skill.frontmatter_source,
...(resourceContents ? { resource_contents: resourceContents } : {})
};
}
Expand Down
76 changes: 41 additions & 35 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,39 @@
export type SkillCapabilities = {
network: boolean;
filesystem: "readonly" | "readwrite";
tools: string[];
};

export type SkillSecretRequirement = {
name: string;
description?: string;
required?: boolean;
};

export type SkillSummary = {
name: string;
title?: string;
description: string;
version: string;
tags: string[];
category?: string;
agents: string[];
when_to_use?: string;
when_not_to_use?: string;
risk_level?: string;
capabilities: SkillCapabilities;
// Public JSON alias for metadata-only consumers.
requires_tools: string[];
// Public JSON alias; internal TypeScript callers should prefer requiresSecrets.
requires_secrets: SkillSecretRequirement[];
requiresSecrets: SkillSecretRequirement[];
};
export type SkillCapabilities = {
network: boolean;
filesystem: "readonly" | "readwrite";
tools: string[];
};

export type SkillSecretRequirement = {
name: string;
description?: string;
required?: boolean;
};

export type SkillSummary = {
name: string;
title?: string;
description: string;
version: string;
tags: string[];
category?: string;
agents: string[];
when_to_use?: string;
when_not_to_use?: string;
risk_level?: string;
capabilities: SkillCapabilities;
// Public JSON alias for metadata-only consumers.
requires_tools: string[];
// Public JSON alias; internal TypeScript callers should prefer requiresSecrets.
requires_secrets: SkillSecretRequirement[];
requiresSecrets: SkillSecretRequirement[];
// Optional attribution from frontmatter metadata (per agentskills.io convention).
// Used by hosts (e.g. Grok) for grouping/filtering in skill lists.
author?: string;
// From frontmatter metadata.source (e.g. upstream GitHub URL). Distinct from
// the AutoVault-internal provenance object returned as `source` by get_skill.
frontmatter_source?: string;
};
Comment thread
jack-arturo marked this conversation as resolved.

export type SkillBinAction = {
command: string;
Expand All @@ -36,11 +42,11 @@ export type SkillBinAction = {
requiresTty: boolean;
};

export type SkillRecord = SkillSummary & {
skillMd: string;
resources: Array<{ path: string; type: string }>;
bin: Record<string, SkillBinAction>;
};
export type SkillRecord = SkillSummary & {
skillMd: string;
resources: Array<{ path: string; type: string }>;
bin: Record<string, SkillBinAction>;
};

export type ValidationResult = {
valid: boolean;
Expand Down
45 changes: 45 additions & 0 deletions src/validation/frontmatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,48 @@ function trimTrailingSpacesAndTabs(input: string): string {
}
return end === input.length ? input : input.slice(0, end);
}

/**
* Extract the metadata map (if present) from frontmatter data or raw SKILL.md.
* Avoids duplicating parse logic; callers can pass an already-parsed data record
* to skip re-parsing YAML.
*/
export function getMetadata(
input: string | Record<string, unknown>
): Record<string, unknown> {
let data: Record<string, unknown>;
if (typeof input === "string") {
try {
const { data: parsed } = parseFrontmatter(input);
data = parsed;
} catch {
return Object.create(null);
}
} else {
data = input;
}
Comment thread
Copilot marked this conversation as resolved.
const rawMeta = (data as Record<string, unknown>).metadata;
if (typeof rawMeta !== "object" || rawMeta === null || Array.isArray(rawMeta)) {
return Object.create(null);
}
Comment on lines +59 to +62
// Defend against prototype pollution (repo already forbids __proto__ etc in other paths).
// Copy only own enumerable properties into a null-prototype object.
const safe: Record<string, unknown> = Object.create(null);
for (const key of Object.keys(rawMeta)) {
if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
safe[key] = (rawMeta as Record<string, unknown>)[key];
}
return safe;
}

export function extractAuthor(input: string | Record<string, unknown>): string | undefined {
const meta = getMetadata(input);
const value = meta.author;
return typeof value === "string" && value.length > 0 ? value : undefined;
}

export function extractSource(input: string | Record<string, unknown>): string | undefined {
const meta = getMetadata(input);
const value = meta.source;
return typeof value === "string" && value.length > 0 ? value : undefined;
}
Loading
Loading