@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 +4 -1
- package/dist/agent.d.cts +1 -1
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/{chunk-KPKMMGVC.js → chunk-MTUBUPIV.js} +4 -1
- package/dist/{chunk-GXHL5CO7.js → chunk-XTUK5I6I.js} +110 -2
- package/dist/cli.cjs +163 -10
- package/dist/cli.js +56 -9
- package/dist/{index-DAxq7Y0R.d.ts → index-B6SkaAjJ.d.ts} +4 -4
- package/dist/{index-CEbS1SlS.d.cts → index-BvAB5eMk.d.cts} +4 -4
- package/dist/index.cjs +167 -9
- package/dist/index.d.cts +30 -12
- package/dist/index.d.ts +30 -12
- package/dist/index.js +57 -8
- package/dist/manifest.cjs +112 -2
- package/dist/manifest.d.cts +131 -10
- package/dist/manifest.d.ts +131 -10
- package/dist/manifest.js +5 -1
- package/package.json +4 -4
- package/src/builder.ts +4 -4
- package/src/cli.ts +98 -13
- package/src/contracts-lockstep.test.ts +4 -4
- package/src/generated/plugin-manifest.ts +1284 -24
- package/src/manifest-schema.test.ts +182 -0
- package/src/manifest.fixtures.test.ts +447 -0
- package/src/manifest.test.ts +100 -16
- package/src/manifest.ts +189 -17
- package/src/plugin/ryu-plugin.ts +1 -1
- package/src/runnable/agent.ts +3 -3
- package/src/runnable/app.test.ts +67 -0
- package/src/runnable/app.ts +58 -4
- package/src/runnable/primitives.test.ts +1 -5
- package/src/runnable/primitives.ts +6 -5
- package/src/runnable/tool.ts +4 -4
- package/src/runnable/turn-hook.ts +33 -4
package/dist/agent.cjs
CHANGED
|
@@ -152,7 +152,10 @@ function dataUrlToBytes(dataUrl) {
|
|
|
152
152
|
}
|
|
153
153
|
return { bytes, mediaType };
|
|
154
154
|
}
|
|
155
|
-
return {
|
|
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-
|
|
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-
|
|
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
|
@@ -24,7 +24,10 @@ function dataUrlToBytes(dataUrl) {
|
|
|
24
24
|
}
|
|
25
25
|
return { bytes, mediaType };
|
|
26
26
|
}
|
|
27
|
-
return {
|
|
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
|
|
623
|
-
" bunx ryu publish <dir> Validate and publish a
|
|
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,
|
|
636
|
-
|
|
637
|
-
|
|
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(`
|
|
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(`
|
|
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
|
|
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-
|
|
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
|
|
219
|
-
" bunx ryu publish <dir> Validate and publish a
|
|
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,
|
|
232
|
-
|
|
233
|
-
|
|
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(`
|
|
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(`
|
|
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
|
|
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 `
|
|
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 `
|
|
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
|