@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.
- package/LICENSE +202 -0
- package/README.md +447 -0
- package/dist/commands/add.js +61 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/agent.js +42 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/create.js +261 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/inspect.js +171 -0
- package/dist/commands/inspect.js.map +1 -0
- package/dist/commands/meta.js +12 -0
- package/dist/commands/meta.js.map +1 -0
- package/dist/commands/onboard.js +447 -0
- package/dist/commands/onboard.js.map +1 -0
- package/dist/commands/run.js +203 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/validate.js +125 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/add-package.js +148 -0
- package/dist/lib/add-package.js.map +1 -0
- package/dist/lib/app-package.js +182 -0
- package/dist/lib/app-package.js.map +1 -0
- package/dist/lib/app-scaffold.js +221 -0
- package/dist/lib/app-scaffold.js.map +1 -0
- package/dist/lib/app-ui-check.js +178 -0
- package/dist/lib/app-ui-check.js.map +1 -0
- package/dist/lib/app-ui-runner.js +122 -0
- package/dist/lib/app-ui-runner.js.map +1 -0
- package/dist/lib/catalog.js +69 -0
- package/dist/lib/catalog.js.map +1 -0
- package/dist/lib/checkout.js +14 -0
- package/dist/lib/checkout.js.map +1 -0
- package/dist/lib/dashboard-apps.js +71 -0
- package/dist/lib/dashboard-apps.js.map +1 -0
- package/dist/lib/dashboard.js +318 -0
- package/dist/lib/dashboard.js.map +1 -0
- package/dist/lib/detect.js +38 -0
- package/dist/lib/detect.js.map +1 -0
- package/dist/lib/env-file.js +57 -0
- package/dist/lib/env-file.js.map +1 -0
- package/dist/lib/fetch-template.js +33 -0
- package/dist/lib/fetch-template.js.map +1 -0
- package/dist/lib/help.js +22 -0
- package/dist/lib/help.js.map +1 -0
- package/dist/lib/names.js +36 -0
- package/dist/lib/names.js.map +1 -0
- package/dist/lib/npm.js +113 -0
- package/dist/lib/npm.js.map +1 -0
- package/dist/lib/package-source.js +76 -0
- package/dist/lib/package-source.js.map +1 -0
- package/dist/lib/pkg.js +5 -0
- package/dist/lib/pkg.js.map +1 -0
- package/dist/lib/plugin-setup.js +204 -0
- package/dist/lib/plugin-setup.js.map +1 -0
- package/dist/lib/ports.js +31 -0
- package/dist/lib/ports.js.map +1 -0
- package/dist/lib/project.js +297 -0
- package/dist/lib/project.js.map +1 -0
- package/dist/lib/run-lock.js +188 -0
- package/dist/lib/run-lock.js.map +1 -0
- package/dist/lib/runtime.js +683 -0
- package/dist/lib/runtime.js.map +1 -0
- package/dist/lib/scaffold.js +82 -0
- package/dist/lib/scaffold.js.map +1 -0
- package/dist/lib/shutdown.js +53 -0
- package/dist/lib/shutdown.js.map +1 -0
- package/dist/lib/skill.js +38 -0
- package/dist/lib/skill.js.map +1 -0
- package/dist/lib/spec.js +375 -0
- package/dist/lib/spec.js.map +1 -0
- package/dist/lib/status.js +245 -0
- package/dist/lib/status.js.map +1 -0
- package/dist/lib/theme.js +50 -0
- package/dist/lib/theme.js.map +1 -0
- package/dist/lib/worker-package.js +74 -0
- package/dist/lib/worker-package.js.map +1 -0
- package/dist/lib/worker-scaffold.js +122 -0
- package/dist/lib/worker-scaffold.js.map +1 -0
- package/dist/lib/worker-smoke.js +243 -0
- package/dist/lib/worker-smoke.js.map +1 -0
- package/dist/program.js +58 -0
- package/dist/program.js.map +1 -0
- package/package.json +59 -1
- package/templates/app/README.md +66 -0
- package/templates/app/mdk-app.yaml +10 -0
- package/templates/app/package.json +17 -0
- package/templates/app/plugin/controllers/summary.js +36 -0
- package/templates/app/plugin/lib/client.js +11 -0
- package/templates/app/plugin/mdk-plugin.json +16 -0
- package/templates/app-ui/tsconfig.json +29 -0
- package/templates/app-ui/ui-src/components/summary-panel.tsx +24 -0
- package/templates/app-ui/ui-src/data/summary.ts +18 -0
- package/templates/app-ui/ui-src/hooks/use-summary.ts +54 -0
- package/templates/app-ui/ui-src/index.tsx +15 -0
- package/templates/app-ui/ui-src/pages/overview-page.tsx +18 -0
- package/templates/app-ui/ui-src/styles.css +13 -0
- package/templates/app-ui/ui-src/styles.d.ts +3 -0
- package/templates/app-ui/ui-src/vite.config.ts +26 -0
- package/templates/worker/README.md +93 -0
- package/templates/worker/mdk-contract.json +26 -0
- package/templates/worker/mock/server.js +93 -0
- package/templates/worker/package.json +23 -0
- package/templates/worker/smoke.config.js +31 -0
- package/templates/worker/src/client.js +47 -0
- package/templates/worker/src/commands/reboot.js +5 -0
- package/templates/worker/src/commands/set-power-mode.js +10 -0
- package/templates/worker/src/telemetry/hashrate-rt.js +5 -0
- package/templates/worker/src/telemetry/power-mode.js +5 -0
- package/templates/worker/src/telemetry/power.js +5 -0
- package/templates/worker/src/telemetry/temperature.js +5 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const TYPE_OK = {
|
|
6
|
+
number: (v) => typeof v === 'number' && Number.isFinite(v),
|
|
7
|
+
string: (v) => typeof v === 'string',
|
|
8
|
+
boolean: (v) => typeof v === 'boolean',
|
|
9
|
+
array: (v) => Array.isArray(v),
|
|
10
|
+
object: (v) => typeof v === 'object' && v !== null && !Array.isArray(v),
|
|
11
|
+
};
|
|
12
|
+
function withDeadline(run, ms, label) {
|
|
13
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
14
|
+
// Deliberately not unref'd: against a hung handler this timer is the only
|
|
15
|
+
// handle on the event loop — unref it and node exits 0 before it fires.
|
|
16
|
+
const timer = setTimeout(() => rejectPromise(new Error(`ERR_SMOKE_TIMEOUT: ${label} did not settle within ${ms}ms`)), ms);
|
|
17
|
+
Promise.resolve()
|
|
18
|
+
.then(run)
|
|
19
|
+
.then(resolvePromise, rejectPromise)
|
|
20
|
+
.finally(() => clearTimeout(timer));
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/** A value the runtime's param gate accepts for `p`, for params a probe is not about. */
|
|
24
|
+
function gateValue(p) {
|
|
25
|
+
if (Array.isArray(p.enum) && p.enum.length)
|
|
26
|
+
return p.enum[0];
|
|
27
|
+
switch (p.type) {
|
|
28
|
+
case 'number':
|
|
29
|
+
return typeof p.min === 'number' ? p.min : typeof p.max === 'number' ? Math.min(p.max, 0) : 0;
|
|
30
|
+
case 'boolean':
|
|
31
|
+
return false;
|
|
32
|
+
case 'string':
|
|
33
|
+
return '';
|
|
34
|
+
default:
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A param set for `cmd` that passes its gate: the smoke config's sample when
|
|
40
|
+
* it has one, every other required param filled with a valid value. The gate
|
|
41
|
+
* checks params in declaration order and `required` before range, so a probe
|
|
42
|
+
* that sends only the param under test fails on the first missing required
|
|
43
|
+
* param instead of the bound it means to test. Optional params stay absent —
|
|
44
|
+
* the gate skips them, and a filler value ('' / {} / false) could make the
|
|
45
|
+
* handler reject for its own reasons, passing the self-rejection check for
|
|
46
|
+
* the wrong one.
|
|
47
|
+
*/
|
|
48
|
+
function baselineParams(cmd, sample) {
|
|
49
|
+
const filled = Object.fromEntries((cmd.params ?? []).filter((p) => p.required === true).map((p) => [p.name, gateValue(p)]));
|
|
50
|
+
return { ...filled, ...(sample ?? {}) };
|
|
51
|
+
}
|
|
52
|
+
async function resolveSpec(workerDir, config) {
|
|
53
|
+
const requireFromWorker = createRequire(join(workerDir, 'noop.js'));
|
|
54
|
+
if (config) {
|
|
55
|
+
if (config.trim().startsWith('{'))
|
|
56
|
+
return { config: JSON.parse(config) };
|
|
57
|
+
const file = isAbsolute(config) ? config : resolve(config);
|
|
58
|
+
if (file.endsWith('.json'))
|
|
59
|
+
return { config: JSON.parse(readFileSync(file, 'utf8')) };
|
|
60
|
+
const mod = requireFromWorker(file);
|
|
61
|
+
return mod.setup ? mod.setup() : { config: mod };
|
|
62
|
+
}
|
|
63
|
+
const smokeConfig = join(workerDir, 'smoke.config.js');
|
|
64
|
+
if (!existsSync(smokeConfig)) {
|
|
65
|
+
throw new Error(`ERR_SMOKE_NO_CONFIG: no --smoke-config given and no ${smokeConfig}`);
|
|
66
|
+
}
|
|
67
|
+
return requireFromWorker(smokeConfig).setup();
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Behavioural checks against the worker's own mock device — no Kernel, no DHT.
|
|
71
|
+
* Live pass: every declared telemetry handler returns its declared type, the
|
|
72
|
+
* runtime's param gate rejects out-of-range values, every smoke.config sample
|
|
73
|
+
* command executes, and a handler called with an out-of-range param rejects
|
|
74
|
+
* with an ERR_* of its own (the worker is reachable on its own RPC key, so it
|
|
75
|
+
* must not rely on the Kernel's gate). Stopped pass, after `teardown()`: every
|
|
76
|
+
* handler rejects with an ERR_* within the deadline — no hang, no crash, no
|
|
77
|
+
* stale value. Two exceptions: a `health` channel may instead report a non-OK
|
|
78
|
+
* status (its job is to say the device is down), and the smoke config's
|
|
79
|
+
* `offlineTelemetry` channels must return their declared type. Every handler
|
|
80
|
+
* call is bounded by `timeoutMs`.
|
|
81
|
+
*/
|
|
82
|
+
export async function runWorkerSmoke(workerDir, opts = {}) {
|
|
83
|
+
const timeoutMs = opts.timeoutMs ?? 10_000;
|
|
84
|
+
const outcome = { passed: 0, failures: [], warnings: [] };
|
|
85
|
+
const check = (ok, label, detail) => {
|
|
86
|
+
if (ok)
|
|
87
|
+
outcome.passed++;
|
|
88
|
+
else
|
|
89
|
+
outcome.failures.push(`smoke: ${label}${detail ? ` — ${detail}` : ''}`);
|
|
90
|
+
};
|
|
91
|
+
const runtime = require('@tetherto/mdk-worker');
|
|
92
|
+
const pluginDir = existsSync(join(workerDir, 'plugin', 'mdk-contract.json')) ? join(workerDir, 'plugin') : workerDir;
|
|
93
|
+
const { contract, entries, dir } = runtime.loadContract(pluginDir);
|
|
94
|
+
const spec = await resolveSpec(workerDir, opts.config);
|
|
95
|
+
// setup() has started the mock: stop it however the checks end, or its open
|
|
96
|
+
// socket keeps the process alive after a handler fails to load.
|
|
97
|
+
let stopped = false;
|
|
98
|
+
const stop = async () => {
|
|
99
|
+
if (stopped || !spec.teardown)
|
|
100
|
+
return;
|
|
101
|
+
stopped = true;
|
|
102
|
+
await withDeadline(() => spec.teardown(), timeoutMs, 'teardown()');
|
|
103
|
+
};
|
|
104
|
+
try {
|
|
105
|
+
const instance = runtime.createInstance({
|
|
106
|
+
dir,
|
|
107
|
+
entries,
|
|
108
|
+
device: {
|
|
109
|
+
id: 'smoke-0',
|
|
110
|
+
opts: Object.freeze({ ...(spec.config ?? {}) }),
|
|
111
|
+
env: Object.freeze({ ...(spec.env ?? {}) }),
|
|
112
|
+
config: Object.freeze({ ...(spec.pluginConfig ?? {}) }),
|
|
113
|
+
workerId: 'smoke',
|
|
114
|
+
logger: () => { },
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
const telemetry = contract.capabilities.telemetry ?? [];
|
|
118
|
+
const commands = contract.capabilities.commands ?? [];
|
|
119
|
+
for (const entry of telemetry) {
|
|
120
|
+
const label = `telemetry '${entry.name}' returns a ${entry.type}`;
|
|
121
|
+
const typeOk = TYPE_OK[entry.type ?? ''] ?? ((v) => v !== undefined);
|
|
122
|
+
try {
|
|
123
|
+
const value = await withDeadline(() => instance.telemetry.get(entry.name)({}), timeoutMs, label);
|
|
124
|
+
check(value !== undefined && typeOk(value), label, value === undefined ? 'returned undefined' : `returned ${JSON.stringify(value)}`);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
check(false, label, message(error));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
for (const cmd of commands) {
|
|
131
|
+
const base = baselineParams(cmd, spec.commands?.[cmd.name]);
|
|
132
|
+
for (const p of cmd.params ?? []) {
|
|
133
|
+
if (p.type !== 'number' || typeof p.min !== 'number' || typeof p.max !== 'number')
|
|
134
|
+
continue;
|
|
135
|
+
const below = runtime.validateParams(cmd.params, { ...base, [p.name]: p.min - 1 });
|
|
136
|
+
check(below !== null && below.startsWith('ERR_PARAM_RANGE'), `param gate rejects '${cmd.name}' ${p.name}=${p.min - 1}`, below ?? 'accepted');
|
|
137
|
+
const above = runtime.validateParams(cmd.params, { ...base, [p.name]: p.max + 1 });
|
|
138
|
+
check(above !== null && above.startsWith('ERR_PARAM_RANGE'), `param gate rejects '${cmd.name}' ${p.name}=${p.max + 1}`, above ?? 'accepted');
|
|
139
|
+
const atMin = runtime.validateParams(cmd.params, { ...base, [p.name]: p.min });
|
|
140
|
+
const atMax = runtime.validateParams(cmd.params, { ...base, [p.name]: p.max });
|
|
141
|
+
check(atMin === null && atMax === null, `param gate accepts '${cmd.name}' ${p.name} at [${p.min}, ${p.max}]`, atMin ?? atMax ?? undefined);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
for (const [name, params] of Object.entries(spec.commands ?? {})) {
|
|
145
|
+
const label = `command '${name}' executes with sample params`;
|
|
146
|
+
const cmd = commands.find((c) => c.name === name);
|
|
147
|
+
const handler = instance.commands.get(name);
|
|
148
|
+
if (!cmd || !handler) {
|
|
149
|
+
check(false, label, 'not declared in contract');
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const gate = runtime.validateParams(cmd.params, params);
|
|
153
|
+
if (gate !== null) {
|
|
154
|
+
check(false, label, gate);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
await withDeadline(() => handler(params), timeoutMs, label);
|
|
159
|
+
check(true, label);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
check(false, label, message(error));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// The worker is reachable on its own RPC key, so each handler must reject
|
|
166
|
+
// bad input itself, not lean on the Kernel's gate.
|
|
167
|
+
for (const cmd of commands) {
|
|
168
|
+
const p = (cmd.params ?? []).find((q) => q.type === 'number' && typeof q.min === 'number');
|
|
169
|
+
if (!p)
|
|
170
|
+
continue;
|
|
171
|
+
const label = `command '${cmd.name}' itself rejects ${p.name}=${p.min - 1} with an ERR_*`;
|
|
172
|
+
// Otherwise valid params, so the handler rejects the bound, not a missing param.
|
|
173
|
+
const params = { ...baselineParams(cmd, spec.commands?.[cmd.name]), [p.name]: p.min - 1 };
|
|
174
|
+
await expectErr(() => instance.commands.get(cmd.name)(params), timeoutMs, label, check);
|
|
175
|
+
}
|
|
176
|
+
if (!spec.teardown) {
|
|
177
|
+
outcome.warnings.push('smoke: no teardown() in the smoke config — device-stopped behaviour not checked');
|
|
178
|
+
return outcome;
|
|
179
|
+
}
|
|
180
|
+
await stop();
|
|
181
|
+
const offline = new Set(spec.offlineTelemetry ?? []);
|
|
182
|
+
for (const name of offline) {
|
|
183
|
+
if (!telemetry.some((entry) => entry.name === name))
|
|
184
|
+
check(false, `offlineTelemetry '${name}' is a declared telemetry channel`);
|
|
185
|
+
}
|
|
186
|
+
for (const entry of telemetry) {
|
|
187
|
+
const read = () => instance.telemetry.get(entry.name)({});
|
|
188
|
+
if (offline.has(entry.name)) {
|
|
189
|
+
const label = `telemetry '${entry.name}' still returns a ${entry.type} from the worker's store when the device is stopped`;
|
|
190
|
+
const typeOk = TYPE_OK[entry.type ?? ''] ?? ((v) => v !== undefined);
|
|
191
|
+
try {
|
|
192
|
+
const value = await withDeadline(read, timeoutMs, label);
|
|
193
|
+
check(value !== undefined && typeOk(value), label, `returned ${JSON.stringify(value)}`);
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
check(false, label, message(error));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else if (entry.name === 'health') {
|
|
200
|
+
const label = `telemetry 'health' reports a non-OK status or fails with an ERR_* when the device is stopped`;
|
|
201
|
+
await expectErr(read, timeoutMs, label, check, (value) => isNonOkHealth(value));
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
const label = `telemetry '${entry.name}' fails with an ERR_* when the device is stopped`;
|
|
205
|
+
await expectErr(read, timeoutMs, label, check);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
for (const [name, params] of Object.entries(spec.commands ?? {})) {
|
|
209
|
+
const handler = instance.commands.get(name);
|
|
210
|
+
if (!handler)
|
|
211
|
+
continue;
|
|
212
|
+
const label = `command '${name}' fails with an ERR_* when the device is stopped`;
|
|
213
|
+
await expectErr(() => handler(params), timeoutMs, label, check);
|
|
214
|
+
}
|
|
215
|
+
return outcome;
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
await stop().catch(() => { });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async function expectErr(run, timeoutMs, label, check,
|
|
222
|
+
/** A resolved value that also counts as failing correctly. */
|
|
223
|
+
acceptResolved) {
|
|
224
|
+
try {
|
|
225
|
+
const value = await withDeadline(run, timeoutMs, label);
|
|
226
|
+
check(acceptResolved?.(value) ?? false, label, `resolved with ${JSON.stringify(value)}`);
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
const text = message(error);
|
|
230
|
+
check(/(^|: )ERR_[A-Z_]+/.test(text) && !text.startsWith('ERR_SMOKE_TIMEOUT'), label, text);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/** A health verdict that says the device is not fine: `{ status }` with any value but OK. */
|
|
234
|
+
function isNonOkHealth(value) {
|
|
235
|
+
if (typeof value !== 'object' || value === null)
|
|
236
|
+
return false;
|
|
237
|
+
const { status } = value;
|
|
238
|
+
return typeof status === 'string' && status !== '' && status !== 'OK';
|
|
239
|
+
}
|
|
240
|
+
function message(error) {
|
|
241
|
+
return error instanceof Error ? error.message : String(error);
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=worker-smoke.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-smoke.js","sourceRoot":"","sources":["../../src/lib/worker-smoke.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { isAbsolute, join, resolve } from 'node:path';\n\nconst require = createRequire(import.meta.url);\n\nconst TYPE_OK: Record<string, (v: unknown) => boolean> = {\n number: (v) => typeof v === 'number' && Number.isFinite(v),\n string: (v) => typeof v === 'string',\n boolean: (v) => typeof v === 'boolean',\n array: (v) => Array.isArray(v),\n object: (v) => typeof v === 'object' && v !== null && !Array.isArray(v),\n};\n\ninterface ContractParam {\n name: string;\n type?: string;\n required?: boolean;\n enum?: unknown[];\n min?: number;\n max?: number;\n}\n\ninterface ContractEntry {\n name: string;\n type?: string;\n params?: ContractParam[];\n}\n\ninterface MdkWorkerRuntime {\n loadContract(dir: string): {\n dir: string;\n contract: {\n metadata: { brand?: string; deviceFamily?: string };\n capabilities: { telemetry?: ContractEntry[]; commands?: ContractEntry[] };\n };\n entries: { telemetry: Map<string, string>; commands: Map<string, string> };\n };\n createInstance(opts: {\n dir: string;\n entries: { telemetry: Map<string, string>; commands: Map<string, string> };\n device: Record<string, unknown>;\n }): { telemetry: Map<string, (params: object) => unknown>; commands: Map<string, (params: object) => unknown> };\n validateParams(declared: ContractParam[] | undefined, params: object): string | null;\n}\n\n/** What smoke.config.js `setup()` resolves to: the mock device and how to reach it. */\ninterface SmokeSpec {\n config?: Record<string, unknown>;\n env?: Record<string, unknown>;\n pluginConfig?: Record<string, unknown>;\n /** Sample params per command name, executed against the mock. */\n commands?: Record<string, object>;\n /** Stops the mock device; its presence enables the device-stopped failure pass. */\n teardown?: () => unknown;\n /**\n * Telemetry served from the worker's own store rather than the device (a\n * `history` log, say), which keeps answering once it stops. These must still\n * settle and return their declared type instead of failing with an ERR_*.\n */\n offlineTelemetry?: string[];\n}\n\nexport interface SmokeOutcome {\n /** Checks that passed. */\n passed: number;\n /** One line per failed check. */\n failures: string[];\n /** Passes that could not run (no teardown to stop the mock with). */\n warnings: string[];\n}\n\nfunction withDeadline<T>(run: () => T | Promise<T>, ms: number, label: string): Promise<T> {\n return new Promise<T>((resolvePromise, rejectPromise) => {\n // Deliberately not unref'd: against a hung handler this timer is the only\n // handle on the event loop — unref it and node exits 0 before it fires.\n const timer = setTimeout(() => rejectPromise(new Error(`ERR_SMOKE_TIMEOUT: ${label} did not settle within ${ms}ms`)), ms);\n Promise.resolve()\n .then(run)\n .then(resolvePromise, rejectPromise)\n .finally(() => clearTimeout(timer));\n });\n}\n\n/** A value the runtime's param gate accepts for `p`, for params a probe is not about. */\nfunction gateValue(p: ContractParam): unknown {\n if (Array.isArray(p.enum) && p.enum.length) return p.enum[0];\n switch (p.type) {\n case 'number':\n return typeof p.min === 'number' ? p.min : typeof p.max === 'number' ? Math.min(p.max, 0) : 0;\n case 'boolean':\n return false;\n case 'string':\n return '';\n default:\n return {};\n }\n}\n\n/**\n * A param set for `cmd` that passes its gate: the smoke config's sample when\n * it has one, every other required param filled with a valid value. The gate\n * checks params in declaration order and `required` before range, so a probe\n * that sends only the param under test fails on the first missing required\n * param instead of the bound it means to test. Optional params stay absent —\n * the gate skips them, and a filler value ('' / {} / false) could make the\n * handler reject for its own reasons, passing the self-rejection check for\n * the wrong one.\n */\nfunction baselineParams(cmd: ContractEntry, sample: object | undefined): Record<string, unknown> {\n const filled = Object.fromEntries((cmd.params ?? []).filter((p) => p.required === true).map((p) => [p.name, gateValue(p)]));\n return { ...filled, ...(sample ?? {}) };\n}\n\nasync function resolveSpec(workerDir: string, config: string | undefined): Promise<SmokeSpec> {\n const requireFromWorker = createRequire(join(workerDir, 'noop.js'));\n if (config) {\n if (config.trim().startsWith('{')) return { config: JSON.parse(config) };\n const file = isAbsolute(config) ? config : resolve(config);\n if (file.endsWith('.json')) return { config: JSON.parse(readFileSync(file, 'utf8')) };\n const mod = requireFromWorker(file);\n return mod.setup ? mod.setup() : { config: mod };\n }\n const smokeConfig = join(workerDir, 'smoke.config.js');\n if (!existsSync(smokeConfig)) {\n throw new Error(`ERR_SMOKE_NO_CONFIG: no --smoke-config given and no ${smokeConfig}`);\n }\n return requireFromWorker(smokeConfig).setup();\n}\n\n/**\n * Behavioural checks against the worker's own mock device — no Kernel, no DHT.\n * Live pass: every declared telemetry handler returns its declared type, the\n * runtime's param gate rejects out-of-range values, every smoke.config sample\n * command executes, and a handler called with an out-of-range param rejects\n * with an ERR_* of its own (the worker is reachable on its own RPC key, so it\n * must not rely on the Kernel's gate). Stopped pass, after `teardown()`: every\n * handler rejects with an ERR_* within the deadline — no hang, no crash, no\n * stale value. Two exceptions: a `health` channel may instead report a non-OK\n * status (its job is to say the device is down), and the smoke config's\n * `offlineTelemetry` channels must return their declared type. Every handler\n * call is bounded by `timeoutMs`.\n */\nexport async function runWorkerSmoke(workerDir: string, opts: { config?: string; timeoutMs?: number } = {}): Promise<SmokeOutcome> {\n const timeoutMs = opts.timeoutMs ?? 10_000;\n const outcome: SmokeOutcome = { passed: 0, failures: [], warnings: [] };\n const check = (ok: boolean, label: string, detail?: string): void => {\n if (ok) outcome.passed++;\n else outcome.failures.push(`smoke: ${label}${detail ? ` — ${detail}` : ''}`);\n };\n\n const runtime = require('@tetherto/mdk-worker') as MdkWorkerRuntime;\n const pluginDir = existsSync(join(workerDir, 'plugin', 'mdk-contract.json')) ? join(workerDir, 'plugin') : workerDir;\n const { contract, entries, dir } = runtime.loadContract(pluginDir);\n const spec = await resolveSpec(workerDir, opts.config);\n\n // setup() has started the mock: stop it however the checks end, or its open\n // socket keeps the process alive after a handler fails to load.\n let stopped = false;\n const stop = async (): Promise<void> => {\n if (stopped || !spec.teardown) return;\n stopped = true;\n await withDeadline(() => spec.teardown!(), timeoutMs, 'teardown()');\n };\n\n try {\n const instance = runtime.createInstance({\n dir,\n entries,\n device: {\n id: 'smoke-0',\n opts: Object.freeze({ ...(spec.config ?? {}) }),\n env: Object.freeze({ ...(spec.env ?? {}) }),\n config: Object.freeze({ ...(spec.pluginConfig ?? {}) }),\n workerId: 'smoke',\n logger: () => {},\n },\n });\n\n const telemetry = contract.capabilities.telemetry ?? [];\n const commands = contract.capabilities.commands ?? [];\n\n for (const entry of telemetry) {\n const label = `telemetry '${entry.name}' returns a ${entry.type}`;\n const typeOk = TYPE_OK[entry.type ?? ''] ?? ((v: unknown) => v !== undefined);\n try {\n const value = await withDeadline(() => instance.telemetry.get(entry.name)!({}), timeoutMs, label);\n check(value !== undefined && typeOk(value), label, value === undefined ? 'returned undefined' : `returned ${JSON.stringify(value)}`);\n } catch (error) {\n check(false, label, message(error));\n }\n }\n\n for (const cmd of commands) {\n const base = baselineParams(cmd, spec.commands?.[cmd.name]);\n for (const p of cmd.params ?? []) {\n if (p.type !== 'number' || typeof p.min !== 'number' || typeof p.max !== 'number') continue;\n const below = runtime.validateParams(cmd.params, { ...base, [p.name]: p.min - 1 });\n check(below !== null && below.startsWith('ERR_PARAM_RANGE'), `param gate rejects '${cmd.name}' ${p.name}=${p.min - 1}`, below ?? 'accepted');\n const above = runtime.validateParams(cmd.params, { ...base, [p.name]: p.max + 1 });\n check(above !== null && above.startsWith('ERR_PARAM_RANGE'), `param gate rejects '${cmd.name}' ${p.name}=${p.max + 1}`, above ?? 'accepted');\n const atMin = runtime.validateParams(cmd.params, { ...base, [p.name]: p.min });\n const atMax = runtime.validateParams(cmd.params, { ...base, [p.name]: p.max });\n check(atMin === null && atMax === null, `param gate accepts '${cmd.name}' ${p.name} at [${p.min}, ${p.max}]`, atMin ?? atMax ?? undefined);\n }\n }\n\n for (const [name, params] of Object.entries(spec.commands ?? {})) {\n const label = `command '${name}' executes with sample params`;\n const cmd = commands.find((c) => c.name === name);\n const handler = instance.commands.get(name);\n if (!cmd || !handler) {\n check(false, label, 'not declared in contract');\n continue;\n }\n const gate = runtime.validateParams(cmd.params, params);\n if (gate !== null) {\n check(false, label, gate);\n continue;\n }\n try {\n await withDeadline(() => handler(params), timeoutMs, label);\n check(true, label);\n } catch (error) {\n check(false, label, message(error));\n }\n }\n\n // The worker is reachable on its own RPC key, so each handler must reject\n // bad input itself, not lean on the Kernel's gate.\n for (const cmd of commands) {\n const p = (cmd.params ?? []).find((q) => q.type === 'number' && typeof q.min === 'number');\n if (!p) continue;\n const label = `command '${cmd.name}' itself rejects ${p.name}=${p.min! - 1} with an ERR_*`;\n // Otherwise valid params, so the handler rejects the bound, not a missing param.\n const params = { ...baselineParams(cmd, spec.commands?.[cmd.name]), [p.name]: p.min! - 1 };\n await expectErr(() => instance.commands.get(cmd.name)!(params), timeoutMs, label, check);\n }\n\n if (!spec.teardown) {\n outcome.warnings.push('smoke: no teardown() in the smoke config — device-stopped behaviour not checked');\n return outcome;\n }\n await stop();\n\n const offline = new Set(spec.offlineTelemetry ?? []);\n for (const name of offline) {\n if (!telemetry.some((entry) => entry.name === name)) check(false, `offlineTelemetry '${name}' is a declared telemetry channel`);\n }\n for (const entry of telemetry) {\n const read = (): unknown => instance.telemetry.get(entry.name)!({});\n if (offline.has(entry.name)) {\n const label = `telemetry '${entry.name}' still returns a ${entry.type} from the worker's store when the device is stopped`;\n const typeOk = TYPE_OK[entry.type ?? ''] ?? ((v: unknown) => v !== undefined);\n try {\n const value = await withDeadline(read, timeoutMs, label);\n check(value !== undefined && typeOk(value), label, `returned ${JSON.stringify(value)}`);\n } catch (error) {\n check(false, label, message(error));\n }\n } else if (entry.name === 'health') {\n const label = `telemetry 'health' reports a non-OK status or fails with an ERR_* when the device is stopped`;\n await expectErr(read, timeoutMs, label, check, (value) => isNonOkHealth(value));\n } else {\n const label = `telemetry '${entry.name}' fails with an ERR_* when the device is stopped`;\n await expectErr(read, timeoutMs, label, check);\n }\n }\n for (const [name, params] of Object.entries(spec.commands ?? {})) {\n const handler = instance.commands.get(name);\n if (!handler) continue;\n const label = `command '${name}' fails with an ERR_* when the device is stopped`;\n await expectErr(() => handler(params), timeoutMs, label, check);\n }\n\n return outcome;\n } finally {\n await stop().catch(() => {});\n }\n}\n\nasync function expectErr(\n run: () => unknown,\n timeoutMs: number,\n label: string,\n check: (ok: boolean, label: string, detail?: string) => void,\n /** A resolved value that also counts as failing correctly. */\n acceptResolved?: (value: unknown) => boolean,\n): Promise<void> {\n try {\n const value = await withDeadline(run, timeoutMs, label);\n check(acceptResolved?.(value) ?? false, label, `resolved with ${JSON.stringify(value)}`);\n } catch (error) {\n const text = message(error);\n check(/(^|: )ERR_[A-Z_]+/.test(text) && !text.startsWith('ERR_SMOKE_TIMEOUT'), label, text);\n }\n}\n\n/** A health verdict that says the device is not fine: `{ status }` with any value but OK. */\nfunction isNonOkHealth(value: unknown): boolean {\n if (typeof value !== 'object' || value === null) return false;\n const { status } = value as { status?: unknown };\n return typeof status === 'string' && status !== '' && status !== 'OK';\n}\n\nfunction message(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"]}
|
package/dist/program.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { pkg } from './lib/pkg.js';
|
|
3
|
+
import { configureHelp, examples } from './lib/help.js';
|
|
4
|
+
import { registerOnboard } from './commands/onboard.js';
|
|
5
|
+
import { registerCreate } from './commands/create.js';
|
|
6
|
+
import { registerAdd } from './commands/add.js';
|
|
7
|
+
import { registerValidate } from './commands/validate.js';
|
|
8
|
+
import { registerRun } from './commands/run.js';
|
|
9
|
+
import { registerStatus } from './commands/inspect.js';
|
|
10
|
+
import { registerSkill } from './commands/agent.js';
|
|
11
|
+
import { registerVersion } from './commands/meta.js';
|
|
12
|
+
/** The help heading each top-level command is listed under. */
|
|
13
|
+
const HELP_GROUPS = {
|
|
14
|
+
onboard: 'Set up:',
|
|
15
|
+
create: 'Build:',
|
|
16
|
+
add: 'Build:',
|
|
17
|
+
validate: 'Build:',
|
|
18
|
+
run: 'Run:',
|
|
19
|
+
status: 'Run:',
|
|
20
|
+
skill: 'Other:',
|
|
21
|
+
version: 'Other:',
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Builds the full `mdk` command tree. Every command reads `mdk <verb> [noun]`.
|
|
25
|
+
*
|
|
26
|
+
* `onboard`, `create`, `add`, `validate`, `run`, `status`, `skill add` and
|
|
27
|
+
* `version` are implemented.
|
|
28
|
+
*/
|
|
29
|
+
export function buildProgram() {
|
|
30
|
+
const program = configureHelp(new Command());
|
|
31
|
+
program
|
|
32
|
+
.name('mdk')
|
|
33
|
+
.usage('<command> [options]')
|
|
34
|
+
.description('Build, run and check an MDK stack: workers, apps, the Kernel and the Gateway.')
|
|
35
|
+
.version(pkg.version, '--version', 'Print the CLI version')
|
|
36
|
+
.option('-o, --output <fmt>', 'Output format for data: table | json | yaml', 'table')
|
|
37
|
+
.option('-v, --verbose', 'Increase log detail', false)
|
|
38
|
+
.option('--debug', 'Print stack traces for unexpected failures', false)
|
|
39
|
+
.showHelpAfterError();
|
|
40
|
+
registerOnboard(program);
|
|
41
|
+
registerCreate(program);
|
|
42
|
+
registerAdd(program);
|
|
43
|
+
registerValidate(program);
|
|
44
|
+
registerRun(program);
|
|
45
|
+
registerStatus(program);
|
|
46
|
+
registerSkill(program);
|
|
47
|
+
registerVersion(program);
|
|
48
|
+
for (const command of program.commands) {
|
|
49
|
+
const group = HELP_GROUPS[command.name()];
|
|
50
|
+
if (group)
|
|
51
|
+
command.helpGroup(group);
|
|
52
|
+
}
|
|
53
|
+
// Its own instance: the one the subcommands inherited stays under their `Commands:`.
|
|
54
|
+
program.addHelpCommand(new Command('help').argument('[command]').description('Show help for a command').helpGroup('Other:'));
|
|
55
|
+
program.addHelpText('after', examples('mdk onboard # new project: writes mdk.yaml', 'mdk create app ops-center # scaffold an app into apps/ops-center', 'mdk add worker @your-org/mdk-worker-rig # install a worker from npm', 'mdk validate app ops-center # check it before publishing', 'mdk run # start the Kernel, workers, apps and Gateway') + '\nRun `mdk <command> --help` for the options of a command.\n');
|
|
56
|
+
return program;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=program.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"program.js","sourceRoot":"","sources":["../src/program.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,+DAA+D;AAC/D,MAAM,WAAW,GAA2B;IAC1C,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,QAAQ;IAChB,GAAG,EAAE,QAAQ;IACb,QAAQ,EAAE,QAAQ;IAClB,GAAG,EAAE,MAAM;IACX,MAAM,EAAE,MAAM;IACd,KAAK,EAAE,QAAQ;IACf,OAAO,EAAE,QAAQ;CAClB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,OAAO,EAAE,CAAC,CAAC;IAE7C,OAAO;SACJ,IAAI,CAAC,KAAK,CAAC;SACX,KAAK,CAAC,qBAAqB,CAAC;SAC5B,WAAW,CAAC,+EAA+E,CAAC;SAC5F,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,uBAAuB,CAAC;SAC1D,MAAM,CAAC,oBAAoB,EAAE,6CAA6C,EAAE,OAAO,CAAC;SACpF,MAAM,CAAC,eAAe,EAAE,qBAAqB,EAAE,KAAK,CAAC;SACrD,MAAM,CAAC,SAAS,EAAE,4CAA4C,EAAE,KAAK,CAAC;SACtE,kBAAkB,EAAE,CAAC;IAExB,eAAe,CAAC,OAAO,CAAC,CAAC;IACzB,cAAc,CAAC,OAAO,CAAC,CAAC;IACxB,WAAW,CAAC,OAAO,CAAC,CAAC;IACrB,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC1B,WAAW,CAAC,OAAO,CAAC,CAAC;IACrB,cAAc,CAAC,OAAO,CAAC,CAAC;IACxB,aAAa,CAAC,OAAO,CAAC,CAAC;IACvB,eAAe,CAAC,OAAO,CAAC,CAAC;IAEzB,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;QAC1C,IAAI,KAAK;YAAE,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC;IACD,qFAAqF;IACrF,OAAO,CAAC,cAAc,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,WAAW,CAAC,yBAAyB,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;IAE7H,OAAO,CAAC,WAAW,CACjB,OAAO,EACP,QAAQ,CACN,yEAAyE,EACzE,iFAAiF,EACjF,0EAA0E,EAC1E,uEAAuE,EACvE,wFAAwF,CACzF,GAAG,8DAA8D,CACnE,CAAC;IAEF,OAAO,OAAO,CAAC;AACjB,CAAC","sourcesContent":["import { Command } from 'commander';\nimport { pkg } from './lib/pkg.js';\nimport { configureHelp, examples } from './lib/help.js';\nimport { registerOnboard } from './commands/onboard.js';\nimport { registerCreate } from './commands/create.js';\nimport { registerAdd } from './commands/add.js';\nimport { registerValidate } from './commands/validate.js';\nimport { registerRun } from './commands/run.js';\nimport { registerStatus } from './commands/inspect.js';\nimport { registerSkill } from './commands/agent.js';\nimport { registerVersion } from './commands/meta.js';\n\n/** The help heading each top-level command is listed under. */\nconst HELP_GROUPS: Record<string, string> = {\n onboard: 'Set up:',\n create: 'Build:',\n add: 'Build:',\n validate: 'Build:',\n run: 'Run:',\n status: 'Run:',\n skill: 'Other:',\n version: 'Other:',\n};\n\n/**\n * Builds the full `mdk` command tree. Every command reads `mdk <verb> [noun]`.\n *\n * `onboard`, `create`, `add`, `validate`, `run`, `status`, `skill add` and\n * `version` are implemented.\n */\nexport function buildProgram(): Command {\n const program = configureHelp(new Command());\n\n program\n .name('mdk')\n .usage('<command> [options]')\n .description('Build, run and check an MDK stack: workers, apps, the Kernel and the Gateway.')\n .version(pkg.version, '--version', 'Print the CLI version')\n .option('-o, --output <fmt>', 'Output format for data: table | json | yaml', 'table')\n .option('-v, --verbose', 'Increase log detail', false)\n .option('--debug', 'Print stack traces for unexpected failures', false)\n .showHelpAfterError();\n\n registerOnboard(program);\n registerCreate(program);\n registerAdd(program);\n registerValidate(program);\n registerRun(program);\n registerStatus(program);\n registerSkill(program);\n registerVersion(program);\n\n for (const command of program.commands) {\n const group = HELP_GROUPS[command.name()];\n if (group) command.helpGroup(group);\n }\n // Its own instance: the one the subcommands inherited stays under their `Commands:`.\n program.addHelpCommand(new Command('help').argument('[command]').description('Show help for a command').helpGroup('Other:'));\n\n program.addHelpText(\n 'after',\n examples(\n 'mdk onboard # new project: writes mdk.yaml',\n 'mdk create app ops-center # scaffold an app into apps/ops-center',\n 'mdk add worker @your-org/mdk-worker-rig # install a worker from npm',\n 'mdk validate app ops-center # check it before publishing',\n 'mdk run # start the Kernel, workers, apps and Gateway',\n ) + '\\nRun `mdk <command> --help` for the options of a command.\\n',\n );\n\n return program;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1 +1,59 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"name": "@tetherto/mdk-cli",
|
|
3
|
+
"version": "0.12.1",
|
|
4
|
+
"description": "MDK command-line tool: onboard, scaffold, run and inspect an MDK stack.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mdk": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"templates"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=24"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc -p tsconfig.json",
|
|
18
|
+
"dev": "tsx src/index.ts",
|
|
19
|
+
"start": "node dist/index.js",
|
|
20
|
+
"clean": "rm -rf dist",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"test:watch": "vitest",
|
|
23
|
+
"test:coverage": "vitest run --coverage",
|
|
24
|
+
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
25
|
+
"test:e2e:stack": "vitest run --config vitest.e2e.config.ts tests/e2e/stack.e2e.ts tests/e2e/split.e2e.ts",
|
|
26
|
+
"test:e2e:scaffold": "vitest run --config vitest.e2e.config.ts tests/e2e/scaffold.e2e.ts",
|
|
27
|
+
"prepack": "npm run clean && npm run build"
|
|
28
|
+
},
|
|
29
|
+
"license": "Apache-2.0",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@clack/prompts": "^1.7.0",
|
|
32
|
+
"@tetherto/mdk-app-runtime": "^0.12.1",
|
|
33
|
+
"@tetherto/mdk-client": "^0.12.1",
|
|
34
|
+
"@tetherto/mdk-core": "^0.12.1",
|
|
35
|
+
"@tetherto/mdk-gateway": "^0.12.1",
|
|
36
|
+
"@tetherto/mdk-mcp": "^0.12.1",
|
|
37
|
+
"@tetherto/mdk-skill": "^0.12.1",
|
|
38
|
+
"@tetherto/mdk-worker": "^0.12.1",
|
|
39
|
+
"commander": "^15.0.0",
|
|
40
|
+
"degit": "^3.6.6",
|
|
41
|
+
"picocolors": "^1.1.1",
|
|
42
|
+
"yaml": "^2.9.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^26.1.1",
|
|
46
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
47
|
+
"tsx": "^4.23.1",
|
|
48
|
+
"typescript": "^7.0.2",
|
|
49
|
+
"vitest": "^4.1.10"
|
|
50
|
+
},
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "git+https://github.com/tetherto/mdk.git",
|
|
54
|
+
"directory": "packages/cli"
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# __PLUGIN_NAME__
|
|
2
|
+
|
|
3
|
+
An MDK **app** — HTTP endpoints that aggregate data from your workers'
|
|
4
|
+
devices over the MDK protocol. An app is an app manifest ([`mdk-app.yaml`](./mdk-app.yaml))
|
|
5
|
+
plus its plugin: a route manifest ([`plugin/mdk-plugin.json`](./plugin/mdk-plugin.json)) and one controller module per route;
|
|
6
|
+
`mdk run` runs it in its own process and the Gateway forwards each route to it. There is no root export.
|
|
7
|
+
|
|
8
|
+
## Layout
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
mdk-app.yaml # app manifest: id, version, displayName, permissions, config defaults
|
|
12
|
+
plugin/mdk-plugin.json # route manifest: id, handler, http.method + http.path
|
|
13
|
+
plugin/lib/client.js # ambient MDK client from @tetherto/mdk-gateway/plugin
|
|
14
|
+
plugin/controllers/*.js # one module per route: async (req) => result
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Each controller receives `req` (`{ params, query, body, headers }`). Talk to
|
|
18
|
+
the Kernel through the plugin-owned client in `plugin/lib/client.js`, which is built
|
|
19
|
+
from the ambient import override:
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
const { config } = require('@tetherto/mdk-gateway/plugin')
|
|
23
|
+
const { createMdkClient } = require('@tetherto/mdk-client')
|
|
24
|
+
module.exports = createMdkClient(config)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The app runtime freezes a per-plugin context onto
|
|
28
|
+
`@tetherto/mdk-gateway/plugin` (same pattern as `backend/apps/agent`), so
|
|
29
|
+
`config` already includes `kernelKey` / `kernelBootstrap` plus the `config`
|
|
30
|
+
defaults in `mdk-app.yaml`, overridden by this app's `spec.apps[].config`
|
|
31
|
+
block. Controllers do **not** take `mdkClient` from a `services` parameter.
|
|
32
|
+
|
|
33
|
+
The return value is sent as JSON (HTTP 200), so return plain data: a `Buffer` would arrive as its JSON form. Throw
|
|
34
|
+
`Error('ERR_...')` to return a 400.
|
|
35
|
+
|
|
36
|
+
Mark a route that only reads with `"safety": "read-only"`: `mdk run mcp` turns routes into agent tools and
|
|
37
|
+
withholds every other one as a write unless it is started with `--allow-write`. Mark a route that changes
|
|
38
|
+
devices with `"safety": "write"`.
|
|
39
|
+
|
|
40
|
+
Routes are open by default. Add `"auth": true` to a route to require a caller
|
|
41
|
+
identity: the Gateway then refuses it (401) unless an auth verifier is configured
|
|
42
|
+
and accepts the request.
|
|
43
|
+
|
|
44
|
+
## Run it
|
|
45
|
+
|
|
46
|
+
This app is registered in your stack's `mdk.yaml` under `spec.apps` by
|
|
47
|
+
**package name** and resolved from `node_modules` (a `file:` dependency in the project's `package.json` links it):
|
|
48
|
+
|
|
49
|
+
```yaml
|
|
50
|
+
spec:
|
|
51
|
+
apps:
|
|
52
|
+
- name: __PLUGIN_APP__
|
|
53
|
+
package: __PACKAGE_NAME__
|
|
54
|
+
config: {}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Then boot the stack:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
mdk run # Kernel, workers, this app and the Gateway together
|
|
61
|
+
# or run the app on its own next to `mdk run kernel` and `mdk run gateway`:
|
|
62
|
+
mdk run app __PLUGIN_APP__
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Call it: `GET http://localhost:<gatewayPort>/apps/__PLUGIN_APP__/api/__PLUGIN_NAME__/summary`
|
|
66
|
+
(set `mount` on the app entry to serve it under another prefix).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# The app manifest `mdk run` reads; `plugin/mdk-plugin.json` holds the route table.
|
|
2
|
+
id: com.example.__PLUGIN_APP__
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
displayName: "__PLUGIN_NAME__"
|
|
5
|
+
# What the app asks the operator to approve, e.g.
|
|
6
|
+
# - resources: [workers, devices]
|
|
7
|
+
# verbs: [list, read]
|
|
8
|
+
permissions: []
|
|
9
|
+
# Defaults for the app's settings, overridden by its `config` in mdk.yaml.
|
|
10
|
+
config: {}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PLUGIN_NAME__",
|
|
3
|
+
"version": "0.12.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "MDK app (mdk-app.yaml + plugin/ route manifest and controllers). Run by `mdk run` behind the Gateway — it serves HTTP endpoints that aggregate worker telemetry over the MDK protocol.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"mdk",
|
|
8
|
+
"mdk-app"
|
|
9
|
+
],
|
|
10
|
+
"license": "Apache-2.0",
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=24"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@tetherto/mdk-client": "^0.12.1"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const mdkClient = require('../lib/client')
|
|
4
|
+
|
|
5
|
+
// A read-only aggregation endpoint: it asks the Kernel (through the plugin's
|
|
6
|
+
// ambient MDK client) for the fleet and returns a small summary. Replace this
|
|
7
|
+
// with your own aggregation, grounded in the telemetry channels your workers
|
|
8
|
+
// expose.
|
|
9
|
+
//
|
|
10
|
+
// Contract: `async (req) => result`. The return value is sent as JSON
|
|
11
|
+
// (HTTP 200). Throw `Error('ERR_...')` to return a 400.
|
|
12
|
+
module.exports = async function summary (req) {
|
|
13
|
+
// createMdkClient fails per request (ERR_MDK_CLIENT_UNAVAILABLE) when the
|
|
14
|
+
// Gateway has no Kernel key / connection — report that instead of failing
|
|
15
|
+
// so the route stays callable during boot.
|
|
16
|
+
let workers
|
|
17
|
+
try {
|
|
18
|
+
const resp = await mdkClient.listWorkers()
|
|
19
|
+
workers = (resp && resp.workers) || []
|
|
20
|
+
} catch (err) {
|
|
21
|
+
if (err && err.code === 'ERR_MDK_CLIENT_UNAVAILABLE') {
|
|
22
|
+
return { ok: true, kernelConnected: false, workerCount: 0, deviceCount: 0, workers: [] }
|
|
23
|
+
}
|
|
24
|
+
throw err
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const deviceCount = workers.reduce((n, w) => n + ((w.deviceIds && w.deviceIds.length) || 0), 0)
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
ok: true,
|
|
31
|
+
kernelConnected: true,
|
|
32
|
+
workerCount: workers.length,
|
|
33
|
+
deviceCount,
|
|
34
|
+
workers: workers.map((w) => ({ workerId: w.workerId, devices: (w.deviceIds || []).length }))
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Plugin-owned MDK client, built from the ambient gateway context. The loader
|
|
4
|
+
// overrides `require('@tetherto/mdk-gateway/plugin')` per plugin (same pattern
|
|
5
|
+
// as backend/apps/agent) so `config` carries this plugin's merged settings
|
|
6
|
+
// including kernelKey / kernelBootstrap. Controllers import this module —
|
|
7
|
+
// they never take mdkClient from a services bag.
|
|
8
|
+
const { config } = require('@tetherto/mdk-gateway/plugin')
|
|
9
|
+
const { createMdkClient } = require('@tetherto/mdk-client')
|
|
10
|
+
|
|
11
|
+
module.exports = createMdkClient(config)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PLUGIN_NAME__",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MDK app plugin — HTTP endpoints that aggregate worker telemetry.",
|
|
5
|
+
"routes": [
|
|
6
|
+
{
|
|
7
|
+
"id": "__PLUGIN_NAME__.summary",
|
|
8
|
+
"handler": "./controllers/summary.js",
|
|
9
|
+
"safety": "read-only",
|
|
10
|
+
"http": {
|
|
11
|
+
"method": "GET",
|
|
12
|
+
"path": "/api/__PLUGIN_NAME__/summary"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Editor-only: lets you edit the starter UI in an MDK checkout without errors.
|
|
2
|
+
// `mdk create app` copies only ui-src/, and writes the app's own tsconfig there.
|
|
3
|
+
// The MDK packages resolve to this checkout's built ui/packages (npm run build in ui/).
|
|
4
|
+
{
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"target": "ES2022",
|
|
7
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
8
|
+
"module": "ESNext",
|
|
9
|
+
"moduleResolution": "bundler",
|
|
10
|
+
"jsx": "react-jsx",
|
|
11
|
+
"strict": true,
|
|
12
|
+
"noUncheckedIndexedAccess": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"isolatedModules": true,
|
|
16
|
+
"typeRoots": ["../../../../ui/node_modules/@types"],
|
|
17
|
+
"paths": {
|
|
18
|
+
"@tetherto/mdk-ui-foundation": ["../../../../ui/packages/ui-foundation/dist/index.d.ts"],
|
|
19
|
+
"@tetherto/mdk-ui-foundation/app-shared-modules": ["../../../../ui/packages/ui-foundation/dist/apps/shared-modules.d.ts"],
|
|
20
|
+
"@tetherto/mdk-react-adapter": ["../../../../ui/packages/react-adapter/dist/index.d.ts"],
|
|
21
|
+
"@tetherto/mdk-react-devkit": ["../../../../ui/packages/react-devkit/dist/index.d.ts"],
|
|
22
|
+
"react": ["../../../../ui/node_modules/@types/react"],
|
|
23
|
+
"react/*": ["../../../../ui/node_modules/@types/react/*"],
|
|
24
|
+
"vite": ["../../../../ui/node_modules/vite"],
|
|
25
|
+
"@vitejs/plugin-react": ["../../../../ui/node_modules/@vitejs/plugin-react/dist/index.d.ts"]
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"include": ["ui-src"]
|
|
29
|
+
}
|