Skip to main content

runx_runtime/scaffold/
templates.rs

1// Native cli-tool skill scaffold: SKILL.md + X.yaml + run.mjs + README + .gitignore.
2// The output has zero dependencies and no build step, so `runx new` produces a
3// skill that runs and harnesses immediately, with nothing pinned that can drift.
4
5#[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}