@ryuhq/sdk 0.1.2 → 0.1.3
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/dist/manifest.d.cts +1 -1
- package/dist/manifest.d.ts +1 -1
- package/package.json +3 -3
- package/src/generated/plugin-manifest.ts +129 -2
package/dist/manifest.d.cts
CHANGED
|
@@ -403,8 +403,8 @@ declare const PluginManifestSchema: z.ZodObject<{
|
|
|
403
403
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
404
404
|
rule: z.ZodOptional<z.ZodObject<{
|
|
405
405
|
type: z.ZodEnum<{
|
|
406
|
-
flat: "flat";
|
|
407
406
|
percent: "percent";
|
|
407
|
+
flat: "flat";
|
|
408
408
|
}>;
|
|
409
409
|
value: z.ZodNumber;
|
|
410
410
|
recurring: z.ZodDefault<z.ZodBoolean>;
|
package/dist/manifest.d.ts
CHANGED
|
@@ -403,8 +403,8 @@ declare const PluginManifestSchema: z.ZodObject<{
|
|
|
403
403
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
404
404
|
rule: z.ZodOptional<z.ZodObject<{
|
|
405
405
|
type: z.ZodEnum<{
|
|
406
|
-
flat: "flat";
|
|
407
406
|
percent: "percent";
|
|
407
|
+
flat: "flat";
|
|
408
408
|
}>;
|
|
409
409
|
value: z.ZodNumber;
|
|
410
410
|
recurring: z.ZodDefault<z.ZodBoolean>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ryuhq/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Ryu developer SDK: typed builders and CLI for authoring manifest.json Plugin bundles",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -44,11 +44,11 @@
|
|
|
44
44
|
"clean": "rm -rf dist"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@ryuhq/sdk-native": "
|
|
47
|
+
"@ryuhq/sdk-native": "0.1.3",
|
|
48
48
|
"zod": "^4.1.13"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@types/bun": "
|
|
51
|
+
"@types/bun": "^1.3.4",
|
|
52
52
|
"json-schema-to-typescript": "^15.0.4",
|
|
53
53
|
"tsup": "^8.5.1",
|
|
54
54
|
"typescript": "^5"
|
|
@@ -226,8 +226,48 @@ export interface PluginManifest {
|
|
|
226
226
|
* Permission grants this app declares it needs (e.g. `"mcp:web_search"`).
|
|
227
227
|
* These are *declarations only* at this layer — no enforcement happens here;
|
|
228
228
|
* the Gateway owns grant enforcement.
|
|
229
|
+
*
|
|
230
|
+
* This is the **app→host** lane and has nothing to do with
|
|
231
|
+
* [`permission_levels`], the **app→human** lane. See that field's doc comment
|
|
232
|
+
* for the three-way table; conflating the two is the likeliest future bug here.
|
|
233
|
+
*
|
|
234
|
+
* [`permission_levels`]: PluginManifest::permission_levels
|
|
229
235
|
*/
|
|
230
236
|
permission_grants?: string[];
|
|
237
|
+
/**
|
|
238
|
+
* **The user-facing permission vocabulary this app declares** — the set of
|
|
239
|
+
* levels ("read", "edit", …) an administrator can later grant to a person or a
|
|
240
|
+
* team *inside* this app. Absent/empty = the app declares no vocabulary, which
|
|
241
|
+
* is every manifest predating this field.
|
|
242
|
+
*
|
|
243
|
+
* Spaces declaring `read` and `edit` is what makes "team X may edit in Spaces"
|
|
244
|
+
* expressible at all: a grant has to name a level, and a UI has to render a
|
|
245
|
+
* list of them. Without a declaration there is nothing to bind to.
|
|
246
|
+
*
|
|
247
|
+
* # Three lanes, one prefix — do not conflate them
|
|
248
|
+
*
|
|
249
|
+
* | field | direction | who decides | what it means |
|
|
250
|
+
* |---|---|---|---|
|
|
251
|
+
* | [`permission_grants`] | app → host | the **Gateway**, at install/enable | which host capabilities the app may *ask* for |
|
|
252
|
+
* | [`permissions`] ([`PermissionSet`]) | app → sandbox | **Core**, at spawn/exec | what the app's code may *touch* (FS paths, hosts, subprocess) |
|
|
253
|
+
* | `permission_levels` | app → human | an **admin**, per person/team | what a *person* may do inside the app |
|
|
254
|
+
*
|
|
255
|
+
* Only the first two are enforced today. This field is **declaration only**:
|
|
256
|
+
* nothing consumes it yet, so declaring `edit` gates nothing by itself. It is
|
|
257
|
+
* the vocabulary the ACL layer will bind grants against.
|
|
258
|
+
*
|
|
259
|
+
* # Ordering and implication
|
|
260
|
+
*
|
|
261
|
+
* Declaration order is display order — render the list as written. Strength is
|
|
262
|
+
* expressed with [`PermissionLevel::implies`] rather than a separate rank, so
|
|
263
|
+
* there is exactly one ordering and it cannot contradict itself: `edit` implying
|
|
264
|
+
* `read` means granting `edit` already conveys `read`, and no admin has to grant
|
|
265
|
+
* the same person both.
|
|
266
|
+
*
|
|
267
|
+
* [`permission_grants`]: PluginManifest::permission_grants
|
|
268
|
+
* [`permissions`]: PluginManifest::permissions
|
|
269
|
+
*/
|
|
270
|
+
permission_levels?: PermissionLevel[];
|
|
231
271
|
/**
|
|
232
272
|
* **Unified, deny-by-default runtime permission set** — the single typed
|
|
233
273
|
* grammar (`{fs, child_process, network, tool}`) Core lowers to every sandbox
|
|
@@ -236,17 +276,21 @@ export interface PluginManifest {
|
|
|
236
276
|
* predating this field), so an app that declares nothing keeps today's exact
|
|
237
277
|
* zero-permission sandbox posture.
|
|
238
278
|
*
|
|
239
|
-
* # Relationship to [`permission_grants`]
|
|
279
|
+
* # Relationship to [`permission_grants`] and [`permission_levels`]
|
|
240
280
|
*
|
|
241
|
-
* These are **
|
|
281
|
+
* These are **three distinct lanes** that must not be conflated:
|
|
242
282
|
* - [`permission_grants`] are opaque strings the **Gateway** approves at
|
|
243
283
|
* install/enable time — the *approval* lane (who is allowed to ask).
|
|
244
284
|
* - `permissions` is the typed set **Core** lowers into the actual sandbox at
|
|
245
285
|
* spawn/exec time — the *runtime-enforcement* lane (what the code can touch).
|
|
286
|
+
* - [`permission_levels`] is the app's *user-facing* vocabulary an admin grants
|
|
287
|
+
* to a person or team — it never reaches the sandbox at all.
|
|
246
288
|
*
|
|
247
289
|
* A grant says "this app may use the filesystem capability"; `permissions.fs`
|
|
248
290
|
* says "…and here are the exact read/write paths the sandbox is opened with."
|
|
249
291
|
*
|
|
292
|
+
* [`permission_levels`]: PluginManifest::permission_levels
|
|
293
|
+
*
|
|
250
294
|
* # Altitude (manifest-level, per-runnable override is a followup)
|
|
251
295
|
*
|
|
252
296
|
* Declared at the manifest root because **both** current enforcement sites
|
|
@@ -1503,6 +1547,51 @@ export interface McpServerDecl {
|
|
|
1503
1547
|
[k: string]: string;
|
|
1504
1548
|
};
|
|
1505
1549
|
}
|
|
1550
|
+
/**
|
|
1551
|
+
* One entry in an app's **user-facing permission vocabulary** — a level an admin
|
|
1552
|
+
* can grant to a person or a team inside that app (see
|
|
1553
|
+
* [`PluginManifest::permission_levels`], which also explains why this is a
|
|
1554
|
+
* different axis from `permission_grants` and `permissions`).
|
|
1555
|
+
*
|
|
1556
|
+
* Deliberately self-describing: an admin UI renders the grant picker from `label`
|
|
1557
|
+
* + `description` alone, so a level whose meaning lives only in the app's own docs
|
|
1558
|
+
* cannot exist.
|
|
1559
|
+
*/
|
|
1560
|
+
export interface PermissionLevel {
|
|
1561
|
+
/**
|
|
1562
|
+
* One sentence telling an admin what granting this level actually allows.
|
|
1563
|
+
* Required for the same reason as [`label`]: the admin deciding is usually
|
|
1564
|
+
* not the person who wrote the app.
|
|
1565
|
+
*
|
|
1566
|
+
* [`label`]: PermissionLevel::label
|
|
1567
|
+
*/
|
|
1568
|
+
description: string;
|
|
1569
|
+
/**
|
|
1570
|
+
* Stable machine id (e.g. `"read"`). Lower-case ASCII alphanumerics plus
|
|
1571
|
+
* `-`, `_` and `.`, at most [`MAX_PLUGIN_ID_LEN`] bytes, and unique within the
|
|
1572
|
+
* manifest.
|
|
1573
|
+
*
|
|
1574
|
+
* The alphabet is narrower than a plugin id's on purpose: these ids end up in
|
|
1575
|
+
* API paths and in persisted grant strings, so `Read` and `read` must not be
|
|
1576
|
+
* two levels that look identical to a human granting them.
|
|
1577
|
+
*/
|
|
1578
|
+
id: string;
|
|
1579
|
+
/**
|
|
1580
|
+
* Ids of other levels in **this same manifest** that this level subsumes.
|
|
1581
|
+
*
|
|
1582
|
+
* This is the whole ordering mechanism — there is no separate rank, so the
|
|
1583
|
+
* order can never contradict itself. `edit` implying `read` means a person
|
|
1584
|
+
* granted `edit` already holds `read`; granting both is redundant, never
|
|
1585
|
+
* required. Resolved transitively by
|
|
1586
|
+
* [`resolve_implied_permission_levels`].
|
|
1587
|
+
*/
|
|
1588
|
+
implies?: string[];
|
|
1589
|
+
/**
|
|
1590
|
+
* Short human label for the grant picker (e.g. `"Can edit"`). Required —
|
|
1591
|
+
* an unlabelled level is unrenderable.
|
|
1592
|
+
*/
|
|
1593
|
+
label: string;
|
|
1594
|
+
}
|
|
1506
1595
|
/**
|
|
1507
1596
|
* The single, typed, **deny-by-default** permission set a plugin manifest
|
|
1508
1597
|
* declares, lowered by Core to every sandbox backend.
|
|
@@ -2243,6 +2332,44 @@ export interface RouteSpec {
|
|
|
2243
2332
|
* sidecar's REST routes (`/inboxes/:id`) can be declared faithfully.
|
|
2244
2333
|
*/
|
|
2245
2334
|
path: string;
|
|
2335
|
+
/**
|
|
2336
|
+
* The [`PluginManifest::permission_levels`] id a caller must hold to reach this
|
|
2337
|
+
* route. Absent (the default) = ungated: Core forwards exactly as it always did,
|
|
2338
|
+
* so annotating is opt-in and no existing app changes behaviour.
|
|
2339
|
+
*
|
|
2340
|
+
* This is the only place a route→permission mapping can honestly live: Core
|
|
2341
|
+
* cannot know that an app's `/tabs/:id/close` is destructive, and the sidecar
|
|
2342
|
+
* cannot enforce it (it never sees the caller's identity, only Core's minted
|
|
2343
|
+
* hop token). Declaring it HERE — on the same [`RouteSpec`] the proxy already
|
|
2344
|
+
* matches to decide forward-or-404 — means the gate and the forward can never
|
|
2345
|
+
* disagree about which route is in play.
|
|
2346
|
+
*
|
|
2347
|
+
* Must name a level THIS manifest declares (enforced by
|
|
2348
|
+
* [`crate::manifest::validate_route_permissions`]); an app cannot gate its
|
|
2349
|
+
* routes on another app's vocabulary or on a level nobody can see to grant.
|
|
2350
|
+
*
|
|
2351
|
+
* Never annotate an [`RouteAuth::Public`] route: a public route exists for a
|
|
2352
|
+
* caller who holds no identity at all (an external webhook), and on an
|
|
2353
|
+
* org-bound node an anonymous caller is refused outright — the annotation would
|
|
2354
|
+
* turn a working inbound webhook into a permanent 403.
|
|
2355
|
+
*
|
|
2356
|
+
* [`PluginManifest::permission_levels`]: crate::manifest::PluginManifest::permission_levels
|
|
2357
|
+
*/
|
|
2358
|
+
permission?: string | null;
|
|
2359
|
+
/**
|
|
2360
|
+
* Which `:param` of [`path`] names the resource [`permission`] is checked
|
|
2361
|
+
* against, so one route can be granted per-object (`"id"` on `/tabs/:id` gates
|
|
2362
|
+
* each tab separately). Absent = the whole app is the resource, which is what an
|
|
2363
|
+
* admin grants when the route identifies nothing (a `/settings` POST).
|
|
2364
|
+
*
|
|
2365
|
+
* Only meaningful alongside [`permission`], and the named param must actually
|
|
2366
|
+
* appear in [`path`] — both enforced at validation, because a typo here would
|
|
2367
|
+
* silently widen a rule the author wrote as per-object into a per-app one.
|
|
2368
|
+
*
|
|
2369
|
+
* [`path`]: RouteSpec::path
|
|
2370
|
+
* [`permission`]: RouteSpec::permission
|
|
2371
|
+
*/
|
|
2372
|
+
resource_param?: string | null;
|
|
2246
2373
|
}
|
|
2247
2374
|
/**
|
|
2248
2375
|
* A single downloaded executable: fetched (checksum-verified) into the
|