runx_runtime/scaffold/
templates.rs1#[derive(Clone, Debug, PartialEq, Eq)]
6pub struct ScaffoldFile {
7 pub relative_path: String,
8 pub contents: String,
9}
10
11pub fn scaffold_package_files(name: &str) -> Vec<ScaffoldFile> {
12 vec![
13 file("SKILL.md", skill_md(name)),
14 file("X.yaml", x_yaml(name)),
15 file("run.mjs", run_mjs()),
16 file("README.md", readme(name)),
17 file(".gitignore", "node_modules/\n.runx/\n*.tgz\n".to_owned()),
18 ]
19}
20
21fn file(relative_path: &str, contents: String) -> ScaffoldFile {
22 ScaffoldFile {
23 relative_path: relative_path.to_owned(),
24 contents,
25 }
26}
27
28fn skill_md(name: &str) -> String {
29 format!(
30 r#"---
31name: {name}
32description: {name} runx skill. Replace this with what the skill does and returns.
33source:
34 type: cli-tool
35 command: node
36 args:
37 - run.mjs
38 timeout_seconds: 30
39 sandbox:
40 profile: readonly
41 cwd_policy: skill-directory
42inputs:
43 message:
44 type: string
45 required: true
46 description: Input the skill acts on. Replace with the real inputs.
47runx:
48 category: ops
49 input_resolution:
50 required:
51 - message
52---
53
54# {name}
55
56Describe what this skill does, when an agent should reach for it, and what it
57returns. Replace the echo in `run.mjs` with the real work, and add cases to
58`X.yaml` so the behaviour is locked by the harness.
59"#
60 )
61}
62
63fn x_yaml(name: &str) -> String {
64 format!(
65 r#"skill: {name}
66version: "0.1.0"
67
68catalog:
69 kind: skill
70 audience: public
71 visibility: public
72 role: canonical
73
74harness:
75 cases:
76 - name: {name}-smoke
77 runner: default
78 inputs:
79 message: hello
80 expect:
81 status: sealed
82 receipt:
83 schema: runx.receipt.v1
84 state: sealed
85 disposition: closed
86 reason_code: process_closed
87 - name: {name}-empty-message-fails
88 runner: default
89 inputs:
90 message: ""
91 expect:
92 status: failure
93 receipt:
94 schema: runx.receipt.v1
95 state: sealed
96 disposition: closed
97 reason_code: process_failed
98
99runners:
100 default:
101 default: true
102 type: cli-tool
103 command: node
104 args:
105 - run.mjs
106 inputs:
107 message:
108 type: string
109 required: true
110 description: Input the skill acts on.
111"#
112 )
113}
114
115fn run_mjs() -> String {
116 r#"// Inputs arrive as RUNX_INPUT_<NAME> environment variables. Do the work and
117// write the result to stdout. Replace this echo with the real logic.
118const message = process.env.RUNX_INPUT_MESSAGE ?? "";
119if (message.trim().length === 0) {
120 process.stderr.write("message is required\n");
121 process.exit(64);
122}
123process.stdout.write(`${message}\n`);
124"#
125 .to_owned()
126}
127
128fn readme(name: &str) -> String {
129 format!(
130 r#"# {name}
131
132A native runx skill: a `SKILL.md` contract, an `X.yaml` execution profile, and a
133`run.mjs` script. No build step and no dependencies.
134
135## Develop
136
137For local development, `runx skill` and inline `runx harness` use
138local-development receipts when no production signing env is configured.
139Publishing and hosted verification still require real authority.
140
141```bash
142runx harness . --json # run the harness cases in X.yaml
143runx skill . --input message=hello --json # run the skill once
144runx history # inspect the signed receipt
145```
146
147Edit `run.mjs` to do the real work, and keep both harness classes in `X.yaml`:
148one happy path and one stop, error, or refusal case.
149
150## Publish
151
152```bash
153runx login --provider github --for publish
154runx registry publish . # the registry runs the harness as the publish gate
155```
156"#
157 )
158}