@tetherto/mdk-cli 0.0.0 → 0.12.1

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.
Files changed (112) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +447 -0
  3. package/dist/commands/add.js +61 -0
  4. package/dist/commands/add.js.map +1 -0
  5. package/dist/commands/agent.js +42 -0
  6. package/dist/commands/agent.js.map +1 -0
  7. package/dist/commands/create.js +261 -0
  8. package/dist/commands/create.js.map +1 -0
  9. package/dist/commands/inspect.js +171 -0
  10. package/dist/commands/inspect.js.map +1 -0
  11. package/dist/commands/meta.js +12 -0
  12. package/dist/commands/meta.js.map +1 -0
  13. package/dist/commands/onboard.js +447 -0
  14. package/dist/commands/onboard.js.map +1 -0
  15. package/dist/commands/run.js +203 -0
  16. package/dist/commands/run.js.map +1 -0
  17. package/dist/commands/validate.js +125 -0
  18. package/dist/commands/validate.js.map +1 -0
  19. package/dist/index.js +9 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/lib/add-package.js +148 -0
  22. package/dist/lib/add-package.js.map +1 -0
  23. package/dist/lib/app-package.js +182 -0
  24. package/dist/lib/app-package.js.map +1 -0
  25. package/dist/lib/app-scaffold.js +221 -0
  26. package/dist/lib/app-scaffold.js.map +1 -0
  27. package/dist/lib/app-ui-check.js +178 -0
  28. package/dist/lib/app-ui-check.js.map +1 -0
  29. package/dist/lib/app-ui-runner.js +122 -0
  30. package/dist/lib/app-ui-runner.js.map +1 -0
  31. package/dist/lib/catalog.js +69 -0
  32. package/dist/lib/catalog.js.map +1 -0
  33. package/dist/lib/checkout.js +14 -0
  34. package/dist/lib/checkout.js.map +1 -0
  35. package/dist/lib/dashboard-apps.js +71 -0
  36. package/dist/lib/dashboard-apps.js.map +1 -0
  37. package/dist/lib/dashboard.js +318 -0
  38. package/dist/lib/dashboard.js.map +1 -0
  39. package/dist/lib/detect.js +38 -0
  40. package/dist/lib/detect.js.map +1 -0
  41. package/dist/lib/env-file.js +57 -0
  42. package/dist/lib/env-file.js.map +1 -0
  43. package/dist/lib/fetch-template.js +33 -0
  44. package/dist/lib/fetch-template.js.map +1 -0
  45. package/dist/lib/help.js +22 -0
  46. package/dist/lib/help.js.map +1 -0
  47. package/dist/lib/names.js +36 -0
  48. package/dist/lib/names.js.map +1 -0
  49. package/dist/lib/npm.js +113 -0
  50. package/dist/lib/npm.js.map +1 -0
  51. package/dist/lib/package-source.js +76 -0
  52. package/dist/lib/package-source.js.map +1 -0
  53. package/dist/lib/pkg.js +5 -0
  54. package/dist/lib/pkg.js.map +1 -0
  55. package/dist/lib/plugin-setup.js +204 -0
  56. package/dist/lib/plugin-setup.js.map +1 -0
  57. package/dist/lib/ports.js +31 -0
  58. package/dist/lib/ports.js.map +1 -0
  59. package/dist/lib/project.js +297 -0
  60. package/dist/lib/project.js.map +1 -0
  61. package/dist/lib/run-lock.js +188 -0
  62. package/dist/lib/run-lock.js.map +1 -0
  63. package/dist/lib/runtime.js +683 -0
  64. package/dist/lib/runtime.js.map +1 -0
  65. package/dist/lib/scaffold.js +82 -0
  66. package/dist/lib/scaffold.js.map +1 -0
  67. package/dist/lib/shutdown.js +53 -0
  68. package/dist/lib/shutdown.js.map +1 -0
  69. package/dist/lib/skill.js +38 -0
  70. package/dist/lib/skill.js.map +1 -0
  71. package/dist/lib/spec.js +375 -0
  72. package/dist/lib/spec.js.map +1 -0
  73. package/dist/lib/status.js +245 -0
  74. package/dist/lib/status.js.map +1 -0
  75. package/dist/lib/theme.js +50 -0
  76. package/dist/lib/theme.js.map +1 -0
  77. package/dist/lib/worker-package.js +74 -0
  78. package/dist/lib/worker-package.js.map +1 -0
  79. package/dist/lib/worker-scaffold.js +122 -0
  80. package/dist/lib/worker-scaffold.js.map +1 -0
  81. package/dist/lib/worker-smoke.js +243 -0
  82. package/dist/lib/worker-smoke.js.map +1 -0
  83. package/dist/program.js +58 -0
  84. package/dist/program.js.map +1 -0
  85. package/package.json +59 -1
  86. package/templates/app/README.md +66 -0
  87. package/templates/app/mdk-app.yaml +10 -0
  88. package/templates/app/package.json +17 -0
  89. package/templates/app/plugin/controllers/summary.js +36 -0
  90. package/templates/app/plugin/lib/client.js +11 -0
  91. package/templates/app/plugin/mdk-plugin.json +16 -0
  92. package/templates/app-ui/tsconfig.json +29 -0
  93. package/templates/app-ui/ui-src/components/summary-panel.tsx +24 -0
  94. package/templates/app-ui/ui-src/data/summary.ts +18 -0
  95. package/templates/app-ui/ui-src/hooks/use-summary.ts +54 -0
  96. package/templates/app-ui/ui-src/index.tsx +15 -0
  97. package/templates/app-ui/ui-src/pages/overview-page.tsx +18 -0
  98. package/templates/app-ui/ui-src/styles.css +13 -0
  99. package/templates/app-ui/ui-src/styles.d.ts +3 -0
  100. package/templates/app-ui/ui-src/vite.config.ts +26 -0
  101. package/templates/worker/README.md +93 -0
  102. package/templates/worker/mdk-contract.json +26 -0
  103. package/templates/worker/mock/server.js +93 -0
  104. package/templates/worker/package.json +23 -0
  105. package/templates/worker/smoke.config.js +31 -0
  106. package/templates/worker/src/client.js +47 -0
  107. package/templates/worker/src/commands/reboot.js +5 -0
  108. package/templates/worker/src/commands/set-power-mode.js +10 -0
  109. package/templates/worker/src/telemetry/hashrate-rt.js +5 -0
  110. package/templates/worker/src/telemetry/power-mode.js +5 -0
  111. package/templates/worker/src/telemetry/power.js +5 -0
  112. package/templates/worker/src/telemetry/temperature.js +5 -0
package/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright 2026 Tether
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,447 @@
1
+ # `@tetherto/mdk-cli`
2
+
3
+ The `mdk` command-line tool. Built with Commander.js and `@clack/prompts`; the implemented commands are:
4
+ `mdk onboard` (a guided wizard that detects the environment, asks the setup
5
+ questions and writes `mdk.yaml`), `mdk create` (`worker`, `app`, `dashboard`
6
+ — each self-registering in the spec), `mdk add` (`app`, `worker` from npm or
7
+ GitHub), `mdk validate` (`app`, `worker`), `mdk run` (boots the stack from the
8
+ spec), `mdk status` (environment + component health) and `mdk skill add`
9
+ (installs the `@tetherto/mdk-skill` suite).
10
+
11
+ ## Stack
12
+
13
+ - **Runtime:** Node.js >= 24, TypeScript, ESM
14
+ - **Framework:** [Commander.js](https://github.com/tj/commander.js) for the
15
+ command tree, parsing, and help; `[@clack/prompts](https://github.com/bombshell-dev/clack)`
16
+ for interactive wizard steps
17
+ - The `mdk` binary is exposed via the package.json `bin` field pointing at
18
+ `dist/index.js` (which carries a `#!/usr/bin/env node` shebang)
19
+
20
+ ## Project layout
21
+
22
+ `onboard` and `create` emit one role-grouped layout, so a path in `mdk.yaml` says
23
+ what it is:
24
+
25
+ ```text
26
+ mdk.yaml the stack spec
27
+ package.json private root manifest; links workers/ and apps/<name>/ by file: deps
28
+ README.md generated: layout + the run commands
29
+ workers/<name>/ worker plugins (mdk create worker)
30
+ apps/<name>/ apps (mdk create app)
31
+ apps/dashboard/ the UI dashboard (mdk create dashboard)
32
+ .mdk/ runtime state written by `mdk run` — gitignored, disposable
33
+ ```
34
+
35
+ Workers and apps are linked from the root manifest by relative `file:`
36
+ dependencies because the runtime resolves them out of the project's
37
+ `node_modules` — so `create worker` and `create app` run `npm install` **at the
38
+ project root**, not in the package. The dashboard is deliberately not linked: it
39
+ links the MDK UI packages by `file:` path, and hoisting its React through the root
40
+ alongside those links invites a duplicate-React bug, so it installs in place.
41
+
42
+ `.mdk/` gives each component its own data root (`kernel/`, `gateway/`, `mcp/`,
43
+ `workers/<name>/`, `apps/<name>/`) plus the files components in other processes
44
+ find each other by: `kernel.key` (read by the Gateway, apps and the MCP server),
45
+ `keys/` (written by Workers, watched by the Kernel), `gateway.key` (the key app
46
+ runtimes accept), `trusted-keys/` (every caller's key, admitted by the Kernel
47
+ and Workers) and `run/` (one lock per running Kernel, Worker and app). Delete
48
+ the whole directory to reset a stack.
49
+
50
+ ## Develop
51
+
52
+ Install once from the packages workspace root (this links `@tetherto/mdk-skill`
53
+ into the CLI), then work from this package:
54
+
55
+ ```bash
56
+ npm install --prefix .. # from packages/ — links workspace deps
57
+ npm run dev -- --help # run from source via tsx
58
+ npm run build # compile to dist/
59
+ node dist/index.js --help # run the built CLI
60
+ ```
61
+
62
+ Link it as a global `mdk` for local testing:
63
+
64
+ ```bash
65
+ npm run build && npm link
66
+ mdk --help
67
+ # when done: npm rm -g @tetherto/mdk-cli
68
+ ```
69
+
70
+ Run the tests:
71
+
72
+ ```bash
73
+ npm test # unit tests, no network (coverage gate: npm run test:coverage)
74
+ npm run test:e2e:stack # ~1 min, needs network: scaffolds a worker and an app, runs the whole
75
+ # stack, then checks it over HTTP, with `mdk status` and against the
76
+ # app runtime's key allow-list
77
+ npm run test:e2e:scaffold # ~2 min, needs network and a built ui/ (npm --prefix ../../ui run build):
78
+ # scaffolds a worker, an app with its starter UI and the dashboard,
79
+ # builds the UIs, serves the app's UI with `mdk run dashboard` and calls
80
+ # the running stack through the dashboard's Gateway proxy
81
+ npm run test:e2e # both
82
+ ```
83
+
84
+ ## Try it out
85
+
86
+ Run the implemented commands against a throwaway project so nothing pollutes the
87
+ repo. From this package after `npm run build`:
88
+
89
+ ```bash
90
+ mkdir -p /tmp/mdk-try && cd /tmp/mdk-try
91
+ CLI=/path/to/mdk/packages/cli/dist/index.js
92
+
93
+ # Install the MDK Developer Skill (via the @tetherto/mdk-skill entry point)
94
+ node "$CLI" skill add --client all --dir .
95
+ ls .cursor/skills .claude/skills # mdk, mdk-gateway-plugin, mdk-deployment, mdk-worker-plugin, mdk-ui-component, mdk-site-sizing, mdk-create-app
96
+
97
+ # Scaffold the MDK UI shell into apps/dashboard, named <stack>-dashboard from mdk.yaml
98
+ node "$CLI" create dashboard --dir .
99
+ ls apps/dashboard # src/, vite.config.ts, package.json, ...
100
+ ```
101
+
102
+ The onboarding wizard needs a real terminal (TTY), so run it directly (not
103
+ piped). It writes `mdk.yaml`, the root [`package.json`](./package.json), `.gitignore` and a project
104
+ [`README.md`](./README.md), optionally installs the skill + scaffolds the UI shell, and prints
105
+ the run commands. Answers to `secret` plugin-setup questions are not written to `mdk.yaml`
106
+ (which you commit): the spec gets a `${MDK_<PLUGIN>_<KEY>}` placeholder and the value goes to
107
+ the project's gitignored, owner-only `.env`. `mdk run` resolves `${VAR}` in
108
+ `spec.gateway.config`, `spec.apps[].config` and `spec.workers[].env` from the
109
+ process environment first, then `.env`:
110
+
111
+ ```bash
112
+ cd /tmp/mdk-try && node "$CLI" onboard
113
+ ```
114
+
115
+ Dev mode works the same without building — run from `packages/cli`:
116
+
117
+ ```bash
118
+ npm run dev -- skill add --client cursor --dir /tmp/mdk-try
119
+ npm run dev -- create dashboard --dir /tmp/mdk-try
120
+ npm run dev -- onboard
121
+ ```
122
+
123
+ ### Run the stack
124
+
125
+ `mdk run` boots the components declared in `mdk.yaml` against project-local state
126
+ under `.mdk/`. With no target, it boots the Kernel, every Worker, every app
127
+ (see [Apps](#apps)) and the Gateway together (the MCP server is never part of
128
+ `all` — it's always started explicitly, see [Run the MCP server](#run-the-mcp-server)).
129
+ Prefer separate terminals instead? Run each component on its own (`mdk run
130
+ kernel`, `mdk run worker <name>`, `mdk run app <name>`, `mdk run gateway`) —
131
+ that's just which commands you type, not a spec setting.
132
+
133
+ A **Worker** is a plugin package (contract + handlers) that the CLI hosts on
134
+ `WorkerRuntimeV2` — the same "caller hosts the plugin" split the monorepo examples
135
+ use. Reference one in `mdk.yaml` by **local path** (what `mdk create worker`
136
+ writes) or by installed package name (what `mdk add worker` writes), and describe
137
+ its seed devices under the plugin-defined `config`:
138
+
139
+ ```yaml
140
+ spec:
141
+ workers:
142
+ - name: miner-a
143
+ package: ./workers/my-miner # local path (or an installed npm name)
144
+ config:
145
+ mock: true # start the plugin's mock/server.js per device
146
+ devices:
147
+ - id: v3-0
148
+ opts: { host: 127.0.0.1, port: 18080, serial: WM3-DEMO-0, hashrateThs: 200, powerW: 3500 }
149
+ - id: v3-1
150
+ opts: { host: 127.0.0.1, port: 18081, serial: WM3-DEMO-1, hashrateThs: 180, powerW: 3300 }
151
+ ```
152
+
153
+ `config.devices[].opts` is the plugin's own per-device config (opaque to the CLI).
154
+ With `mock: true` the CLI starts one simulator from `<package>/mock/server.js` per
155
+ device so the Worker talks to a mock instead of hardware.
156
+
157
+ #### Apps
158
+
159
+ An **app** is declared under `spec.apps[]` as
160
+ `{ name, package, config, mount? }`; the package needs an `mdk-app.yaml` and a
161
+ `mdk-plugin.json` route table in `plugin/` or at its root (`mdk create app` scaffolds both).
162
+ `spec.gateway.plugins` is rejected. That list is the whole of what
163
+ the Gateway serves. A package that ships several apps as subdirectories names
164
+ each by its subpath (`package: "@your-org/apps/ops"`):
165
+
166
+ ```yaml
167
+ spec:
168
+ apps:
169
+ - name: agent
170
+ package: "@tetherto/mdk-app-agent" # bundled; the UI calls it at /agent
171
+ mount: / # serve at the root, not /apps/agent
172
+ - name: demo
173
+ package: "@tetherto/mdk-app-demo" # an installed npm name
174
+ - name: site
175
+ package: ./apps/site # or a local path
176
+ config:
177
+ agent: { provider: { kind: qvac } }
178
+ ```
179
+
180
+ `mdk run` starts one runtime child process per app before the Gateway, restarts
181
+ one that exits unexpectedly, and stops them on shutdown; `mdk run app <name>`
182
+ runs a single app in the foreground instead. The Gateway serves each app under
183
+ `/apps/<name>` unless `mount` is set. An app accepts calls only from the key in
184
+ `.mdk/gateway.key`, and publishes its own keys to `.mdk/apps/<name>/runtime.key`
185
+ (read by the Gateway) and `.mdk/trusted-keys/app-<name>.pub` (its Kernel client key, which the Kernel admits).
186
+
187
+ Undeclared means unserved: any path no declared app serves gets `404`. `mdk
188
+ status` reports an app whose package does not resolve (subpath included) and
189
+ whether each app is running.
190
+
191
+ Scaffold a Worker from the bundled template (a device client for an HTTP JSON API, with
192
+ a simulator under `mock/`) — it lands under `workers/<name>`, ready to reference by local path:
193
+
194
+ ```bash
195
+ node "$CLI" create worker demo-miner --dir /tmp/mdk-try # → /tmp/mdk-try/workers/demo-miner
196
+ ```
197
+
198
+ `create worker` also writes the new Worker into the project's `mdk.yaml` under
199
+ `spec.workers` (with a `mock: true` seed device) so it is runnable right away;
200
+ pass `--no-stack-entry` to skip and print the snippet instead. It leaves an
201
+ existing same-named entry untouched.
202
+
203
+ The seed device is given a stack-unique identity — mock port counting up from
204
+ `18080`, and a device id/serial derived from the Worker name — so scaffolding
205
+ several Workers into one project never produces a conflict. Both matter: a
206
+ duplicate port fails the boot, and a duplicate device id is worse, because the
207
+ Kernel registers ids globally and silently keeps only the first. `mdk run`
208
+ rejects a spec that repeats a Worker name or device id, naming both offenders.
209
+
210
+ `mdk run` runs in the foreground and owns Ctrl+C (and `SIGTERM`). Components are
211
+ stopped in reverse boot order — gateway, apps, then Workers and their mocks, then the
212
+ Kernel — but the process exits regardless of how that goes: a stop that throws is
213
+ skipped, a stop that wedges is abandoned after 5s with a forced exit, and a
214
+ second Ctrl+C exits immediately. So the ports a run holds are always released
215
+ when it is stopped. If a port is still busy afterward, the run did not actually
216
+ exit — check for a forgotten foreground `mdk run` in another terminal
217
+ (`ps -o pid,stat,etime,command -p <pid>`; a `+` in `STAT` means it is still the
218
+ foreground job of a live tab).
219
+
220
+ Each Kernel, Worker and app runs once per project. `mdk run` takes a lock for
221
+ every one it starts (`.mdk/run/<component>.pid`) and refuses a second copy with
222
+ the pid of the first: two copies of a Worker or app share its identity keys and
223
+ store, so the second takes over the first one's network key and the first goes
224
+ unreachable. A lock left behind by a process that is gone is taken over. The
225
+ Gateway and the MCP server are covered by their port check instead.
226
+
227
+ ### Run the dashboard
228
+
229
+ `mdk run dashboard` starts the scaffolded UI shell's dev server
230
+ (`npm run dev` under `apps/dashboard`, or the first `apps/*` app with a `dev`
231
+ script if the dashboard was created with a custom `--name`). Unlike the Kernel,
232
+ Gateway and Workers — all booted in-process — the dashboard is a separate
233
+ Vite/React app with its own dependency tree (kept out of the project's npm
234
+ workspaces so its React is never hoisted alongside anyone else's, see
235
+ [Project layout](#project-layout)), so it always runs as its own child process.
236
+ It shares `mdk run`'s Ctrl+C/`SIGTERM` handling: the dev server is sent
237
+ `SIGTERM` and the command waits for it to exit before releasing its port.
238
+
239
+ ```bash
240
+ node "$CLI" create dashboard --dir /tmp/mdk-try
241
+ node "$CLI" run dashboard --dir /tmp/mdk-try # separate terminal from `mdk run`
242
+ ```
243
+
244
+ Mock ports are also resolved at boot. The configured port is used whenever it is
245
+ free; when something else already holds it (another stack on the machine, a
246
+ Worker left running) the mock moves to the next free port and says so, since the
247
+ port is a private contract between the simulator and the plugin that dials it.
248
+ Workers step over ports their not-yet-booted neighbours declared, so one squatter
249
+ relocates one Worker rather than shifting all of them. The Gateway port is never
250
+ relocated — it is a published endpoint, so a conflict there is a fast, explicit
251
+ failure before anything boots.
252
+
253
+ Every `create` command installs dependencies by default (`--no-install` to skip):
254
+ a Worker or app installs from the project root, so npm links it through its `file:`
255
+ dependency, while the dashboard installs in place. If the project has no root
256
+ [`package.json`](./package.json) yet, `create worker` and `create app` write one.
257
+
258
+ ```bash
259
+ node "$CLI" run --dir /tmp/mdk-try # everything together: kernel + workers + apps + gateway
260
+ node "$CLI" run worker demo-miner --dir /tmp/mdk-try # just the worker (+ its mocks)
261
+ ```
262
+
263
+ ### Run the MCP server
264
+
265
+ `mdk run mcp` boots a standalone [`@tetherto/mdk-mcp`](../../backend/core/mcp/README.md)
266
+ server — a separate process from the Gateway, which has no MCP code of its own.
267
+ It requires `mdk.yaml` exactly like every other `run` target, and reads the same
268
+ `spec.apps` the Gateway serves: each declared app's
269
+ `mdk-plugin.json` contract is converted into MCP tools with no separate tool
270
+ authoring. Like the Gateway, it runs no app code: each tool call is forwarded to
271
+ the app's runtime, so an app's tools answer once `mdk run` has started it
272
+ (until then a call returns `ERR_APP_RUNTIME_UNAVAILABLE`). Like `dashboard`,
273
+ `mcp` is never part of the default `all` target — it's always started
274
+ explicitly, and needs the Kernel already reachable (an in-process handle isn't
275
+ available to a standalone `run mcp`, so start the Kernel first: `mdk run kernel`).
276
+
277
+ ```bash
278
+ node "$CLI" run --dir /tmp/mdk-try # first, in its own terminal
279
+ node "$CLI" run mcp --dir /tmp/mdk-try # then, in another terminal
280
+ ```
281
+
282
+ The server listens on `spec.mcp.port` if set in `mdk.yaml`, else
283
+ `spec.gateway.port + 100`:
284
+
285
+ ```yaml
286
+ spec:
287
+ gateway:
288
+ port: 3000
289
+ mcp:
290
+ port: 3100 # optional — defaults to gateway.port + 100
291
+ allowWrite: false # optional — same as `mdk run mcp --allow-write`
292
+ allowTools: [demo.summary] # optional — expose only these tool ids (write tools included)
293
+ token: ${MDK_MCP_TOKEN} # optional — require `Authorization: Bearer <token>` on /mcp; `mdk run mcp` fails if the variable is unset
294
+ identity: { user: agent } # optional — needed to expose `auth: true` Gateway routes
295
+ ```
296
+
297
+ Tools that are not declared read-only (`"safety": "read-only"` on the route, as the
298
+ `mdk create app` template does for its summary route) stay hidden unless you pass `--allow-write`
299
+ (or set `spec.mcp.allowWrite`) or name them in `allowTools`. The CLI owns
300
+ `Ctrl+C`/`SIGTERM` and closes the server itself.
301
+
302
+
303
+ ### Check the stack
304
+
305
+ `mdk status` is a one-shot report — it never starts, stops, or repairs
306
+ anything. Its one write is the CLI's own caller key, published to
307
+ `.mdk/trusted-keys/cli.pub` so the fail-closed Kernel answers its probe. It covers
308
+ the **environment** (Node 24 or newer, package manager, `mdk.yaml` validity, and
309
+ whether every declared Worker and app package actually resolves) and the **stack**
310
+ (Kernel, Gateway, each Worker with its state, health, and device count, and each app).
311
+
312
+ ```bash
313
+ node "$CLI" status --dir /tmp/mdk-try
314
+ node "$CLI" status --dir /tmp/mdk-try -o json | jq .health
315
+ ```
316
+
317
+ Liveness is probed, never inferred from files: `.mdk/kernel.key` and
318
+ `.mdk/keys/*.key` survive a shutdown by design (the keys are stable across
319
+ restarts), so the Kernel is probed over HRPC with that key and the Gateway over
320
+ HTTP on its configured port (any answer below `500` counts). An app runtime accepts
321
+ calls from the Gateway's key only, so an app counts as running while a live
322
+ process holds its run lock.
323
+
324
+ Exit codes make it scriptable (`mdk status && deploy…`):
325
+
326
+ | Exit code | Fires when | Fix |
327
+ | --------- | ---------------------------------------------------------------------- | ----------------------------------- |
328
+ | `0` | Environment checks pass and every component reports `healthy` | None |
329
+ | `1` | `collectStatus` throws while gathering the report, an unexpected error | Check the printed error message and the project directory (`--dir`) |
330
+ | `2` | `--output` names a format outside `table`/`json`/`yaml` | Pass one of `table`, `json`, `yaml` |
331
+ | `4` | An environment precondition fails: old Node, a missing or invalid `mdk.yaml`, or a declared package that doesn't resolve | Fix the precondition the report names |
332
+ | `5` | The stack isn't fully up: Kernel or Gateway unreachable, a declared Worker not registered/serving, or a declared app not running | Check the component the report marks unhealthy |
333
+
334
+ Notes:
335
+
336
+ - `skill add` resolves `@tetherto/mdk-skill` through node/npm (no path walking),
337
+ assembling on demand inside the monorepo and copying the bundled skills once
338
+ published
339
+ - `create dashboard` (and the onboarding UI step) scaffold the MDK UI shell from
340
+ [`examples/mdk-ui-shell-template`](../../examples/mdk-ui-shell-template/README.md) (a real, runnable
341
+ Vite app that doubles as the template). **Inside the monorepo** it copies that template locally (no network)
342
+ and rewrites the template's `file:` MDK deps to absolute links into the
343
+ monorepo's `ui/packages/*`. **Standalone**, it downloads the same subtree from
344
+ GitHub (`tetherto/mdk .../examples/mdk-ui-shell-template`) via `degit`, which
345
+ needs network access, and pins MDK deps to a published range. Either way it
346
+ lands in `apps/dashboard`, takes the package + `APP_NAME` from
347
+ `<stack>-dashboard`, and copies
348
+ `.env.example` → `.env` with `VITE_GATEWAY_URL` pointed at this stack's gateway
349
+ port. Passing a name puts the app in `apps/<name>` instead.
350
+
351
+ | Flag | Status | Type | Default | Description |
352
+ | -------------- | -------- | -------- | -------- | ----------------------------------------------- |
353
+ | `--ref` | Optional | `string` | `v<cli version>` | Branch/tag/SHA to scaffold from; ignored in monorepo (local template always wins), applies only to the standalone GitHub path |
354
+ | `--force` | Optional | `boolean` | `false` | Overwrite the target directory; recursively deletes it first, no confirmation prompt |
355
+ | `--no-install` | Optional | `boolean` | `false` | Skip the post-scaffold dependency install |
356
+
357
+ - The scaffolded dashboard is a normal Vite app: `npm run build` inside it
358
+ produces a `dist/` you serve however you serve static assets. `create dashboard`
359
+ only sets `VITE_GATEWAY_URL`/`VITE_OAUTH_BASE_URL` for local dev against this
360
+ stack — update `apps/dashboard/.env` before building for any other target.
361
+ - Reset a scratch run with `rm -rf /tmp/mdk-try`
362
+
363
+ Or link it as a real `mdk` command (optional; best for testing):
364
+
365
+ ```bash
366
+ cd packages/cli
367
+ npm run build && npm link # exposes a global `mdk`
368
+
369
+ mkdir mdk-try && cd mdk-try
370
+ mdk --help
371
+ mdk skill add --client cursor
372
+ mdk create dashboard
373
+ mdk onboard
374
+
375
+ # when done: npm rm -g @tetherto/mdk-cli
376
+ ```
377
+
378
+ ## Adding and validating apps and workers
379
+
380
+ ```bash
381
+ # From npm (a scope, a version range and, for packages that ship several apps, a subpath all work)
382
+ mdk add app @your-org/mdk-app-ops-center@^1
383
+ mdk add worker @your-org/mdk-worker-rig
384
+ # From GitHub: github:owner/repo[#ref], owner/repo or a github.com URL
385
+ mdk add app github:me/my-app#v0.2.0 --name ops
386
+
387
+ mdk validate app ops # the app mdk.yaml lists as `ops` under spec.apps
388
+ mdk validate worker rig --json
389
+ ```
390
+
391
+ `mdk add` runs `npm install --ignore-scripts --save-exact <source>` in the project (so npm records the
392
+ exact version in `package.json`, and no install script of an unchecked package runs), checks the installed
393
+ package with the same rules as `mdk validate`, and only then adds it under `spec.apps` / `spec.workers` in
394
+ `mdk.yaml`, keeping the file's comments and formatting. A refused package leaves `mdk.yaml` untouched and
395
+ puts `package.json` and the lockfile back, so it neither stays installed nor replaces a version an app
396
+ already runs. Adding the same package again updates it under the name it already has; giving it another
397
+ `--name` adds it as a second app. A
398
+ GitHub source has to be installed (no `--no-install`), because its package name is only known after
399
+ npm has fetched it. A worker without a bundled mock is added without `mock: true`; fill in its
400
+ `config.devices[].opts`.
401
+
402
+ `mdk validate app <name>` and `mdk validate worker <name>` take the name the component has in `mdk.yaml` (as
403
+ `mdk run app <name>` does), from the project in `--dir` (default `.`), and check the package that entry resolves to.
404
+ `mdk validate app` checks `mdk-app.yaml`, its version against `package.json`, the permission rules and the
405
+ route table (`plugin/mdk-plugin.json`, else `mdk-plugin.json` at the package root), by the same rules the app
406
+ runtime and the Gateway apply at boot: a reverse-DNS `id`, a semver `version`, and handlers that resolve inside
407
+ the plugin directory, paths that start with `/` and appear once per method, and handlers (and `main`) that compile.
408
+ Unknown `mdk-app.yaml` keys are reported, so a typo does not pass silently. `mdk validate worker` checks `package.json`
409
+ and the `mdk-contract.json` Worker Plugin contract: at least one telemetry entry or command, command `params` as lists,
410
+ and handlers resolved on disk and compiling. Nothing is run, so a module that throws when loaded only shows when the
411
+ component starts. Both exit 1 on any error. The UI is not validated yet.
412
+
413
+ `mdk validate worker --path <dir>` checks a package directory that no `mdk.yaml` declares, such as a partner's own
414
+ repo in CI. `--smoke` adds behavioral checks against the package's bundled mock, which `setup()` in its
415
+ `smoke.config.js` starts. While the mock runs, every telemetry channel must return its declared type and every sample
416
+ command in `commands` must succeed. After `teardown()`, every channel and command must fail with an `ERR_*` error code
417
+ within `--timeout`, except a `health` channel reporting a status other than `OK`, and the channels `offlineTelemetry`
418
+ lists because the Worker serves them from its own store. The [Worker testing guide](../../docs/guides/workers/test-a-worker.md#check-the-package)
419
+ lists every `smoke.config.js` field.
420
+
421
+ ## Command surface
422
+
423
+ | Group | Commands |
424
+ | ---------------- | ------------------------------------------------------------------------------------- |
425
+ | Onboarding | `mdk onboard` |
426
+ | Scaffold | `mdk create worker <name>`, `mdk create app <name>`, `mdk create dashboard [name]` |
427
+ | Add | `mdk add app <source>`, `mdk add worker <source>` |
428
+ | Validate | `mdk validate app <name>`, `mdk validate worker <name>` |
429
+ | Run & manage | `mdk run [target] [name]` (target: `all`\|`kernel`\|`gateway`\|`mcp`\|`worker <name>`\|`app <name>`\|`dashboard`), `mdk status` |
430
+ | Agent enablement | `mdk skill add` |
431
+ | Meta | `mdk version` |
432
+
433
+ Global flags:
434
+
435
+ | Flag | Status | Type | Default | Description |
436
+ | -------------------- | -------- | --------- | ------- | -------------------------------------------------- |
437
+ | `-o, --output <fmt>` | Optional | `string` | `table` | Output format for data: `table`, `json`, or `yaml` |
438
+ | `-v, --verbose` | Optional | `boolean` | `false` | Increase log detail |
439
+ | `--debug` | Optional | `boolean` | `false` | Print stack traces for unexpected failures |
440
+ | `--version` | Optional | `boolean` | `false` | Print the CLI version and exit |
441
+ | `-h, --help` | Optional | `boolean` | `false` | Print usage help and exit |
442
+
443
+ ## Next steps
444
+
445
+ - See [`instruction.md`](instruction.md) for the full guide (conventions, output
446
+ rules, and definition of done). Read it before contributing, especially if you
447
+ are an AI coding agent.