@ryuhq/sdk 0.0.5 → 0.1.2

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/agent.cjs CHANGED
@@ -152,7 +152,10 @@ function dataUrlToBytes(dataUrl) {
152
152
  }
153
153
  return { bytes, mediaType };
154
154
  }
155
- return { bytes: new TextEncoder().encode(decodeURIComponent(payload)), mediaType };
155
+ return {
156
+ bytes: new TextEncoder().encode(decodeURIComponent(payload)),
157
+ mediaType
158
+ };
156
159
  }
157
160
  function bytesToDataUrl(bytes, mediaType) {
158
161
  let binary = "";
package/dist/agent.d.cts CHANGED
@@ -1,3 +1,3 @@
1
- export { A as Agent, b as AgentConfig, c as AgentEvent, U as AgentEventAuthRequired, V as AgentEventError, W as AgentEventResult, X as AgentEventText, Y as AgentEventToolCall, Z as AgentEventToolResult, d as AgentTool, _ as AssistantMessage, $ as Elicitation, E as Endpoint, h as GenerateResult, a0 as LoopConfig, a1 as LoopMessage, a2 as ModelCallOptions, a3 as ModelCallResult, a4 as ModelUsage, Q as QueryInput, n as QueryOptions, t as RemoteToolRef, a5 as RyuToolOptions, a6 as ToolCall, a7 as ToolExecContext, a8 as ToolExecResult, a9 as ToolFunctionDef, aa as callModelWithTools, y as createAgent, ab as detectElicitation, ac as executeTool, N as query, ad as resolveToolDefs, ae as runAgentLoop, O as ryuTool } from './index-CEbS1SlS.cjs';
1
+ export { A as Agent, b as AgentConfig, c as AgentEvent, U as AgentEventAuthRequired, V as AgentEventError, W as AgentEventResult, X as AgentEventText, Y as AgentEventToolCall, Z as AgentEventToolResult, d as AgentTool, _ as AssistantMessage, $ as Elicitation, E as Endpoint, h as GenerateResult, a0 as LoopConfig, a1 as LoopMessage, a2 as ModelCallOptions, a3 as ModelCallResult, a4 as ModelUsage, Q as QueryInput, n as QueryOptions, t as RemoteToolRef, a5 as RyuToolOptions, a6 as ToolCall, a7 as ToolExecContext, a8 as ToolExecResult, a9 as ToolFunctionDef, aa as callModelWithTools, y as createAgent, ab as detectElicitation, ac as executeTool, N as query, ad as resolveToolDefs, ae as runAgentLoop, O as ryuTool } from './index-BvAB5eMk.cjs';
2
2
  import './manifest.cjs';
3
3
  import 'zod';
package/dist/agent.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { A as Agent, b as AgentConfig, c as AgentEvent, U as AgentEventAuthRequired, V as AgentEventError, W as AgentEventResult, X as AgentEventText, Y as AgentEventToolCall, Z as AgentEventToolResult, d as AgentTool, _ as AssistantMessage, $ as Elicitation, E as Endpoint, h as GenerateResult, a0 as LoopConfig, a1 as LoopMessage, a2 as ModelCallOptions, a3 as ModelCallResult, a4 as ModelUsage, Q as QueryInput, n as QueryOptions, t as RemoteToolRef, a5 as RyuToolOptions, a6 as ToolCall, a7 as ToolExecContext, a8 as ToolExecResult, a9 as ToolFunctionDef, aa as callModelWithTools, y as createAgent, ab as detectElicitation, ac as executeTool, N as query, ad as resolveToolDefs, ae as runAgentLoop, O as ryuTool } from './index-DAxq7Y0R.js';
1
+ export { A as Agent, b as AgentConfig, c as AgentEvent, U as AgentEventAuthRequired, V as AgentEventError, W as AgentEventResult, X as AgentEventText, Y as AgentEventToolCall, Z as AgentEventToolResult, d as AgentTool, _ as AssistantMessage, $ as Elicitation, E as Endpoint, h as GenerateResult, a0 as LoopConfig, a1 as LoopMessage, a2 as ModelCallOptions, a3 as ModelCallResult, a4 as ModelUsage, Q as QueryInput, n as QueryOptions, t as RemoteToolRef, a5 as RyuToolOptions, a6 as ToolCall, a7 as ToolExecContext, a8 as ToolExecResult, a9 as ToolFunctionDef, aa as callModelWithTools, y as createAgent, ab as detectElicitation, ac as executeTool, N as query, ad as resolveToolDefs, ae as runAgentLoop, O as ryuTool } from './index-B6SkaAjJ.js';
2
2
  import './manifest.js';
3
3
  import 'zod';
package/dist/agent.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  resolveToolDefs,
9
9
  runAgentLoop,
10
10
  ryuTool
11
- } from "./chunk-KPKMMGVC.js";
11
+ } from "./chunk-MTUBUPIV.js";
12
12
  import "./chunk-ODFEUVPW.js";
13
13
  export {
14
14
  Agent,
@@ -24,7 +24,10 @@ function dataUrlToBytes(dataUrl) {
24
24
  }
25
25
  return { bytes, mediaType };
26
26
  }
27
- return { bytes: new TextEncoder().encode(decodeURIComponent(payload)), mediaType };
27
+ return {
28
+ bytes: new TextEncoder().encode(decodeURIComponent(payload)),
29
+ mediaType
30
+ };
28
31
  }
29
32
  function bytesToDataUrl(bytes, mediaType) {
30
33
  let binary = "";
@@ -76,7 +76,47 @@ var TurnHookContributionSchema = z.object({
76
76
  /** Turn boundary this fires on. Today only `"post_assistant_turn"`. */
77
77
  on: z.string().min(1).default("post_assistant_turn"),
78
78
  /** The JS hook body executed in the sandbox (returns a directive). */
79
- code: z.string().min(1)
79
+ code: z.string().min(1).optional(),
80
+ /** Path to the hook body, relative to the plugin root (`hooks/<name>.js`). */
81
+ code_file: z.string().min(1).optional(),
82
+ /**
83
+ * Cheap pre-gate mirroring Core's `HookMatch` (serde name `match` on
84
+ * `TurnHookContribution.run_when`). MUST round-trip through this schema:
85
+ * `ryu pack`/`publish` persist `safeParse(...).data`, so a field missing here
86
+ * is silently STRIPPED before signing — a tool-gated `pre_tool_use` hook
87
+ * (e.g. `tools: ["bash*"]`) would lose its gate and run on EVERY tool call.
88
+ */
89
+ match: z.object({
90
+ /** Run only if the request set this composer flag true. */
91
+ flag: z.string().optional(),
92
+ /** Run if the last user message starts with any of these prefixes. */
93
+ commands: z.array(z.string()).default([]),
94
+ /** Run if the plugin has stored state for this conversation. */
95
+ stateful: z.boolean().default(false),
96
+ /** Run if `ctx.tool_name` matches any of these `*`-wildcard patterns. */
97
+ tools: z.array(z.string()).default([])
98
+ }).optional()
99
+ }).refine((h) => Boolean(h.code) !== Boolean(h.code_file), {
100
+ message: "a turn hook must declare exactly one of 'code' (inline body) or 'code_file' (path to hooks/<name>.js)",
101
+ path: ["code_file"]
102
+ });
103
+ var HookEventContributionSchema = z.object({
104
+ /** Fully-qualified event id: `<plugin id>#<event name>`, e.g. `@acme/meetings#meeting.ended`. */
105
+ id: z.string().min(1),
106
+ /** Human-readable title for the event picker. */
107
+ title: z.string().min(1),
108
+ /** What the event means and when it fires. */
109
+ description: z.string().optional(),
110
+ /** Example of the `ctx.event` payload. Documentation, not a validated schema. */
111
+ payload_example: z.record(z.string(), z.unknown()).optional()
112
+ });
113
+ var PiExtensionContributionSchema = z.object({
114
+ /** Stable id for this extension within the plugin (`[a-z0-9][a-z0-9._-]*`). */
115
+ id: z.string().min(1),
116
+ /** Path to the source, relative to the plugin root: `pi-extensions/<name>.ts`. */
117
+ file: z.string().min(1),
118
+ /** Optional one-liner describing what the extension adds to the agent. */
119
+ description: z.string().optional()
80
120
  });
81
121
  var DEFAULT_WIDGET_MIME = "text/html+skybridge";
82
122
  var DEFAULT_WIDGET_DISPLAY_MODE = "inline";
@@ -114,6 +154,11 @@ var ToolAppConfigSchema = z.object({
114
154
  });
115
155
  var ContributesSchema = z.object({
116
156
  turn_hooks: z.array(TurnHookContributionSchema).default([]),
157
+ /** App events this plugin EMITS — the provider half of the hook system, whose
158
+ * consumer half is `turn_hooks`. Mirrors the Rust `Contributes.hook_events`;
159
+ * omitting it here would have `ryu pack` strip every declared event before
160
+ * signing, leaving an app that emits events nothing is allowed to subscribe to. */
161
+ hook_events: z.array(HookEventContributionSchema).default([]),
117
162
  composer_controls: z.array(z.record(z.string(), z.unknown())).default([]),
118
163
  settings_tabs: z.array(z.record(z.string(), z.unknown())).default([]),
119
164
  slash_commands: z.array(z.record(z.string(), z.unknown())).default([]),
@@ -121,7 +166,49 @@ var ContributesSchema = z.object({
121
166
  * `ui://widget/<slug>.html` template. Mirrors the Rust-side
122
167
  * `Contributes.widgets` field, without which the CLI's zod parse would strip
123
168
  * every widget an app authored here declares. */
124
- widgets: z.array(WidgetContributionSchema).default([])
169
+ widgets: z.array(WidgetContributionSchema).default([]),
170
+ /** App-registered sidebar sections (header + live list) and buttons (single nav
171
+ * rows). Loosely typed here — the shell owns the spec vocabulary — matching how
172
+ * `composer_controls`/`settings_tabs` are declared. Mirrors the Rust-side
173
+ * `Contributes.sidebar_sections` / `Contributes.sidebar_buttons`. */
174
+ sidebar_sections: z.array(z.record(z.string(), z.unknown())).default([]),
175
+ sidebar_buttons: z.array(z.record(z.string(), z.unknown())).default([]),
176
+ /** App-registered workspace dock panels (a tab in the desktop's bottom/right
177
+ * dock). Loosely typed for the same reason as the surfaces above — the shell
178
+ * owns the `panel` render-mode vocabulary and the `spec` payload. Mirrors the
179
+ * Rust-side `Contributes.dock_panels`; without it the CLI's zod parse would
180
+ * strip the dock panel an app declares here. */
181
+ dock_panels: z.array(z.record(z.string(), z.unknown())).default([]),
182
+ /** Deletable data categories the app owns — one "Delete all X" row in Settings
183
+ * → Danger Zone. Mirrors the Rust-side `Contributes.data_categories`; without
184
+ * it the CLI's zod parse would strip the declaration before signing, and the
185
+ * app's danger-zone row would simply never appear on any node that installed
186
+ * the packed bundle. Loosely typed here for the same reason as the surfaces
187
+ * above — Core is the layer that types it, because Core is the layer that has
188
+ * to resolve the id to something that can actually delete the rows. */
189
+ data_categories: z.array(z.record(z.string(), z.unknown())).default([]),
190
+ /** Language servers the plugin declares, keyed by server name — the mirror of
191
+ * Claude Code's `.lsp.json` / `lspServers`, so a config written for either host
192
+ * loads in the other. Mirrors the Rust-side `Contributes.lsp_servers`; without
193
+ * it the CLI's zod parse would strip every language server a plugin declares,
194
+ * before the manifest is signed.
195
+ *
196
+ * The ENTRY is deliberately a loose record and not a 13-field `z.object()`
197
+ * mirroring `LspServerContribution`. Claude Code owns this field vocabulary,
198
+ * not Ryu: a typed object here would strip a field from a newer Claude release
199
+ * on its way through `ryu pack` — the same silent-deletion bug this field
200
+ * exists to fix, one level down. Core is the layer that types it, because Core
201
+ * is the layer that acts on it. */
202
+ lsp_servers: z.record(z.string(), z.record(z.string(), z.unknown())).default({}),
203
+ /** Pi extensions the plugin ships — TypeScript the managed `ryu` (Pi) agent
204
+ * loads at process start. Mirrors the Rust-side `Contributes.pi_extensions`;
205
+ * without it the CLI's zod parse would strip the declaration before signing,
206
+ * and the packed plugin would ship a `pi-extensions/` folder nothing loads.
207
+ *
208
+ * Typed (not a loose record) because Ryu owns this vocabulary — three fields,
209
+ * all of them Core-interpreted — unlike `lsp_servers`, whose entry shape is
210
+ * Claude Code's to extend. */
211
+ pi_extensions: z.array(PiExtensionContributionSchema).default([])
125
212
  });
126
213
  var SetupStepSchema = z.object({
127
214
  /** Card heading (e.g. the companion app name). */
@@ -302,6 +389,25 @@ var PluginManifestSchema = z.object({
302
389
  license: z.string().optional(),
303
390
  /** Square logo/icon URL for the listing card + detail header. */
304
391
  iconUrl: z.string().optional(),
392
+ /**
393
+ * Icon-primitive id for the listing card (Ryu extension): an Iconify/icons0
394
+ * `prefix:name`, a bare Hugeicons name, or a URL, resolved by the shared `Icon`
395
+ * primitive. A monochrome GLYPH masked with the current text colour — distinct
396
+ * from `iconUrl` (a raster logo). Falls back to `iconUrl` when omitted.
397
+ */
398
+ icon: z.string().optional(),
399
+ /**
400
+ * Dithered-gradient background for the card's icon square (Ryu extension),
401
+ * mirroring dither-kit's `DitherGradient` props. `from`/`to` are a palette-colour
402
+ * name (`green`, `blue`, `purple`, `pink`, `orange`, `red`, `grey`) or a hue
403
+ * number (0–360); `direction` is where `to` ends up. Renders behind the glyph in
404
+ * place of a flat `iconBackground`; the render layer validates + falls back.
405
+ */
406
+ iconDither: z.object({
407
+ from: z.union([z.string(), z.number()]),
408
+ to: z.union([z.string(), z.number()]).optional(),
409
+ direction: z.enum(["up", "down", "left", "right"]).optional()
410
+ }).optional(),
305
411
  /** Ordered App-Store-style screenshot gallery URLs (Ryu extension). */
306
412
  screenshots: z.array(z.string()).optional(),
307
413
  /** Privacy policy URL surfaced on detail (Ryu extension). */
@@ -338,6 +444,8 @@ export {
338
444
  labelImpersonatesSystemChrome,
339
445
  CompanionSurfaceSchema,
340
446
  TurnHookContributionSchema,
447
+ HookEventContributionSchema,
448
+ PiExtensionContributionSchema,
341
449
  WidgetContributionSchema,
342
450
  ToolAppConfigSchema,
343
451
  ContributesSchema,
package/dist/cli.cjs CHANGED
@@ -362,7 +362,47 @@ var TurnHookContributionSchema = import_zod.z.object({
362
362
  /** Turn boundary this fires on. Today only `"post_assistant_turn"`. */
363
363
  on: import_zod.z.string().min(1).default("post_assistant_turn"),
364
364
  /** The JS hook body executed in the sandbox (returns a directive). */
365
- code: import_zod.z.string().min(1)
365
+ code: import_zod.z.string().min(1).optional(),
366
+ /** Path to the hook body, relative to the plugin root (`hooks/<name>.js`). */
367
+ code_file: import_zod.z.string().min(1).optional(),
368
+ /**
369
+ * Cheap pre-gate mirroring Core's `HookMatch` (serde name `match` on
370
+ * `TurnHookContribution.run_when`). MUST round-trip through this schema:
371
+ * `ryu pack`/`publish` persist `safeParse(...).data`, so a field missing here
372
+ * is silently STRIPPED before signing — a tool-gated `pre_tool_use` hook
373
+ * (e.g. `tools: ["bash*"]`) would lose its gate and run on EVERY tool call.
374
+ */
375
+ match: import_zod.z.object({
376
+ /** Run only if the request set this composer flag true. */
377
+ flag: import_zod.z.string().optional(),
378
+ /** Run if the last user message starts with any of these prefixes. */
379
+ commands: import_zod.z.array(import_zod.z.string()).default([]),
380
+ /** Run if the plugin has stored state for this conversation. */
381
+ stateful: import_zod.z.boolean().default(false),
382
+ /** Run if `ctx.tool_name` matches any of these `*`-wildcard patterns. */
383
+ tools: import_zod.z.array(import_zod.z.string()).default([])
384
+ }).optional()
385
+ }).refine((h) => Boolean(h.code) !== Boolean(h.code_file), {
386
+ message: "a turn hook must declare exactly one of 'code' (inline body) or 'code_file' (path to hooks/<name>.js)",
387
+ path: ["code_file"]
388
+ });
389
+ var HookEventContributionSchema = import_zod.z.object({
390
+ /** Fully-qualified event id: `<plugin id>#<event name>`, e.g. `@acme/meetings#meeting.ended`. */
391
+ id: import_zod.z.string().min(1),
392
+ /** Human-readable title for the event picker. */
393
+ title: import_zod.z.string().min(1),
394
+ /** What the event means and when it fires. */
395
+ description: import_zod.z.string().optional(),
396
+ /** Example of the `ctx.event` payload. Documentation, not a validated schema. */
397
+ payload_example: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
398
+ });
399
+ var PiExtensionContributionSchema = import_zod.z.object({
400
+ /** Stable id for this extension within the plugin (`[a-z0-9][a-z0-9._-]*`). */
401
+ id: import_zod.z.string().min(1),
402
+ /** Path to the source, relative to the plugin root: `pi-extensions/<name>.ts`. */
403
+ file: import_zod.z.string().min(1),
404
+ /** Optional one-liner describing what the extension adds to the agent. */
405
+ description: import_zod.z.string().optional()
366
406
  });
367
407
  var DEFAULT_WIDGET_MIME = "text/html+skybridge";
368
408
  var DEFAULT_WIDGET_DISPLAY_MODE = "inline";
@@ -400,6 +440,11 @@ var ToolAppConfigSchema = import_zod.z.object({
400
440
  });
401
441
  var ContributesSchema = import_zod.z.object({
402
442
  turn_hooks: import_zod.z.array(TurnHookContributionSchema).default([]),
443
+ /** App events this plugin EMITS — the provider half of the hook system, whose
444
+ * consumer half is `turn_hooks`. Mirrors the Rust `Contributes.hook_events`;
445
+ * omitting it here would have `ryu pack` strip every declared event before
446
+ * signing, leaving an app that emits events nothing is allowed to subscribe to. */
447
+ hook_events: import_zod.z.array(HookEventContributionSchema).default([]),
403
448
  composer_controls: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
404
449
  settings_tabs: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
405
450
  slash_commands: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
@@ -407,7 +452,49 @@ var ContributesSchema = import_zod.z.object({
407
452
  * `ui://widget/<slug>.html` template. Mirrors the Rust-side
408
453
  * `Contributes.widgets` field, without which the CLI's zod parse would strip
409
454
  * every widget an app authored here declares. */
410
- widgets: import_zod.z.array(WidgetContributionSchema).default([])
455
+ widgets: import_zod.z.array(WidgetContributionSchema).default([]),
456
+ /** App-registered sidebar sections (header + live list) and buttons (single nav
457
+ * rows). Loosely typed here — the shell owns the spec vocabulary — matching how
458
+ * `composer_controls`/`settings_tabs` are declared. Mirrors the Rust-side
459
+ * `Contributes.sidebar_sections` / `Contributes.sidebar_buttons`. */
460
+ sidebar_sections: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
461
+ sidebar_buttons: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
462
+ /** App-registered workspace dock panels (a tab in the desktop's bottom/right
463
+ * dock). Loosely typed for the same reason as the surfaces above — the shell
464
+ * owns the `panel` render-mode vocabulary and the `spec` payload. Mirrors the
465
+ * Rust-side `Contributes.dock_panels`; without it the CLI's zod parse would
466
+ * strip the dock panel an app declares here. */
467
+ dock_panels: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
468
+ /** Deletable data categories the app owns — one "Delete all X" row in Settings
469
+ * → Danger Zone. Mirrors the Rust-side `Contributes.data_categories`; without
470
+ * it the CLI's zod parse would strip the declaration before signing, and the
471
+ * app's danger-zone row would simply never appear on any node that installed
472
+ * the packed bundle. Loosely typed here for the same reason as the surfaces
473
+ * above — Core is the layer that types it, because Core is the layer that has
474
+ * to resolve the id to something that can actually delete the rows. */
475
+ data_categories: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
476
+ /** Language servers the plugin declares, keyed by server name — the mirror of
477
+ * Claude Code's `.lsp.json` / `lspServers`, so a config written for either host
478
+ * loads in the other. Mirrors the Rust-side `Contributes.lsp_servers`; without
479
+ * it the CLI's zod parse would strip every language server a plugin declares,
480
+ * before the manifest is signed.
481
+ *
482
+ * The ENTRY is deliberately a loose record and not a 13-field `z.object()`
483
+ * mirroring `LspServerContribution`. Claude Code owns this field vocabulary,
484
+ * not Ryu: a typed object here would strip a field from a newer Claude release
485
+ * on its way through `ryu pack` — the same silent-deletion bug this field
486
+ * exists to fix, one level down. Core is the layer that types it, because Core
487
+ * is the layer that acts on it. */
488
+ lsp_servers: import_zod.z.record(import_zod.z.string(), import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default({}),
489
+ /** Pi extensions the plugin ships — TypeScript the managed `ryu` (Pi) agent
490
+ * loads at process start. Mirrors the Rust-side `Contributes.pi_extensions`;
491
+ * without it the CLI's zod parse would strip the declaration before signing,
492
+ * and the packed plugin would ship a `pi-extensions/` folder nothing loads.
493
+ *
494
+ * Typed (not a loose record) because Ryu owns this vocabulary — three fields,
495
+ * all of them Core-interpreted — unlike `lsp_servers`, whose entry shape is
496
+ * Claude Code's to extend. */
497
+ pi_extensions: import_zod.z.array(PiExtensionContributionSchema).default([])
411
498
  });
412
499
  var SetupStepSchema = import_zod.z.object({
413
500
  /** Card heading (e.g. the companion app name). */
@@ -588,6 +675,25 @@ var PluginManifestSchema = import_zod.z.object({
588
675
  license: import_zod.z.string().optional(),
589
676
  /** Square logo/icon URL for the listing card + detail header. */
590
677
  iconUrl: import_zod.z.string().optional(),
678
+ /**
679
+ * Icon-primitive id for the listing card (Ryu extension): an Iconify/icons0
680
+ * `prefix:name`, a bare Hugeicons name, or a URL, resolved by the shared `Icon`
681
+ * primitive. A monochrome GLYPH masked with the current text colour — distinct
682
+ * from `iconUrl` (a raster logo). Falls back to `iconUrl` when omitted.
683
+ */
684
+ icon: import_zod.z.string().optional(),
685
+ /**
686
+ * Dithered-gradient background for the card's icon square (Ryu extension),
687
+ * mirroring dither-kit's `DitherGradient` props. `from`/`to` are a palette-colour
688
+ * name (`green`, `blue`, `purple`, `pink`, `orange`, `red`, `grey`) or a hue
689
+ * number (0–360); `direction` is where `to` ends up. Renders behind the glyph in
690
+ * place of a flat `iconBackground`; the render layer validates + falls back.
691
+ */
692
+ iconDither: import_zod.z.object({
693
+ from: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]),
694
+ to: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]).optional(),
695
+ direction: import_zod.z.enum(["up", "down", "left", "right"]).optional()
696
+ }).optional(),
591
697
  /** Ordered App-Store-style screenshot gallery URLs (Ryu extension). */
592
698
  screenshots: import_zod.z.array(import_zod.z.string()).optional(),
593
699
  /** Privacy policy URL surfaced on detail (Ryu extension). */
@@ -619,8 +725,8 @@ function printUsage() {
619
725
  "Ryu dev SDK",
620
726
  "",
621
727
  "Usage:",
622
- " bunx ryu pack <dir> Validate and bundle a plugin.json Plugin",
623
- " bunx ryu publish <dir> Validate and publish a plugin.json Plugin to the Ryu Marketplace",
728
+ " bunx ryu pack <dir> Validate and bundle a manifest.json Plugin",
729
+ " bunx ryu publish <dir> Validate and publish a manifest.json Plugin to the Ryu Marketplace",
624
730
  " bunx ryu dev <entry> Run a Runnable locally with an interactive chat loop",
625
731
  ""
626
732
  ].join("\n")
@@ -631,10 +737,17 @@ function exitError(message) {
631
737
  `);
632
738
  process.exit(1);
633
739
  }
740
+ var MANIFEST_FILE_NAMES = [
741
+ "manifest.json",
742
+ "plugin.json",
743
+ "ryu.json"
744
+ ];
634
745
  function loadManifest(dir) {
635
- const manifestPath = (0, import_node_path.join)(dir, "plugin.json");
636
- if (!(0, import_node_fs.existsSync)(manifestPath)) {
637
- exitError(`plugin.json not found in: ${dir}`);
746
+ const manifestPath = MANIFEST_FILE_NAMES.map((name) => (0, import_node_path.join)(dir, name)).find(
747
+ (candidate) => (0, import_node_fs.existsSync)(candidate)
748
+ );
749
+ if (!manifestPath) {
750
+ exitError(`manifest.json not found in: ${dir}`);
638
751
  }
639
752
  let raw;
640
753
  try {
@@ -646,16 +759,56 @@ function loadManifest(dir) {
646
759
  try {
647
760
  parsed = JSON.parse(raw);
648
761
  } catch {
649
- exitError(`plugin.json is not valid JSON: ${manifestPath}`);
762
+ exitError(`manifest.json is not valid JSON: ${manifestPath}`);
650
763
  }
651
764
  const result = PluginManifestSchema.safeParse(parsed);
652
765
  if (!result.success) {
653
766
  const first = result.error.issues[0];
654
767
  const field = first?.path.join(".") ?? "unknown";
655
768
  const message = first?.message ?? "validation failed";
656
- exitError(`plugin.json validation failed at '${field}': ${message}`);
769
+ exitError(`manifest.json validation failed at '${field}': ${message}`);
770
+ }
771
+ return inlineCodeFiles(result.data, dir);
772
+ }
773
+ var CODE_FILE_DIRS = ["hooks", "adapters"];
774
+ var CODE_FILE_PATH = /^(hooks|adapters)\/[A-Za-z0-9_][A-Za-z0-9._-]*\.m?js$/;
775
+ function readCodeFile(dir, rel, label) {
776
+ if (!CODE_FILE_PATH.test(rel)) {
777
+ exitError(
778
+ `${label}: code_file '${rel}' must be exactly '<${CODE_FILE_DIRS.join("|")}>/<name>.js' with no traversal`
779
+ );
780
+ }
781
+ const path = (0, import_node_path.join)(dir, rel);
782
+ let body;
783
+ try {
784
+ body = (0, import_node_fs.readFileSync)(path, "utf8");
785
+ } catch (err) {
786
+ exitError(`${label}: could not read code_file '${rel}': ${String(err)}`);
787
+ }
788
+ if (!body.trim()) {
789
+ exitError(`${label}: code_file '${rel}' is empty`);
790
+ }
791
+ return body;
792
+ }
793
+ function inlineCodeFiles(manifest, dir) {
794
+ const out = manifest;
795
+ for (const hook of out.contributes?.turn_hooks ?? []) {
796
+ const rel = hook.code_file;
797
+ if (typeof rel === "string") {
798
+ hook.code = readCodeFile(dir, rel, `turn hook '${String(hook.id)}'`);
799
+ hook.code_file = void 0;
800
+ }
801
+ }
802
+ for (const entry of out.provides ?? []) {
803
+ for (const [verb, binding] of Object.entries(entry.tools ?? {})) {
804
+ const rel = binding.adapter?.code_file;
805
+ if (binding.adapter && typeof rel === "string") {
806
+ binding.adapter.code = readCodeFile(dir, rel, `adapter '${verb}'`);
807
+ binding.adapter.code_file = void 0;
808
+ }
809
+ }
657
810
  }
658
- return result.data;
811
+ return out;
659
812
  }
660
813
  function resolveUiEntry(manifest) {
661
814
  for (const runnable of manifest.runnables) {
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
  import {
3
3
  PluginManifestSchema
4
- } from "./chunk-GXHL5CO7.js";
4
+ } from "./chunk-XTUK5I6I.js";
5
5
  import {
6
6
  ModelClient,
7
7
  resolveGatewayUrl
@@ -215,8 +215,8 @@ function printUsage() {
215
215
  "Ryu dev SDK",
216
216
  "",
217
217
  "Usage:",
218
- " bunx ryu pack <dir> Validate and bundle a plugin.json Plugin",
219
- " bunx ryu publish <dir> Validate and publish a plugin.json Plugin to the Ryu Marketplace",
218
+ " bunx ryu pack <dir> Validate and bundle a manifest.json Plugin",
219
+ " bunx ryu publish <dir> Validate and publish a manifest.json Plugin to the Ryu Marketplace",
220
220
  " bunx ryu dev <entry> Run a Runnable locally with an interactive chat loop",
221
221
  ""
222
222
  ].join("\n")
@@ -227,10 +227,17 @@ function exitError(message) {
227
227
  `);
228
228
  process.exit(1);
229
229
  }
230
+ var MANIFEST_FILE_NAMES = [
231
+ "manifest.json",
232
+ "plugin.json",
233
+ "ryu.json"
234
+ ];
230
235
  function loadManifest(dir) {
231
- const manifestPath = join(dir, "plugin.json");
232
- if (!existsSync(manifestPath)) {
233
- exitError(`plugin.json not found in: ${dir}`);
236
+ const manifestPath = MANIFEST_FILE_NAMES.map((name) => join(dir, name)).find(
237
+ (candidate) => existsSync(candidate)
238
+ );
239
+ if (!manifestPath) {
240
+ exitError(`manifest.json not found in: ${dir}`);
234
241
  }
235
242
  let raw;
236
243
  try {
@@ -242,16 +249,56 @@ function loadManifest(dir) {
242
249
  try {
243
250
  parsed = JSON.parse(raw);
244
251
  } catch {
245
- exitError(`plugin.json is not valid JSON: ${manifestPath}`);
252
+ exitError(`manifest.json is not valid JSON: ${manifestPath}`);
246
253
  }
247
254
  const result = PluginManifestSchema.safeParse(parsed);
248
255
  if (!result.success) {
249
256
  const first = result.error.issues[0];
250
257
  const field = first?.path.join(".") ?? "unknown";
251
258
  const message = first?.message ?? "validation failed";
252
- exitError(`plugin.json validation failed at '${field}': ${message}`);
259
+ exitError(`manifest.json validation failed at '${field}': ${message}`);
260
+ }
261
+ return inlineCodeFiles(result.data, dir);
262
+ }
263
+ var CODE_FILE_DIRS = ["hooks", "adapters"];
264
+ var CODE_FILE_PATH = /^(hooks|adapters)\/[A-Za-z0-9_][A-Za-z0-9._-]*\.m?js$/;
265
+ function readCodeFile(dir, rel, label) {
266
+ if (!CODE_FILE_PATH.test(rel)) {
267
+ exitError(
268
+ `${label}: code_file '${rel}' must be exactly '<${CODE_FILE_DIRS.join("|")}>/<name>.js' with no traversal`
269
+ );
270
+ }
271
+ const path = join(dir, rel);
272
+ let body;
273
+ try {
274
+ body = readFileSync(path, "utf8");
275
+ } catch (err) {
276
+ exitError(`${label}: could not read code_file '${rel}': ${String(err)}`);
277
+ }
278
+ if (!body.trim()) {
279
+ exitError(`${label}: code_file '${rel}' is empty`);
280
+ }
281
+ return body;
282
+ }
283
+ function inlineCodeFiles(manifest, dir) {
284
+ const out = manifest;
285
+ for (const hook of out.contributes?.turn_hooks ?? []) {
286
+ const rel = hook.code_file;
287
+ if (typeof rel === "string") {
288
+ hook.code = readCodeFile(dir, rel, `turn hook '${String(hook.id)}'`);
289
+ hook.code_file = void 0;
290
+ }
291
+ }
292
+ for (const entry of out.provides ?? []) {
293
+ for (const [verb, binding] of Object.entries(entry.tools ?? {})) {
294
+ const rel = binding.adapter?.code_file;
295
+ if (binding.adapter && typeof rel === "string") {
296
+ binding.adapter.code = readCodeFile(dir, rel, `adapter '${verb}'`);
297
+ binding.adapter.code_file = void 0;
298
+ }
299
+ }
253
300
  }
254
- return result.data;
301
+ return out;
255
302
  }
256
303
  function resolveUiEntry(manifest) {
257
304
  for (const runnable of manifest.runnables) {
@@ -637,9 +637,6 @@ interface ToolOptions<TInput extends Record<string, unknown>, TOutput> {
637
637
  * be forwarded verbatim to Core's MCP/ACP layer.
638
638
  */
639
639
  interface ToolRunnable<TInput extends Record<string, unknown> = Record<string, unknown>, TOutput = unknown> extends Runnable<TInput, TOutput> {
640
- readonly kind: "tool";
641
- /** JSON Schema for this tool's input — compatible with Core's ToolInfo.schema. */
642
- readonly schema: ToolSchema;
643
640
  /**
644
641
  * The `run` body serialized for Core's `inline_deno` tool backend — the exact
645
642
  * same technique `defineTurnHook` uses for its `code`. This is what makes a
@@ -658,6 +655,9 @@ interface ToolRunnable<TInput extends Record<string, unknown> = Record<string, u
658
655
  * the sandbox form is the second parameter aliased to `host`.
659
656
  */
660
657
  readonly code: string;
658
+ readonly kind: "tool";
659
+ /** JSON Schema for this tool's input — compatible with Core's ToolInfo.schema. */
660
+ readonly schema: ToolSchema;
661
661
  }
662
662
  /**
663
663
  * Create a Runnable tool with input validation.
@@ -690,7 +690,7 @@ interface ToolRunnable<TInput extends Record<string, unknown> = Record<string, u
690
690
  */
691
691
  declare function defineTool<TInput extends Record<string, unknown> = Record<string, unknown>, TOutput = unknown>(options: ToolOptions<TInput, TOutput>): ToolRunnable<TInput, TOutput>;
692
692
  /**
693
- * Convert a {@link ToolRunnable} into a `plugin.json` `kind:"tool"` runnable that
693
+ * Convert a {@link ToolRunnable} into a `manifest.json` `kind:"tool"` runnable that
694
694
  * ships its `run` body as Core's `inline_deno` backend. The emitted config
695
695
  * mirrors Core's `ToolConfig` (`apps/core/src/plugin_manifest/schema.rs`):
696
696
  * `{ slug, backend:"inline_deno", code, description?, input_schema }`. Core
@@ -637,9 +637,6 @@ interface ToolOptions<TInput extends Record<string, unknown>, TOutput> {
637
637
  * be forwarded verbatim to Core's MCP/ACP layer.
638
638
  */
639
639
  interface ToolRunnable<TInput extends Record<string, unknown> = Record<string, unknown>, TOutput = unknown> extends Runnable<TInput, TOutput> {
640
- readonly kind: "tool";
641
- /** JSON Schema for this tool's input — compatible with Core's ToolInfo.schema. */
642
- readonly schema: ToolSchema;
643
640
  /**
644
641
  * The `run` body serialized for Core's `inline_deno` tool backend — the exact
645
642
  * same technique `defineTurnHook` uses for its `code`. This is what makes a
@@ -658,6 +655,9 @@ interface ToolRunnable<TInput extends Record<string, unknown> = Record<string, u
658
655
  * the sandbox form is the second parameter aliased to `host`.
659
656
  */
660
657
  readonly code: string;
658
+ readonly kind: "tool";
659
+ /** JSON Schema for this tool's input — compatible with Core's ToolInfo.schema. */
660
+ readonly schema: ToolSchema;
661
661
  }
662
662
  /**
663
663
  * Create a Runnable tool with input validation.
@@ -690,7 +690,7 @@ interface ToolRunnable<TInput extends Record<string, unknown> = Record<string, u
690
690
  */
691
691
  declare function defineTool<TInput extends Record<string, unknown> = Record<string, unknown>, TOutput = unknown>(options: ToolOptions<TInput, TOutput>): ToolRunnable<TInput, TOutput>;
692
692
  /**
693
- * Convert a {@link ToolRunnable} into a `plugin.json` `kind:"tool"` runnable that
693
+ * Convert a {@link ToolRunnable} into a `manifest.json` `kind:"tool"` runnable that
694
694
  * ships its `run` body as Core's `inline_deno` backend. The emitted config
695
695
  * mirrors Core's `ToolConfig` (`apps/core/src/plugin_manifest/schema.rs`):
696
696
  * `{ slug, backend:"inline_deno", code, description?, input_schema }`. Core