@getbourdon/participants 0.5.5 → 0.6.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.
@@ -0,0 +1,295 @@
1
+ /**
2
+ * Skill readers: advertise the local skills an agent can see in its L5
3
+ * manifest (plan 005, discovery side). Port of `participants/_skills.py`.
4
+ *
5
+ * A participant that knows where its agent keeps skills (a folder per skill
6
+ * holding a `SKILL.md` with `name` / `description` front matter) lists them as
7
+ * `SkillRef` rows: name, description and a content hash. Nothing is shared or
8
+ * promoted here; the body never enters the manifest.
9
+ *
10
+ * Two kinds of skill directory:
11
+ * - **The agent's own** (`~/.claude/skills`, `$CODEX_HOME/skills`,
12
+ * `~/.cursor/skills` and their project forms).
13
+ * - **The cross-agent store**, `~/.agents/skills` (and `<project>/.agents/skills`):
14
+ * a vendor-neutral directory no single agent owns. The cross-agent skills
15
+ * installer CLI keeps its installs there and records each one in
16
+ * `~/.agents/.skill-lock.json`; people also write skills there by hand. Refs
17
+ * from it carry `store: cross_agent`. Every skill-reading participant reads
18
+ * it with its own directories; a reader of several manifests collapses the
19
+ * copies (`collectAdvertisedSkills` in @getbourdon/federation).
20
+ *
21
+ * One skill, one ref per manifest: when an agent's own folder holds a skill
22
+ * that is also in the cross-agent store, the agent-folder ref is kept and
23
+ * records the store copy in `also_in` (`via: copy` for a copy with the same
24
+ * `content_sha256`, `via: link` for a link into the store); the store's own ref
25
+ * is then left out. A link counts only when it resolves to a direct child
26
+ * folder of the store at the same scope, and the skill is then read through
27
+ * the store's own path, never through the link. Any other link is skipped.
28
+ *
29
+ * Provenance (user-level store only): a skill with an entry in the installer
30
+ * lock carries `origin: {kind: installed, ...}`; one without carries
31
+ * `origin: {kind: authored}`. Nothing else is read from the lock, and a
32
+ * missing, linked, oversized or malformed lock never fails a scan.
33
+ *
34
+ * Scope: one skill at a time. By default a participant lists only the skills
35
+ * the person approved one by one with `bourdon skills share` (stored locally
36
+ * in `<library>/.skill-shares.json`, see skill-shares.ts), and only while the
37
+ * folder still hashes to the approved content; no other skill folder is read.
38
+ * `BOURDON_SKILLS_ADVERTISE` set to an on value lists every skill; an off
39
+ * value or an unrecognised one lists none, shared or not. A caller that builds
40
+ * a participant with explicit `skillSources` lists every skill in them
41
+ * ({@link skillsListingMode}). The directories: user-level skill directories,
42
+ * and a project's directories
43
+ * only when the project is named in `BOURDON_SKILL_PROJECTS` (absolute paths
44
+ * separated by the platform's path delimiter). Disallowed files are skipped
45
+ * and flagged, never fatal; no link is followed; no absolute path reaches the
46
+ * manifest (`path_hint` starts at a symbolic root); refs are emitted as
47
+ * `team`.
48
+ */
49
+ import { type SkillOriginModel, type SkillRefModel } from "@getbourdon/l5";
50
+ /** Opt-in list of project roots whose skill directories may be indexed. */
51
+ export declare const SKILL_PROJECTS_ENV = "BOURDON_SKILL_PROJECTS";
52
+ /**
53
+ * The listing switch for every participant. Skill descriptions are free text
54
+ * and can name private projects, clients or infrastructure, so by default
55
+ * (unset or empty) only the skills approved one by one are listed. `1` /
56
+ * `true` / `yes` / `on` list every skill; `0` / `false` / `no` / `off` list
57
+ * none, approved or not, and so does any other value (fail closed): a typo
58
+ * never lists anything.
59
+ */
60
+ export declare const SKILLS_ADVERTISE_ENV = "BOURDON_SKILLS_ADVERTISE";
61
+ /**
62
+ * Turns named participants off in every mode: agent ids separated by commas
63
+ * (or whitespace), e.g. `codex,cursor`. Matching ignores case.
64
+ */
65
+ export declare const SKILLS_NO_ADVERTISE_ENV = "BOURDON_SKILLS_NO_ADVERTISE";
66
+ /** {@link skillsListingMode} results. */
67
+ export declare const LIST_ALL = "all";
68
+ export declare const LIST_APPROVED = "approved";
69
+ export declare const LIST_OFF = "off";
70
+ export type SkillsListingMode = typeof LIST_ALL | typeof LIST_APPROVED | typeof LIST_OFF;
71
+ /** Every advertised ref carries this visibility. */
72
+ export declare const SKILL_REF_VISIBILITY: "team";
73
+ /** The L5 schema bounds (`skills.maxItems` and the `SkillRef` fields). */
74
+ export declare const MAX_SKILL_REFS = 512;
75
+ export declare const MAX_SKIPPED_FILES = 32;
76
+ export declare const MAX_PATH_HINT_CHARS = 255;
77
+ export declare const MAX_PROJECT_NAME_CHARS = 128;
78
+ export declare const MAX_ORIGIN_SOURCE_CHARS = 255;
79
+ export declare const MAX_ORIGIN_URL_CHARS = 512;
80
+ export declare const MAX_ORIGIN_SOURCE_TYPE_CHARS = 32;
81
+ /** An installer lock larger than this is not read (origin is then omitted). */
82
+ export declare const MAX_LOCK_BYTES: number;
83
+ export declare const SCOPE_USER = "user";
84
+ export declare const SCOPE_PROJECT = "project";
85
+ /** Whose directory a skill folder sits in; `agent` is the default and never written. */
86
+ export declare const STORE_AGENT = "agent";
87
+ export declare const STORE_CROSS_AGENT = "cross_agent";
88
+ export declare const VIA_COPY = "copy";
89
+ export declare const VIA_LINK = "link";
90
+ export declare const ORIGIN_INSTALLED = "installed";
91
+ export declare const ORIGIN_AUTHORED = "authored";
92
+ /** The cross-agent installer's lock file, beside the user-level store. */
93
+ export declare const CROSS_AGENT_LOCK_NAME = ".skill-lock.json";
94
+ /**
95
+ * One directory a reader indexes, one skill per child folder. `label` is the
96
+ * symbolic form of `root` that `path_hint` starts with; it never contains an
97
+ * absolute path. `store` says whose directory it is; `lockFile` is the
98
+ * installer lock that records where the store's skills came from.
99
+ */
100
+ export interface SkillSource {
101
+ root: string;
102
+ scope: string;
103
+ label: string;
104
+ project?: string | null;
105
+ store?: string;
106
+ lockFile?: string | null;
107
+ }
108
+ /** A skill (or a whole skills directory) that was not advertised, and why. */
109
+ export interface SkippedSkill {
110
+ pathHint: string;
111
+ scope: string;
112
+ reason: string;
113
+ project?: string | null;
114
+ }
115
+ export interface SkillScanReport {
116
+ refs: SkillRefModel[];
117
+ skipped: SkippedSkill[];
118
+ }
119
+ /** The home directory user-level skill directories are found under. */
120
+ export declare function userHome(): string;
121
+ /**
122
+ * Project roots opted in through {@link SKILL_PROJECTS_ENV}, in order. Only
123
+ * absolute paths count: a relative entry is ignored (and logged) rather than
124
+ * resolved against whatever directory the export happens to run in.
125
+ */
126
+ export declare function skillProjects(): string[];
127
+ export interface SkillSourceOptions {
128
+ /** The home directory to use instead of {@link userHome}. */
129
+ home?: string;
130
+ /** Project roots instead of {@link skillProjects}. */
131
+ projects?: readonly string[];
132
+ }
133
+ /**
134
+ * Claude Code: `<config dir>/skills` and, on opt-in, `<project>/.claude/skills`.
135
+ * The config directory is `CLAUDE_CONFIG_DIR` (Claude Code's own override),
136
+ * else `CLAUDE_HOME`, else `~/.claude`.
137
+ */
138
+ export declare function claudeCodeSkillSources(options?: SkillSourceOptions): SkillSource[];
139
+ /**
140
+ * Codex: `$CODEX_HOME/skills`. `codexHome` is an explicit Codex home, if one
141
+ * was given; otherwise `CODEX_HOME`, else `~/.codex`. Codex keeps its bundled
142
+ * skills in a hidden `.system` folder there, which is not indexed. The mirror
143
+ * has no Codex participant yet; this is the source a Codex reader would use.
144
+ */
145
+ export declare function codexSkillSources(options?: {
146
+ codexHome?: string | null;
147
+ home?: string;
148
+ }): SkillSource[];
149
+ /**
150
+ * Cursor: `~/.cursor/skills` and, on opt-in, `<project>/.cursor/skills`.
151
+ * `CURSOR_HOME` overrides `~/.cursor`. `~/.cursor/skills-cursor` holds
152
+ * Cursor's own built-in skills and is not indexed.
153
+ */
154
+ export declare function cursorSkillSources(options?: SkillSourceOptions): SkillSource[];
155
+ /**
156
+ * The vendor-neutral store: `~/.agents/skills` and, on opt-in,
157
+ * `<project>/.agents/skills`. The user-level store comes with the installer
158
+ * lock for provenance; no lock is read for a project store.
159
+ */
160
+ export declare function crossAgentSkillSources(options?: SkillSourceOptions): SkillSource[];
161
+ type LockEntry = Record<string, unknown>;
162
+ /**
163
+ * The `skills` table of the installer lock, keyed by skill name. An empty map
164
+ * when there is no lock (every store skill is then `authored`); `null` when a
165
+ * lock exists but cannot be trusted or parsed (a link, not a regular file, too
166
+ * large, not JSON, or without a `skills` mapping), in which case origin is left
167
+ * off rather than guessed.
168
+ */
169
+ export declare function readInstallLock(path: string): Map<string, LockEntry> | null;
170
+ /**
171
+ * `installed` with the lock entry's sanitised source fields, else `authored`.
172
+ * Reads only `source`, `sourceType`, `sourceUrl` and `updatedAt`; a field
173
+ * that is missing, of the wrong type or fails its check is left out.
174
+ */
175
+ export declare function originFromLockEntry(entry: LockEntry | null | undefined): SkillOriginModel;
176
+ /** Which child folders of a source to read: absent reads every one. */
177
+ export type SkillFolderFilter = (source: SkillSource, folderName: string) => boolean;
178
+ export interface ScanSkillSourcesOptions {
179
+ /**
180
+ * Limits the scan to the child folders it accepts; every other folder is
181
+ * passed over unread. A store folder that an accepted agent folder links to
182
+ * is read too, so the link resolves as in a full scan. An accepted agent
183
+ * folder that is a copy of a store folder the scan did not read carries no
184
+ * `also_in`.
185
+ */
186
+ include?: SkillFolderFilter | null;
187
+ }
188
+ /**
189
+ * Index every source; one ref per skill, first occurrence of a name wins per
190
+ * store and scope. Cross-agent stores are scanned first so the agent's own
191
+ * folders can be matched against them (`also_in`). Refs come back with the
192
+ * agent's own folders first, in source order, then the cross-agent store
193
+ * skills that no advertised agent folder already represents.
194
+ */
195
+ export declare function scanSkillSources(sources: Iterable<SkillSource>, options?: ScanSkillSourcesOptions): SkillScanReport;
196
+ /** A participant that advertises skills (plan 005). */
197
+ export interface SkillAdvertiser {
198
+ agentId: string;
199
+ exportSkills(): SkillRefModel[];
200
+ /**
201
+ * Whether the participant was built with explicit `skillSources`, which
202
+ * lists every skill in them ({@link skillsListingMode}). Absent = no.
203
+ */
204
+ hasExplicitSkillSources?(): boolean;
205
+ /**
206
+ * The directories the reader indexes. When present, the default
207
+ * (approved-only) mode reads just the approved folders in them; without it
208
+ * the reader's own scan runs and only approved refs are kept.
209
+ */
210
+ skillSources?(): SkillSource[];
211
+ }
212
+ /**
213
+ * Which skills the participant `agentId` may list in its manifest.
214
+ *
215
+ * - {@link LIST_APPROVED}: {@link SKILLS_ADVERTISE_ENV} unset or empty: only
216
+ * the skills approved one by one (`bourdon skills share`) whose folder still
217
+ * hashes to the approved content.
218
+ * - {@link LIST_ALL}: an on value; or, while the variable is unset, a
219
+ * participant built with explicit `skillSources` (the caller chose what to
220
+ * index).
221
+ * - {@link LIST_OFF}: an off value, or any unrecognised one (fail closed):
222
+ * nothing is listed, approved or not.
223
+ *
224
+ * {@link SKILLS_NO_ADVERTISE_ENV} turns named participants off in every mode.
225
+ */
226
+ export declare function skillsListingMode(agentId: string, options?: {
227
+ explicitSources?: boolean;
228
+ }): SkillsListingMode;
229
+ /**
230
+ * Whether the participant `agentId` lists every skill it can see: true only
231
+ * in {@link LIST_ALL} mode ({@link skillsListingMode}). In the default mode a
232
+ * participant lists only the skills shared one by one.
233
+ */
234
+ export declare function skillsAdvertisingEnabled(agentId: string, options?: {
235
+ explicitSources?: boolean;
236
+ }): boolean;
237
+ /**
238
+ * The refs `participant`'s manifest carries, for `exportL5`.
239
+ *
240
+ * Follows {@link skillsListingMode}: nothing, and no skill directory read,
241
+ * when off; by default only the skills with a current approval, reading only
242
+ * their folders; every skill the reader sees when listing all. A participant
243
+ * built with explicit `skillSources` lists all unless turned off. A failure
244
+ * here never fails an export: skills are an addition to the manifest, not a
245
+ * reason to lose its memory rows. Private refs are dropped, as private
246
+ * entities are.
247
+ */
248
+ export declare function advertisedSkills(participant: SkillAdvertiser): SkillRefModel[];
249
+ /**
250
+ * The default skill directories of a built-in skill-reading participant.
251
+ *
252
+ * The same directories the participant's own export reads (its user-level
253
+ * directory honouring its home override, the opted-in projects, and the
254
+ * cross-agent store). `null` for an agent with no skill reader here, so a skill
255
+ * it advertises can be listed but not proposed from this machine.
256
+ */
257
+ export declare function skillSourcesForAgent(agentId: string): SkillSource[] | null;
258
+ /**
259
+ * The folder an advertised ref was scanned from, found through `sources` only.
260
+ *
261
+ * `ref` is a SkillRef as it sits in a manifest. Its `path_hint` is matched
262
+ * against each source's symbolic label (same scope and project), so the result
263
+ * is always one direct child of a directory the reader indexes; a path from
264
+ * anywhere else cannot be produced. A ref that is a link into the cross-agent
265
+ * store resolves to the store's own folder, as the reader read it. `null` when
266
+ * nothing matches.
267
+ */
268
+ export declare function resolveAdvertisedFolder(sources: readonly SkillSource[], ref: Readonly<Record<string, unknown>>): string | null;
269
+ /** The built-in skill readers, in listing order. */
270
+ export declare const SKILL_READERS: readonly ["claude-code", "codex", "cursor"];
271
+ /**
272
+ * One skill folder a built-in reader sees, for `bourdon skills available|share`.
273
+ *
274
+ * `agent` is the reader that owns the folder, or `null` for a cross-agent
275
+ * store folder; `agents` names every reader that sees it. Local use only:
276
+ * `folder` is an absolute path and never leaves the terminal command that
277
+ * asked for it.
278
+ */
279
+ export interface LocalSkill {
280
+ ref: SkillRefModel;
281
+ agent: string | null;
282
+ agents: string[];
283
+ folder: string;
284
+ /** `agent` or `cross_agent`. */
285
+ store: string;
286
+ }
287
+ /**
288
+ * Every skill folder the built-in readers see on this machine (each one is
289
+ * read). The same directories the readers' exports use: user-level, opted-in
290
+ * projects and the cross-agent store. One row per folder; a store folder seen
291
+ * by several readers is one row naming them all. `agent` keeps the folders
292
+ * that one reader sees.
293
+ */
294
+ export declare function localSkills(agent?: string | null): LocalSkill[];
295
+ export {};