@hasna/skills 0.10.50 → 0.10.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -5
- package/bin/index.js +1827 -1374
- package/bin/maintenance.js +199 -2
- package/bin/mcp.js +730 -482
- package/bin/migrate.js +1 -1
- package/bin/server.js +11 -4
- package/bin/worker.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1052 -704
- package/dist/lib/agent-discovery.d.ts +38 -2
- package/dist/lib/agent-integration.d.ts +4 -1
- package/dist/lib/claude-marketplace-entry-witness.d.ts +26 -0
- package/dist/lib/claude-settings-witness.d.ts +25 -1
- package/dist/lib/codex-hook-trust-files.d.ts +17 -0
- package/dist/lib/codex-native-compatibility.d.ts +4 -1
- package/dist/lib/selection-cache.d.ts +2 -2
- package/dist/lib/session-reconciliation.d.ts +9 -0
- package/dist/sdk/index.js +1001 -658
- package/dist/server/maintenance-entry.d.ts +14 -0
- package/dist/server/schema-census.d.ts +77 -0
- package/docs/plugin-admission.md +190 -3
- package/package.json +1 -1
|
@@ -9,7 +9,21 @@ type InspectionManifest = EnrollmentInspectionInput & {
|
|
|
9
9
|
expiresAt: string;
|
|
10
10
|
manifestDigest: string;
|
|
11
11
|
};
|
|
12
|
+
/** The schema census has its own authorization; no enrollment or inspection manifest can authorize it. */
|
|
13
|
+
type SchemaCensusManifest = {
|
|
14
|
+
app: "skills";
|
|
15
|
+
operation: "inspect-schema";
|
|
16
|
+
manifestVersion: 1;
|
|
17
|
+
operationId: string;
|
|
18
|
+
stationId: string;
|
|
19
|
+
keyId: string;
|
|
20
|
+
orgId: string;
|
|
21
|
+
expiresAt: string;
|
|
22
|
+
manifestDigest: string;
|
|
23
|
+
};
|
|
12
24
|
/** Inspection has its own authorization; a failed enrollment manifest cannot authorize it. */
|
|
13
25
|
export declare function readInspectionManifest(path: string, receiptPath: string): InspectionManifest;
|
|
26
|
+
/** The census has its own authorization; a failed enrollment or inspection manifest cannot authorize it. */
|
|
27
|
+
export declare function readSchemaCensusManifest(path: string, receiptPath: string): SchemaCensusManifest;
|
|
14
28
|
export declare function registerMaintenance(parent: Command): void;
|
|
15
29
|
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One fixed, closed census of the Skills database maintenance surface.
|
|
3
|
+
*
|
|
4
|
+
* Why it exists (I6-00024): a production migration is held until the exact
|
|
5
|
+
* current registry, version ledger and migration ledger are read from the live
|
|
6
|
+
* database. The enrollment inspection answers a different question (one
|
|
7
|
+
* operator key), and no existing reader returns the fixed aggregate + ledger
|
|
8
|
+
* census. This module is that reader, and it is deliberately shaped so it
|
|
9
|
+
* cannot become a general SQL console:
|
|
10
|
+
*
|
|
11
|
+
* - No target, query, schema or table input exists. The only argument is the
|
|
12
|
+
* database URL, and the operation refuses to run unless the connection
|
|
13
|
+
* resolves to the fixed public schema the Skills store uses.
|
|
14
|
+
* - Every owned-table read is schema-qualified to `public`. A connection whose
|
|
15
|
+
* search path falls through to a later schema cannot substitute another
|
|
16
|
+
* schema's tables: a missing public table refuses (CENSUS_SCHEMA_UNAVAILABLE)
|
|
17
|
+
* instead of counting rows that do not belong to the fixed target.
|
|
18
|
+
* - The transaction is READ ONLY, REPEATABLE READ, with a five-second
|
|
19
|
+
* statement timeout and a one-second lock timeout, asserted from inside the
|
|
20
|
+
* read path rather than assumed.
|
|
21
|
+
* - The output is a closed shape with per-field size bounds, a bounded
|
|
22
|
+
* migration ledger and a total serialized-size gate. Nothing is truncated
|
|
23
|
+
* silently: a census that would exceed a bound refuses instead.
|
|
24
|
+
* - Nothing imports the application store: no initialization, migrations,
|
|
25
|
+
* backfills or writes happen on this path.
|
|
26
|
+
*/
|
|
27
|
+
export interface SchemaCensusResult {
|
|
28
|
+
status: "inspected";
|
|
29
|
+
database: string;
|
|
30
|
+
role: string;
|
|
31
|
+
serverVersion: string;
|
|
32
|
+
schema: "public";
|
|
33
|
+
transaction: {
|
|
34
|
+
readOnly: true;
|
|
35
|
+
isolation: "repeatable read";
|
|
36
|
+
statementTimeoutMs: number;
|
|
37
|
+
lockTimeoutMs: number;
|
|
38
|
+
};
|
|
39
|
+
migrations: {
|
|
40
|
+
count: number;
|
|
41
|
+
versions: string[];
|
|
42
|
+
};
|
|
43
|
+
registry: {
|
|
44
|
+
rows: number;
|
|
45
|
+
};
|
|
46
|
+
versions: {
|
|
47
|
+
rows: number;
|
|
48
|
+
};
|
|
49
|
+
lifecycle: {
|
|
50
|
+
active: {
|
|
51
|
+
live: number;
|
|
52
|
+
tombstoned: number;
|
|
53
|
+
};
|
|
54
|
+
archived: {
|
|
55
|
+
live: number;
|
|
56
|
+
tombstoned: number;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
authorizationEpoch: {
|
|
60
|
+
exists: false;
|
|
61
|
+
} | {
|
|
62
|
+
exists: true;
|
|
63
|
+
nullCount: number;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
type CensusCode = "CENSUS_POSTGRES_REQUIRED" | "CENSUS_SCHEMA_UNAVAILABLE" | "CENSUS_ACCESS_DENIED" | "CENSUS_WRITE_REFUSED" | "CENSUS_TIMEOUT" | "CENSUS_CONNECTION_FAILED" | "CENSUS_TARGET_MISMATCH" | "CENSUS_INVALID_RESULT" | "CENSUS_OUTPUT_UNBOUNDED" | "CENSUS_FAILED";
|
|
67
|
+
export declare class SchemaCensusError extends Error {
|
|
68
|
+
readonly code: CensusCode;
|
|
69
|
+
constructor(code: CensusCode);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Census metadata only. Deliberately does not import or initialize the application store.
|
|
73
|
+
*
|
|
74
|
+
* @param databaseUrl explicit PostgreSQL URL; there is no implicit localhost fallback.
|
|
75
|
+
*/
|
|
76
|
+
export declare function inspectSchemaCensus(databaseUrl: string): Promise<SchemaCensusResult>;
|
|
77
|
+
export {};
|
package/docs/plugin-admission.md
CHANGED
|
@@ -173,6 +173,74 @@ new loaders still require their dedicated review. API failure refuses an update;
|
|
|
173
173
|
stale projection as synchronized. Claude may retain its previous installation
|
|
174
174
|
after a refused update, whose existing local drift checks continue to apply.
|
|
175
175
|
|
|
176
|
+
## Reviewed plugin-hook attestation scope
|
|
177
|
+
|
|
178
|
+
A reviewed discovery input carries `pluginHooks: "reviewed-no-skill-injection"`.
|
|
179
|
+
That value is the reviewer's statement; Skills does not inspect hook behavior.
|
|
180
|
+
What the software currently enforces is narrower:
|
|
181
|
+
|
|
182
|
+
- It is point-in-time. It binds the listed sources as they were when the review
|
|
183
|
+
was applied, apart from the manifest projection described here and the
|
|
184
|
+
retired-root exemption described below. A manifest listed as a plain byte
|
|
185
|
+
witness with a non-null `sha256` is stored as a `claude-plugin-manifest-v1`
|
|
186
|
+
projection that ignores the descriptive fields `description`, `version`,
|
|
187
|
+
`author`, `homepage`, `repository`, `license` and `keywords`. The projection
|
|
188
|
+
also normalises whitespace and JSON string escaping; key order stays bound.
|
|
189
|
+
Every other manifest field, including `hooks`, `skills`, `commands` and
|
|
190
|
+
unknown fields, stays bound.
|
|
191
|
+
- It enforces one coupling only, and only for a Claude
|
|
192
|
+
`.claude-plugin/plugin.json` listed as a plain byte witness (`hashMode`
|
|
193
|
+
omitted or `"bytes"`) with a non-null `sha256`. The review must then also list
|
|
194
|
+
that plugin's `hooks/hooks.json` (when the file exists at review time) and any
|
|
195
|
+
string `hooks` target in the manifest, each as a plain byte witness with a
|
|
196
|
+
non-null hash. A non-string `hooks` value and a `hooks` target outside the
|
|
197
|
+
plugin root are refused. A manifest listed as `claude-plugin-manifest-v1`
|
|
198
|
+
(the form Skills writes into the stored policy) or as `path-bytes` triggers
|
|
199
|
+
no hook-file requirement. Nothing else is required.
|
|
200
|
+
- A `hooks/hooks.json` that is absent at review time and appears later is
|
|
201
|
+
detected if the review pins it with `sha256: null` or covers it with a
|
|
202
|
+
reviewed `directories` membership witness on the plugin root or its `hooks/`
|
|
203
|
+
directory. Such a witness, from `captureDiscoveryDirectories(paths)`, binds
|
|
204
|
+
the recursive path and type of every member, so every check detects added or
|
|
205
|
+
removed files and directories under it, but not content changes.
|
|
206
|
+
- For plugins not covered by a `claude-plugin-registry` admission witness (see
|
|
207
|
+
[Discovery transition contract](#discovery-transition-contract)):
|
|
208
|
+
- other plugin files, such as hook modules (`register.ts`, `runtime.ts` and
|
|
209
|
+
their imports), `bin/` files and `package.json`, are bound only if the
|
|
210
|
+
review lists them. A content change to an unlisted file is not detected,
|
|
211
|
+
and an added or removed file is detected only under a reviewed
|
|
212
|
+
`directories` witness that covers it;
|
|
213
|
+
- the review is not anchored to the roots registered in
|
|
214
|
+
`~/.claude/plugins/installed_plugins.json`. When a review is present, the
|
|
215
|
+
enabled-plugin walk does not run, so Skills does not check that the listed
|
|
216
|
+
plugin roots are the registered ones or that every enabled plugin is listed.
|
|
217
|
+
- A `claude-plugin-registry` witness covers its admitted, receipt-backed
|
|
218
|
+
plugins on every check. It re-reads `installed_plugins.json`, requires each
|
|
219
|
+
managed row's `installPath` to be `cache/<marketplace>/<plugin>/<version>`,
|
|
220
|
+
checks every file in every retained cache version against the admission
|
|
221
|
+
receipts without the review listing them, and binds unmanaged rows exactly.
|
|
222
|
+
- An entirely absent retired Claude version root exempts only its plain byte
|
|
223
|
+
sources (`hashMode` omitted or `"bytes"`, with no `format`, `fields` or
|
|
224
|
+
`managedPlugins`). Such a source with a non-null `sha256` is also the only
|
|
225
|
+
thing that identifies the root, and the root qualifies only while it stays
|
|
226
|
+
entirely absent and the reviewed settings and registry select a different,
|
|
227
|
+
verified version. Every other source under that root is re-hashed as usual,
|
|
228
|
+
and one whose file existed at review no longer matches its hash, so the
|
|
229
|
+
check refuses. That includes the plugin manifest in the
|
|
230
|
+
`claude-plugin-manifest-v1` form Skills stores, so a stored review that lists
|
|
231
|
+
the retired version's manifest refuses until a fresh review replaces it. A
|
|
232
|
+
`directories` witness that covers the retired root is not exempt either. If
|
|
233
|
+
the root reappears, its sources are checked again.
|
|
234
|
+
- It is detection-only. Later checks re-hash the stored sources and re-walk any
|
|
235
|
+
reviewed `directories` witnesses. Drift makes the
|
|
236
|
+
Skills hooks refuse with `NATIVE_SKILL_DRIFT`; it does not stop, unload or
|
|
237
|
+
sandbox plugin code, which the native client keeps running.
|
|
238
|
+
- It is a tripwire, not a security boundary. A process running as the same user
|
|
239
|
+
can rewrite the plugin files and the managed policy that holds the witnesses.
|
|
240
|
+
|
|
241
|
+
For plugins outside receipt-backed admission, a stronger witness anchored to the
|
|
242
|
+
registered plugin roots is planned and not yet implemented.
|
|
243
|
+
|
|
176
244
|
## Marketplace registry timestamps
|
|
177
245
|
|
|
178
246
|
For an explicitly reviewed `known_marketplaces.json`,
|
|
@@ -196,6 +264,123 @@ not proof of a timestamp-only change: review the complete current registration
|
|
|
196
264
|
and its complementary sources before explicitly replacing an old witness.
|
|
197
265
|
Capturing this witness does not write a policy or approve native registration.
|
|
198
266
|
|
|
267
|
+
## Marketplace plugin entry witness
|
|
268
|
+
|
|
269
|
+
`claude-marketplace-entry-v1` witnesses one plugin entry of a Claude
|
|
270
|
+
`marketplace.json`, for a plugin whose only declaration is its marketplace entry
|
|
271
|
+
(for example a `strict: false` entry with no `plugin.json`, such as
|
|
272
|
+
`swift-lsp@claude-plugins-official`). A bytes witness of the whole catalog
|
|
273
|
+
drifts every time Claude refreshes the marketplace by itself; this mode moves
|
|
274
|
+
only when something that decides how the selected entry resolves or what it
|
|
275
|
+
injects changes.
|
|
276
|
+
|
|
277
|
+
The source names an absolute, normalized `<root>/.claude-plugin/marketplace.json`
|
|
278
|
+
path, the exact marketplace `name` and the exact plugin entry `name`:
|
|
279
|
+
|
|
280
|
+
```json
|
|
281
|
+
{ "path": "/home/user/.claude/plugins/marketplaces/claude-plugins-official/.claude-plugin/marketplace.json",
|
|
282
|
+
"hashMode": "claude-marketplace-entry-v1", "marketplace": "claude-plugins-official",
|
|
283
|
+
"plugin": "swift-lsp", "sha256": "<digest>" }
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
It is accepted only in explicit reviewed Claude discovery. Paths with `..`,
|
|
287
|
+
`//`, a trailing slash, control characters, another file name or another parent
|
|
288
|
+
directory refuse, as do names outside `[A-Za-z0-9][A-Za-z0-9._-]{0,127}`.
|
|
289
|
+
|
|
290
|
+
The digest is SHA-256 over the domain prefix
|
|
291
|
+
`hasna.skills.claude-marketplace-entry.v1\0` and canonical JSON (sorted object
|
|
292
|
+
keys, array order and number spelling kept, no whitespace) of:
|
|
293
|
+
|
|
294
|
+
- the marketplace `name`, `metadata.pluginRoot` (`null` when absent) and
|
|
295
|
+
`allowCrossMarketplaceDependenciesOn` (`null` when absent), for every entry
|
|
296
|
+
whether or not it declares `dependencies`: the root allowlist also governs
|
|
297
|
+
dependencies declared in the plugin's own `plugin.json`, which Claude's
|
|
298
|
+
auto-update and `/reload-plugins` act on. A present allowlist must be an
|
|
299
|
+
array of strings, so `null` only ever means absent;
|
|
300
|
+
- the selected entry projected to its bound keys: `name`, `source`, `strict`,
|
|
301
|
+
`defaultEnabled`, `dependencies`, `relevance`, `headers`, `headersHelper`,
|
|
302
|
+
`settings`, `userConfig`, `types`, `channels`, `skills`, `commands`, `agents`,
|
|
303
|
+
`hooks`, `mcpServers`, `lspServers`, `outputStyles`, `workflows`,
|
|
304
|
+
`experimental`, `themes` and `monitors`, each with its full value.
|
|
305
|
+
|
|
306
|
+
Key order: this canonical JSON sorts object keys at every level, under the
|
|
307
|
+
domain prefix above. That deliberately differs from `claude-plugin-manifest-v1`,
|
|
308
|
+
which keeps the manifest's own key order and has no domain prefix. Reordering
|
|
309
|
+
keys in the catalog therefore never moves this digest, while array order,
|
|
310
|
+
including the order of `allowCrossMarketplaceDependenciesOn`, stays bound.
|
|
311
|
+
|
|
312
|
+
The entry's display and catalog metadata is validated and omitted:
|
|
313
|
+
`$schema`, `description`, `version`, `author`, `homepage`, `repository`,
|
|
314
|
+
`license`, `keywords`, `category`, `tags`, `displayName` and `metadata`. A real
|
|
315
|
+
version or install-path change is still caught by the separate
|
|
316
|
+
`installed_plugins.json` witness, which this mode never replaces. Other entries,
|
|
317
|
+
the catalog `description`, `version`, `owner` and `$schema`,
|
|
318
|
+
`forceRemoveDeletedPlugins` and renames of other plugins do not move the digest.
|
|
319
|
+
Adding, removing or changing `allowCrossMarketplaceDependenciesOn` always does.
|
|
320
|
+
|
|
321
|
+
The mode fails closed. Capture and verification refuse when:
|
|
322
|
+
|
|
323
|
+
- the file is missing, a link, a special file, larger than 1 MiB, not strict
|
|
324
|
+
UTF-8, not one JSON object, has trailing content or a duplicate key;
|
|
325
|
+
- the marketplace `name` differs from the bound name, or `plugins` is not an
|
|
326
|
+
array;
|
|
327
|
+
- the selected entry is missing, appears twice, or has a case-only variant;
|
|
328
|
+
- `renames` maps the selected name to another entry or to `null`;
|
|
329
|
+
- the entry has a key outside the two lists above (including the directory
|
|
330
|
+
listing fields), `experimental` has a key other than `themes`, `monitors` or
|
|
331
|
+
`evals`, or an object `source` has an unknown type or a field outside that
|
|
332
|
+
type's documented fields (a `url` source may also carry `path`, as the
|
|
333
|
+
official catalog does);
|
|
334
|
+
- the catalog has a top-level key outside `$schema`, `name`, `owner`,
|
|
335
|
+
`plugins`, `description`, `version`, `metadata`, `forceRemoveDeletedPlugins`,
|
|
336
|
+
`allowCrossMarketplaceDependenciesOn` and `renames`, or a `metadata` key other
|
|
337
|
+
than `description`, `version` and `pluginRoot`;
|
|
338
|
+
- an omitted metadata field has the wrong type.
|
|
339
|
+
|
|
340
|
+
New fields are refused, never ignored, until a reviewed version of this mode
|
|
341
|
+
covers them. A refusal for an unknown key names the mode, the bound marketplace
|
|
342
|
+
name and the exact key path, and never echoes a value. Key names are bounded to
|
|
343
|
+
64 characters and JSON-quoted, with every character outside printable ASCII
|
|
344
|
+
escaped. A plain key extends the path with `.key`; any other key appears as
|
|
345
|
+
`["key"]`. For example:
|
|
346
|
+
|
|
347
|
+
```text
|
|
348
|
+
claude-marketplace-entry-v1: unknown top-level key "pluginSearchPaths" in claude-plugins-official
|
|
349
|
+
claude-marketplace-entry-v1: unknown key "metadata.skillRoot" in claude-plugins-official
|
|
350
|
+
claude-marketplace-entry-v1: unknown key "plugins[swift-lsp].futureInjector" in claude-plugins-official
|
|
351
|
+
claude-marketplace-entry-v1: unknown key "plugins[swift-lsp].source.script" in claude-plugins-official
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Inside a hook check the same text follows `NATIVE_SKILL_DRIFT:`. The only way
|
|
355
|
+
forward after such a refusal is a fresh human review of the changed catalog
|
|
356
|
+
and a guarded exact re-pin through hook installation: write the reviewed
|
|
357
|
+
witnesses to a discovery inputs file, preview `skills hook install --agent
|
|
358
|
+
claude --discovery-inputs <file>`, then run the same command with `--apply`.
|
|
359
|
+
While the unknown key is present this mode refuses capture too, so that review
|
|
360
|
+
must bind the catalog another way, for example an exact `bytes` witness from
|
|
361
|
+
`captureDiscoveryByteSources`, until a reviewed version of this mode covers the
|
|
362
|
+
key. There is no bypass, ignore list or relaxed mode.
|
|
363
|
+
|
|
364
|
+
The field lists follow the
|
|
365
|
+
[marketplace reference](https://code.claude.com/docs/en/plugins/marketplace-reference)
|
|
366
|
+
and the [plugin manifest reference](https://code.claude.com/docs/en/plugins/manifest-reference),
|
|
367
|
+
read on 2026-10-07.
|
|
368
|
+
|
|
369
|
+
Capture a witness with
|
|
370
|
+
`skills hook witness --kind claude-marketplace-entry-v1 --path <marketplace.json>
|
|
371
|
+
--marketplace <name> --plugin <name> --json` (or
|
|
372
|
+
`captureClaudeMarketplaceEntry(path, marketplace, plugin)`). Capture writes
|
|
373
|
+
nothing and does not authorize a review. Put the witness in reviewed discovery
|
|
374
|
+
inputs, keep the `settings.json`, `installed_plugins.json` and
|
|
375
|
+
`known_marketplaces.json` witnesses and the plugin's absence witnesses, and
|
|
376
|
+
preview `skills hook install --discovery-inputs <file>` before applying. Hook
|
|
377
|
+
installation never rewrites the catalog, so an entry change between planning
|
|
378
|
+
and applying refuses.
|
|
379
|
+
|
|
380
|
+
Install a runtime that recognizes `claude-marketplace-entry-v1`, including any
|
|
381
|
+
bundled copy of the verifier, before a policy carries it; an older runtime
|
|
382
|
+
refuses the whole policy because the hash mode is unknown.
|
|
383
|
+
|
|
199
384
|
## Claude settings preferences
|
|
200
385
|
|
|
201
386
|
`captureClaudeSettings(canonicalSettingsPath)` returns a versioned
|
|
@@ -404,8 +589,8 @@ witness intersecting the write still refuses instead of adopting a new inode.
|
|
|
404
589
|
|
|
405
590
|
## Codex bundled cleanup controls
|
|
406
591
|
|
|
407
|
-
Qualified native skill review supports a narrow subset of Codex 0.159.2
|
|
408
|
-
0.160.0's bundled cleanup hooks. The identities `browser`, `chrome`, `chrome-dev`,
|
|
592
|
+
Qualified native skill review supports a narrow subset of Codex 0.159.2,
|
|
593
|
+
0.160.0 and 0.160.1's bundled cleanup hooks. The identities `browser`, `chrome`, `chrome-dev`,
|
|
409
594
|
`chrome-internal` and `computer-use` in `openai-bundled` may call
|
|
410
595
|
`node_repl.turn_ended`; `unified-computer-use` may call `cua_repl.turn_ended`.
|
|
411
596
|
Only `Interrupt`, `SubagentStop` and `Stop` are accepted, with one group and one
|
|
@@ -421,4 +606,6 @@ apply and subsequent discovery checks. This review preserves native cleanup;
|
|
|
421
606
|
it neither enables plugins nor grants hook trust, browser authority or signing.
|
|
422
607
|
The upstream contracts are
|
|
423
608
|
[0.159.2 bundled hooks](https://github.com/openai/codex/blob/ff6aec96948b70d94983af2641a6b67c94faeff5/codex-rs/plugin/src/bundled_hooks.rs)
|
|
424
|
-
and [0.160.0 bundled hooks](https://github.com/openai/codex/blob/a956835d020762cb2b570053af06f643a11c0ecc/codex-rs/plugin/src/bundled_hooks.rs)
|
|
609
|
+
and [0.160.0 bundled hooks](https://github.com/openai/codex/blob/a956835d020762cb2b570053af06f643a11c0ecc/codex-rs/plugin/src/bundled_hooks.rs);
|
|
610
|
+
0.160.1 carries the identical file
|
|
611
|
+
([0.160.1 bundled hooks](https://github.com/openai/codex/blob/d27764b82f7118f674371e6d6e76271d9d606edb/codex-rs/plugin/src/bundled_hooks.rs)).
|