@ryuhq/sdk 0.1.13 → 0.2.0
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/README.md +19 -6
- package/dist/agent-plugin.cjs +46 -0
- package/dist/agent-plugin.d.cts +42 -32
- package/dist/agent-plugin.d.ts +42 -32
- package/dist/agent-plugin.js +1 -1
- package/dist/agent.cjs +9 -0
- package/dist/agent.d.cts +377 -1
- package/dist/agent.d.ts +377 -1
- package/dist/agent.js +4 -2
- package/dist/app-Bkw7LlCK.d.ts +129 -0
- package/dist/app-DNaGmLVf.d.cts +129 -0
- package/dist/builder.cjs +1095 -0
- package/dist/builder.d.cts +212 -0
- package/dist/builder.d.ts +212 -0
- package/dist/builder.js +28 -0
- package/dist/chunk-A3RGEPDG.js +250 -0
- package/dist/chunk-FZSFZOIN.js +200 -0
- package/dist/{chunk-ODFEUVPW.js → chunk-HACAGK65.js} +1 -18
- package/dist/{chunk-AO2KJRDD.js → chunk-IEUQ3CDG.js} +125 -2
- package/dist/chunk-IKEDLLFY.js +19 -0
- package/dist/chunk-IOLP5FFE.js +354 -0
- package/dist/{chunk-G6FLVEC4.js → chunk-JX6DDRXV.js} +46 -0
- package/dist/chunk-T5676WL2.js +240 -0
- package/dist/chunk-TLDPEGC7.js +21 -0
- package/dist/chunk-TXSHHZF2.js +0 -0
- package/dist/{chunk-MTUBUPIV.js → chunk-ULSVL7EC.js} +8 -227
- package/dist/chunk-VLIRNNAE.js +154 -0
- package/dist/chunk-W3KPP4WN.js +135 -0
- package/dist/cli.cjs +258 -14
- package/dist/cli.js +96 -16
- package/dist/client-D5U6ssPc.d.cts +84 -0
- package/dist/client-D5U6ssPc.d.ts +84 -0
- package/dist/index.cjs +519 -5
- package/dist/index.d.cts +16 -632
- package/dist/index.d.ts +16 -632
- package/dist/index.js +63 -695
- package/dist/manifest.cjs +129 -3
- package/dist/manifest.d.cts +105 -2
- package/dist/manifest.d.ts +105 -2
- package/dist/manifest.js +7 -1
- package/dist/mcp/client.cjs +180 -0
- package/dist/mcp/client.d.cts +49 -0
- package/dist/mcp/client.d.ts +49 -0
- package/dist/mcp/client.js +10 -0
- package/dist/mcp/server.cjs +370 -0
- package/dist/mcp/server.d.cts +126 -0
- package/dist/mcp/server.d.ts +126 -0
- package/dist/mcp/server.js +9 -0
- package/dist/mcp.cjs +376 -0
- package/dist/mcp.d.cts +2 -0
- package/dist/mcp.d.ts +2 -0
- package/dist/mcp.js +17 -0
- package/dist/model.cjs +141 -0
- package/dist/model.d.cts +33 -0
- package/dist/model.d.ts +33 -0
- package/dist/model.js +18 -0
- package/dist/plugin.cjs +46 -0
- package/dist/plugin.d.cts +215 -0
- package/dist/plugin.d.ts +215 -0
- package/dist/plugin.js +8 -0
- package/dist/runnable.cjs +1230 -0
- package/dist/runnable.d.cts +271 -0
- package/dist/runnable.d.ts +271 -0
- package/dist/runnable.js +28 -0
- package/dist/{index-B6SkaAjJ.d.ts → tool-DSx2bFx8.d.ts} +35 -458
- package/dist/{index-BvAB5eMk.d.cts → tool-u-VR0fLF.d.cts} +35 -458
- package/package.json +38 -2
- package/src/agent/loop.test.ts +4 -4
- package/src/agent/tools.ts +3 -3
- package/src/agent-plugin.test.ts +58 -0
- package/src/agent-plugin.ts +108 -35
- package/src/cli/dev.test.ts +26 -47
- package/src/cli/dev.ts +10 -2
- package/src/cli.ts +98 -15
- package/src/exports-lockstep.test.ts +92 -0
- package/src/generated/plugin-manifest.ts +243 -27
- package/src/index.ts +35 -0
- package/src/manifest-schema.test.ts +30 -1
- package/src/manifest.fixtures.test.ts +13 -3
- package/src/manifest.test.ts +96 -10
- package/src/manifest.ts +334 -187
- package/src/mcp/index.ts +18 -0
- package/src/model/index.ts +22 -0
- package/src/plugin/ryu-plugin.ts +82 -0
- package/src/runnable/app.test.ts +2 -0
- package/src/runnable/app.ts +5 -2
- package/src/runnable/index.ts +2 -0
- package/src/runnable/primitives.ts +57 -0
- package/src/runnable/tool.ts +1 -1
- package/src/runnable/turn-hook.ts +4 -1
package/dist/cli.cjs
CHANGED
|
@@ -103,6 +103,21 @@ function toSpecEnv(value) {
|
|
|
103
103
|
}
|
|
104
104
|
return Object.keys(env).length > 0 ? env : void 0;
|
|
105
105
|
}
|
|
106
|
+
function validateRemoteUrl(name, raw) {
|
|
107
|
+
let parsed;
|
|
108
|
+
try {
|
|
109
|
+
parsed = new URL(raw);
|
|
110
|
+
} catch {
|
|
111
|
+
return `mcp server '${name}' remote url ${JSON.stringify(raw)} is invalid and was omitted`;
|
|
112
|
+
}
|
|
113
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
114
|
+
return `mcp server '${name}' remote url must use http or https and was omitted`;
|
|
115
|
+
}
|
|
116
|
+
if (parsed.username || parsed.password || parsed.hash) {
|
|
117
|
+
return `mcp server '${name}' remote url must not contain credentials or a fragment and was omitted`;
|
|
118
|
+
}
|
|
119
|
+
return void 0;
|
|
120
|
+
}
|
|
106
121
|
function toSpecServer(name, decl) {
|
|
107
122
|
const extras = {};
|
|
108
123
|
const commandEnv = asString(decl.command_env);
|
|
@@ -120,6 +135,37 @@ function toSpecServer(name, decl) {
|
|
|
120
135
|
note: `mcp server '${name}' is disabled in the native manifest and was omitted from ${AGENT_PLUGIN_MCP_FILE}`
|
|
121
136
|
};
|
|
122
137
|
}
|
|
138
|
+
const declaredType = asString(decl.type);
|
|
139
|
+
const remoteUrl = asString(decl.url);
|
|
140
|
+
const remoteType = declaredType === "sse" ? "sse" : declaredType === "streamable-http" || declaredType === "streamable_http" || declaredType === "http" || !declaredType && remoteUrl ? "streamable-http" : void 0;
|
|
141
|
+
if (remoteType) {
|
|
142
|
+
if (!remoteUrl) {
|
|
143
|
+
return {
|
|
144
|
+
extras,
|
|
145
|
+
note: `mcp server '${name}' has remote type '${remoteType}' but no url and was omitted`
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
const urlNote = validateRemoteUrl(name, remoteUrl);
|
|
149
|
+
if (urlNote) {
|
|
150
|
+
return { extras, note: urlNote };
|
|
151
|
+
}
|
|
152
|
+
const server2 = {
|
|
153
|
+
type: remoteType,
|
|
154
|
+
url: remoteUrl
|
|
155
|
+
};
|
|
156
|
+
const headers = toSpecEnv(decl.headers);
|
|
157
|
+
if (headers) {
|
|
158
|
+
server2.headers = headers;
|
|
159
|
+
}
|
|
160
|
+
const note = decl.auth ? `mcp server '${name}' uses Ryu OAuth metadata that is not portable; static headers were exported` : void 0;
|
|
161
|
+
return { server: server2, extras, note };
|
|
162
|
+
}
|
|
163
|
+
if (declaredType && declaredType !== "stdio") {
|
|
164
|
+
return {
|
|
165
|
+
extras,
|
|
166
|
+
note: `mcp server '${name}' has unsupported transport '${declaredType}' and was omitted`
|
|
167
|
+
};
|
|
168
|
+
}
|
|
123
169
|
const command2 = asString(decl.command);
|
|
124
170
|
if (!command2) {
|
|
125
171
|
return {
|
|
@@ -313,9 +359,9 @@ function resolveGatewayUrl3() {
|
|
|
313
359
|
|
|
314
360
|
// src/cli/dev.ts
|
|
315
361
|
var RE_ABSOLUTE_PATH = /^[A-Za-z]:[\\/]/;
|
|
316
|
-
async function probeGateway(baseUrl) {
|
|
362
|
+
async function probeGateway(baseUrl, fetchImpl = fetch) {
|
|
317
363
|
try {
|
|
318
|
-
await
|
|
364
|
+
await fetchImpl(`${baseUrl}/health`, {
|
|
319
365
|
method: "HEAD",
|
|
320
366
|
signal: AbortSignal.timeout(3e3)
|
|
321
367
|
});
|
|
@@ -630,8 +676,30 @@ var WidgetContributionSchema = import_zod.z.object({
|
|
|
630
676
|
/** Default display mode (`inline` | `fullscreen` | `pip`). */
|
|
631
677
|
default_display_mode: import_zod.z.string().default(DEFAULT_WIDGET_DISPLAY_MODE)
|
|
632
678
|
});
|
|
679
|
+
var ChatWidgetTemplateSchema = import_zod.z.object({
|
|
680
|
+
id: import_zod.z.string().regex(/^[a-z0-9][a-z0-9._:-]*$/),
|
|
681
|
+
title: import_zod.z.string().min(1),
|
|
682
|
+
description: import_zod.z.string().optional(),
|
|
683
|
+
triggers: import_zod.z.array(import_zod.z.string()).default([]),
|
|
684
|
+
examples: import_zod.z.array(import_zod.z.string()).default([]),
|
|
685
|
+
backing: import_zod.z.object({
|
|
686
|
+
tool_id: import_zod.z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:/-]*$/).optional(),
|
|
687
|
+
view_id: import_zod.z.string().regex(/^[a-z0-9][a-z0-9._:-]*$/).optional()
|
|
688
|
+
}),
|
|
689
|
+
display_mode: import_zod.z.string().min(1),
|
|
690
|
+
safe_action_ids: import_zod.z.array(import_zod.z.string().regex(/^[a-z0-9][a-z0-9._-]*$/)).default([]),
|
|
691
|
+
availability: import_zod.z.string().default("available")
|
|
692
|
+
}).superRefine((value, ctx) => {
|
|
693
|
+
const count = Number(Boolean(value.backing.tool_id)) + Number(Boolean(value.backing.view_id));
|
|
694
|
+
if (count !== 1 && value.availability === "available") {
|
|
695
|
+
ctx.addIssue({ code: "custom", path: ["backing"], message: "available templates need exactly one backing tool_id or view_id" });
|
|
696
|
+
}
|
|
697
|
+
if (count > 1) {
|
|
698
|
+
ctx.addIssue({ code: "custom", path: ["backing"], message: "backing must declare at most one of tool_id or view_id" });
|
|
699
|
+
}
|
|
700
|
+
});
|
|
633
701
|
var ToolAppConfigSchema = import_zod.z.object({
|
|
634
|
-
/** MCP tool slug this runnable wraps — the fully-qualified `<server
|
|
702
|
+
/** MCP tool slug this runnable wraps — the fully-qualified `<server>.<name>` id. */
|
|
635
703
|
slug: import_zod.z.string().min(1),
|
|
636
704
|
/** The tool description the model reads when choosing it. Carried here because a
|
|
637
705
|
* packed app's manifest is the only channel (there is no `generated.rs`); Core's
|
|
@@ -658,6 +726,11 @@ var ContributesSchema = import_zod.z.object({
|
|
|
658
726
|
* signing, leaving an app that emits events nothing is allowed to subscribe to. */
|
|
659
727
|
hook_events: import_zod.z.array(HookEventContributionSchema).default([]),
|
|
660
728
|
composer_controls: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
|
|
729
|
+
/** Chat feature descriptors whose behavior is implemented by the host shell.
|
|
730
|
+
* Mirrors the Rust-side `Contributes.chat_features`; keeping this field in the
|
|
731
|
+
* authoring schema prevents `ryu pack` from silently deleting a plugin's chat
|
|
732
|
+
* feature declaration before signing. */
|
|
733
|
+
chat_features: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
|
|
661
734
|
settings_tabs: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
|
|
662
735
|
slash_commands: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
|
|
663
736
|
/** App widgets (Ryu Apps). Each binds a render tool id to its
|
|
@@ -665,6 +738,8 @@ var ContributesSchema = import_zod.z.object({
|
|
|
665
738
|
* `Contributes.widgets` field, without which the CLI's zod parse would strip
|
|
666
739
|
* every widget an app authored here declares. */
|
|
667
740
|
widgets: import_zod.z.array(WidgetContributionSchema).default([]),
|
|
741
|
+
/** Metadata-only chat widget templates. */
|
|
742
|
+
chat_widget_templates: import_zod.z.array(ChatWidgetTemplateSchema).optional(),
|
|
668
743
|
/** App-registered sidebar sections (header + live list) and buttons (single nav
|
|
669
744
|
* rows). Loosely typed here — the shell owns the spec vocabulary — matching how
|
|
670
745
|
* `composer_controls`/`settings_tabs` are declared. Mirrors the Rust-side
|
|
@@ -677,6 +752,13 @@ var ContributesSchema = import_zod.z.object({
|
|
|
677
752
|
* Rust-side `Contributes.dock_panels`; without it the CLI's zod parse would
|
|
678
753
|
* strip the dock panel an app declares here. */
|
|
679
754
|
dock_panels: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
|
|
755
|
+
/** App-registered live activities (the desktop "Dynamic Island" cards). Loosely
|
|
756
|
+
* typed for the same reason as the surfaces above — the shell owns the
|
|
757
|
+
* `spec` vocabulary. Mirrors the Rust-side `Contributes.live_activities`;
|
|
758
|
+
* without it the CLI's zod parse would strip every live activity an app
|
|
759
|
+
* declares here, so a packed bundle would ship a dock that silently stays
|
|
760
|
+
* empty. */
|
|
761
|
+
live_activities: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([]),
|
|
680
762
|
/** Deletable data categories the app owns — one "Delete all X" row in Settings
|
|
681
763
|
* → Danger Zone. Mirrors the Rust-side `Contributes.data_categories`; without
|
|
682
764
|
* it the CLI's zod parse would strip the declaration before signing, and the
|
|
@@ -714,7 +796,10 @@ var ContributesSchema = import_zod.z.object({
|
|
|
714
796
|
* bug: the styles' `.md` files are not carried by the bundle either, so there
|
|
715
797
|
* would be no residue to notice — the plugin would install clean and contribute
|
|
716
798
|
* nothing. */
|
|
717
|
-
output_styles: import_zod.z.array(OutputStyleContributionSchema).default([])
|
|
799
|
+
output_styles: import_zod.z.array(OutputStyleContributionSchema).default([]),
|
|
800
|
+
/** Per-message actions contributed by an enabled plugin. Kept as loose records
|
|
801
|
+
* so renderer-specific `kind`/`args` payloads survive `ryu pack` unchanged. */
|
|
802
|
+
message_actions: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).default([])
|
|
718
803
|
});
|
|
719
804
|
var SetupStepSchema = import_zod.z.object({
|
|
720
805
|
/** Card heading (e.g. the companion app name). */
|
|
@@ -795,6 +880,61 @@ var EnginesReqSchema = import_zod.z.object({
|
|
|
795
880
|
/** Floor for the Next.js web app. */
|
|
796
881
|
web: import_zod.z.string().optional()
|
|
797
882
|
});
|
|
883
|
+
var McpServerAuthSchema = import_zod.z.object({
|
|
884
|
+
client_id: import_zod.z.string().min(1).optional(),
|
|
885
|
+
type: import_zod.z.literal("oauth")
|
|
886
|
+
}).strict();
|
|
887
|
+
var McpServerDeclSchema = import_zod.z.object({
|
|
888
|
+
args: import_zod.z.array(import_zod.z.string()).default([]),
|
|
889
|
+
auth: McpServerAuthSchema.optional(),
|
|
890
|
+
command: import_zod.z.string().optional(),
|
|
891
|
+
command_env: import_zod.z.string().optional(),
|
|
892
|
+
description: import_zod.z.string().optional(),
|
|
893
|
+
enabled: import_zod.z.boolean().default(true),
|
|
894
|
+
env: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).default({}),
|
|
895
|
+
headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).default({}),
|
|
896
|
+
type: import_zod.z.enum(["stdio", "http", "streamable-http", "streamable_http", "sse"]).optional(),
|
|
897
|
+
url: import_zod.z.url().optional()
|
|
898
|
+
}).superRefine((server, context) => {
|
|
899
|
+
if (!(server.command || server.url)) {
|
|
900
|
+
context.addIssue({
|
|
901
|
+
code: "custom",
|
|
902
|
+
message: "an MCP server requires command or url"
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
if (!server.auth) {
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
if (server.command || server.type === "stdio" || !server.url) {
|
|
909
|
+
context.addIssue({
|
|
910
|
+
code: "custom",
|
|
911
|
+
message: "OAuth is supported only for remote HTTP MCP servers"
|
|
912
|
+
});
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
if (Object.keys(server.headers).some(
|
|
916
|
+
(name) => name.toLowerCase() === "authorization"
|
|
917
|
+
)) {
|
|
918
|
+
context.addIssue({
|
|
919
|
+
code: "custom",
|
|
920
|
+
message: "OAuth cannot be combined with a static Authorization header"
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
const url = new URL(server.url);
|
|
924
|
+
const loopback = url.hostname === "localhost" || url.hostname.startsWith("127.") || url.hostname === "[::1]" || url.hostname === "::1";
|
|
925
|
+
if (url.username || url.password || url.hash) {
|
|
926
|
+
context.addIssue({
|
|
927
|
+
code: "custom",
|
|
928
|
+
message: "OAuth MCP URLs cannot contain credentials or fragments"
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
|
|
932
|
+
context.addIssue({
|
|
933
|
+
code: "custom",
|
|
934
|
+
message: "OAuth MCP URLs must use HTTPS except on loopback"
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
});
|
|
798
938
|
var PluginManifestSchema = import_zod.z.object({
|
|
799
939
|
/** Reverse-domain unique identifier (e.g. `"com.example.my-plugin"`). */
|
|
800
940
|
id: import_zod.z.string().min(1, "id is required"),
|
|
@@ -826,6 +966,8 @@ var PluginManifestSchema = import_zod.z.object({
|
|
|
826
966
|
* Declarations only — grant enforcement is the Gateway's responsibility.
|
|
827
967
|
*/
|
|
828
968
|
permission_grants: import_zod.z.array(import_zod.z.string()).default([]),
|
|
969
|
+
/** Remote or stdio MCP servers registered by this plugin. */
|
|
970
|
+
mcp_servers: import_zod.z.record(import_zod.z.string(), McpServerDeclSchema).optional(),
|
|
829
971
|
/**
|
|
830
972
|
* Optional Companion surface (an in-desktop overlay or sidebar panel).
|
|
831
973
|
* Absent when the plugin has no Companion surface.
|
|
@@ -919,10 +1061,16 @@ var PluginManifestSchema = import_zod.z.object({
|
|
|
919
1061
|
url: import_zod.z.string().optional()
|
|
920
1062
|
})
|
|
921
1063
|
]).optional(),
|
|
1064
|
+
/** Public source repository URL (Claude/Codex `repository`). */
|
|
1065
|
+
repository: import_zod.z.string().url().optional(),
|
|
1066
|
+
/** True when the provider operates outside the local Ryu runtime. */
|
|
1067
|
+
external: import_zod.z.boolean().optional(),
|
|
922
1068
|
/** Project/marketing homepage — maps to the listing `website` (Claude field). */
|
|
923
1069
|
homepage: import_zod.z.string().optional(),
|
|
924
1070
|
/** Free-text search keywords (Claude field). */
|
|
925
1071
|
keywords: import_zod.z.array(import_zod.z.string()).optional(),
|
|
1072
|
+
/** Stable Marketplace filter labels (Ryu extension). */
|
|
1073
|
+
tags: import_zod.z.array(import_zod.z.string()).optional(),
|
|
926
1074
|
/** Taxonomy category beyond the runnable kinds (Claude field). */
|
|
927
1075
|
category: import_zod.z.string().optional(),
|
|
928
1076
|
/** SPDX-ish license identifier (Claude field). */
|
|
@@ -936,6 +1084,8 @@ var PluginManifestSchema = import_zod.z.object({
|
|
|
936
1084
|
* from `iconUrl` (a raster logo). Falls back to `iconUrl` when omitted.
|
|
937
1085
|
*/
|
|
938
1086
|
icon: import_zod.z.string().optional(),
|
|
1087
|
+
/** Optional detail-page hero banner metadata forwarded to the marketplace. */
|
|
1088
|
+
banner: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
|
|
939
1089
|
/**
|
|
940
1090
|
* Dithered-gradient background for the card's icon square (Ryu extension),
|
|
941
1091
|
* mirroring dither-kit's `DitherGradient` props. `from`/`to` are a palette-colour
|
|
@@ -967,6 +1117,22 @@ var PluginManifestSchema = import_zod.z.object({
|
|
|
967
1117
|
* array of steps guiding the user through post-install configuration.
|
|
968
1118
|
*/
|
|
969
1119
|
setup: import_zod.z.union([SetupStepSchema, import_zod.z.array(SetupStepSchema)]).optional()
|
|
1120
|
+
}).superRefine((manifest, context) => {
|
|
1121
|
+
const hasOAuthServer = Object.values(manifest.mcp_servers ?? {}).some(
|
|
1122
|
+
(server) => server.auth?.type === "oauth"
|
|
1123
|
+
);
|
|
1124
|
+
if (!hasOAuthServer) {
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
for (const grant of ["mcp:server", "identity.read"]) {
|
|
1128
|
+
if (!manifest.permission_grants.includes(grant)) {
|
|
1129
|
+
context.addIssue({
|
|
1130
|
+
code: "custom",
|
|
1131
|
+
message: `OAuth MCP servers require the ${grant} permission grant`,
|
|
1132
|
+
path: ["permission_grants"]
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
970
1136
|
});
|
|
971
1137
|
|
|
972
1138
|
// src/cli.ts
|
|
@@ -980,7 +1146,9 @@ function printUsage() {
|
|
|
980
1146
|
"",
|
|
981
1147
|
"Usage:",
|
|
982
1148
|
" bunx ryu pack <dir> Validate and bundle a manifest.json Plugin",
|
|
983
|
-
" bunx ryu publish <
|
|
1149
|
+
" bunx ryu github-publish <url>",
|
|
1150
|
+
" Submit a GitHub repository/package URL to the Marketplace",
|
|
1151
|
+
" bunx ryu publish <dir> Deprecated server-artifact publishing (migration only)",
|
|
984
1152
|
" bunx ryu dev <entry> Run a Runnable locally with an interactive chat loop",
|
|
985
1153
|
" bunx ryu agent-plugin <dir>",
|
|
986
1154
|
" Emit the Agent Plugins v1 interop pair (plugin.json + mcp.json)",
|
|
@@ -999,16 +1167,20 @@ var MANIFEST_FILE_NAMES = [
|
|
|
999
1167
|
"ryu.json"
|
|
1000
1168
|
];
|
|
1001
1169
|
function resolveNativeManifestPath(dir) {
|
|
1002
|
-
return MANIFEST_FILE_NAMES.map((name) => (0, import_node_path.join)(dir, name)).find(
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1170
|
+
return MANIFEST_FILE_NAMES.map((name) => (0, import_node_path.join)(dir, name)).find(
|
|
1171
|
+
(candidate) => {
|
|
1172
|
+
if (!(0, import_node_fs.existsSync)(candidate)) {
|
|
1173
|
+
return false;
|
|
1174
|
+
}
|
|
1175
|
+
try {
|
|
1176
|
+
return !isAgentPluginManifest(
|
|
1177
|
+
JSON.parse((0, import_node_fs.readFileSync)(candidate, "utf8"))
|
|
1178
|
+
);
|
|
1179
|
+
} catch {
|
|
1180
|
+
return true;
|
|
1181
|
+
}
|
|
1010
1182
|
}
|
|
1011
|
-
|
|
1183
|
+
);
|
|
1012
1184
|
}
|
|
1013
1185
|
function loadManifest(dir) {
|
|
1014
1186
|
const manifestPath = resolveNativeManifestPath(dir);
|
|
@@ -1271,8 +1443,16 @@ async function commandPublish(rawDir) {
|
|
|
1271
1443
|
...manifest.description ? { description: manifest.description } : {},
|
|
1272
1444
|
...manifest.tagline ? { tagline: manifest.tagline } : {},
|
|
1273
1445
|
...developer ? { developer } : {},
|
|
1446
|
+
...manifest.author ? { author: manifest.author } : {},
|
|
1447
|
+
...manifest.repository ? { repository: manifest.repository } : {},
|
|
1448
|
+
...manifest.external ? { external: true } : {},
|
|
1274
1449
|
...manifest.category ? { category: manifest.category } : {},
|
|
1450
|
+
...manifest.license ? { license: manifest.license } : {},
|
|
1451
|
+
...manifest.keywords?.length ? { keywords: manifest.keywords } : {},
|
|
1275
1452
|
...manifest.iconUrl ? { iconUrl: manifest.iconUrl } : {},
|
|
1453
|
+
...manifest.icon ? { icon: manifest.icon } : {},
|
|
1454
|
+
...manifest.iconDither ? { iconDither: manifest.iconDither } : {},
|
|
1455
|
+
...manifest.banner ? { banner: manifest.banner } : {},
|
|
1276
1456
|
...manifest.screenshots?.length ? { screenshots: manifest.screenshots } : {},
|
|
1277
1457
|
...manifest.homepage ? { website: manifest.homepage } : {},
|
|
1278
1458
|
...manifest.privacyPolicyUrl ? { privacyPolicyUrl: manifest.privacyPolicyUrl } : {},
|
|
@@ -1328,6 +1508,55 @@ ${text}
|
|
|
1328
1508
|
`
|
|
1329
1509
|
);
|
|
1330
1510
|
}
|
|
1511
|
+
async function commandGithubPublish(repositoryUrl) {
|
|
1512
|
+
const input = repositoryUrl.trim();
|
|
1513
|
+
if (!input) {
|
|
1514
|
+
exitError(
|
|
1515
|
+
"github-publish requires a GitHub repository or package URL, for example https://github.com/acme/my-app"
|
|
1516
|
+
);
|
|
1517
|
+
}
|
|
1518
|
+
const token = authToken();
|
|
1519
|
+
const url = `${publishBaseUrl()}/api/marketplace/github/publish`;
|
|
1520
|
+
const proof = process.env.RYU_GITHUB_INSTALLATION_PROOF?.trim();
|
|
1521
|
+
const body = {
|
|
1522
|
+
url: input,
|
|
1523
|
+
...proof ? { installationProof: proof } : {}
|
|
1524
|
+
};
|
|
1525
|
+
let response;
|
|
1526
|
+
try {
|
|
1527
|
+
response = await fetch(url, {
|
|
1528
|
+
method: "POST",
|
|
1529
|
+
headers: {
|
|
1530
|
+
authorization: `Bearer ${token}`,
|
|
1531
|
+
"content-type": "application/json"
|
|
1532
|
+
},
|
|
1533
|
+
body: JSON.stringify(body)
|
|
1534
|
+
});
|
|
1535
|
+
} catch (error) {
|
|
1536
|
+
exitError(`could not reach ${url}: ${String(error)}`);
|
|
1537
|
+
}
|
|
1538
|
+
const text = await response.text();
|
|
1539
|
+
if (!response.ok) {
|
|
1540
|
+
try {
|
|
1541
|
+
const error = JSON.parse(text);
|
|
1542
|
+
if (error.installationUrl) {
|
|
1543
|
+
exitError(
|
|
1544
|
+
`${error.error ?? "GitHub App installation required"}. Open ${error.installationUrl}, then set RYU_GITHUB_INSTALLATION_PROOF to the callback proof and retry.`
|
|
1545
|
+
);
|
|
1546
|
+
}
|
|
1547
|
+
exitError(
|
|
1548
|
+
`github-publish failed (${response.status}): ${error.error ?? text}`
|
|
1549
|
+
);
|
|
1550
|
+
} catch {
|
|
1551
|
+
exitError(`github-publish failed (${response.status}): ${text}`);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
process.stdout.write(
|
|
1555
|
+
`GitHub-backed listing submitted for moderation:
|
|
1556
|
+
${text}
|
|
1557
|
+
`
|
|
1558
|
+
);
|
|
1559
|
+
}
|
|
1331
1560
|
var [, , command, ...args] = process.argv;
|
|
1332
1561
|
if (!command) {
|
|
1333
1562
|
printUsage();
|
|
@@ -1346,9 +1575,24 @@ if (command === "pack") {
|
|
|
1346
1575
|
if (!dir) {
|
|
1347
1576
|
exitError("publish requires a directory argument: bunx ryu publish <dir>");
|
|
1348
1577
|
}
|
|
1578
|
+
if (process.env.RYU_ENABLE_LEGACY_MARKETPLACE_WRITES !== "true") {
|
|
1579
|
+
exitError(
|
|
1580
|
+
"ryu publish is deprecated because marketplace package contents now live in GitHub Releases; use bunx ryu github-publish <repository-or-package-url>"
|
|
1581
|
+
);
|
|
1582
|
+
}
|
|
1349
1583
|
commandPublish(dir).catch((err) => {
|
|
1350
1584
|
exitError(String(err));
|
|
1351
1585
|
});
|
|
1586
|
+
} else if (command === "github-publish") {
|
|
1587
|
+
const repositoryUrl = args[0];
|
|
1588
|
+
if (!repositoryUrl) {
|
|
1589
|
+
exitError(
|
|
1590
|
+
"github-publish requires a GitHub repository or package URL, for example https://github.com/acme/my-app"
|
|
1591
|
+
);
|
|
1592
|
+
}
|
|
1593
|
+
commandGithubPublish(repositoryUrl).catch((err) => {
|
|
1594
|
+
exitError(String(err));
|
|
1595
|
+
});
|
|
1352
1596
|
} else if (command === "agent-plugin") {
|
|
1353
1597
|
const dir = args[0];
|
|
1354
1598
|
if (!dir) {
|
package/dist/cli.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import {
|
|
3
3
|
PluginManifestSchema
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-IEUQ3CDG.js";
|
|
5
5
|
import {
|
|
6
6
|
AGENT_PLUGIN_MANIFEST_FILE,
|
|
7
7
|
AGENT_PLUGIN_MCP_FILE,
|
|
8
8
|
isAgentPluginManifest,
|
|
9
9
|
toAgentPlugin
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-JX6DDRXV.js";
|
|
11
|
+
import {
|
|
12
|
+
ModelClient
|
|
13
|
+
} from "./chunk-HACAGK65.js";
|
|
11
14
|
import {
|
|
12
|
-
ModelClient,
|
|
13
15
|
resolveGatewayUrl
|
|
14
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-IKEDLLFY.js";
|
|
15
17
|
|
|
16
18
|
// src/cli.ts
|
|
17
19
|
import { createHash } from "crypto";
|
|
@@ -27,9 +29,9 @@ import { join, resolve } from "path";
|
|
|
27
29
|
// src/cli/dev.ts
|
|
28
30
|
import { createInterface } from "readline";
|
|
29
31
|
var RE_ABSOLUTE_PATH = /^[A-Za-z]:[\\/]/;
|
|
30
|
-
async function probeGateway(baseUrl) {
|
|
32
|
+
async function probeGateway(baseUrl, fetchImpl = fetch) {
|
|
31
33
|
try {
|
|
32
|
-
await
|
|
34
|
+
await fetchImpl(`${baseUrl}/health`, {
|
|
33
35
|
method: "HEAD",
|
|
34
36
|
signal: AbortSignal.timeout(3e3)
|
|
35
37
|
});
|
|
@@ -228,7 +230,9 @@ function printUsage() {
|
|
|
228
230
|
"",
|
|
229
231
|
"Usage:",
|
|
230
232
|
" bunx ryu pack <dir> Validate and bundle a manifest.json Plugin",
|
|
231
|
-
" bunx ryu publish <
|
|
233
|
+
" bunx ryu github-publish <url>",
|
|
234
|
+
" Submit a GitHub repository/package URL to the Marketplace",
|
|
235
|
+
" bunx ryu publish <dir> Deprecated server-artifact publishing (migration only)",
|
|
232
236
|
" bunx ryu dev <entry> Run a Runnable locally with an interactive chat loop",
|
|
233
237
|
" bunx ryu agent-plugin <dir>",
|
|
234
238
|
" Emit the Agent Plugins v1 interop pair (plugin.json + mcp.json)",
|
|
@@ -247,16 +251,20 @@ var MANIFEST_FILE_NAMES = [
|
|
|
247
251
|
"ryu.json"
|
|
248
252
|
];
|
|
249
253
|
function resolveNativeManifestPath(dir) {
|
|
250
|
-
return MANIFEST_FILE_NAMES.map((name) => join(dir, name)).find(
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
254
|
+
return MANIFEST_FILE_NAMES.map((name) => join(dir, name)).find(
|
|
255
|
+
(candidate) => {
|
|
256
|
+
if (!existsSync(candidate)) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
return !isAgentPluginManifest(
|
|
261
|
+
JSON.parse(readFileSync(candidate, "utf8"))
|
|
262
|
+
);
|
|
263
|
+
} catch {
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
258
266
|
}
|
|
259
|
-
|
|
267
|
+
);
|
|
260
268
|
}
|
|
261
269
|
function loadManifest(dir) {
|
|
262
270
|
const manifestPath = resolveNativeManifestPath(dir);
|
|
@@ -519,8 +527,16 @@ async function commandPublish(rawDir) {
|
|
|
519
527
|
...manifest.description ? { description: manifest.description } : {},
|
|
520
528
|
...manifest.tagline ? { tagline: manifest.tagline } : {},
|
|
521
529
|
...developer ? { developer } : {},
|
|
530
|
+
...manifest.author ? { author: manifest.author } : {},
|
|
531
|
+
...manifest.repository ? { repository: manifest.repository } : {},
|
|
532
|
+
...manifest.external ? { external: true } : {},
|
|
522
533
|
...manifest.category ? { category: manifest.category } : {},
|
|
534
|
+
...manifest.license ? { license: manifest.license } : {},
|
|
535
|
+
...manifest.keywords?.length ? { keywords: manifest.keywords } : {},
|
|
523
536
|
...manifest.iconUrl ? { iconUrl: manifest.iconUrl } : {},
|
|
537
|
+
...manifest.icon ? { icon: manifest.icon } : {},
|
|
538
|
+
...manifest.iconDither ? { iconDither: manifest.iconDither } : {},
|
|
539
|
+
...manifest.banner ? { banner: manifest.banner } : {},
|
|
524
540
|
...manifest.screenshots?.length ? { screenshots: manifest.screenshots } : {},
|
|
525
541
|
...manifest.homepage ? { website: manifest.homepage } : {},
|
|
526
542
|
...manifest.privacyPolicyUrl ? { privacyPolicyUrl: manifest.privacyPolicyUrl } : {},
|
|
@@ -576,6 +592,55 @@ ${text}
|
|
|
576
592
|
`
|
|
577
593
|
);
|
|
578
594
|
}
|
|
595
|
+
async function commandGithubPublish(repositoryUrl) {
|
|
596
|
+
const input = repositoryUrl.trim();
|
|
597
|
+
if (!input) {
|
|
598
|
+
exitError(
|
|
599
|
+
"github-publish requires a GitHub repository or package URL, for example https://github.com/acme/my-app"
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
const token = authToken();
|
|
603
|
+
const url = `${publishBaseUrl()}/api/marketplace/github/publish`;
|
|
604
|
+
const proof = process.env.RYU_GITHUB_INSTALLATION_PROOF?.trim();
|
|
605
|
+
const body = {
|
|
606
|
+
url: input,
|
|
607
|
+
...proof ? { installationProof: proof } : {}
|
|
608
|
+
};
|
|
609
|
+
let response;
|
|
610
|
+
try {
|
|
611
|
+
response = await fetch(url, {
|
|
612
|
+
method: "POST",
|
|
613
|
+
headers: {
|
|
614
|
+
authorization: `Bearer ${token}`,
|
|
615
|
+
"content-type": "application/json"
|
|
616
|
+
},
|
|
617
|
+
body: JSON.stringify(body)
|
|
618
|
+
});
|
|
619
|
+
} catch (error) {
|
|
620
|
+
exitError(`could not reach ${url}: ${String(error)}`);
|
|
621
|
+
}
|
|
622
|
+
const text = await response.text();
|
|
623
|
+
if (!response.ok) {
|
|
624
|
+
try {
|
|
625
|
+
const error = JSON.parse(text);
|
|
626
|
+
if (error.installationUrl) {
|
|
627
|
+
exitError(
|
|
628
|
+
`${error.error ?? "GitHub App installation required"}. Open ${error.installationUrl}, then set RYU_GITHUB_INSTALLATION_PROOF to the callback proof and retry.`
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
exitError(
|
|
632
|
+
`github-publish failed (${response.status}): ${error.error ?? text}`
|
|
633
|
+
);
|
|
634
|
+
} catch {
|
|
635
|
+
exitError(`github-publish failed (${response.status}): ${text}`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
process.stdout.write(
|
|
639
|
+
`GitHub-backed listing submitted for moderation:
|
|
640
|
+
${text}
|
|
641
|
+
`
|
|
642
|
+
);
|
|
643
|
+
}
|
|
579
644
|
var [, , command, ...args] = process.argv;
|
|
580
645
|
if (!command) {
|
|
581
646
|
printUsage();
|
|
@@ -594,9 +659,24 @@ if (command === "pack") {
|
|
|
594
659
|
if (!dir) {
|
|
595
660
|
exitError("publish requires a directory argument: bunx ryu publish <dir>");
|
|
596
661
|
}
|
|
662
|
+
if (process.env.RYU_ENABLE_LEGACY_MARKETPLACE_WRITES !== "true") {
|
|
663
|
+
exitError(
|
|
664
|
+
"ryu publish is deprecated because marketplace package contents now live in GitHub Releases; use bunx ryu github-publish <repository-or-package-url>"
|
|
665
|
+
);
|
|
666
|
+
}
|
|
597
667
|
commandPublish(dir).catch((err) => {
|
|
598
668
|
exitError(String(err));
|
|
599
669
|
});
|
|
670
|
+
} else if (command === "github-publish") {
|
|
671
|
+
const repositoryUrl = args[0];
|
|
672
|
+
if (!repositoryUrl) {
|
|
673
|
+
exitError(
|
|
674
|
+
"github-publish requires a GitHub repository or package URL, for example https://github.com/acme/my-app"
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
commandGithubPublish(repositoryUrl).catch((err) => {
|
|
678
|
+
exitError(String(err));
|
|
679
|
+
});
|
|
600
680
|
} else if (command === "agent-plugin") {
|
|
601
681
|
const dir = args[0];
|
|
602
682
|
if (!dir) {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway-mandatory model client for the Ryu SDK.
|
|
3
|
+
*
|
|
4
|
+
* Rust-cored: this is a thin TypeScript wrapper over the `@ryuhq/sdk-native`
|
|
5
|
+
* addon's `ModelClient`, which is the `crates/ryu-sdk` Rust core. Every model
|
|
6
|
+
* call is routed by the Rust core to the Ryu gateway's OpenAI-compatible
|
|
7
|
+
* `/v1/chat/completions` endpoint; direct-provider base URLs are rejected at
|
|
8
|
+
* construction. No provider SDK or base URL is ever imported here.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
*
|
|
12
|
+
* const model = defineModel("gpt-4o");
|
|
13
|
+
* const reply = await model.chat([{ role: "user", content: "hello" }]);
|
|
14
|
+
* for await (const delta of model.stream([{ role: "user", content: "hi" }])) {
|
|
15
|
+
* process.stdout.write(delta.content);
|
|
16
|
+
* }
|
|
17
|
+
*/
|
|
18
|
+
/** A single message in a chat conversation. */
|
|
19
|
+
interface ChatMessage {
|
|
20
|
+
/** The message text. */
|
|
21
|
+
content: string;
|
|
22
|
+
/** The speaker: "system", "user", or "assistant". */
|
|
23
|
+
role: "system" | "user" | "assistant";
|
|
24
|
+
}
|
|
25
|
+
/** A streaming chat completion delta. */
|
|
26
|
+
interface ChatDelta {
|
|
27
|
+
/** Incremental text fragment from the model. */
|
|
28
|
+
content: string | null;
|
|
29
|
+
/** Non-null on the final chunk when `finish_reason` is set. */
|
|
30
|
+
finishReason: string | null;
|
|
31
|
+
}
|
|
32
|
+
/** Non-streaming chat completion result. */
|
|
33
|
+
interface ChatResult {
|
|
34
|
+
/** The full assistant reply text. */
|
|
35
|
+
content: string;
|
|
36
|
+
/** The gateway/model-reported finish reason. */
|
|
37
|
+
finishReason: string | null;
|
|
38
|
+
/** Usage stats as reported by the gateway (optional — gateway may omit). */
|
|
39
|
+
usage?: {
|
|
40
|
+
promptTokens: number;
|
|
41
|
+
completionTokens: number;
|
|
42
|
+
totalTokens: number;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Options accepted by `defineModel`. */
|
|
46
|
+
interface ModelClientOptions {
|
|
47
|
+
/**
|
|
48
|
+
* Gateway base URL (no trailing `/v1`). Defaults to `RYU_GATEWAY_URL` then
|
|
49
|
+
* the Rust core's default. Direct provider URLs are rejected at construction.
|
|
50
|
+
*/
|
|
51
|
+
baseUrl?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Bearer token forwarded to the gateway. Defaults to `RYU_GATEWAY_TOKEN`.
|
|
54
|
+
* This is the gateway token, never a provider API key.
|
|
55
|
+
*/
|
|
56
|
+
token?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A gateway-mandatory model client backed by the Rust core.
|
|
60
|
+
*
|
|
61
|
+
* All calls go through the native `ModelClient`; if the configured base URL is a
|
|
62
|
+
* direct provider, construction throws.
|
|
63
|
+
*/
|
|
64
|
+
declare class ModelClient {
|
|
65
|
+
readonly model: string;
|
|
66
|
+
readonly baseUrl: string;
|
|
67
|
+
private readonly native;
|
|
68
|
+
constructor(model: string, options?: ModelClientOptions);
|
|
69
|
+
/** Send a non-streaming chat completion request to the gateway. */
|
|
70
|
+
chat(messages: ChatMessage[]): Promise<ChatResult>;
|
|
71
|
+
/** Send a streaming chat completion request, yielding deltas as they arrive. */
|
|
72
|
+
stream(messages: ChatMessage[]): AsyncGenerator<ChatDelta>;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Create a gateway-mandatory model client for `modelId`.
|
|
76
|
+
*
|
|
77
|
+
* const model = defineModel("gpt-4o");
|
|
78
|
+
* const model = defineModel("claude-3-5-sonnet", { baseUrl: "http://my-gateway:7981" });
|
|
79
|
+
*
|
|
80
|
+
* A direct provider URL throws immediately (egress enforcement in the Rust core).
|
|
81
|
+
*/
|
|
82
|
+
declare function defineModel(modelId: string, options?: ModelClientOptions): ModelClient;
|
|
83
|
+
|
|
84
|
+
export { type ChatDelta as C, ModelClient as M, type ChatMessage as a, type ChatResult as b, type ModelClientOptions as c, defineModel as d };
|