@graphorin/skills 0.5.0
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/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/anthropic-spec-snapshot.json +114 -0
- package/dist/activation/index.d.ts +30 -0
- package/dist/activation/index.d.ts.map +1 -0
- package/dist/activation/index.js +71 -0
- package/dist/activation/index.js.map +1 -0
- package/dist/errors/index.d.ts +110 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +126 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/frontmatter/index.d.ts +120 -0
- package/dist/frontmatter/index.d.ts.map +1 -0
- package/dist/frontmatter/index.js +451 -0
- package/dist/frontmatter/index.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/loader/index.d.ts +55 -0
- package/dist/loader/index.d.ts.map +1 -0
- package/dist/loader/index.js +463 -0
- package/dist/loader/index.js.map +1 -0
- package/dist/migration/index.d.ts +62 -0
- package/dist/migration/index.d.ts.map +1 -0
- package/dist/migration/index.js +131 -0
- package/dist/migration/index.js.map +1 -0
- package/dist/registry/bridge.d.ts +47 -0
- package/dist/registry/bridge.d.ts.map +1 -0
- package/dist/registry/bridge.js +91 -0
- package/dist/registry/bridge.js.map +1 -0
- package/dist/registry/index.d.ts +140 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +193 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/spec/index.d.ts +92 -0
- package/dist/spec/index.d.ts.map +1 -0
- package/dist/spec/index.js +105 -0
- package/dist/spec/index.js.map +1 -0
- package/dist/types/index.d.ts +305 -0
- package/dist/types/index.d.ts.map +1 -0
- package/package.json +104 -0
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Oleksiy Stepurenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# @graphorin/skills
|
|
2
|
+
|
|
3
|
+
> Skills surface for the Graphorin framework.
|
|
4
|
+
|
|
5
|
+
`@graphorin/skills` ships the loader, registry, validator, and
|
|
6
|
+
migration library that let a Graphorin agent consume skills written
|
|
7
|
+
to the `SKILL.md` packaging format with three-tier progressive
|
|
8
|
+
disclosure plus the Graphorin-specific extensions namespaced under
|
|
9
|
+
the `graphorin-*` prefix.
|
|
10
|
+
|
|
11
|
+
## Highlights
|
|
12
|
+
|
|
13
|
+
- **Compatibility-first loader.** A skill written to the public
|
|
14
|
+
`SKILL.md` packaging format loads in Graphorin without
|
|
15
|
+
modification. Author-time fields under the `graphorin-*` prefix and
|
|
16
|
+
the spec-recommended `metadata.graphorin.*` bucket are honoured
|
|
17
|
+
through a deterministic field-resolution algorithm (Anthropic-base
|
|
18
|
+
> `metadata.graphorin.*` > `graphorin-*` legacy > caller fallback).
|
|
19
|
+
- **Three-tier progressive disclosure.** Metadata (Tier 1) is parsed
|
|
20
|
+
at load time. The skill body (Tier 2) is read on first
|
|
21
|
+
`Skill.body()` call. Resources (Tier 3) are listed lazily and the
|
|
22
|
+
bytes are only read when `SkillResource.read()` is invoked.
|
|
23
|
+
- **Conflict policy.** Direct collisions (Anthropic-base set + the
|
|
24
|
+
matching `graphorin-*` set) honour the operator-resolved
|
|
25
|
+
`conflictPolicy` (`'warn' | 'error' | 'silent'`); the default is
|
|
26
|
+
`'warn'`. The validator returns a typed `FrontmatterDiagnostic[]`
|
|
27
|
+
so callers can route the report to logs, audit, or CI.
|
|
28
|
+
- **Spec-snapshot tracking.** A bundled
|
|
29
|
+
`anthropic-spec-snapshot.json` records the upstream fields the
|
|
30
|
+
framework recognises and the migration policy for every
|
|
31
|
+
`graphorin-*` extension. The `pnpm run check-anthropic-spec` CI
|
|
32
|
+
helper diffs the snapshot against the published specification so
|
|
33
|
+
maintainers can promote new fields with a clear changelog entry.
|
|
34
|
+
- **Supply-chain aware.** Loading from `npm-package` and `git-repo`
|
|
35
|
+
sources delegates to the supply-chain helpers in
|
|
36
|
+
`@graphorin/security` so untrusted skills install with
|
|
37
|
+
`--ignore-scripts` enforced and a verifiable ed25519 signature is
|
|
38
|
+
required before the loader trusts the bytes. Local `folder`
|
|
39
|
+
installations remain trusted-by-default but inherit the same
|
|
40
|
+
validation pipeline.
|
|
41
|
+
- **Slash-command activation.** `/skill:<name>` parses into a
|
|
42
|
+
structured activation request the agent runtime consumes alongside
|
|
43
|
+
the model-emitted auto-activation requests; skills that opt out via
|
|
44
|
+
`disable-model-invocation: true` are excluded from auto-activation
|
|
45
|
+
but remain reachable through the slash command.
|
|
46
|
+
- **Migration library.** `migrateFrontmatter()` is idempotent and
|
|
47
|
+
dry-run by default. Re-running the migrator on an already-migrated
|
|
48
|
+
manifest returns `changed: false`.
|
|
49
|
+
|
|
50
|
+
## Stable sub-paths
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
// Loader (folder / npm-package / git-repo / inline sources).
|
|
54
|
+
import { loadSkills, loadSkillFromSource } from '@graphorin/skills/loader';
|
|
55
|
+
|
|
56
|
+
// Registry (auto-activation metadata + slash-command resolver +
|
|
57
|
+
// trust-level-aware tool-declaration listing).
|
|
58
|
+
import { createSkillRegistry } from '@graphorin/skills/registry';
|
|
59
|
+
|
|
60
|
+
// Frontmatter validator + field-resolution algorithm.
|
|
61
|
+
import { validateFrontmatter, resolveSkillField } from '@graphorin/skills/frontmatter';
|
|
62
|
+
|
|
63
|
+
// Idempotent migrate-frontmatter helper used by the CLI.
|
|
64
|
+
import { migrateFrontmatter } from '@graphorin/skills/migration';
|
|
65
|
+
|
|
66
|
+
// Activation utilities (`/skill:<name>` parser).
|
|
67
|
+
import { parseSlashCommand, isSlashCommand } from '@graphorin/skills/activation';
|
|
68
|
+
|
|
69
|
+
// Spec-snapshot helpers.
|
|
70
|
+
import { getSpecSnapshot, getKnownField, compareAuthorSpecHint } from '@graphorin/skills/spec';
|
|
71
|
+
|
|
72
|
+
// Typed errors.
|
|
73
|
+
import { SkillFrontmatterConflictError, InputFilterRequiredError } from '@graphorin/skills/errors';
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Hello-world
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { loadSkillFromSource, createSkillRegistry } from '@graphorin/skills';
|
|
80
|
+
|
|
81
|
+
const skill = await loadSkillFromSource(
|
|
82
|
+
{ kind: 'folder', path: './skills/finance-helper' },
|
|
83
|
+
{ conflictPolicy: 'warn', runtimeVersion: '0.5.0' },
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const registry = createSkillRegistry();
|
|
87
|
+
registry.register(skill);
|
|
88
|
+
|
|
89
|
+
const metadataBlock = registry.getAutoActivationMetadata();
|
|
90
|
+
// → metadata fed into the system prompt by the agent runtime (Phase 12).
|
|
91
|
+
|
|
92
|
+
const activation = registry.resolveTrigger('/skill:finance-helper run-quarterly-report');
|
|
93
|
+
if (activation !== null) {
|
|
94
|
+
const body = await activation.skill.body();
|
|
95
|
+
// → Tier-2 body resolved lazily on first call; cached afterwards.
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Frontmatter cheat sheet
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
---
|
|
103
|
+
# === Public SKILL.md packaging format (no prefix) ===
|
|
104
|
+
name: finance-helper
|
|
105
|
+
description: |
|
|
106
|
+
Surface monthly P&L reports. Use when the user asks about cash
|
|
107
|
+
flow, runway, or quarterly business reports.
|
|
108
|
+
license: MIT
|
|
109
|
+
disable-model-invocation: false
|
|
110
|
+
|
|
111
|
+
# === Graphorin extensions (namespaced) ===
|
|
112
|
+
graphorin-runtime-compat: ^0.5.0
|
|
113
|
+
graphorin-trust-level: trusted
|
|
114
|
+
graphorin-sensitivity: internal
|
|
115
|
+
graphorin-handoff-input-filter: lastUser
|
|
116
|
+
graphorin-tools:
|
|
117
|
+
- name: pull_quarterly_report
|
|
118
|
+
description: Read the latest quarterly close.
|
|
119
|
+
tags: ['finance', 'reporting']
|
|
120
|
+
graphorin-signature:
|
|
121
|
+
algorithm: ed25519-sha256
|
|
122
|
+
publisher: example.com
|
|
123
|
+
publishedAt: 2026-04-19T12:00:00Z
|
|
124
|
+
signature: <base64url>
|
|
125
|
+
publicKeyRef:
|
|
126
|
+
kind: well-known
|
|
127
|
+
url: https://example.com/.well-known/graphorin-skill-pubkey.pem
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
# Finance Helper
|
|
131
|
+
|
|
132
|
+
The body of the SKILL.md is loaded lazily on activation.
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Trust levels
|
|
136
|
+
|
|
137
|
+
| Source | Default `graphorin-trust-level` | `--ignore-scripts` | Signature |
|
|
138
|
+
|----------------|------------------------------------------|----------------------------|-----------------------------|
|
|
139
|
+
| `folder` | `trusted` (override `trusted-with-scripts`) | default ON; opt-in override | optional |
|
|
140
|
+
| `npm-package` | `untrusted` (override via `trustLevel`) | mandatory (no override) | mandatory |
|
|
141
|
+
| `git-repo` | `untrusted` | mandatory | mandatory |
|
|
142
|
+
| `inline` | `trusted` (test fixture) | n/a | n/a |
|
|
143
|
+
|
|
144
|
+
The supply-chain layer enforces `--ignore-scripts` and signature
|
|
145
|
+
verification through `@graphorin/security/supply-chain`. The skills
|
|
146
|
+
loader does not duplicate that logic — it delegates and surfaces the
|
|
147
|
+
resulting `ResolvedSkillTrustPolicy` alongside the loaded skill.
|
|
148
|
+
|
|
149
|
+
## Dependencies
|
|
150
|
+
|
|
151
|
+
- `yaml@^2.8.0` — required runtime dependency. Used for frontmatter
|
|
152
|
+
parsing and migrator output.
|
|
153
|
+
- `@graphorin/core` — typed contracts (`ResolvedTool`, …) consumed by
|
|
154
|
+
the registry's tool-declaration surface.
|
|
155
|
+
- `@graphorin/security` — supply-chain installer, ed25519 signature
|
|
156
|
+
verifier, trust-policy resolver. Skills loaded from `npm-package`
|
|
157
|
+
and `git-repo` sources go through this package.
|
|
158
|
+
- `@graphorin/tools` — peer surface the agent runtime uses to
|
|
159
|
+
register skill-bundled tools after activation. The skills loader
|
|
160
|
+
does not import the runtime registry directly; it returns a typed
|
|
161
|
+
`RegisteredToolDeclaration[]` the runtime consumes.
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
MIT. Copyright © 2026 Oleksiy Stepurenko.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
**Project Graphorin** · v0.5.0 · MIT License · © 2026 Oleksiy Stepurenko · <https://github.com/o-stepper/graphorin>
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/o-stepper/graphorin/main/packages/skills/anthropic-spec-snapshot.json",
|
|
4
|
+
"snapshotDate": "2026-04-19",
|
|
5
|
+
"specSource": "https://agentskills.io/specification",
|
|
6
|
+
"specCommit": null,
|
|
7
|
+
"rationale": "Bundled snapshot of the publicly published `SKILL.md` file-format specification used to determine which frontmatter fields are recognised by the upstream loader. Updated through the `pnpm run check-anthropic-spec` CI cron whenever the published specification changes.",
|
|
8
|
+
"knownFields": {
|
|
9
|
+
"name": {
|
|
10
|
+
"since": "2025-01-01",
|
|
11
|
+
"required": true,
|
|
12
|
+
"type": "string",
|
|
13
|
+
"stability": "stable"
|
|
14
|
+
},
|
|
15
|
+
"description": {
|
|
16
|
+
"since": "2025-01-01",
|
|
17
|
+
"required": true,
|
|
18
|
+
"type": "string",
|
|
19
|
+
"stability": "stable"
|
|
20
|
+
},
|
|
21
|
+
"license": {
|
|
22
|
+
"since": "2025-06-01",
|
|
23
|
+
"required": false,
|
|
24
|
+
"type": "string",
|
|
25
|
+
"stability": "standardized"
|
|
26
|
+
},
|
|
27
|
+
"compatibility": {
|
|
28
|
+
"since": "2025-06-01",
|
|
29
|
+
"required": false,
|
|
30
|
+
"type": "string",
|
|
31
|
+
"stability": "standardized"
|
|
32
|
+
},
|
|
33
|
+
"metadata": {
|
|
34
|
+
"since": "2025-09-01",
|
|
35
|
+
"required": false,
|
|
36
|
+
"type": "object",
|
|
37
|
+
"stability": "standardized"
|
|
38
|
+
},
|
|
39
|
+
"allowed-tools": {
|
|
40
|
+
"since": "2025-12-01",
|
|
41
|
+
"required": false,
|
|
42
|
+
"type": "string-or-array",
|
|
43
|
+
"stability": "experimental"
|
|
44
|
+
},
|
|
45
|
+
"disable-model-invocation": {
|
|
46
|
+
"since": "2025-09-01",
|
|
47
|
+
"required": false,
|
|
48
|
+
"type": "boolean",
|
|
49
|
+
"stability": "stable"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"graphorinMapping": {
|
|
53
|
+
"graphorin-allowed-tools": {
|
|
54
|
+
"anthropicEquivalent": "allowed-tools",
|
|
55
|
+
"since": "agentskills-2025-12",
|
|
56
|
+
"policy": "deprecate-graphorin-prefix",
|
|
57
|
+
"deprecateAt": "graphorin@0.5",
|
|
58
|
+
"removeAt": "graphorin@1.0"
|
|
59
|
+
},
|
|
60
|
+
"graphorin-version": {
|
|
61
|
+
"anthropicEquivalent": "metadata.version",
|
|
62
|
+
"policy": "co-exist",
|
|
63
|
+
"rationale": "graphorin-version means runtime-compat semver range; metadata.version means skill version"
|
|
64
|
+
},
|
|
65
|
+
"graphorin-runtime-compat": {
|
|
66
|
+
"anthropicEquivalent": null,
|
|
67
|
+
"policy": "graphorin-only",
|
|
68
|
+
"rationale": "Successor naming for graphorin-version (clearer intent); both honoured during transition."
|
|
69
|
+
},
|
|
70
|
+
"graphorin-trust-level": {
|
|
71
|
+
"anthropicEquivalent": null,
|
|
72
|
+
"policy": "graphorin-only"
|
|
73
|
+
},
|
|
74
|
+
"graphorin-allowed-tools-overrides": {
|
|
75
|
+
"anthropicEquivalent": null,
|
|
76
|
+
"policy": "graphorin-only"
|
|
77
|
+
},
|
|
78
|
+
"graphorin-sandbox": {
|
|
79
|
+
"anthropicEquivalent": null,
|
|
80
|
+
"policy": "graphorin-only"
|
|
81
|
+
},
|
|
82
|
+
"graphorin-sensitivity": {
|
|
83
|
+
"anthropicEquivalent": null,
|
|
84
|
+
"policy": "graphorin-only"
|
|
85
|
+
},
|
|
86
|
+
"graphorin-sensitivity-defaults": {
|
|
87
|
+
"anthropicEquivalent": null,
|
|
88
|
+
"policy": "graphorin-only"
|
|
89
|
+
},
|
|
90
|
+
"graphorin-handoff-input-filter": {
|
|
91
|
+
"anthropicEquivalent": null,
|
|
92
|
+
"policy": "graphorin-only"
|
|
93
|
+
},
|
|
94
|
+
"graphorin-disable-model-invocation": {
|
|
95
|
+
"anthropicEquivalent": "disable-model-invocation",
|
|
96
|
+
"policy": "deprecate-graphorin-prefix",
|
|
97
|
+
"deprecateAt": "graphorin@0.5",
|
|
98
|
+
"removeAt": "graphorin@1.0"
|
|
99
|
+
},
|
|
100
|
+
"graphorin-signature": {
|
|
101
|
+
"anthropicEquivalent": null,
|
|
102
|
+
"policy": "graphorin-only"
|
|
103
|
+
},
|
|
104
|
+
"graphorin-tools": {
|
|
105
|
+
"anthropicEquivalent": null,
|
|
106
|
+
"policy": "graphorin-only"
|
|
107
|
+
},
|
|
108
|
+
"graphorin-anthropic-spec": {
|
|
109
|
+
"anthropicEquivalent": null,
|
|
110
|
+
"policy": "graphorin-only",
|
|
111
|
+
"rationale": "Author-declared snapshot date the skill was tested against."
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { SlashCommandActivation } from "../types/index.js";
|
|
2
|
+
|
|
3
|
+
//#region src/activation/index.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parse a single message body for a `/skill:<name>` invocation. The
|
|
7
|
+
* grammar accepts:
|
|
8
|
+
*
|
|
9
|
+
* ```
|
|
10
|
+
* /skill:<name>
|
|
11
|
+
* /skill:<name> <free-form-args>
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* `<name>` must match `^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$` (kebab-case
|
|
15
|
+
* conventional). Whitespace before the leading `/` is tolerated; any
|
|
16
|
+
* other prefix triggers a {@link SlashCommandParseError}.
|
|
17
|
+
*
|
|
18
|
+
* @stable
|
|
19
|
+
*/
|
|
20
|
+
declare function parseSlashCommand(raw: string): SlashCommandActivation;
|
|
21
|
+
/**
|
|
22
|
+
* Convenience predicate. Returns `true` when {@link parseSlashCommand}
|
|
23
|
+
* would succeed against the supplied body.
|
|
24
|
+
*
|
|
25
|
+
* @stable
|
|
26
|
+
*/
|
|
27
|
+
declare function isSlashCommand(raw: string): boolean;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { isSlashCommand, parseSlashCommand };
|
|
30
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/activation/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;;;iBAsCgB,iBAAA,eAAgC;;;;;;;iBA8BhC,cAAA"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { SlashCommandParseError } from "../errors/index.js";
|
|
2
|
+
|
|
3
|
+
//#region src/activation/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* Activation surface for `@graphorin/skills`.
|
|
6
|
+
*
|
|
7
|
+
* Two activation paths are supported per DEC-206:
|
|
8
|
+
*
|
|
9
|
+
* 1. **Auto** — every step the agent runtime injects the metadata of
|
|
10
|
+
* every registered skill into the system prompt; the model elects
|
|
11
|
+
* a skill by invoking the synthetic `activate_skill(name)` tool.
|
|
12
|
+
* Skills with `disable-model-invocation: true` are excluded from
|
|
13
|
+
* the metadata advertisement so the model never sees them.
|
|
14
|
+
* 2. **Slash command** — the user types `/skill:<name>` (optionally
|
|
15
|
+
* followed by free-form arguments) and the agent runtime activates
|
|
16
|
+
* the matched skill regardless of `disable-model-invocation`.
|
|
17
|
+
*
|
|
18
|
+
* The runtime owns the actual activation; this module only parses the
|
|
19
|
+
* inputs into a structured payload.
|
|
20
|
+
*
|
|
21
|
+
* @packageDocumentation
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Parse a single message body for a `/skill:<name>` invocation. The
|
|
25
|
+
* grammar accepts:
|
|
26
|
+
*
|
|
27
|
+
* ```
|
|
28
|
+
* /skill:<name>
|
|
29
|
+
* /skill:<name> <free-form-args>
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* `<name>` must match `^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$` (kebab-case
|
|
33
|
+
* conventional). Whitespace before the leading `/` is tolerated; any
|
|
34
|
+
* other prefix triggers a {@link SlashCommandParseError}.
|
|
35
|
+
*
|
|
36
|
+
* @stable
|
|
37
|
+
*/
|
|
38
|
+
function parseSlashCommand(raw) {
|
|
39
|
+
if (typeof raw !== "string" || raw.length === 0) throw new SlashCommandParseError(raw ?? "", "message body must be a non-empty string.");
|
|
40
|
+
const trimmed = raw.replace(/^\s+/u, "");
|
|
41
|
+
if (!trimmed.startsWith("/skill:")) throw new SlashCommandParseError(raw, "message must begin with '/skill:'.");
|
|
42
|
+
const remainder = trimmed.slice(7);
|
|
43
|
+
if (remainder.length === 0) throw new SlashCommandParseError(raw, "missing skill name after '/skill:'.");
|
|
44
|
+
const match = /^([A-Za-z0-9][A-Za-z0-9_.-]{0,127})(?:\s+([\s\S]*))?$/u.exec(remainder);
|
|
45
|
+
if (match === null) throw new SlashCommandParseError(raw, "skill name must match /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/.");
|
|
46
|
+
const name = match[1] ?? "";
|
|
47
|
+
const args = (match[2] ?? "").trim();
|
|
48
|
+
return Object.freeze({
|
|
49
|
+
name,
|
|
50
|
+
args,
|
|
51
|
+
raw
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Convenience predicate. Returns `true` when {@link parseSlashCommand}
|
|
56
|
+
* would succeed against the supplied body.
|
|
57
|
+
*
|
|
58
|
+
* @stable
|
|
59
|
+
*/
|
|
60
|
+
function isSlashCommand(raw) {
|
|
61
|
+
try {
|
|
62
|
+
parseSlashCommand(raw);
|
|
63
|
+
return true;
|
|
64
|
+
} catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
export { isSlashCommand, parseSlashCommand };
|
|
71
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/activation/index.ts"],"sourcesContent":["/**\n * Activation surface for `@graphorin/skills`.\n *\n * Two activation paths are supported per DEC-206:\n *\n * 1. **Auto** — every step the agent runtime injects the metadata of\n * every registered skill into the system prompt; the model elects\n * a skill by invoking the synthetic `activate_skill(name)` tool.\n * Skills with `disable-model-invocation: true` are excluded from\n * the metadata advertisement so the model never sees them.\n * 2. **Slash command** — the user types `/skill:<name>` (optionally\n * followed by free-form arguments) and the agent runtime activates\n * the matched skill regardless of `disable-model-invocation`.\n *\n * The runtime owns the actual activation; this module only parses the\n * inputs into a structured payload.\n *\n * @packageDocumentation\n */\n\nimport { SlashCommandParseError } from '../errors/index.js';\nimport type { SlashCommandActivation } from '../types/index.js';\n\n/**\n * Parse a single message body for a `/skill:<name>` invocation. The\n * grammar accepts:\n *\n * ```\n * /skill:<name>\n * /skill:<name> <free-form-args>\n * ```\n *\n * `<name>` must match `^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$` (kebab-case\n * conventional). Whitespace before the leading `/` is tolerated; any\n * other prefix triggers a {@link SlashCommandParseError}.\n *\n * @stable\n */\nexport function parseSlashCommand(raw: string): SlashCommandActivation {\n if (typeof raw !== 'string' || raw.length === 0) {\n throw new SlashCommandParseError(raw ?? '', 'message body must be a non-empty string.');\n }\n const trimmed = raw.replace(/^\\s+/u, '');\n if (!trimmed.startsWith('/skill:')) {\n throw new SlashCommandParseError(raw, \"message must begin with '/skill:'.\");\n }\n const remainder = trimmed.slice('/skill:'.length);\n if (remainder.length === 0) {\n throw new SlashCommandParseError(raw, \"missing skill name after '/skill:'.\");\n }\n const match = /^([A-Za-z0-9][A-Za-z0-9_.-]{0,127})(?:\\s+([\\s\\S]*))?$/u.exec(remainder);\n if (match === null) {\n throw new SlashCommandParseError(\n raw,\n 'skill name must match /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/.',\n );\n }\n const name = match[1] ?? '';\n const args = (match[2] ?? '').trim();\n return Object.freeze({ name, args, raw });\n}\n\n/**\n * Convenience predicate. Returns `true` when {@link parseSlashCommand}\n * would succeed against the supplied body.\n *\n * @stable\n */\nexport function isSlashCommand(raw: string): boolean {\n try {\n parseSlashCommand(raw);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,SAAgB,kBAAkB,KAAqC;AACrE,KAAI,OAAO,QAAQ,YAAY,IAAI,WAAW,EAC5C,OAAM,IAAI,uBAAuB,OAAO,IAAI,2CAA2C;CAEzF,MAAM,UAAU,IAAI,QAAQ,SAAS,GAAG;AACxC,KAAI,CAAC,QAAQ,WAAW,UAAU,CAChC,OAAM,IAAI,uBAAuB,KAAK,qCAAqC;CAE7E,MAAM,YAAY,QAAQ,MAAM,EAAiB;AACjD,KAAI,UAAU,WAAW,EACvB,OAAM,IAAI,uBAAuB,KAAK,sCAAsC;CAE9E,MAAM,QAAQ,yDAAyD,KAAK,UAAU;AACtF,KAAI,UAAU,KACZ,OAAM,IAAI,uBACR,KACA,8DACD;CAEH,MAAM,OAAO,MAAM,MAAM;CACzB,MAAM,QAAQ,MAAM,MAAM,IAAI,MAAM;AACpC,QAAO,OAAO,OAAO;EAAE;EAAM;EAAM;EAAK,CAAC;;;;;;;;AAS3C,SAAgB,eAAe,KAAsB;AACnD,KAAI;AACF,oBAAkB,IAAI;AACtB,SAAO;SACD;AACN,SAAO"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
//#region src/errors/index.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Typed error hierarchy for `@graphorin/skills`.
|
|
4
|
+
*
|
|
5
|
+
* Every error carries a stable lowercase `kind` discriminator so the
|
|
6
|
+
* agent runtime, the CLI, and the audit emitter can branch without
|
|
7
|
+
* resorting to `instanceof` chains, plus an optional `hint` field with
|
|
8
|
+
* an actionable remediation step (a CLI command or a documentation
|
|
9
|
+
* link).
|
|
10
|
+
*
|
|
11
|
+
* @packageDocumentation
|
|
12
|
+
*/
|
|
13
|
+
/** Base class for every error thrown by `@graphorin/skills`. */
|
|
14
|
+
declare abstract class GraphorinSkillsError extends Error {
|
|
15
|
+
abstract readonly kind: string;
|
|
16
|
+
readonly hint?: string;
|
|
17
|
+
readonly cause?: unknown;
|
|
18
|
+
constructor(message: string, options?: {
|
|
19
|
+
hint?: string;
|
|
20
|
+
cause?: unknown;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The frontmatter validator detected an Anthropic-base / `graphorin-*`
|
|
25
|
+
* collision and the operator-resolved policy is `'error'`.
|
|
26
|
+
*/
|
|
27
|
+
declare class SkillFrontmatterConflictError extends GraphorinSkillsError {
|
|
28
|
+
readonly kind: "frontmatter:conflict";
|
|
29
|
+
readonly skillName: string;
|
|
30
|
+
readonly field: string;
|
|
31
|
+
readonly conflictingFields: ReadonlyArray<string>;
|
|
32
|
+
constructor(skillName: string, field: string, conflictingFields: ReadonlyArray<string>, options?: {
|
|
33
|
+
hint?: string;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/** A skill manifest could not be parsed (missing frontmatter / invalid YAML). */
|
|
37
|
+
declare class SkillManifestParseError extends GraphorinSkillsError {
|
|
38
|
+
readonly kind: "manifest:parse";
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The runtime-compat declaration on a skill does not satisfy the
|
|
42
|
+
* loader's installed runtime version.
|
|
43
|
+
*/
|
|
44
|
+
declare class SkillRuntimeCompatError extends GraphorinSkillsError {
|
|
45
|
+
readonly kind: "runtime-compat:mismatch";
|
|
46
|
+
readonly skillName: string;
|
|
47
|
+
readonly declared: string;
|
|
48
|
+
readonly installed: string;
|
|
49
|
+
constructor(skillName: string, declared: string, installed: string, options?: {
|
|
50
|
+
hint?: string;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/** A required Anthropic-base field is missing from the frontmatter. */
|
|
54
|
+
declare class SkillRequiredFieldMissingError extends GraphorinSkillsError {
|
|
55
|
+
readonly kind: "frontmatter:missing-required";
|
|
56
|
+
readonly field: string;
|
|
57
|
+
constructor(field: string, options?: {
|
|
58
|
+
hint?: string;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* `Agent.toTool()` / `handoff(...)` would be invoked inside an
|
|
63
|
+
* untrusted skill, but the skill did not declare
|
|
64
|
+
* `graphorin-handoff-input-filter`. Throwing the error at activation
|
|
65
|
+
* time prevents the runtime from materialising a sub-agent without an
|
|
66
|
+
* explicit filter.
|
|
67
|
+
*/
|
|
68
|
+
declare class InputFilterRequiredError extends GraphorinSkillsError {
|
|
69
|
+
readonly kind: "handoff:input-filter-required";
|
|
70
|
+
readonly skillName: string;
|
|
71
|
+
constructor(skillName: string, options?: {
|
|
72
|
+
hint?: string;
|
|
73
|
+
cause?: unknown;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/** A skill source could not be loaded from disk. */
|
|
77
|
+
declare class SkillLoadError extends GraphorinSkillsError {
|
|
78
|
+
readonly kind: "load:failed";
|
|
79
|
+
readonly source: string;
|
|
80
|
+
constructor(source: string, message: string, options?: {
|
|
81
|
+
hint?: string;
|
|
82
|
+
cause?: unknown;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/** A skill name in a registry registration collided with another already-loaded skill. */
|
|
86
|
+
declare class SkillNameCollisionError extends GraphorinSkillsError {
|
|
87
|
+
readonly kind: "registry:name-collision";
|
|
88
|
+
readonly skillName: string;
|
|
89
|
+
constructor(skillName: string, options?: {
|
|
90
|
+
hint?: string;
|
|
91
|
+
cause?: unknown;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The slash-command parser received a string that did not match the
|
|
96
|
+
* `/skill:<name>` grammar.
|
|
97
|
+
*/
|
|
98
|
+
declare class SlashCommandParseError extends GraphorinSkillsError {
|
|
99
|
+
readonly kind: "slash:parse";
|
|
100
|
+
readonly raw: string;
|
|
101
|
+
constructor(raw: string, message: string, options?: {
|
|
102
|
+
hint?: string;
|
|
103
|
+
cause?: unknown;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/** Convenience union — every `kind` discriminator the package may emit. */
|
|
107
|
+
type GraphorinSkillsErrorKind = 'frontmatter:conflict' | 'manifest:parse' | 'runtime-compat:mismatch' | 'frontmatter:missing-required' | 'handoff:input-filter-required' | 'load:failed' | 'registry:name-collision' | 'slash:parse';
|
|
108
|
+
//#endregion
|
|
109
|
+
export { GraphorinSkillsError, GraphorinSkillsErrorKind, InputFilterRequiredError, SkillFrontmatterConflictError, SkillLoadError, SkillManifestParseError, SkillNameCollisionError, SkillRequiredFieldMissingError, SkillRuntimeCompatError, SlashCommandParseError };
|
|
110
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/errors/index.ts"],"sourcesContent":[],"mappings":";;AAaA;AAgBA;;;;;AAwBA;AAQA;AAiBA;AAgBA;AAkBA;AAUa,uBA7GS,oBAAA,SAA6B,KAAA,CA6Gc;EAkBpD,kBAAA,IAAA,EAAA,MAAuB;EAaxB,SAAA,IAAA,CAAA,EAAA,MAAA;;;;;;;;;;;cA5HC,6BAAA,SAAsC,oBAAA;;;;8BAIrB;mEAIP;;;;;cAgBV,uBAAA,SAAgC,oBAAA;;;;;;;cAQhC,uBAAA,SAAgC,oBAAA;;;;;;;;;;cAiBhC,8BAAA,SAAuC,oBAAA;;;;;;;;;;;;;;cAgBvC,wBAAA,SAAiC,oBAAA;;;;;;;;;cAkBjC,cAAA,SAAuB,oBAAA;;;;;;;;;cAUvB,uBAAA,SAAgC,oBAAA;;;;;;;;;;;;cAkBhC,sBAAA,SAA+B,oBAAA;;;;;;;;;KAahC,wBAAA"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
//#region src/errors/index.ts
|
|
2
|
+
/**
|
|
3
|
+
* Typed error hierarchy for `@graphorin/skills`.
|
|
4
|
+
*
|
|
5
|
+
* Every error carries a stable lowercase `kind` discriminator so the
|
|
6
|
+
* agent runtime, the CLI, and the audit emitter can branch without
|
|
7
|
+
* resorting to `instanceof` chains, plus an optional `hint` field with
|
|
8
|
+
* an actionable remediation step (a CLI command or a documentation
|
|
9
|
+
* link).
|
|
10
|
+
*
|
|
11
|
+
* @packageDocumentation
|
|
12
|
+
*/
|
|
13
|
+
/** Base class for every error thrown by `@graphorin/skills`. */
|
|
14
|
+
var GraphorinSkillsError = class extends Error {
|
|
15
|
+
hint;
|
|
16
|
+
cause;
|
|
17
|
+
constructor(message, options) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = new.target.name;
|
|
20
|
+
if (options?.hint !== void 0) this.hint = options.hint;
|
|
21
|
+
if (options?.cause !== void 0) this.cause = options.cause;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The frontmatter validator detected an Anthropic-base / `graphorin-*`
|
|
26
|
+
* collision and the operator-resolved policy is `'error'`.
|
|
27
|
+
*/
|
|
28
|
+
var SkillFrontmatterConflictError = class extends GraphorinSkillsError {
|
|
29
|
+
kind = "frontmatter:conflict";
|
|
30
|
+
skillName;
|
|
31
|
+
field;
|
|
32
|
+
conflictingFields;
|
|
33
|
+
constructor(skillName, field, conflictingFields, options) {
|
|
34
|
+
super(`Skill '${skillName}' frontmatter conflict on '${field}' (also set: ${conflictingFields.filter((f) => f !== field).join(", ")}). Validator policy: 'error'.`, options);
|
|
35
|
+
this.skillName = skillName;
|
|
36
|
+
this.field = field;
|
|
37
|
+
this.conflictingFields = conflictingFields;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
/** A skill manifest could not be parsed (missing frontmatter / invalid YAML). */
|
|
41
|
+
var SkillManifestParseError = class extends GraphorinSkillsError {
|
|
42
|
+
kind = "manifest:parse";
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* The runtime-compat declaration on a skill does not satisfy the
|
|
46
|
+
* loader's installed runtime version.
|
|
47
|
+
*/
|
|
48
|
+
var SkillRuntimeCompatError = class extends GraphorinSkillsError {
|
|
49
|
+
kind = "runtime-compat:mismatch";
|
|
50
|
+
skillName;
|
|
51
|
+
declared;
|
|
52
|
+
installed;
|
|
53
|
+
constructor(skillName, declared, installed, options) {
|
|
54
|
+
super(`Skill '${skillName}' declares runtime-compat '${declared}' which does not match installed Graphorin '${installed}'.`, options);
|
|
55
|
+
this.skillName = skillName;
|
|
56
|
+
this.declared = declared;
|
|
57
|
+
this.installed = installed;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
/** A required Anthropic-base field is missing from the frontmatter. */
|
|
61
|
+
var SkillRequiredFieldMissingError = class extends GraphorinSkillsError {
|
|
62
|
+
kind = "frontmatter:missing-required";
|
|
63
|
+
field;
|
|
64
|
+
constructor(field, options) {
|
|
65
|
+
super(`Skill frontmatter is missing required field '${field}'.`, options);
|
|
66
|
+
this.field = field;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* `Agent.toTool()` / `handoff(...)` would be invoked inside an
|
|
71
|
+
* untrusted skill, but the skill did not declare
|
|
72
|
+
* `graphorin-handoff-input-filter`. Throwing the error at activation
|
|
73
|
+
* time prevents the runtime from materialising a sub-agent without an
|
|
74
|
+
* explicit filter.
|
|
75
|
+
*/
|
|
76
|
+
var InputFilterRequiredError = class extends GraphorinSkillsError {
|
|
77
|
+
kind = "handoff:input-filter-required";
|
|
78
|
+
skillName;
|
|
79
|
+
constructor(skillName, options) {
|
|
80
|
+
super(`Untrusted skill '${skillName}' uses Agent.toTool()/handoff() but did not declare 'graphorin-handoff-input-filter'.`, {
|
|
81
|
+
hint: options?.hint ?? "Add 'graphorin-handoff-input-filter: lastUser' (or another supported value) to the SKILL.md frontmatter.",
|
|
82
|
+
...options?.cause === void 0 ? {} : { cause: options.cause }
|
|
83
|
+
});
|
|
84
|
+
this.skillName = skillName;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
/** A skill source could not be loaded from disk. */
|
|
88
|
+
var SkillLoadError = class extends GraphorinSkillsError {
|
|
89
|
+
kind = "load:failed";
|
|
90
|
+
source;
|
|
91
|
+
constructor(source, message, options) {
|
|
92
|
+
super(`Skill load failed (source='${source}'): ${message}`, options);
|
|
93
|
+
this.source = source;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
/** A skill name in a registry registration collided with another already-loaded skill. */
|
|
97
|
+
var SkillNameCollisionError = class extends GraphorinSkillsError {
|
|
98
|
+
kind = "registry:name-collision";
|
|
99
|
+
skillName;
|
|
100
|
+
constructor(skillName, options) {
|
|
101
|
+
super(`Skill '${skillName}' is already registered.`, {
|
|
102
|
+
hint: options?.hint ?? "Either rename one of the colliding skills or call `registry.unregister(name)` before re-registering.",
|
|
103
|
+
...options?.cause === void 0 ? {} : { cause: options.cause }
|
|
104
|
+
});
|
|
105
|
+
this.skillName = skillName;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* The slash-command parser received a string that did not match the
|
|
110
|
+
* `/skill:<name>` grammar.
|
|
111
|
+
*/
|
|
112
|
+
var SlashCommandParseError = class extends GraphorinSkillsError {
|
|
113
|
+
kind = "slash:parse";
|
|
114
|
+
raw;
|
|
115
|
+
constructor(raw, message, options) {
|
|
116
|
+
super(`Slash command '${raw}': ${message}`, {
|
|
117
|
+
hint: options?.hint ?? "Use `/skill:<name>` to activate a registered skill.",
|
|
118
|
+
...options?.cause === void 0 ? {} : { cause: options.cause }
|
|
119
|
+
});
|
|
120
|
+
this.raw = raw;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
//#endregion
|
|
125
|
+
export { GraphorinSkillsError, InputFilterRequiredError, SkillFrontmatterConflictError, SkillLoadError, SkillManifestParseError, SkillNameCollisionError, SkillRequiredFieldMissingError, SkillRuntimeCompatError, SlashCommandParseError };
|
|
126
|
+
//# sourceMappingURL=index.js.map
|