@tetherto/mdk-skill 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 (62) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +133 -0
  3. package/dist/skills/mdk/SKILL.md +114 -0
  4. package/dist/skills/mdk/references/architecture.md +89 -0
  5. package/dist/skills/mdk/references/glossary.md +27 -0
  6. package/dist/skills/mdk/references/mdk-contract.schema.json +138 -0
  7. package/dist/skills/mdk/references/package-index.md +53 -0
  8. package/dist/skills/mdk/references/protocol.md +95 -0
  9. package/dist/skills/mdk-create-app/SKILL.md +231 -0
  10. package/dist/skills/mdk-deployment/SKILL.md +161 -0
  11. package/dist/skills/mdk-gateway-plugin/SKILL.md +192 -0
  12. package/dist/skills/mdk-gateway-plugin/references/controller-patterns.md +99 -0
  13. package/dist/skills/mdk-gateway-plugin/references/plugin-authoring.md +121 -0
  14. package/dist/skills/mdk-publish-security/SKILL.md +177 -0
  15. package/dist/skills/mdk-publish-security/references/vuln-classes.md +151 -0
  16. package/dist/skills/mdk-publish-security/scripts/scan-packages.mjs +544 -0
  17. package/dist/skills/mdk-site-sizing/SKILL.md +160 -0
  18. package/dist/skills/mdk-site-sizing/eval/held-out.json +104 -0
  19. package/dist/skills/mdk-site-sizing/eval/profiles/cap-100devices-2workers-mixed.json +24 -0
  20. package/dist/skills/mdk-site-sizing/eval/profiles/cap-100devices-2workers.json +23 -0
  21. package/dist/skills/mdk-site-sizing/eval/profiles/cap-10devices-1workers.json +23 -0
  22. package/dist/skills/mdk-site-sizing/eval/profiles/cap-15devices-2workers.json +24 -0
  23. package/dist/skills/mdk-site-sizing/eval/profiles/cap-200devices-4workers.json +23 -0
  24. package/dist/skills/mdk-site-sizing/eval/profiles/cap-500devices-4workers.json +23 -0
  25. package/dist/skills/mdk-site-sizing/eval/profiles/cap-50devices-2workers.json +23 -0
  26. package/dist/skills/mdk-site-sizing/references/envelope.json +284 -0
  27. package/dist/skills/mdk-site-sizing/references/envelope.md +42 -0
  28. package/dist/skills/mdk-site-sizing/scripts/compile-envelope.mjs +45 -0
  29. package/dist/skills/mdk-site-sizing/scripts/eval-held-out.mjs +50 -0
  30. package/dist/skills/mdk-site-sizing/scripts/lib.mjs +591 -0
  31. package/dist/skills/mdk-site-sizing/scripts/parse-site.mjs +25 -0
  32. package/dist/skills/mdk-site-sizing/scripts/size-site.mjs +49 -0
  33. package/dist/skills/mdk-ui-component/SKILL.md +207 -0
  34. package/dist/skills/mdk-ui-component/references/page-recipe.md +78 -0
  35. package/dist/skills/mdk-ui-component/references/ui-registry.json +1 -0
  36. package/dist/skills/mdk-worker-plugin/SKILL.md +198 -0
  37. package/dist/skills/mdk-worker-plugin/assets/mdk-contract.template.json +45 -0
  38. package/dist/skills/mdk-worker-plugin/assets/worker-template/README.md +93 -0
  39. package/dist/skills/mdk-worker-plugin/assets/worker-template/mdk-contract.json +26 -0
  40. package/dist/skills/mdk-worker-plugin/assets/worker-template/mock/server.js +93 -0
  41. package/dist/skills/mdk-worker-plugin/assets/worker-template/package.json +23 -0
  42. package/dist/skills/mdk-worker-plugin/assets/worker-template/smoke.config.js +41 -0
  43. package/dist/skills/mdk-worker-plugin/assets/worker-template/src/client.js +47 -0
  44. package/dist/skills/mdk-worker-plugin/assets/worker-template/src/commands/reboot.js +5 -0
  45. package/dist/skills/mdk-worker-plugin/assets/worker-template/src/commands/set-power-mode.js +10 -0
  46. package/dist/skills/mdk-worker-plugin/assets/worker-template/src/telemetry/hashrate-rt.js +5 -0
  47. package/dist/skills/mdk-worker-plugin/assets/worker-template/src/telemetry/power-mode.js +5 -0
  48. package/dist/skills/mdk-worker-plugin/assets/worker-template/src/telemetry/power.js +5 -0
  49. package/dist/skills/mdk-worker-plugin/assets/worker-template/src/telemetry/temperature.js +5 -0
  50. package/dist/skills/mdk-worker-plugin/references/contract-authoring.md +135 -0
  51. package/dist/skills/mdk-worker-plugin/references/device-families.md +96 -0
  52. package/dist/skills/mdk-worker-plugin/references/local-testing.md +134 -0
  53. package/dist/skills/mdk-worker-plugin/references/mdk-contract.schema.json +138 -0
  54. package/dist/skills/mdk-worker-plugin/references/worker-base-api.md +152 -0
  55. package/dist/skills/mdk-worker-plugin/scripts/validate-contract.mjs +129 -0
  56. package/dist/skills/mdk-worker-plugin/scripts/worker-smoke.mjs +256 -0
  57. package/package.json +47 -1
  58. package/src/assemble.mjs +11 -0
  59. package/src/index.d.ts +57 -0
  60. package/src/index.mjs +254 -0
  61. package/src/install.mjs +57 -0
  62. package/src/sources.map.json +34 -0
package/src/index.mjs ADDED
@@ -0,0 +1,254 @@
1
+ // Programmatic entry point for @tetherto/mdk-skill.
2
+ //
3
+ // Consumers (e.g. the MDK CLI) import this module and call `installSkills()`
4
+ // to copy the assembled Agent Skills into a project's client directories.
5
+ // When published to npm the package ships a prebuilt `dist/skills/`; inside
6
+ // the monorepo the skills are assembled on demand from the source artifacts.
7
+
8
+ import fs from 'node:fs'
9
+ import path from 'node:path'
10
+ import { fileURLToPath } from 'node:url'
11
+
12
+ const SRC_DIR = path.dirname(fileURLToPath(import.meta.url))
13
+ const PKG_DIR = path.dirname(SRC_DIR)
14
+ const REPO_ROOT = path.resolve(PKG_DIR, '..', '..')
15
+ const DIST = path.join(PKG_DIR, 'dist', 'skills')
16
+
17
+ const SKILL_MD_MAX_LINES = 500
18
+
19
+ // Frontmatter key every suite skill carries (`metadata.suite`). installSkills
20
+ // only replaces an existing skill dir that has it, and only sweeps a stale one
21
+ // (no longer shipped) that has it, so a user's own skill is never deleted.
22
+ const SUITE_MARKER = /^\s+suite:\s*["']?mdk-developer-skill["']?\s*$/m
23
+
24
+ export const CLIENT_DIRS = {
25
+ cursor: ['.cursor', 'skills'],
26
+ claude: ['.claude', 'skills']
27
+ }
28
+
29
+ // Flat sibling skills — clients discover <skills-dir>/<name>/SKILL.md one
30
+ // level deep, so nested sub-skills would never trigger.
31
+ const REQUIRED = [
32
+ 'mdk/SKILL.md',
33
+ 'mdk/references/architecture.md',
34
+ 'mdk/references/package-index.md',
35
+ 'mdk/references/protocol.md',
36
+ 'mdk/references/glossary.md',
37
+ 'mdk/references/mdk-contract.schema.json',
38
+ 'mdk-gateway-plugin/SKILL.md',
39
+ 'mdk-create-app/SKILL.md',
40
+ 'mdk-ui-component/SKILL.md',
41
+ 'mdk-ui-component/references/ui-registry.json',
42
+ 'mdk-deployment/SKILL.md',
43
+ 'mdk-site-sizing/SKILL.md',
44
+ 'mdk-site-sizing/references/envelope.json',
45
+ 'mdk-site-sizing/scripts/size-site.mjs',
46
+ 'mdk-site-sizing/scripts/eval-held-out.mjs',
47
+ 'mdk-worker-plugin/SKILL.md',
48
+ 'mdk-worker-plugin/references/mdk-contract.schema.json',
49
+ 'mdk-worker-plugin/references/contract-authoring.md',
50
+ 'mdk-worker-plugin/references/worker-base-api.md',
51
+ 'mdk-worker-plugin/references/device-families.md',
52
+ 'mdk-worker-plugin/references/local-testing.md',
53
+ 'mdk-worker-plugin/assets/mdk-contract.template.json',
54
+ 'mdk-worker-plugin/assets/worker-template/mdk-contract.json',
55
+ 'mdk-worker-plugin/assets/worker-template/src/client.js',
56
+ 'mdk-worker-plugin/assets/worker-template/mock/server.js',
57
+ 'mdk-worker-plugin/assets/worker-template/smoke.config.js',
58
+ 'mdk-worker-plugin/scripts/validate-contract.mjs',
59
+ 'mdk-worker-plugin/scripts/worker-smoke.mjs'
60
+ ]
61
+
62
+ function copyEntry (entry) {
63
+ const src = path.resolve(REPO_ROOT, entry.source)
64
+ const dest = path.resolve(DIST, entry.dest)
65
+ if (!fs.existsSync(src)) throw new Error(`source missing: ${entry.source}`)
66
+
67
+ const excluded = new Set(entry.exclude || [])
68
+ fs.cpSync(src, dest, {
69
+ recursive: true,
70
+ filter: (from) => !excluded.has(path.basename(from))
71
+ })
72
+ }
73
+
74
+ function walk (dir, out = []) {
75
+ for (const name of fs.readdirSync(dir)) {
76
+ const p = path.join(dir, name)
77
+ if (fs.statSync(p).isDirectory()) walk(p, out)
78
+ else out.push(p)
79
+ }
80
+ return out
81
+ }
82
+
83
+ function injectVersion (files, version) {
84
+ for (const file of files) {
85
+ if (!file.endsWith('.md')) continue
86
+ const body = fs.readFileSync(file, 'utf8')
87
+ if (!body.includes('{{MDK_VERSION}}')) continue
88
+ fs.writeFileSync(file, body.replaceAll('{{MDK_VERSION}}', version))
89
+ }
90
+ }
91
+
92
+ function check (files) {
93
+ for (const rel of REQUIRED) {
94
+ if (!fs.existsSync(path.join(DIST, rel))) throw new Error(`required artifact missing after assembly: ${rel}`)
95
+ }
96
+ for (const file of files) {
97
+ if (path.basename(file) !== 'SKILL.md') continue
98
+ const lines = fs.readFileSync(file, 'utf8').split('\n').length
99
+ if (lines > SKILL_MD_MAX_LINES) {
100
+ throw new Error(`${path.relative(DIST, file)} is ${lines} lines (budget ${SKILL_MD_MAX_LINES}) — move content to references/`)
101
+ }
102
+ }
103
+ }
104
+
105
+ /** True when the source map and repo root are available to assemble from. */
106
+ export function canAssemble () {
107
+ return (
108
+ fs.existsSync(path.join(SRC_DIR, 'sources.map.json')) &&
109
+ fs.existsSync(path.join(REPO_ROOT, 'package.json'))
110
+ )
111
+ }
112
+
113
+ /** True when `dist/skills/` already holds at least one assembled skill. */
114
+ export function isAssembled () {
115
+ try {
116
+ return fs.existsSync(DIST) && fs.readdirSync(DIST).length > 0
117
+ } catch {
118
+ return false
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Assemble the skill suite from source artifacts into `dist/skills/`.
124
+ * Only usable inside the monorepo (or the published package's own tree).
125
+ * Returns the number of files assembled and the MDK version stamped in.
126
+ */
127
+ export function assemble () {
128
+ const map = JSON.parse(fs.readFileSync(path.join(SRC_DIR, 'sources.map.json'), 'utf8'))
129
+ const mdkVersion = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'package.json'), 'utf8')).version
130
+
131
+ fs.rmSync(DIST, { recursive: true, force: true })
132
+ fs.mkdirSync(DIST, { recursive: true })
133
+
134
+ for (const entry of map.entries) copyEntry(entry)
135
+
136
+ const files = walk(DIST)
137
+ injectVersion(files, mdkVersion)
138
+ check(files)
139
+
140
+ return { files: files.length, version: mdkVersion }
141
+ }
142
+
143
+ function isSuiteSkill (dir) {
144
+ let body
145
+ try {
146
+ body = fs.readFileSync(path.join(dir, 'SKILL.md'), 'utf8')
147
+ } catch {
148
+ return false
149
+ }
150
+ const fm = body.match(/^---\r?\n([\s\S]*?)\r?\n---/)
151
+ return Boolean(fm && SUITE_MARKER.test(fm[1]))
152
+ }
153
+
154
+ function lstatOrNull (p) {
155
+ try {
156
+ return fs.lstatSync(p)
157
+ } catch (err) {
158
+ if (err.code === 'ENOENT') return null
159
+ throw err
160
+ }
161
+ }
162
+
163
+ // Create <root>/<segments...> one level at a time, refusing any segment that is
164
+ // a symlink, so a planted `.claude -> /elsewhere` cannot redirect the install
165
+ // (and its deletes) outside the project.
166
+ function ensureRealDir (root, segments) {
167
+ let dir = root
168
+ for (const seg of segments) {
169
+ dir = path.join(dir, seg)
170
+ const st = lstatOrNull(dir)
171
+ if (!st) fs.mkdirSync(dir)
172
+ else if (st.isSymbolicLink()) throw new Error(`refusing to install through symlink ${dir}`)
173
+ else if (!st.isDirectory()) throw new Error(`${dir} exists and is not a directory`)
174
+ }
175
+ return dir
176
+ }
177
+
178
+ /**
179
+ * Remove MDK-shipped skill dirs in `targetDir` that the current suite no longer
180
+ * ships (renamed or dropped between MDK versions). Installs are copies, so
181
+ * without this a rerun would leave the stale dir behind and the agent would
182
+ * keep loading it. Dirs without the suite marker, and symlinks, are left alone.
183
+ */
184
+ function sweepStale (targetDir, shipped) {
185
+ const removed = []
186
+ for (const entry of fs.readdirSync(targetDir, { withFileTypes: true })) {
187
+ if (!entry.isDirectory() || shipped.has(entry.name)) continue
188
+ const dir = path.join(targetDir, entry.name)
189
+ if (!isSuiteSkill(dir)) continue
190
+ fs.rmSync(dir, { recursive: true, force: true })
191
+ removed.push(entry.name)
192
+ }
193
+ return removed
194
+ }
195
+
196
+ /**
197
+ * Install the assembled skills into a project's client skills directories.
198
+ * Stale MDK-shipped skills (present in the target but absent from the current
199
+ * suite) are removed; user-authored skills are never touched.
200
+ *
201
+ * @param {object} [options]
202
+ * @param {'cursor'|'claude'|'all'} [options.client='all']
203
+ * @param {string} [options.target=process.cwd()] Project root to install into.
204
+ * @param {boolean} [options.assembleIfMissing=true] Assemble first if `dist/skills/` is empty.
205
+ * @param {(msg: string) => void} [options.onWarn=console.warn] Called for each skill dir left untouched.
206
+ * @returns {{ skills: string[], installed: Array<{ client: string, dir: string, removed: string[] }>, skipped: Array<{ client: string, skill: string, dir: string, reason: string }> }}
207
+ *
208
+ * Throws if `.cursor`/`.claude` or `skills` under the target is a symlink. An
209
+ * existing `<skills-dir>/<name>` is replaced only when its SKILL.md carries the
210
+ * suite marker (`metadata.suite: mdk-developer-skill`); a symlink or a foreign
211
+ * skill with the same name is skipped with a warning.
212
+ */
213
+ export function installSkills ({ client = 'all', target = process.cwd(), assembleIfMissing = true, onWarn = console.warn } = {}) {
214
+ const clients = client === 'all' ? Object.keys(CLIENT_DIRS) : [client]
215
+ for (const c of clients) {
216
+ if (!CLIENT_DIRS[c]) throw new Error(`unknown client '${client}' — use cursor | claude | all`)
217
+ }
218
+
219
+ if (!isAssembled()) {
220
+ if (assembleIfMissing && canAssemble()) assemble()
221
+ else throw new Error(`no assembled skills found at ${DIST} — run \`npm run assemble\` in @tetherto/mdk-skill`)
222
+ }
223
+
224
+ const skills = fs.readdirSync(DIST, { withFileTypes: true }).filter((e) => e.isDirectory())
225
+ if (!skills.length) throw new Error(`no skills found in ${DIST}`)
226
+
227
+ fs.mkdirSync(path.resolve(target), { recursive: true })
228
+ const projectRoot = fs.realpathSync(path.resolve(target))
229
+ const shipped = new Set(skills.map((s) => s.name))
230
+ const installed = []
231
+ const skipped = []
232
+ for (const c of clients) {
233
+ const targetDir = ensureRealDir(projectRoot, CLIENT_DIRS[c])
234
+ const removed = sweepStale(targetDir, shipped)
235
+ for (const skill of skills) {
236
+ const dest = path.join(targetDir, skill.name)
237
+ const st = lstatOrNull(dest)
238
+ let reason = null
239
+ if (st?.isSymbolicLink()) reason = 'is a symlink'
240
+ else if (st && !st.isDirectory()) reason = 'is not a directory'
241
+ else if (st && !isSuiteSkill(dest)) reason = 'is not an MDK suite skill (no metadata.suite: mdk-developer-skill)'
242
+ if (reason) {
243
+ skipped.push({ client: c, skill: skill.name, dir: dest, reason })
244
+ onWarn(`mdk-skill: skipped ${dest}: ${reason}; remove or rename it to install ${skill.name}`)
245
+ continue
246
+ }
247
+ if (st) fs.rmSync(dest, { recursive: true, force: true })
248
+ fs.cpSync(path.join(DIST, skill.name), dest, { recursive: true })
249
+ }
250
+ installed.push({ client: c, dir: targetDir, removed })
251
+ }
252
+
253
+ return { skills: skills.map((s) => s.name), installed, skipped }
254
+ }
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ // CLI wrapper around the programmatic `installSkills()` (see index.mjs).
3
+ //
4
+ // node src/install.mjs [--client cursor|claude|all] [--target <project-root>]
5
+ //
6
+ // The project root defaults to the enclosing git repo of cwd, so this works
7
+ // both inside the monorepo and when consumed from npm.
8
+
9
+ import fs from 'node:fs'
10
+ import path from 'node:path'
11
+ import { parseArgs } from 'node:util'
12
+ import { installSkills } from './index.mjs'
13
+
14
+ function findProjectRoot (startDir) {
15
+ let dir = startDir
16
+ for (;;) {
17
+ if (fs.existsSync(path.join(dir, '.git'))) return dir
18
+ const parent = path.dirname(dir)
19
+ if (parent === dir) return startDir
20
+ dir = parent
21
+ }
22
+ }
23
+
24
+ const USAGE = 'usage: node src/install.mjs [--client cursor|claude|all] [--target <project-root>]'
25
+
26
+ let values
27
+ try {
28
+ ({ values } = parseArgs({
29
+ options: {
30
+ client: { type: 'string', default: 'all' },
31
+ target: { type: 'string' },
32
+ help: { type: 'boolean', short: 'h' }
33
+ }
34
+ }))
35
+ } catch (err) {
36
+ console.error(`ERR_ARGS: ${err.message}\n${USAGE}`)
37
+ process.exit(2)
38
+ }
39
+ if (values.help) {
40
+ console.log(USAGE)
41
+ process.exit(0)
42
+ }
43
+
44
+ const { client, target } = values
45
+ const projectRoot = target ? path.resolve(process.cwd(), target) : findProjectRoot(process.cwd())
46
+
47
+ try {
48
+ const { skills, installed } = installSkills({ client, target: projectRoot })
49
+ for (const { dir, removed } of installed) {
50
+ console.log(`installed ${skills.length} skill(s) -> ${path.relative(process.cwd(), dir) || '.'}`)
51
+ if (removed.length) console.log(`removed ${removed.length} stale skill(s): ${removed.join(', ')}`)
52
+ }
53
+ console.log('done — installed skill dirs are generated artifacts; keep them gitignored')
54
+ } catch (err) {
55
+ console.error(`ERR_INSTALL: ${err instanceof Error ? err.message : String(err)}`)
56
+ process.exit(1)
57
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "$comment": "Source -> dist/skills/ artifact mapping, applied in order by assemble.mjs (later entries layer onto earlier copies). Skills are flat siblings — clients discover <skills-dir>/<name>/SKILL.md one level deep. Regenerate with `node packages/mdk-skill/src/assemble.mjs`; never hand-edit dist/.",
3
+ "entries": [
4
+ {
5
+ "source": "packages/mdk-skill/src/skills",
6
+ "dest": ".",
7
+ "note": "Hand-authored suite content: router + mdk-worker-plugin, mdk-gateway-plugin, mdk-ui-component, mdk-deployment, mdk-site-sizing (full workflows). One file in this tree is generated, not hand-authored: skills/mdk-ui-component/references/ui-registry.json is copied verbatim from ui/packages/react-devkit/dist/registry.json by docs/scripts/sync-ui-registry.mjs. Regenerate it with `npm run generate:ui-registry` from the repo root; never hand-edit it. mdk-site-sizing/references/envelope.json is compiled from eval/profiles (or harness results) by skills/mdk-site-sizing/scripts/compile-envelope.mjs."
8
+ },
9
+ {
10
+ "source": "backend/core/mdk-worker/mdk-contract.schema.json",
11
+ "dest": "mdk/references/mdk-contract.schema.json",
12
+ "note": "Verbatim copy of the runtime contract schema owned by backend/core/mdk-worker, so agents author against exactly what generate-catalogue.js and the Kernel validate. Change the schema there, never in a bundle."
13
+ },
14
+ {
15
+ "source": "backend/core/mdk-worker/mdk-contract.schema.json",
16
+ "dest": "mdk-worker-plugin/references/mdk-contract.schema.json",
17
+ "note": "Second copy so the device-worker skill is self-contained when installed alone; same source, cannot drift apart."
18
+ },
19
+ {
20
+ "source": "packages/cli/templates/worker",
21
+ "dest": "mdk-worker-plugin/assets/worker-template",
22
+ "exclude": [
23
+ "node_modules",
24
+ "package-lock.json"
25
+ ],
26
+ "note": "Verbatim copy of the CLI worker scaffold (mdk create worker) so the skill template cannot drift from what agents are told to run."
27
+ },
28
+ {
29
+ "source": "packages/mdk-skill/src/worker-template-extras/smoke.config.js",
30
+ "dest": "mdk-worker-plugin/assets/worker-template/smoke.config.js",
31
+ "note": "Smoke-harness glue layered onto the template copy."
32
+ }
33
+ ]
34
+ }