@ryuhq/sdk 0.0.5 → 0.0.17

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,39 @@ 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()
80
112
  });
81
113
  var DEFAULT_WIDGET_MIME = "text/html+skybridge";
82
114
  var DEFAULT_WIDGET_DISPLAY_MODE = "inline";
@@ -114,6 +146,11 @@ var ToolAppConfigSchema = z.object({
114
146
  });
115
147
  var ContributesSchema = z.object({
116
148
  turn_hooks: z.array(TurnHookContributionSchema).default([]),
149
+ /** App events this plugin EMITS — the provider half of the hook system, whose
150
+ * consumer half is `turn_hooks`. Mirrors the Rust `Contributes.hook_events`;
151
+ * omitting it here would have `ryu pack` strip every declared event before
152
+ * signing, leaving an app that emits events nothing is allowed to subscribe to. */
153
+ hook_events: z.array(HookEventContributionSchema).default([]),
117
154
  composer_controls: z.array(z.record(z.string(), z.unknown())).default([]),
118
155
  settings_tabs: z.array(z.record(z.string(), z.unknown())).default([]),
119
156
  slash_commands: z.array(z.record(z.string(), z.unknown())).default([]),
@@ -121,7 +158,32 @@ var ContributesSchema = z.object({
121
158
  * `ui://widget/<slug>.html` template. Mirrors the Rust-side
122
159
  * `Contributes.widgets` field, without which the CLI's zod parse would strip
123
160
  * every widget an app authored here declares. */
124
- widgets: z.array(WidgetContributionSchema).default([])
161
+ widgets: z.array(WidgetContributionSchema).default([]),
162
+ /** App-registered sidebar sections (header + live list) and buttons (single nav
163
+ * rows). Loosely typed here — the shell owns the spec vocabulary — matching how
164
+ * `composer_controls`/`settings_tabs` are declared. Mirrors the Rust-side
165
+ * `Contributes.sidebar_sections` / `Contributes.sidebar_buttons`. */
166
+ sidebar_sections: z.array(z.record(z.string(), z.unknown())).default([]),
167
+ sidebar_buttons: z.array(z.record(z.string(), z.unknown())).default([]),
168
+ /** App-registered workspace dock panels (a tab in the desktop's bottom/right
169
+ * dock). Loosely typed for the same reason as the surfaces above — the shell
170
+ * owns the `panel` render-mode vocabulary and the `spec` payload. Mirrors the
171
+ * Rust-side `Contributes.dock_panels`; without it the CLI's zod parse would
172
+ * strip the dock panel an app declares here. */
173
+ dock_panels: z.array(z.record(z.string(), z.unknown())).default([]),
174
+ /** Language servers the plugin declares, keyed by server name — the mirror of
175
+ * Claude Code's `.lsp.json` / `lspServers`, so a config written for either host
176
+ * loads in the other. Mirrors the Rust-side `Contributes.lsp_servers`; without
177
+ * it the CLI's zod parse would strip every language server a plugin declares,
178
+ * before the manifest is signed.
179
+ *
180
+ * The ENTRY is deliberately a loose record and not a 13-field `z.object()`
181
+ * mirroring `LspServerContribution`. Claude Code owns this field vocabulary,
182
+ * not Ryu: a typed object here would strip a field from a newer Claude release
183
+ * on its way through `ryu pack` — the same silent-deletion bug this field
184
+ * exists to fix, one level down. Core is the layer that types it, because Core
185
+ * is the layer that acts on it. */
186
+ lsp_servers: z.record(z.string(), z.record(z.string(), z.unknown())).default({})
125
187
  });
126
188
  var SetupStepSchema = z.object({
127
189
  /** Card heading (e.g. the companion app name). */
@@ -302,6 +364,25 @@ var PluginManifestSchema = z.object({
302
364
  license: z.string().optional(),
303
365
  /** Square logo/icon URL for the listing card + detail header. */
304
366
  iconUrl: z.string().optional(),
367
+ /**
368
+ * Icon-primitive id for the listing card (Ryu extension): an Iconify/icons0
369
+ * `prefix:name`, a bare Hugeicons name, or a URL, resolved by the shared `Icon`
370
+ * primitive. A monochrome GLYPH masked with the current text colour — distinct
371
+ * from `iconUrl` (a raster logo). Falls back to `iconUrl` when omitted.
372
+ */
373
+ icon: z.string().optional(),
374
+ /**
375
+ * Dithered-gradient background for the card's icon square (Ryu extension),
376
+ * mirroring dither-kit's `DitherGradient` props. `from`/`to` are a palette-colour
377
+ * name (`green`, `blue`, `purple`, `pink`, `orange`, `red`, `grey`) or a hue
378
+ * number (0–360); `direction` is where `to` ends up. Renders behind the glyph in
379
+ * place of a flat `iconBackground`; the render layer validates + falls back.
380
+ */
381
+ iconDither: z.object({
382
+ from: z.union([z.string(), z.number()]),
383
+ to: z.union([z.string(), z.number()]).optional(),
384
+ direction: z.enum(["up", "down", "left", "right"]).optional()
385
+ }).optional(),
305
386
  /** Ordered App-Store-style screenshot gallery URLs (Ryu extension). */
306
387
  screenshots: z.array(z.string()).optional(),
307
388
  /** Privacy policy URL surfaced on detail (Ryu extension). */
@@ -338,6 +419,7 @@ export {
338
419
  labelImpersonatesSystemChrome,
339
420
  CompanionSurfaceSchema,
340
421
  TurnHookContributionSchema,
422
+ HookEventContributionSchema,
341
423
  WidgetContributionSchema,
342
424
  ToolAppConfigSchema,
343
425
  ContributesSchema,
package/dist/cli.cjs CHANGED
@@ -362,7 +362,39 @@ 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()
366
398
  });
367
399
  var DEFAULT_WIDGET_MIME = "text/html+skybridge";
368
400
  var DEFAULT_WIDGET_DISPLAY_MODE = "inline";
@@ -400,6 +432,11 @@ var ToolAppConfigSchema = import_zod.z.object({
400
432
  });
401
433
  var ContributesSchema = import_zod.z.object({
402
434
  turn_hooks: import_zod.z.array(TurnHookContributionSchema).default([]),
435
+ /** App events this plugin EMITS — the provider half of the hook system, whose
436
+ * consumer half is `turn_hooks`. Mirrors the Rust `Contributes.hook_events`;
437
+ * omitting it here would have `ryu pack` strip every declared event before
438
+ * signing, leaving an app that emits events nothing is allowed to subscribe to. */
439
+ hook_events: import_zod.z.array(HookEventContributionSchema).default([]),
403
440
  composer_controls: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
404
441
  settings_tabs: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
405
442
  slash_commands: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
@@ -407,7 +444,32 @@ var ContributesSchema = import_zod.z.object({
407
444
  * `ui://widget/<slug>.html` template. Mirrors the Rust-side
408
445
  * `Contributes.widgets` field, without which the CLI's zod parse would strip
409
446
  * every widget an app authored here declares. */
410
- widgets: import_zod.z.array(WidgetContributionSchema).default([])
447
+ widgets: import_zod.z.array(WidgetContributionSchema).default([]),
448
+ /** App-registered sidebar sections (header + live list) and buttons (single nav
449
+ * rows). Loosely typed here — the shell owns the spec vocabulary — matching how
450
+ * `composer_controls`/`settings_tabs` are declared. Mirrors the Rust-side
451
+ * `Contributes.sidebar_sections` / `Contributes.sidebar_buttons`. */
452
+ sidebar_sections: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
453
+ sidebar_buttons: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
454
+ /** App-registered workspace dock panels (a tab in the desktop's bottom/right
455
+ * dock). Loosely typed for the same reason as the surfaces above — the shell
456
+ * owns the `panel` render-mode vocabulary and the `spec` payload. Mirrors the
457
+ * Rust-side `Contributes.dock_panels`; without it the CLI's zod parse would
458
+ * strip the dock panel an app declares here. */
459
+ dock_panels: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
460
+ /** Language servers the plugin declares, keyed by server name — the mirror of
461
+ * Claude Code's `.lsp.json` / `lspServers`, so a config written for either host
462
+ * loads in the other. Mirrors the Rust-side `Contributes.lsp_servers`; without
463
+ * it the CLI's zod parse would strip every language server a plugin declares,
464
+ * before the manifest is signed.
465
+ *
466
+ * The ENTRY is deliberately a loose record and not a 13-field `z.object()`
467
+ * mirroring `LspServerContribution`. Claude Code owns this field vocabulary,
468
+ * not Ryu: a typed object here would strip a field from a newer Claude release
469
+ * on its way through `ryu pack` — the same silent-deletion bug this field
470
+ * exists to fix, one level down. Core is the layer that types it, because Core
471
+ * is the layer that acts on it. */
472
+ lsp_servers: import_zod.z.record(import_zod.z.string(), import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default({})
411
473
  });
412
474
  var SetupStepSchema = import_zod.z.object({
413
475
  /** Card heading (e.g. the companion app name). */
@@ -588,6 +650,25 @@ var PluginManifestSchema = import_zod.z.object({
588
650
  license: import_zod.z.string().optional(),
589
651
  /** Square logo/icon URL for the listing card + detail header. */
590
652
  iconUrl: import_zod.z.string().optional(),
653
+ /**
654
+ * Icon-primitive id for the listing card (Ryu extension): an Iconify/icons0
655
+ * `prefix:name`, a bare Hugeicons name, or a URL, resolved by the shared `Icon`
656
+ * primitive. A monochrome GLYPH masked with the current text colour — distinct
657
+ * from `iconUrl` (a raster logo). Falls back to `iconUrl` when omitted.
658
+ */
659
+ icon: import_zod.z.string().optional(),
660
+ /**
661
+ * Dithered-gradient background for the card's icon square (Ryu extension),
662
+ * mirroring dither-kit's `DitherGradient` props. `from`/`to` are a palette-colour
663
+ * name (`green`, `blue`, `purple`, `pink`, `orange`, `red`, `grey`) or a hue
664
+ * number (0–360); `direction` is where `to` ends up. Renders behind the glyph in
665
+ * place of a flat `iconBackground`; the render layer validates + falls back.
666
+ */
667
+ iconDither: import_zod.z.object({
668
+ from: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]),
669
+ to: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]).optional(),
670
+ direction: import_zod.z.enum(["up", "down", "left", "right"]).optional()
671
+ }).optional(),
591
672
  /** Ordered App-Store-style screenshot gallery URLs (Ryu extension). */
592
673
  screenshots: import_zod.z.array(import_zod.z.string()).optional(),
593
674
  /** Privacy policy URL surfaced on detail (Ryu extension). */
@@ -619,8 +700,8 @@ function printUsage() {
619
700
  "Ryu dev SDK",
620
701
  "",
621
702
  "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",
703
+ " bunx ryu pack <dir> Validate and bundle a manifest.json Plugin",
704
+ " bunx ryu publish <dir> Validate and publish a manifest.json Plugin to the Ryu Marketplace",
624
705
  " bunx ryu dev <entry> Run a Runnable locally with an interactive chat loop",
625
706
  ""
626
707
  ].join("\n")
@@ -631,10 +712,17 @@ function exitError(message) {
631
712
  `);
632
713
  process.exit(1);
633
714
  }
715
+ var MANIFEST_FILE_NAMES = [
716
+ "manifest.json",
717
+ "plugin.json",
718
+ "ryu.json"
719
+ ];
634
720
  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}`);
721
+ const manifestPath = MANIFEST_FILE_NAMES.map((name) => (0, import_node_path.join)(dir, name)).find(
722
+ (candidate) => (0, import_node_fs.existsSync)(candidate)
723
+ );
724
+ if (!manifestPath) {
725
+ exitError(`manifest.json not found in: ${dir}`);
638
726
  }
639
727
  let raw;
640
728
  try {
@@ -646,16 +734,56 @@ function loadManifest(dir) {
646
734
  try {
647
735
  parsed = JSON.parse(raw);
648
736
  } catch {
649
- exitError(`plugin.json is not valid JSON: ${manifestPath}`);
737
+ exitError(`manifest.json is not valid JSON: ${manifestPath}`);
650
738
  }
651
739
  const result = PluginManifestSchema.safeParse(parsed);
652
740
  if (!result.success) {
653
741
  const first = result.error.issues[0];
654
742
  const field = first?.path.join(".") ?? "unknown";
655
743
  const message = first?.message ?? "validation failed";
656
- exitError(`plugin.json validation failed at '${field}': ${message}`);
744
+ exitError(`manifest.json validation failed at '${field}': ${message}`);
745
+ }
746
+ return inlineCodeFiles(result.data, dir);
747
+ }
748
+ var CODE_FILE_DIRS = ["hooks", "adapters"];
749
+ var CODE_FILE_PATH = /^(hooks|adapters)\/[A-Za-z0-9_][A-Za-z0-9._-]*\.m?js$/;
750
+ function readCodeFile(dir, rel, label) {
751
+ if (!CODE_FILE_PATH.test(rel)) {
752
+ exitError(
753
+ `${label}: code_file '${rel}' must be exactly '<${CODE_FILE_DIRS.join("|")}>/<name>.js' with no traversal`
754
+ );
755
+ }
756
+ const path = (0, import_node_path.join)(dir, rel);
757
+ let body;
758
+ try {
759
+ body = (0, import_node_fs.readFileSync)(path, "utf8");
760
+ } catch (err) {
761
+ exitError(`${label}: could not read code_file '${rel}': ${String(err)}`);
762
+ }
763
+ if (!body.trim()) {
764
+ exitError(`${label}: code_file '${rel}' is empty`);
765
+ }
766
+ return body;
767
+ }
768
+ function inlineCodeFiles(manifest, dir) {
769
+ const out = manifest;
770
+ for (const hook of out.contributes?.turn_hooks ?? []) {
771
+ const rel = hook.code_file;
772
+ if (typeof rel === "string") {
773
+ hook.code = readCodeFile(dir, rel, `turn hook '${String(hook.id)}'`);
774
+ hook.code_file = void 0;
775
+ }
776
+ }
777
+ for (const entry of out.provides ?? []) {
778
+ for (const [verb, binding] of Object.entries(entry.tools ?? {})) {
779
+ const rel = binding.adapter?.code_file;
780
+ if (binding.adapter && typeof rel === "string") {
781
+ binding.adapter.code = readCodeFile(dir, rel, `adapter '${verb}'`);
782
+ binding.adapter.code_file = void 0;
783
+ }
784
+ }
657
785
  }
658
- return result.data;
786
+ return out;
659
787
  }
660
788
  function resolveUiEntry(manifest) {
661
789
  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-SKVIJH5I.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