@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/src/cli.ts
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
* bunx ryu publish <dir>
|
|
8
8
|
*
|
|
9
9
|
* Commands:
|
|
10
|
-
* pack <dir> Validate the
|
|
10
|
+
* pack <dir> Validate the manifest.json in <dir> and emit a publish-ready
|
|
11
11
|
* Plugin bundle at <dir>/dist/plugin.bundle.json.
|
|
12
12
|
* Exits 0 on success; exits 1 with the failing field on error.
|
|
13
|
-
* publish <dir> Validate the
|
|
13
|
+
* publish <dir> Validate the manifest.json and POST it to the Ryu Marketplace
|
|
14
14
|
* publish endpoint with the author's auth token. The item is
|
|
15
15
|
* stored as `pending` until a moderator approves it.
|
|
16
16
|
*/
|
|
@@ -38,8 +38,8 @@ function printUsage(): void {
|
|
|
38
38
|
"Ryu dev SDK",
|
|
39
39
|
"",
|
|
40
40
|
"Usage:",
|
|
41
|
-
" bunx ryu pack <dir> Validate and bundle a
|
|
42
|
-
" bunx ryu publish <dir> Validate and publish a
|
|
41
|
+
" bunx ryu pack <dir> Validate and bundle a manifest.json Plugin",
|
|
42
|
+
" bunx ryu publish <dir> Validate and publish a manifest.json Plugin to the Ryu Marketplace",
|
|
43
43
|
" bunx ryu dev <entry> Run a Runnable locally with an interactive chat loop",
|
|
44
44
|
"",
|
|
45
45
|
].join("\n")
|
|
@@ -55,12 +55,24 @@ function exitError(message: string): never {
|
|
|
55
55
|
|
|
56
56
|
type LoadedManifest = ReturnType<typeof PluginManifestSchema.parse>;
|
|
57
57
|
|
|
58
|
-
//
|
|
58
|
+
// Manifest file names, in preference order — mirrors Core's resolver
|
|
59
|
+
// (`plugin_manifest::MANIFEST_FILE_NAMES`). `manifest.json` is canonical; the
|
|
60
|
+
// legacy `plugin.json` / `ryu.json` are still accepted so an author's existing
|
|
61
|
+
// project directory keeps packing without a rename.
|
|
62
|
+
const MANIFEST_FILE_NAMES = [
|
|
63
|
+
"manifest.json",
|
|
64
|
+
"plugin.json",
|
|
65
|
+
"ryu.json",
|
|
66
|
+
] as const;
|
|
67
|
+
|
|
68
|
+
// Read + parse + validate the manifest in `dir`. Exits with the failing
|
|
59
69
|
// field on any error. Shared by pack and publish so both validate identically.
|
|
60
70
|
function loadManifest(dir: string): LoadedManifest {
|
|
61
|
-
const manifestPath = join(dir,
|
|
62
|
-
|
|
63
|
-
|
|
71
|
+
const manifestPath = MANIFEST_FILE_NAMES.map((name) => join(dir, name)).find(
|
|
72
|
+
(candidate) => existsSync(candidate)
|
|
73
|
+
);
|
|
74
|
+
if (!manifestPath) {
|
|
75
|
+
exitError(`manifest.json not found in: ${dir}`);
|
|
64
76
|
}
|
|
65
77
|
|
|
66
78
|
let raw: string;
|
|
@@ -74,7 +86,7 @@ function loadManifest(dir: string): LoadedManifest {
|
|
|
74
86
|
try {
|
|
75
87
|
parsed = JSON.parse(raw);
|
|
76
88
|
} catch {
|
|
77
|
-
exitError(`
|
|
89
|
+
exitError(`manifest.json is not valid JSON: ${manifestPath}`);
|
|
78
90
|
}
|
|
79
91
|
|
|
80
92
|
const result = PluginManifestSchema.safeParse(parsed);
|
|
@@ -82,9 +94,82 @@ function loadManifest(dir: string): LoadedManifest {
|
|
|
82
94
|
const first = result.error.issues[0];
|
|
83
95
|
const field = first?.path.join(".") ?? "unknown";
|
|
84
96
|
const message = first?.message ?? "validation failed";
|
|
85
|
-
exitError(`
|
|
97
|
+
exitError(`manifest.json validation failed at '${field}': ${message}`);
|
|
98
|
+
}
|
|
99
|
+
return inlineCodeFiles(result.data, dir);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Directories a `code_file` may name — mirrors Rust's `CODE_FILE_DIRS`. */
|
|
103
|
+
const CODE_FILE_DIRS = ["hooks", "adapters"];
|
|
104
|
+
const CODE_FILE_PATH = /^(hooks|adapters)\/[A-Za-z0-9_][A-Za-z0-9._-]*\.m?js$/;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Read one `code_file` and return its contents, or exit with a clear error.
|
|
108
|
+
*
|
|
109
|
+
* The path is joined onto the plugin directory, so it is validated against the
|
|
110
|
+
* same flat allowlist Core enforces (`<hooks|adapters>/<name>.js`, no traversal)
|
|
111
|
+
* rather than trusted.
|
|
112
|
+
*/
|
|
113
|
+
function readCodeFile(dir: string, rel: string, label: string): string {
|
|
114
|
+
if (!CODE_FILE_PATH.test(rel)) {
|
|
115
|
+
exitError(
|
|
116
|
+
`${label}: code_file '${rel}' must be exactly '<${CODE_FILE_DIRS.join("|")}>/<name>.js' with no traversal`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
const path = join(dir, rel);
|
|
120
|
+
let body: string;
|
|
121
|
+
try {
|
|
122
|
+
body = readFileSync(path, "utf8");
|
|
123
|
+
} catch (err) {
|
|
124
|
+
exitError(`${label}: could not read code_file '${rel}': ${String(err)}`);
|
|
125
|
+
}
|
|
126
|
+
if (!body.trim()) {
|
|
127
|
+
exitError(`${label}: code_file '${rel}' is empty`);
|
|
86
128
|
}
|
|
87
|
-
return
|
|
129
|
+
return body;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Replace every `code_file` reference with the file's contents — the source form
|
|
134
|
+
* becoming the wire form.
|
|
135
|
+
*
|
|
136
|
+
* `code_file` exists so a plugin's sandboxed JS lives in real, reviewable `.js`
|
|
137
|
+
* files instead of a one-line escaped JSON string. But the BUNDLE must stay
|
|
138
|
+
* self-contained and, for a marketplace plugin, the Gateway signs the manifest
|
|
139
|
+
* verbatim — so inlining here is what keeps the entire hook/adapter body inside the
|
|
140
|
+
* signed surface. A published bundle that still carried `code_file` would be a new
|
|
141
|
+
* unsigned-code carriage channel, which is exactly what this must not become.
|
|
142
|
+
*/
|
|
143
|
+
function inlineCodeFiles(
|
|
144
|
+
manifest: LoadedManifest,
|
|
145
|
+
dir: string
|
|
146
|
+
): LoadedManifest {
|
|
147
|
+
const out = manifest as LoadedManifest & {
|
|
148
|
+
contributes?: { turn_hooks?: Record<string, unknown>[] };
|
|
149
|
+
provides?: {
|
|
150
|
+
tools?: Record<string, { adapter?: Record<string, unknown> }>;
|
|
151
|
+
}[];
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
for (const hook of out.contributes?.turn_hooks ?? []) {
|
|
155
|
+
const rel = hook.code_file;
|
|
156
|
+
if (typeof rel === "string") {
|
|
157
|
+
hook.code = readCodeFile(dir, rel, `turn hook '${String(hook.id)}'`);
|
|
158
|
+
hook.code_file = undefined;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
for (const entry of out.provides ?? []) {
|
|
163
|
+
for (const [verb, binding] of Object.entries(entry.tools ?? {})) {
|
|
164
|
+
const rel = binding.adapter?.code_file;
|
|
165
|
+
if (binding.adapter && typeof rel === "string") {
|
|
166
|
+
binding.adapter.code = readCodeFile(dir, rel, `adapter '${verb}'`);
|
|
167
|
+
binding.adapter.code_file = undefined;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return out;
|
|
88
173
|
}
|
|
89
174
|
|
|
90
175
|
// ── pack command ──────────────────────────────────────────────────────────────
|
|
@@ -236,10 +321,10 @@ function authToken(): string {
|
|
|
236
321
|
return token;
|
|
237
322
|
}
|
|
238
323
|
|
|
239
|
-
// An SDK-authored Plugin always publishes as a `plugin` (a
|
|
324
|
+
// An SDK-authored Plugin always publishes as a `plugin` (a manifest.json bundle of
|
|
240
325
|
// runnables). It is deliberately NOT published as `skill`: Core's skill install
|
|
241
326
|
// path needs a `descriptor.raw.install_source` (a from-source owner/repo), which
|
|
242
|
-
// a
|
|
327
|
+
// a manifest.json manifest does not carry, so a skill-kind publish would be
|
|
243
328
|
// uninstallable. Model / mcp items are published through their own tools.
|
|
244
329
|
const SDK_PUBLISH_KIND = "plugin" as const;
|
|
245
330
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* that the SDK's deliberately simpler zod authoring schema, the blessed JSON
|
|
9
9
|
* Schema, and the generated types stay describing the same manifest:
|
|
10
10
|
*
|
|
11
|
-
* 1. a known-good repo manifest (apps-store/mail/
|
|
11
|
+
* 1. a known-good repo manifest (apps-store/mail/manifest.json — the first
|
|
12
12
|
* fully manifest-driven app) parses with the zod `PluginManifestSchema`;
|
|
13
13
|
* 2. every key the schema marks required exists in the schema, the generated
|
|
14
14
|
* TS, and the fixture;
|
|
@@ -24,7 +24,7 @@ import { PluginManifestSchema } from "./manifest";
|
|
|
24
24
|
|
|
25
25
|
const FIXTURE_PATH = join(
|
|
26
26
|
import.meta.dir,
|
|
27
|
-
"../../../apps-store/mail/
|
|
27
|
+
"../../../apps-store/mail/manifest.json"
|
|
28
28
|
);
|
|
29
29
|
const SCHEMA_PATH = join(
|
|
30
30
|
import.meta.dir,
|
|
@@ -44,9 +44,9 @@ const schema = JSON.parse(readFileSync(SCHEMA_PATH, "utf8")) as {
|
|
|
44
44
|
const generatedSource = readFileSync(GENERATED_PATH, "utf8");
|
|
45
45
|
|
|
46
46
|
describe("contracts lockstep (zod ↔ blessed JSON Schema ↔ generated TS)", () => {
|
|
47
|
-
test("the known-good mail
|
|
47
|
+
test("the known-good mail manifest.json parses with the zod authoring schema", () => {
|
|
48
48
|
const parsed = PluginManifestSchema.parse(fixture);
|
|
49
|
-
expect(parsed.id).toBe("
|
|
49
|
+
expect(parsed.id).toBe("@ryu/mail");
|
|
50
50
|
expect(parsed.runnables.length).toBeGreaterThan(0);
|
|
51
51
|
});
|
|
52
52
|
|