@ryuhq/sdk 0.1.12 → 0.1.14
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/{chunk-CUY2QOFC.js → chunk-AO2KJRDD.js} +35 -0
- package/dist/cli.cjs +34 -0
- package/dist/cli.js +1 -1
- package/dist/index.cjs +34 -0
- package/dist/index.js +1 -1
- package/dist/manifest.cjs +36 -0
- package/dist/manifest.d.cts +36 -1
- package/dist/manifest.d.ts +36 -1
- package/dist/manifest.js +3 -1
- package/package.json +2 -2
- package/src/generated/plugin-manifest.ts +249 -6
- package/src/manifest.test.ts +82 -0
- package/src/manifest.ts +54 -0
- package/src/runnable/primitives.ts +6 -3
|
@@ -293,6 +293,24 @@ var SurfaceSchema = z.enum([
|
|
|
293
293
|
/** The terminal client. */
|
|
294
294
|
"cli"
|
|
295
295
|
]);
|
|
296
|
+
var EnginesReqSchema = z.object({
|
|
297
|
+
/** Floor for the terminal (`cli`) surface. */
|
|
298
|
+
cli: z.string().optional(),
|
|
299
|
+
/** Floor for the Tauri desktop app. */
|
|
300
|
+
desktop: z.string().optional(),
|
|
301
|
+
/** Floor for the browser extension. */
|
|
302
|
+
extension: z.string().optional(),
|
|
303
|
+
/** Floor for the Ryu Gateway. */
|
|
304
|
+
gateway: z.string().optional(),
|
|
305
|
+
/** Floor for the dynamic-island companion. */
|
|
306
|
+
island: z.string().optional(),
|
|
307
|
+
/** Floor for the mobile app. */
|
|
308
|
+
mobile: z.string().optional(),
|
|
309
|
+
/** Floor for the running **Core** (e.g. `">=0.3.0"`). Required. */
|
|
310
|
+
ryu: z.string().min(1, "engines.ryu is required when engines is present"),
|
|
311
|
+
/** Floor for the Next.js web app. */
|
|
312
|
+
web: z.string().optional()
|
|
313
|
+
});
|
|
296
314
|
var PluginManifestSchema = z.object({
|
|
297
315
|
/** Reverse-domain unique identifier (e.g. `"com.example.my-plugin"`). */
|
|
298
316
|
id: z.string().min(1, "id is required"),
|
|
@@ -359,6 +377,22 @@ var PluginManifestSchema = z.object({
|
|
|
359
377
|
* an unsupported-target plugin stays installable and inspectable.
|
|
360
378
|
*/
|
|
361
379
|
targets: z.array(SurfaceSchema).default([]),
|
|
380
|
+
/**
|
|
381
|
+
* Host version floors — the semver requirement each surface must satisfy for
|
|
382
|
+
* this plugin to install. Mirrors Core's `EnginesReq`
|
|
383
|
+
* (`crates/core/kernel-contracts/src/manifest.rs`).
|
|
384
|
+
*
|
|
385
|
+
* `ryu` is the **Core** floor and the only required key (it is the legacy
|
|
386
|
+
* spelling; every manifest in the wild carries just that one). The rest are
|
|
387
|
+
* optional per-surface floors.
|
|
388
|
+
*
|
|
389
|
+
* REGRESSION THIS FIXES: `engines` was absent from this schema entirely, and
|
|
390
|
+
* zod strips unlisted keys — so `ryu pack` silently dropped the whole block
|
|
391
|
+
* from every bundle it produced. A plugin could declare a Core floor, publish,
|
|
392
|
+
* and ship a bundle that declared none. Any new host floor MUST be added here
|
|
393
|
+
* as well as in the Rust contract, or it does not survive packing.
|
|
394
|
+
*/
|
|
395
|
+
engines: EnginesReqSchema.optional(),
|
|
362
396
|
/**
|
|
363
397
|
* Optional per-item AFFILIATE terms: the commission paid to a referrer when a
|
|
364
398
|
* referred user buys this (paid) item. `value` is basis points for `percent`
|
|
@@ -477,6 +511,7 @@ export {
|
|
|
477
511
|
CapabilityReqSchema,
|
|
478
512
|
RequiresSchema,
|
|
479
513
|
SurfaceSchema,
|
|
514
|
+
EnginesReqSchema,
|
|
480
515
|
PluginManifestSchema,
|
|
481
516
|
validatePluginId,
|
|
482
517
|
validateManifestStrict,
|
package/dist/cli.cjs
CHANGED
|
@@ -777,6 +777,24 @@ var SurfaceSchema = import_zod.z.enum([
|
|
|
777
777
|
/** The terminal client. */
|
|
778
778
|
"cli"
|
|
779
779
|
]);
|
|
780
|
+
var EnginesReqSchema = import_zod.z.object({
|
|
781
|
+
/** Floor for the terminal (`cli`) surface. */
|
|
782
|
+
cli: import_zod.z.string().optional(),
|
|
783
|
+
/** Floor for the Tauri desktop app. */
|
|
784
|
+
desktop: import_zod.z.string().optional(),
|
|
785
|
+
/** Floor for the browser extension. */
|
|
786
|
+
extension: import_zod.z.string().optional(),
|
|
787
|
+
/** Floor for the Ryu Gateway. */
|
|
788
|
+
gateway: import_zod.z.string().optional(),
|
|
789
|
+
/** Floor for the dynamic-island companion. */
|
|
790
|
+
island: import_zod.z.string().optional(),
|
|
791
|
+
/** Floor for the mobile app. */
|
|
792
|
+
mobile: import_zod.z.string().optional(),
|
|
793
|
+
/** Floor for the running **Core** (e.g. `">=0.3.0"`). Required. */
|
|
794
|
+
ryu: import_zod.z.string().min(1, "engines.ryu is required when engines is present"),
|
|
795
|
+
/** Floor for the Next.js web app. */
|
|
796
|
+
web: import_zod.z.string().optional()
|
|
797
|
+
});
|
|
780
798
|
var PluginManifestSchema = import_zod.z.object({
|
|
781
799
|
/** Reverse-domain unique identifier (e.g. `"com.example.my-plugin"`). */
|
|
782
800
|
id: import_zod.z.string().min(1, "id is required"),
|
|
@@ -843,6 +861,22 @@ var PluginManifestSchema = import_zod.z.object({
|
|
|
843
861
|
* an unsupported-target plugin stays installable and inspectable.
|
|
844
862
|
*/
|
|
845
863
|
targets: import_zod.z.array(SurfaceSchema).default([]),
|
|
864
|
+
/**
|
|
865
|
+
* Host version floors — the semver requirement each surface must satisfy for
|
|
866
|
+
* this plugin to install. Mirrors Core's `EnginesReq`
|
|
867
|
+
* (`crates/core/kernel-contracts/src/manifest.rs`).
|
|
868
|
+
*
|
|
869
|
+
* `ryu` is the **Core** floor and the only required key (it is the legacy
|
|
870
|
+
* spelling; every manifest in the wild carries just that one). The rest are
|
|
871
|
+
* optional per-surface floors.
|
|
872
|
+
*
|
|
873
|
+
* REGRESSION THIS FIXES: `engines` was absent from this schema entirely, and
|
|
874
|
+
* zod strips unlisted keys — so `ryu pack` silently dropped the whole block
|
|
875
|
+
* from every bundle it produced. A plugin could declare a Core floor, publish,
|
|
876
|
+
* and ship a bundle that declared none. Any new host floor MUST be added here
|
|
877
|
+
* as well as in the Rust contract, or it does not survive packing.
|
|
878
|
+
*/
|
|
879
|
+
engines: EnginesReqSchema.optional(),
|
|
846
880
|
/**
|
|
847
881
|
* Optional per-item AFFILIATE terms: the commission paid to a referrer when a
|
|
848
882
|
* referred user buys this (paid) item. `value` is basis points for `percent`
|
package/dist/cli.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -1123,6 +1123,24 @@ var SurfaceSchema = import_zod.z.enum([
|
|
|
1123
1123
|
/** The terminal client. */
|
|
1124
1124
|
"cli"
|
|
1125
1125
|
]);
|
|
1126
|
+
var EnginesReqSchema = import_zod.z.object({
|
|
1127
|
+
/** Floor for the terminal (`cli`) surface. */
|
|
1128
|
+
cli: import_zod.z.string().optional(),
|
|
1129
|
+
/** Floor for the Tauri desktop app. */
|
|
1130
|
+
desktop: import_zod.z.string().optional(),
|
|
1131
|
+
/** Floor for the browser extension. */
|
|
1132
|
+
extension: import_zod.z.string().optional(),
|
|
1133
|
+
/** Floor for the Ryu Gateway. */
|
|
1134
|
+
gateway: import_zod.z.string().optional(),
|
|
1135
|
+
/** Floor for the dynamic-island companion. */
|
|
1136
|
+
island: import_zod.z.string().optional(),
|
|
1137
|
+
/** Floor for the mobile app. */
|
|
1138
|
+
mobile: import_zod.z.string().optional(),
|
|
1139
|
+
/** Floor for the running **Core** (e.g. `">=0.3.0"`). Required. */
|
|
1140
|
+
ryu: import_zod.z.string().min(1, "engines.ryu is required when engines is present"),
|
|
1141
|
+
/** Floor for the Next.js web app. */
|
|
1142
|
+
web: import_zod.z.string().optional()
|
|
1143
|
+
});
|
|
1126
1144
|
var PluginManifestSchema = import_zod.z.object({
|
|
1127
1145
|
/** Reverse-domain unique identifier (e.g. `"com.example.my-plugin"`). */
|
|
1128
1146
|
id: import_zod.z.string().min(1, "id is required"),
|
|
@@ -1189,6 +1207,22 @@ var PluginManifestSchema = import_zod.z.object({
|
|
|
1189
1207
|
* an unsupported-target plugin stays installable and inspectable.
|
|
1190
1208
|
*/
|
|
1191
1209
|
targets: import_zod.z.array(SurfaceSchema).default([]),
|
|
1210
|
+
/**
|
|
1211
|
+
* Host version floors — the semver requirement each surface must satisfy for
|
|
1212
|
+
* this plugin to install. Mirrors Core's `EnginesReq`
|
|
1213
|
+
* (`crates/core/kernel-contracts/src/manifest.rs`).
|
|
1214
|
+
*
|
|
1215
|
+
* `ryu` is the **Core** floor and the only required key (it is the legacy
|
|
1216
|
+
* spelling; every manifest in the wild carries just that one). The rest are
|
|
1217
|
+
* optional per-surface floors.
|
|
1218
|
+
*
|
|
1219
|
+
* REGRESSION THIS FIXES: `engines` was absent from this schema entirely, and
|
|
1220
|
+
* zod strips unlisted keys — so `ryu pack` silently dropped the whole block
|
|
1221
|
+
* from every bundle it produced. A plugin could declare a Core floor, publish,
|
|
1222
|
+
* and ship a bundle that declared none. Any new host floor MUST be added here
|
|
1223
|
+
* as well as in the Rust contract, or it does not survive packing.
|
|
1224
|
+
*/
|
|
1225
|
+
engines: EnginesReqSchema.optional(),
|
|
1192
1226
|
/**
|
|
1193
1227
|
* Optional per-item AFFILIATE terms: the commission paid to a referrer when a
|
|
1194
1228
|
* referred user buys this (paid) item. `value` is basis points for `percent`
|
package/dist/index.js
CHANGED
package/dist/manifest.cjs
CHANGED
|
@@ -24,6 +24,7 @@ __export(manifest_exports, {
|
|
|
24
24
|
CapabilityReqSchema: () => CapabilityReqSchema,
|
|
25
25
|
CompanionSurfaceSchema: () => CompanionSurfaceSchema,
|
|
26
26
|
ContributesSchema: () => ContributesSchema,
|
|
27
|
+
EnginesReqSchema: () => EnginesReqSchema,
|
|
27
28
|
HookEventContributionSchema: () => HookEventContributionSchema,
|
|
28
29
|
OutputStyleContributionSchema: () => OutputStyleContributionSchema,
|
|
29
30
|
PiExtensionContributionSchema: () => PiExtensionContributionSchema,
|
|
@@ -342,6 +343,24 @@ var SurfaceSchema = import_zod.z.enum([
|
|
|
342
343
|
/** The terminal client. */
|
|
343
344
|
"cli"
|
|
344
345
|
]);
|
|
346
|
+
var EnginesReqSchema = import_zod.z.object({
|
|
347
|
+
/** Floor for the terminal (`cli`) surface. */
|
|
348
|
+
cli: import_zod.z.string().optional(),
|
|
349
|
+
/** Floor for the Tauri desktop app. */
|
|
350
|
+
desktop: import_zod.z.string().optional(),
|
|
351
|
+
/** Floor for the browser extension. */
|
|
352
|
+
extension: import_zod.z.string().optional(),
|
|
353
|
+
/** Floor for the Ryu Gateway. */
|
|
354
|
+
gateway: import_zod.z.string().optional(),
|
|
355
|
+
/** Floor for the dynamic-island companion. */
|
|
356
|
+
island: import_zod.z.string().optional(),
|
|
357
|
+
/** Floor for the mobile app. */
|
|
358
|
+
mobile: import_zod.z.string().optional(),
|
|
359
|
+
/** Floor for the running **Core** (e.g. `">=0.3.0"`). Required. */
|
|
360
|
+
ryu: import_zod.z.string().min(1, "engines.ryu is required when engines is present"),
|
|
361
|
+
/** Floor for the Next.js web app. */
|
|
362
|
+
web: import_zod.z.string().optional()
|
|
363
|
+
});
|
|
345
364
|
var PluginManifestSchema = import_zod.z.object({
|
|
346
365
|
/** Reverse-domain unique identifier (e.g. `"com.example.my-plugin"`). */
|
|
347
366
|
id: import_zod.z.string().min(1, "id is required"),
|
|
@@ -408,6 +427,22 @@ var PluginManifestSchema = import_zod.z.object({
|
|
|
408
427
|
* an unsupported-target plugin stays installable and inspectable.
|
|
409
428
|
*/
|
|
410
429
|
targets: import_zod.z.array(SurfaceSchema).default([]),
|
|
430
|
+
/**
|
|
431
|
+
* Host version floors — the semver requirement each surface must satisfy for
|
|
432
|
+
* this plugin to install. Mirrors Core's `EnginesReq`
|
|
433
|
+
* (`crates/core/kernel-contracts/src/manifest.rs`).
|
|
434
|
+
*
|
|
435
|
+
* `ryu` is the **Core** floor and the only required key (it is the legacy
|
|
436
|
+
* spelling; every manifest in the wild carries just that one). The rest are
|
|
437
|
+
* optional per-surface floors.
|
|
438
|
+
*
|
|
439
|
+
* REGRESSION THIS FIXES: `engines` was absent from this schema entirely, and
|
|
440
|
+
* zod strips unlisted keys — so `ryu pack` silently dropped the whole block
|
|
441
|
+
* from every bundle it produced. A plugin could declare a Core floor, publish,
|
|
442
|
+
* and ship a bundle that declared none. Any new host floor MUST be added here
|
|
443
|
+
* as well as in the Rust contract, or it does not survive packing.
|
|
444
|
+
*/
|
|
445
|
+
engines: EnginesReqSchema.optional(),
|
|
411
446
|
/**
|
|
412
447
|
* Optional per-item AFFILIATE terms: the commission paid to a referrer when a
|
|
413
448
|
* referred user buys this (paid) item. `value` is basis points for `percent`
|
|
@@ -514,6 +549,7 @@ function coreManifestJsonSchema() {
|
|
|
514
549
|
CapabilityReqSchema,
|
|
515
550
|
CompanionSurfaceSchema,
|
|
516
551
|
ContributesSchema,
|
|
552
|
+
EnginesReqSchema,
|
|
517
553
|
HookEventContributionSchema,
|
|
518
554
|
OutputStyleContributionSchema,
|
|
519
555
|
PiExtensionContributionSchema,
|
package/dist/manifest.d.cts
CHANGED
|
@@ -334,6 +334,31 @@ declare const SurfaceSchema: z.ZodEnum<{
|
|
|
334
334
|
cli: "cli";
|
|
335
335
|
}>;
|
|
336
336
|
type Surface = z.infer<typeof SurfaceSchema>;
|
|
337
|
+
/**
|
|
338
|
+
* The `engines` block: a semver **requirement** per host surface, mirroring
|
|
339
|
+
* VS-Code's `engines.vscode`. Mirrors Core's `EnginesReq`.
|
|
340
|
+
*
|
|
341
|
+
* `ryu` is the Core floor, named that way for backwards compatibility — every
|
|
342
|
+
* manifest written before per-surface floors existed spells it `ryu`, and Core's
|
|
343
|
+
* `EnginesReq::floor_for` maps the `core` surface onto it. The remaining keys are
|
|
344
|
+
* optional floors for the surfaces a plugin actually touches.
|
|
345
|
+
*
|
|
346
|
+
* Values are NOT validated as semver ranges here: Core is the authority and
|
|
347
|
+
* rejects an unparseable requirement at manifest load with a precise message.
|
|
348
|
+
* Duplicating a range parser in the SDK would only create a second, drifting
|
|
349
|
+
* opinion about what `">=1.2, <2"` means.
|
|
350
|
+
*/
|
|
351
|
+
declare const EnginesReqSchema: z.ZodObject<{
|
|
352
|
+
cli: z.ZodOptional<z.ZodString>;
|
|
353
|
+
desktop: z.ZodOptional<z.ZodString>;
|
|
354
|
+
extension: z.ZodOptional<z.ZodString>;
|
|
355
|
+
gateway: z.ZodOptional<z.ZodString>;
|
|
356
|
+
island: z.ZodOptional<z.ZodString>;
|
|
357
|
+
mobile: z.ZodOptional<z.ZodString>;
|
|
358
|
+
ryu: z.ZodString;
|
|
359
|
+
web: z.ZodOptional<z.ZodString>;
|
|
360
|
+
}, z.core.$strip>;
|
|
361
|
+
type EnginesReq = z.infer<typeof EnginesReqSchema>;
|
|
337
362
|
/**
|
|
338
363
|
* Full schema for a `manifest.json` Plugin manifest. Mirrors `PluginManifest` in
|
|
339
364
|
* `apps/core/src/plugin_manifest/mod.rs`.
|
|
@@ -434,6 +459,16 @@ declare const PluginManifestSchema: z.ZodObject<{
|
|
|
434
459
|
web: "web";
|
|
435
460
|
cli: "cli";
|
|
436
461
|
}>>>;
|
|
462
|
+
engines: z.ZodOptional<z.ZodObject<{
|
|
463
|
+
cli: z.ZodOptional<z.ZodString>;
|
|
464
|
+
desktop: z.ZodOptional<z.ZodString>;
|
|
465
|
+
extension: z.ZodOptional<z.ZodString>;
|
|
466
|
+
gateway: z.ZodOptional<z.ZodString>;
|
|
467
|
+
island: z.ZodOptional<z.ZodString>;
|
|
468
|
+
mobile: z.ZodOptional<z.ZodString>;
|
|
469
|
+
ryu: z.ZodString;
|
|
470
|
+
web: z.ZodOptional<z.ZodString>;
|
|
471
|
+
}, z.core.$strip>>;
|
|
437
472
|
affiliate: z.ZodOptional<z.ZodObject<{
|
|
438
473
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
439
474
|
rule: z.ZodOptional<z.ZodObject<{
|
|
@@ -508,4 +543,4 @@ declare function validateManifestStrict(manifestJson: string): string;
|
|
|
508
543
|
*/
|
|
509
544
|
declare function coreManifestJsonSchema(): unknown;
|
|
510
545
|
|
|
511
|
-
export { type AppDependency, AppDependencySchema, type CapabilityReq, CapabilityReqSchema, type CompanionSurface, CompanionSurfaceSchema, type Contributes, ContributesSchema, type HookEventContribution, HookEventContributionSchema, type OutputStyleContribution, OutputStyleContributionSchema, type PiExtensionContribution, PiExtensionContributionSchema, type PluginManifest, PluginManifestSchema, type Requires, RequiresSchema, type RunnableKind, RunnableKindSchema, type RunnableMeta, RunnableMetaSchema, type SetupStep, SetupStepSchema, type Surface, SurfaceSchema, type ToolAppConfig, ToolAppConfigSchema, type TurnHookContribution, TurnHookContributionSchema, type WidgetContribution, WidgetContributionSchema, coreManifestJsonSchema, labelImpersonatesSystemChrome, validateManifestStrict, validatePluginId };
|
|
546
|
+
export { type AppDependency, AppDependencySchema, type CapabilityReq, CapabilityReqSchema, type CompanionSurface, CompanionSurfaceSchema, type Contributes, ContributesSchema, type EnginesReq, EnginesReqSchema, type HookEventContribution, HookEventContributionSchema, type OutputStyleContribution, OutputStyleContributionSchema, type PiExtensionContribution, PiExtensionContributionSchema, type PluginManifest, PluginManifestSchema, type Requires, RequiresSchema, type RunnableKind, RunnableKindSchema, type RunnableMeta, RunnableMetaSchema, type SetupStep, SetupStepSchema, type Surface, SurfaceSchema, type ToolAppConfig, ToolAppConfigSchema, type TurnHookContribution, TurnHookContributionSchema, type WidgetContribution, WidgetContributionSchema, coreManifestJsonSchema, labelImpersonatesSystemChrome, validateManifestStrict, validatePluginId };
|
package/dist/manifest.d.ts
CHANGED
|
@@ -334,6 +334,31 @@ declare const SurfaceSchema: z.ZodEnum<{
|
|
|
334
334
|
cli: "cli";
|
|
335
335
|
}>;
|
|
336
336
|
type Surface = z.infer<typeof SurfaceSchema>;
|
|
337
|
+
/**
|
|
338
|
+
* The `engines` block: a semver **requirement** per host surface, mirroring
|
|
339
|
+
* VS-Code's `engines.vscode`. Mirrors Core's `EnginesReq`.
|
|
340
|
+
*
|
|
341
|
+
* `ryu` is the Core floor, named that way for backwards compatibility — every
|
|
342
|
+
* manifest written before per-surface floors existed spells it `ryu`, and Core's
|
|
343
|
+
* `EnginesReq::floor_for` maps the `core` surface onto it. The remaining keys are
|
|
344
|
+
* optional floors for the surfaces a plugin actually touches.
|
|
345
|
+
*
|
|
346
|
+
* Values are NOT validated as semver ranges here: Core is the authority and
|
|
347
|
+
* rejects an unparseable requirement at manifest load with a precise message.
|
|
348
|
+
* Duplicating a range parser in the SDK would only create a second, drifting
|
|
349
|
+
* opinion about what `">=1.2, <2"` means.
|
|
350
|
+
*/
|
|
351
|
+
declare const EnginesReqSchema: z.ZodObject<{
|
|
352
|
+
cli: z.ZodOptional<z.ZodString>;
|
|
353
|
+
desktop: z.ZodOptional<z.ZodString>;
|
|
354
|
+
extension: z.ZodOptional<z.ZodString>;
|
|
355
|
+
gateway: z.ZodOptional<z.ZodString>;
|
|
356
|
+
island: z.ZodOptional<z.ZodString>;
|
|
357
|
+
mobile: z.ZodOptional<z.ZodString>;
|
|
358
|
+
ryu: z.ZodString;
|
|
359
|
+
web: z.ZodOptional<z.ZodString>;
|
|
360
|
+
}, z.core.$strip>;
|
|
361
|
+
type EnginesReq = z.infer<typeof EnginesReqSchema>;
|
|
337
362
|
/**
|
|
338
363
|
* Full schema for a `manifest.json` Plugin manifest. Mirrors `PluginManifest` in
|
|
339
364
|
* `apps/core/src/plugin_manifest/mod.rs`.
|
|
@@ -434,6 +459,16 @@ declare const PluginManifestSchema: z.ZodObject<{
|
|
|
434
459
|
web: "web";
|
|
435
460
|
cli: "cli";
|
|
436
461
|
}>>>;
|
|
462
|
+
engines: z.ZodOptional<z.ZodObject<{
|
|
463
|
+
cli: z.ZodOptional<z.ZodString>;
|
|
464
|
+
desktop: z.ZodOptional<z.ZodString>;
|
|
465
|
+
extension: z.ZodOptional<z.ZodString>;
|
|
466
|
+
gateway: z.ZodOptional<z.ZodString>;
|
|
467
|
+
island: z.ZodOptional<z.ZodString>;
|
|
468
|
+
mobile: z.ZodOptional<z.ZodString>;
|
|
469
|
+
ryu: z.ZodString;
|
|
470
|
+
web: z.ZodOptional<z.ZodString>;
|
|
471
|
+
}, z.core.$strip>>;
|
|
437
472
|
affiliate: z.ZodOptional<z.ZodObject<{
|
|
438
473
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
439
474
|
rule: z.ZodOptional<z.ZodObject<{
|
|
@@ -508,4 +543,4 @@ declare function validateManifestStrict(manifestJson: string): string;
|
|
|
508
543
|
*/
|
|
509
544
|
declare function coreManifestJsonSchema(): unknown;
|
|
510
545
|
|
|
511
|
-
export { type AppDependency, AppDependencySchema, type CapabilityReq, CapabilityReqSchema, type CompanionSurface, CompanionSurfaceSchema, type Contributes, ContributesSchema, type HookEventContribution, HookEventContributionSchema, type OutputStyleContribution, OutputStyleContributionSchema, type PiExtensionContribution, PiExtensionContributionSchema, type PluginManifest, PluginManifestSchema, type Requires, RequiresSchema, type RunnableKind, RunnableKindSchema, type RunnableMeta, RunnableMetaSchema, type SetupStep, SetupStepSchema, type Surface, SurfaceSchema, type ToolAppConfig, ToolAppConfigSchema, type TurnHookContribution, TurnHookContributionSchema, type WidgetContribution, WidgetContributionSchema, coreManifestJsonSchema, labelImpersonatesSystemChrome, validateManifestStrict, validatePluginId };
|
|
546
|
+
export { type AppDependency, AppDependencySchema, type CapabilityReq, CapabilityReqSchema, type CompanionSurface, CompanionSurfaceSchema, type Contributes, ContributesSchema, type EnginesReq, EnginesReqSchema, type HookEventContribution, HookEventContributionSchema, type OutputStyleContribution, OutputStyleContributionSchema, type PiExtensionContribution, PiExtensionContributionSchema, type PluginManifest, PluginManifestSchema, type Requires, RequiresSchema, type RunnableKind, RunnableKindSchema, type RunnableMeta, RunnableMetaSchema, type SetupStep, SetupStepSchema, type Surface, SurfaceSchema, type ToolAppConfig, ToolAppConfigSchema, type TurnHookContribution, TurnHookContributionSchema, type WidgetContribution, WidgetContributionSchema, coreManifestJsonSchema, labelImpersonatesSystemChrome, validateManifestStrict, validatePluginId };
|
package/dist/manifest.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
CapabilityReqSchema,
|
|
4
4
|
CompanionSurfaceSchema,
|
|
5
5
|
ContributesSchema,
|
|
6
|
+
EnginesReqSchema,
|
|
6
7
|
HookEventContributionSchema,
|
|
7
8
|
OutputStyleContributionSchema,
|
|
8
9
|
PiExtensionContributionSchema,
|
|
@@ -19,12 +20,13 @@ import {
|
|
|
19
20
|
labelImpersonatesSystemChrome,
|
|
20
21
|
validateManifestStrict,
|
|
21
22
|
validatePluginId
|
|
22
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-AO2KJRDD.js";
|
|
23
24
|
export {
|
|
24
25
|
AppDependencySchema,
|
|
25
26
|
CapabilityReqSchema,
|
|
26
27
|
CompanionSurfaceSchema,
|
|
27
28
|
ContributesSchema,
|
|
29
|
+
EnginesReqSchema,
|
|
28
30
|
HookEventContributionSchema,
|
|
29
31
|
OutputStyleContributionSchema,
|
|
30
32
|
PiExtensionContributionSchema,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ryuhq/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
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",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"clean": "rm -rf dist"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@ryuhq/sdk-native": "0.1.
|
|
52
|
+
"@ryuhq/sdk-native": "0.1.14",
|
|
53
53
|
"zod": "^4.1.13"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
@@ -117,7 +117,33 @@ export interface PluginManifest {
|
|
|
117
117
|
*/
|
|
118
118
|
backend_sha256?: string | null;
|
|
119
119
|
/**
|
|
120
|
-
* Detail-page hero banner spec
|
|
120
|
+
* Detail-page hero banner spec; opaque passthrough (Ryu ext).
|
|
121
|
+
*
|
|
122
|
+
* The banner is the listing's OWN background, not its icon enlarged. Declared
|
|
123
|
+
* or not, the hero always paints something: with no `banner` the detail page
|
|
124
|
+
* derives its wash from `icon_dither`, so an app that never thinks about this
|
|
125
|
+
* key still opens on its own colour rather than a grey slab. Declaring one is
|
|
126
|
+
* how an author says "my hero is not just my icon, bigger".
|
|
127
|
+
*
|
|
128
|
+
* Accepted keys, all optional — the render layer picks the first that paints
|
|
129
|
+
* and falls back down the list, so an unknown or malformed value degrades to
|
|
130
|
+
* the derived wash rather than failing:
|
|
131
|
+
*
|
|
132
|
+
* - `background` — a flat CSS background (a colour, a `linear-gradient(…)`).
|
|
133
|
+
* - `imageUrl` — a raster banner, painted `object-cover`. http(s) only.
|
|
134
|
+
* - `colors: [String]` — two or more stops, ramped 135°.
|
|
135
|
+
* - `style: "gradient" | "dither" | "flat" | "image"` — how to treat the
|
|
136
|
+
* above; `dither` adds the noise overlay, `flat`/`image` select `background`
|
|
137
|
+
* / `imageUrl` explicitly.
|
|
138
|
+
* - `seed: Number` — the dither noise seed, so two apps sharing a palette do
|
|
139
|
+
* not share a texture.
|
|
140
|
+
*
|
|
141
|
+
* Kept as raw JSON like `icon_dither`, for the same reason: this is
|
|
142
|
+
* PUBLISHER-supplied and reaches a CSS background, so it must never fail the
|
|
143
|
+
* manifest parse, and the client validates before painting (`safeHttpUrl` for
|
|
144
|
+
* `imageUrl`; the flat string is trusted exactly as far as `icon_background`
|
|
145
|
+
* already is). Core does not read any of these keys — it copies the whole
|
|
146
|
+
* value onto the catalog entry — so a new one needs no Core release.
|
|
121
147
|
*/
|
|
122
148
|
banner?: {
|
|
123
149
|
[k: string]: unknown;
|
|
@@ -214,6 +240,24 @@ export interface PluginManifest {
|
|
|
214
240
|
iconDither?: {
|
|
215
241
|
[k: string]: unknown;
|
|
216
242
|
};
|
|
243
|
+
/**
|
|
244
|
+
* Inset for the card's icon square (Ryu extension: `iconPadding`).
|
|
245
|
+
*
|
|
246
|
+
* A product LOGO that is edge-to-edge in its own art has no breathing room
|
|
247
|
+
* inside the square and reads as a sticker rather than an icon. One of
|
|
248
|
+
* `none` | `sm` | `md` | `lg`.
|
|
249
|
+
*
|
|
250
|
+
* Any value other than `none` ALSO letterboxes the art (`object-contain`)
|
|
251
|
+
* instead of cropping it. That coupling is load-bearing, not a convenience:
|
|
252
|
+
* a listing declaring a bare `iconUrl` (no `icon`) is not in the brand lane,
|
|
253
|
+
* so it is painted `object-cover` — inset alone would be silently inert for
|
|
254
|
+
* exactly the raw-logo case this field exists for.
|
|
255
|
+
*
|
|
256
|
+
* `Option<String>` rather than a Rust enum, for the same forward-compat
|
|
257
|
+
* reason `icon_dither` is raw JSON: an unknown value must never fail the
|
|
258
|
+
* manifest parse. The render layer validates and falls back.
|
|
259
|
+
*/
|
|
260
|
+
iconPadding?: string | null;
|
|
217
261
|
/**
|
|
218
262
|
* Logo URL (contract key `iconUrl`; Ryu extension).
|
|
219
263
|
*/
|
|
@@ -595,6 +639,22 @@ export interface Contributes {
|
|
|
595
639
|
* [`Contributes::message_actions`].
|
|
596
640
|
*/
|
|
597
641
|
context_menu_items?: ContextMenuContribution[];
|
|
642
|
+
/**
|
|
643
|
+
* "New X" rows the app contributes to the shell's create menu (the sidebar
|
|
644
|
+
* footer "+"). See [`CreateActionContribution`].
|
|
645
|
+
*
|
|
646
|
+
* This exists because the create menu's only app seam used to be
|
|
647
|
+
* `sidebar_sections[].spec.create` — section-scoped, so an app that
|
|
648
|
+
* contributes no sidebar section could not put a row there at all. The shell
|
|
649
|
+
* therefore hardcoded rows for apps it happened to know about, and those rows
|
|
650
|
+
* stayed in the menu when the app was not installed, leading straight to an
|
|
651
|
+
* error page. A create action is its own contribution precisely so the row
|
|
652
|
+
* appears and disappears with the app.
|
|
653
|
+
*
|
|
654
|
+
* **Stored raw, validated at the chokepoint** — same rule as
|
|
655
|
+
* [`Contributes::context_menu_items`].
|
|
656
|
+
*/
|
|
657
|
+
create_actions?: CreateActionContribution[];
|
|
598
658
|
/**
|
|
599
659
|
* **Deletable data categories** the app owns — one "Delete all X" row in
|
|
600
660
|
* Settings → Danger Zone (see [`DataCategoryContribution`]).
|
|
@@ -847,6 +907,24 @@ export interface Contributes {
|
|
|
847
907
|
* (no live list, just a label/icon + a client route). See [`SidebarButtonContribution`].
|
|
848
908
|
*/
|
|
849
909
|
sidebar_buttons?: SidebarButtonContribution[];
|
|
910
|
+
/**
|
|
911
|
+
* App-registered sidebar **modes** — a named preset of the whole left sidebar:
|
|
912
|
+
* which sections it offers as tabs, and which one it opens on.
|
|
913
|
+
*
|
|
914
|
+
* The third axis of the sidebar contract, after "what sections exist"
|
|
915
|
+
* ([`Contributes::sidebar_sections`]) and "what nav rows exist"
|
|
916
|
+
* ([`Contributes::sidebar_buttons`]): **how the sidebar as a whole is
|
|
917
|
+
* arranged**. The shell ships three modes of its own (every section stacked;
|
|
918
|
+
* every section as a tab; Agent mode, which is the pair Sessions ⇄ Agents), and
|
|
919
|
+
* before this member an app could add a section to that list but could not
|
|
920
|
+
* propose an arrangement — so a plugin wanting the Grok/Hermes bot-mode posture
|
|
921
|
+
* had to ask for a shell change. See [`SidebarModeContribution`].
|
|
922
|
+
*
|
|
923
|
+
* Self-contained (it names sections, not runnables), so it stays out of
|
|
924
|
+
* [`Contributes::referenced_ids`]; served + tagged with the owning `plugin` id
|
|
925
|
+
* at `GET /api/plugins/contributions`.
|
|
926
|
+
*/
|
|
927
|
+
sidebar_modes?: SidebarModeContribution[];
|
|
850
928
|
/**
|
|
851
929
|
* App-registered sidebar **sections** — a header plus a live list of rows the
|
|
852
930
|
* shell fetches from a declared Core `/api/` path. Lets an app own its sidebar
|
|
@@ -1031,6 +1109,47 @@ export interface ContextMenuContribution {
|
|
|
1031
1109
|
*/
|
|
1032
1110
|
order?: number | null;
|
|
1033
1111
|
}
|
|
1112
|
+
/**
|
|
1113
|
+
* One "New X" row a plugin contributes to the shell's create menu (see
|
|
1114
|
+
* [`Contributes::create_actions`]).
|
|
1115
|
+
*/
|
|
1116
|
+
export interface CreateActionContribution {
|
|
1117
|
+
args?: unknown;
|
|
1118
|
+
/**
|
|
1119
|
+
* Granted capability to invoke instead of navigating, plus static `args` —
|
|
1120
|
+
* for a create that is an action rather than a destination. Dispatched
|
|
1121
|
+
* through the same host seam as a context-menu row.
|
|
1122
|
+
*/
|
|
1123
|
+
capability?: string | null;
|
|
1124
|
+
/**
|
|
1125
|
+
* Optional glyph id resolved by the shell's Icon primitive. The desktop's
|
|
1126
|
+
* create menu draws no icons today (its rows are label-only by design), so
|
|
1127
|
+
* this is read and ignored there — it exists for shells that do.
|
|
1128
|
+
*/
|
|
1129
|
+
icon?: string | null;
|
|
1130
|
+
/**
|
|
1131
|
+
* Stable id for this row within the plugin.
|
|
1132
|
+
*/
|
|
1133
|
+
id: string;
|
|
1134
|
+
/**
|
|
1135
|
+
* Row label, written as the user reads it — "New workflow", not "Workflow".
|
|
1136
|
+
*/
|
|
1137
|
+
label: string;
|
|
1138
|
+
/**
|
|
1139
|
+
* Sort position among contributed rows (ascending).
|
|
1140
|
+
*/
|
|
1141
|
+
order?: number | null;
|
|
1142
|
+
/**
|
|
1143
|
+
* In-app route the shell opens, e.g. `/workflows/new`. Must be a path, not a
|
|
1144
|
+
* URL: this is a navigation inside the shell, and accepting a scheme here
|
|
1145
|
+
* would turn a create row into an arbitrary-link affordance.
|
|
1146
|
+
*/
|
|
1147
|
+
target?: string | null;
|
|
1148
|
+
/**
|
|
1149
|
+
* Title for the tab `target` opens. Falls back to `label`.
|
|
1150
|
+
*/
|
|
1151
|
+
title?: string | null;
|
|
1152
|
+
}
|
|
1034
1153
|
/**
|
|
1035
1154
|
* One **deletable data category** an app owns (see [`Contributes::data_categories`]).
|
|
1036
1155
|
*
|
|
@@ -1598,6 +1717,68 @@ export interface SidebarButtonContribution {
|
|
|
1598
1717
|
*/
|
|
1599
1718
|
title: string;
|
|
1600
1719
|
}
|
|
1720
|
+
/**
|
|
1721
|
+
* One app-registered **sidebar mode** — a named arrangement of the whole left
|
|
1722
|
+
* sidebar: the sections it offers as tabs, and the one it opens on.
|
|
1723
|
+
*
|
|
1724
|
+
* The shape is deliberately thin, and every field it does NOT have is the point:
|
|
1725
|
+
*
|
|
1726
|
+
* - **No renderer, no code.** A mode names existing sections. It cannot draw a row,
|
|
1727
|
+
* which is why it needs no grants and cannot be a carriage channel — the worst a
|
|
1728
|
+
* hostile mode can do is offer a tab list the user does not want, one menu row
|
|
1729
|
+
* away from being switched off.
|
|
1730
|
+
* - **No row style.** How a section's rows draw belongs to that SECTION
|
|
1731
|
+
* (`SidebarSectionSpec.rowStyle` in `@ryu/app-host/views`), because it is a
|
|
1732
|
+
* property of the feed, not of an arrangement: a roster of named bots wants
|
|
1733
|
+
* avatars whether or not the user is in a mode that features it. Putting it here
|
|
1734
|
+
* would also mean a mode reaching across into another contribution's rendering,
|
|
1735
|
+
* which is the coupling this member exists to avoid.
|
|
1736
|
+
* - **No `hidden` list.** A mode is a positive statement about what to show. The
|
|
1737
|
+
* sections it does not name are simply not tabs in it.
|
|
1738
|
+
*
|
|
1739
|
+
* Section ids are the shell's own keys (`agents`, `chats`, `spaces`, …) or another
|
|
1740
|
+
* contributed section's namespaced key (`plugin:<pluginId>:<sectionId>`). A named
|
|
1741
|
+
* section that does not resolve is dropped rather than failing the mode — an app
|
|
1742
|
+
* may legitimately name a section from a sibling app the user has not installed,
|
|
1743
|
+
* and losing one tab is a better answer than losing the mode.
|
|
1744
|
+
*/
|
|
1745
|
+
export interface SidebarModeContribution {
|
|
1746
|
+
/**
|
|
1747
|
+
* Which of `sections` the mode opens on. Absent (or naming a section not in
|
|
1748
|
+
* `sections`) = the first one. This is the field that makes a mode an opinion
|
|
1749
|
+
* rather than a filter: the shell's own Agent mode lists Sessions first but
|
|
1750
|
+
* opens on Agents, because the roster is what the mode is for.
|
|
1751
|
+
*/
|
|
1752
|
+
default_section?: string | null;
|
|
1753
|
+
/**
|
|
1754
|
+
* One-line description shown under the title where the mode is offered.
|
|
1755
|
+
*/
|
|
1756
|
+
description?: string | null;
|
|
1757
|
+
/**
|
|
1758
|
+
* Optional glyph id resolved by the shell's Icon primitive (Iconify/Hugeicons).
|
|
1759
|
+
*/
|
|
1760
|
+
icon?: string | null;
|
|
1761
|
+
/**
|
|
1762
|
+
* Stable id for this mode within the plugin (namespaced by the shell into the
|
|
1763
|
+
* stored mode key as `plugin:<pluginId>:<id>`, so two apps can both ship a
|
|
1764
|
+
* `bots` mode).
|
|
1765
|
+
*/
|
|
1766
|
+
id: string;
|
|
1767
|
+
/**
|
|
1768
|
+
* Optional ordering hint among the modes on offer (lower = earlier).
|
|
1769
|
+
*/
|
|
1770
|
+
order?: number | null;
|
|
1771
|
+
/**
|
|
1772
|
+
* The sections this mode offers as tabs, in display order. Empty = the mode is
|
|
1773
|
+
* inert and the shell ignores it; a mode with one entry is a legitimate
|
|
1774
|
+
* single-surface arrangement, not an error.
|
|
1775
|
+
*/
|
|
1776
|
+
sections?: string[];
|
|
1777
|
+
/**
|
|
1778
|
+
* Label shown in the sidebar's mode menu and the Appearance tab.
|
|
1779
|
+
*/
|
|
1780
|
+
title: string;
|
|
1781
|
+
}
|
|
1601
1782
|
/**
|
|
1602
1783
|
* One app-registered **sidebar section** — a header plus a live list of rows the
|
|
1603
1784
|
* desktop's compact sidebar renderer draws (the app-owned replacement for the
|
|
@@ -1914,16 +2095,78 @@ export interface WidgetContribution {
|
|
|
1914
2095
|
uri: string;
|
|
1915
2096
|
}
|
|
1916
2097
|
/**
|
|
1917
|
-
* `engines` block — the
|
|
1918
|
-
* `engines.vscode`.
|
|
2098
|
+
* `engines` block — the **host** version floors, mirroring VS-Code's
|
|
2099
|
+
* `engines.vscode`. Every value is a semver **requirement** string.
|
|
2100
|
+
*
|
|
2101
|
+
* `ryu` is the Core floor and is the only required key (every manifest written
|
|
2102
|
+
* before per-surface floors existed carries just that one). The rest are optional
|
|
2103
|
+
* per-[`Surface`] floors: a plugin that needs a Gateway API added in 0.1.5 and a
|
|
2104
|
+
* desktop panel API added in 0.2.0 says so, instead of over-declaring one Core
|
|
2105
|
+
* floor and hoping the release train kept them in step.
|
|
2106
|
+
*
|
|
2107
|
+
* ## Why this is a flat struct and not a `BTreeMap<Surface, String>`
|
|
2108
|
+
*
|
|
2109
|
+
* The [`PluginManifest::surfaces`] map would be the obvious home, but it is
|
|
2110
|
+
* **absent from the SDK's zod mirror** (`packages/sdk/src/manifest.ts`), and zod
|
|
2111
|
+
* strips unlisted keys — so a floor declared there would be silently dropped from
|
|
2112
|
+
* every bundle `ryu pack` produces. `engines` is the block that already means
|
|
2113
|
+
* "host floor", it is what a manifest author reaches for, and mirroring it costs
|
|
2114
|
+
* one schema addition rather than a nested map.
|
|
2115
|
+
*
|
|
2116
|
+
* ## Unknown ≠ unsatisfied
|
|
2117
|
+
*
|
|
2118
|
+
* Core observes its own version and (via `/health`) the Gateway's. It does NOT
|
|
2119
|
+
* know the desktop, island, mobile, extension or web version — those are separate
|
|
2120
|
+
* installs that never report in. A floor against a surface whose version is
|
|
2121
|
+
* unknown is therefore **advisory, never blocking**: see
|
|
2122
|
+
* `HostVersions::evaluate`. Blocking on unknown would delist every plugin from
|
|
2123
|
+
* every surface Core cannot see, which is most of them.
|
|
1919
2124
|
*/
|
|
1920
2125
|
export interface EnginesReq {
|
|
1921
2126
|
/**
|
|
1922
|
-
*
|
|
1923
|
-
* `
|
|
1924
|
-
|
|
2127
|
+
* Floor for the **terminal** (`cli`) surface — the TUI that dispatches
|
|
2128
|
+
* `ryu <app> <cmd>`.
|
|
2129
|
+
*/
|
|
2130
|
+
cli?: string | null;
|
|
2131
|
+
/**
|
|
2132
|
+
* Floor for the **desktop** app (Tauri shell).
|
|
2133
|
+
*/
|
|
2134
|
+
desktop?: string | null;
|
|
2135
|
+
/**
|
|
2136
|
+
* Floor for the **browser extension** surface.
|
|
2137
|
+
*/
|
|
2138
|
+
extension?: string | null;
|
|
2139
|
+
/**
|
|
2140
|
+
* Floor for the **Gateway**. The one non-Core surface Core can actually
|
|
2141
|
+
* observe (it spawns the Gateway and reads `version` from its `/health`), so a
|
|
2142
|
+
* floor here is genuinely enforceable rather than advisory.
|
|
2143
|
+
*/
|
|
2144
|
+
gateway?: string | null;
|
|
2145
|
+
/**
|
|
2146
|
+
* Floor for the **island** (the always-on overlay surface).
|
|
2147
|
+
*/
|
|
2148
|
+
island?: string | null;
|
|
2149
|
+
/**
|
|
2150
|
+
* Floor for the **mobile** app. The one surface with a genuinely independent
|
|
2151
|
+
* release train (App Store / Play review lag), so it is the floor most likely
|
|
2152
|
+
* to be unsatisfied in practice.
|
|
2153
|
+
*/
|
|
2154
|
+
mobile?: string | null;
|
|
2155
|
+
/**
|
|
2156
|
+
* Semver requirement the running **Core** version must satisfy (e.g.
|
|
2157
|
+
* `">=0.3.0"`, `"^1.2"`). Parsed as a [`semver::VersionReq`]; an unparseable
|
|
2158
|
+
* value causes the loader to reject the manifest, and an unsatisfied one moves
|
|
2159
|
+
* it to the incompatible lane (shown in the marketplace, refused at install).
|
|
2160
|
+
*
|
|
2161
|
+
* Named `ryu` rather than `core` for backwards compatibility: every manifest
|
|
2162
|
+
* in the wild spells it this way. [`EnginesReq::floor_for`] maps
|
|
2163
|
+
* [`Surface::Core`] onto it.
|
|
1925
2164
|
*/
|
|
1926
2165
|
ryu: string;
|
|
2166
|
+
/**
|
|
2167
|
+
* Floor for the **web** surface.
|
|
2168
|
+
*/
|
|
2169
|
+
web?: string | null;
|
|
1927
2170
|
}
|
|
1928
2171
|
/**
|
|
1929
2172
|
* One declarative **stdio MCP server** a plugin registers (see
|
package/src/manifest.test.ts
CHANGED
|
@@ -455,6 +455,88 @@ describe("AppBuilder", () => {
|
|
|
455
455
|
// silently delete a plugin's dependencies BEFORE the manifest is signed. So every
|
|
456
456
|
// case below asserts the field SURVIVES the parse, not merely that it parses.
|
|
457
457
|
|
|
458
|
+
describe("engines (host version floors)", () => {
|
|
459
|
+
/** The regression: `engines` was absent from `PluginManifestSchema`, and zod
|
|
460
|
+
* strips unlisted keys — so `ryu pack` dropped the block from every bundle. A
|
|
461
|
+
* plugin could declare a Core floor, publish, and ship a bundle declaring none. */
|
|
462
|
+
it("does not strip `engines` — the whole block survives the parse", () => {
|
|
463
|
+
const parsed = PluginManifestSchema.safeParse({
|
|
464
|
+
engines: {
|
|
465
|
+
cli: ">=0.1.0",
|
|
466
|
+
desktop: ">=0.2.0",
|
|
467
|
+
extension: ">=0.1.0",
|
|
468
|
+
gateway: ">=0.1.5",
|
|
469
|
+
island: ">=0.1.0",
|
|
470
|
+
mobile: ">=1.0.0",
|
|
471
|
+
ryu: ">=0.1.0",
|
|
472
|
+
web: ">=0.1.0",
|
|
473
|
+
},
|
|
474
|
+
id: "com.example.floors",
|
|
475
|
+
name: "Floors",
|
|
476
|
+
runnables: [],
|
|
477
|
+
version: "1.0.0",
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
expect(parsed.success).toBe(true);
|
|
481
|
+
if (!parsed.success) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
expect(parsed.data.engines).toEqual({
|
|
485
|
+
cli: ">=0.1.0",
|
|
486
|
+
desktop: ">=0.2.0",
|
|
487
|
+
extension: ">=0.1.0",
|
|
488
|
+
gateway: ">=0.1.5",
|
|
489
|
+
island: ">=0.1.0",
|
|
490
|
+
mobile: ">=1.0.0",
|
|
491
|
+
ryu: ">=0.1.0",
|
|
492
|
+
web: ">=0.1.0",
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it("keeps a legacy `{ ryu }`-only block intact and adds no sibling keys", () => {
|
|
497
|
+
const parsed = PluginManifestSchema.safeParse({
|
|
498
|
+
engines: { ryu: ">=0.1.0" },
|
|
499
|
+
id: "com.example.legacy-engines",
|
|
500
|
+
name: "Legacy",
|
|
501
|
+
runnables: [],
|
|
502
|
+
version: "1.0.0",
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
expect(parsed.success).toBe(true);
|
|
506
|
+
if (!parsed.success) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
expect(parsed.data.engines).toEqual({ ryu: ">=0.1.0" });
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it("leaves `engines` undefined when the manifest declares none", () => {
|
|
513
|
+
const parsed = PluginManifestSchema.safeParse({
|
|
514
|
+
id: "com.example.no-engines",
|
|
515
|
+
name: "None",
|
|
516
|
+
runnables: [],
|
|
517
|
+
version: "1.0.0",
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
expect(parsed.success).toBe(true);
|
|
521
|
+
if (!parsed.success) {
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
expect(parsed.data.engines).toBeUndefined();
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it("rejects an `engines` block with no Core floor", () => {
|
|
528
|
+
const parsed = PluginManifestSchema.safeParse({
|
|
529
|
+
engines: { desktop: ">=0.2.0" },
|
|
530
|
+
id: "com.example.bad-engines",
|
|
531
|
+
name: "Bad",
|
|
532
|
+
runnables: [],
|
|
533
|
+
version: "1.0.0",
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
expect(parsed.success).toBe(false);
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
|
|
458
540
|
describe("requires / targets", () => {
|
|
459
541
|
it("keeps a manifest with NEITHER requires nor targets valid (all 37 shipped plugins)", () => {
|
|
460
542
|
const parsed = PluginManifestSchema.safeParse({
|
package/src/manifest.ts
CHANGED
|
@@ -578,6 +578,43 @@ export const SurfaceSchema = z.enum([
|
|
|
578
578
|
|
|
579
579
|
export type Surface = z.infer<typeof SurfaceSchema>;
|
|
580
580
|
|
|
581
|
+
// ── engines (host version floors) ────────────────────────────────────────────
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* The `engines` block: a semver **requirement** per host surface, mirroring
|
|
585
|
+
* VS-Code's `engines.vscode`. Mirrors Core's `EnginesReq`.
|
|
586
|
+
*
|
|
587
|
+
* `ryu` is the Core floor, named that way for backwards compatibility — every
|
|
588
|
+
* manifest written before per-surface floors existed spells it `ryu`, and Core's
|
|
589
|
+
* `EnginesReq::floor_for` maps the `core` surface onto it. The remaining keys are
|
|
590
|
+
* optional floors for the surfaces a plugin actually touches.
|
|
591
|
+
*
|
|
592
|
+
* Values are NOT validated as semver ranges here: Core is the authority and
|
|
593
|
+
* rejects an unparseable requirement at manifest load with a precise message.
|
|
594
|
+
* Duplicating a range parser in the SDK would only create a second, drifting
|
|
595
|
+
* opinion about what `">=1.2, <2"` means.
|
|
596
|
+
*/
|
|
597
|
+
export const EnginesReqSchema = z.object({
|
|
598
|
+
/** Floor for the terminal (`cli`) surface. */
|
|
599
|
+
cli: z.string().optional(),
|
|
600
|
+
/** Floor for the Tauri desktop app. */
|
|
601
|
+
desktop: z.string().optional(),
|
|
602
|
+
/** Floor for the browser extension. */
|
|
603
|
+
extension: z.string().optional(),
|
|
604
|
+
/** Floor for the Ryu Gateway. */
|
|
605
|
+
gateway: z.string().optional(),
|
|
606
|
+
/** Floor for the dynamic-island companion. */
|
|
607
|
+
island: z.string().optional(),
|
|
608
|
+
/** Floor for the mobile app. */
|
|
609
|
+
mobile: z.string().optional(),
|
|
610
|
+
/** Floor for the running **Core** (e.g. `">=0.3.0"`). Required. */
|
|
611
|
+
ryu: z.string().min(1, "engines.ryu is required when engines is present"),
|
|
612
|
+
/** Floor for the Next.js web app. */
|
|
613
|
+
web: z.string().optional(),
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
export type EnginesReq = z.infer<typeof EnginesReqSchema>;
|
|
617
|
+
|
|
581
618
|
// ── PluginManifest ───────────────────────────────────────────────────────────
|
|
582
619
|
|
|
583
620
|
/**
|
|
@@ -669,6 +706,23 @@ export const PluginManifestSchema = z.object({
|
|
|
669
706
|
*/
|
|
670
707
|
targets: z.array(SurfaceSchema).default([]),
|
|
671
708
|
|
|
709
|
+
/**
|
|
710
|
+
* Host version floors — the semver requirement each surface must satisfy for
|
|
711
|
+
* this plugin to install. Mirrors Core's `EnginesReq`
|
|
712
|
+
* (`crates/core/kernel-contracts/src/manifest.rs`).
|
|
713
|
+
*
|
|
714
|
+
* `ryu` is the **Core** floor and the only required key (it is the legacy
|
|
715
|
+
* spelling; every manifest in the wild carries just that one). The rest are
|
|
716
|
+
* optional per-surface floors.
|
|
717
|
+
*
|
|
718
|
+
* REGRESSION THIS FIXES: `engines` was absent from this schema entirely, and
|
|
719
|
+
* zod strips unlisted keys — so `ryu pack` silently dropped the whole block
|
|
720
|
+
* from every bundle it produced. A plugin could declare a Core floor, publish,
|
|
721
|
+
* and ship a bundle that declared none. Any new host floor MUST be added here
|
|
722
|
+
* as well as in the Rust contract, or it does not survive packing.
|
|
723
|
+
*/
|
|
724
|
+
engines: EnginesReqSchema.optional(),
|
|
725
|
+
|
|
672
726
|
/**
|
|
673
727
|
* Optional per-item AFFILIATE terms: the commission paid to a referrer when a
|
|
674
728
|
* referred user buys this (paid) item. `value` is basis points for `percent`
|
|
@@ -204,9 +204,12 @@ export function httpPrimitiveTransport(
|
|
|
204
204
|
return (parsed.text ?? "").trim();
|
|
205
205
|
};
|
|
206
206
|
|
|
207
|
-
// `/api/voice/speak` — Core streams raw
|
|
208
|
-
// data: URL).
|
|
209
|
-
//
|
|
207
|
+
// `/api/voice/speak` — Core streams raw audio bytes back (not JSON, not a
|
|
208
|
+
// data: URL). Usually `audio/wav`; the cloud engine (`engine: "gateway"`) can
|
|
209
|
+
// answer with whatever the routed provider produced, so the media type is read
|
|
210
|
+
// off the response rather than assumed. Convert to a renderable data: URL, as
|
|
211
|
+
// the desktop host's rpc.ts does, so the shipped type contract ("returns a
|
|
212
|
+
// data: URL") holds.
|
|
210
213
|
const speakDirect = async (body: unknown): Promise<string> => {
|
|
211
214
|
const res = await doFetch(`${base}/api/voice/speak`, {
|
|
212
215
|
method: "POST",
|