skillscript-runtime 0.9.9 → 0.13.2

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 (92) hide show
  1. package/README.md +39 -46
  2. package/dist/bootstrap.d.ts +57 -18
  3. package/dist/bootstrap.d.ts.map +1 -1
  4. package/dist/bootstrap.js +188 -54
  5. package/dist/bootstrap.js.map +1 -1
  6. package/dist/cli.js +5 -6
  7. package/dist/cli.js.map +1 -1
  8. package/dist/connectors/agent-noop.d.ts +2 -2
  9. package/dist/connectors/agent-noop.d.ts.map +1 -1
  10. package/dist/connectors/config.d.ts +46 -0
  11. package/dist/connectors/config.d.ts.map +1 -1
  12. package/dist/connectors/config.js +109 -1
  13. package/dist/connectors/config.js.map +1 -1
  14. package/dist/connectors/index.d.ts +3 -1
  15. package/dist/connectors/index.d.ts.map +1 -1
  16. package/dist/connectors/index.js +6 -3
  17. package/dist/connectors/index.js.map +1 -1
  18. package/dist/connectors/local-model-mcp.d.ts +3 -3
  19. package/dist/connectors/local-model-mcp.d.ts.map +1 -1
  20. package/dist/connectors/local-model.d.ts +30 -11
  21. package/dist/connectors/local-model.d.ts.map +1 -1
  22. package/dist/connectors/local-model.js +44 -12
  23. package/dist/connectors/local-model.js.map +1 -1
  24. package/dist/connectors/mcp-remote.d.ts +3 -3
  25. package/dist/connectors/mcp-remote.d.ts.map +1 -1
  26. package/dist/connectors/mcp.d.ts +3 -3
  27. package/dist/connectors/mcp.d.ts.map +1 -1
  28. package/dist/connectors/memory-store-mcp.d.ts +3 -3
  29. package/dist/connectors/memory-store-mcp.d.ts.map +1 -1
  30. package/dist/connectors/memory-store-mcp.js +3 -1
  31. package/dist/connectors/memory-store-mcp.js.map +1 -1
  32. package/dist/connectors/memory-store.d.ts +26 -6
  33. package/dist/connectors/memory-store.d.ts.map +1 -1
  34. package/dist/connectors/memory-store.js +84 -15
  35. package/dist/connectors/memory-store.js.map +1 -1
  36. package/dist/connectors/registry.d.ts +28 -5
  37. package/dist/connectors/registry.d.ts.map +1 -1
  38. package/dist/connectors/registry.js +34 -11
  39. package/dist/connectors/registry.js.map +1 -1
  40. package/dist/connectors/skill-store.d.ts +3 -3
  41. package/dist/connectors/skill-store.d.ts.map +1 -1
  42. package/dist/connectors/sqlite-skill-store.d.ts +89 -0
  43. package/dist/connectors/sqlite-skill-store.d.ts.map +1 -0
  44. package/dist/connectors/sqlite-skill-store.js +506 -0
  45. package/dist/connectors/sqlite-skill-store.js.map +1 -0
  46. package/dist/connectors/types.d.ts +86 -9
  47. package/dist/connectors/types.d.ts.map +1 -1
  48. package/dist/connectors/types.js.map +1 -1
  49. package/dist/errors.d.ts +15 -1
  50. package/dist/errors.d.ts.map +1 -1
  51. package/dist/errors.js +47 -6
  52. package/dist/errors.js.map +1 -1
  53. package/dist/help-content.d.ts.map +1 -1
  54. package/dist/help-content.js +18 -4
  55. package/dist/help-content.js.map +1 -1
  56. package/dist/index.d.ts +2 -0
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +1 -0
  59. package/dist/index.js.map +1 -1
  60. package/dist/runtime-config.d.ts +0 -2
  61. package/dist/runtime-config.d.ts.map +1 -1
  62. package/dist/runtime-config.js +1 -1
  63. package/dist/runtime-config.js.map +1 -1
  64. package/dist/runtime.d.ts.map +1 -1
  65. package/dist/runtime.js +29 -30
  66. package/dist/runtime.js.map +1 -1
  67. package/dist/testing/conformance.d.ts.map +1 -1
  68. package/dist/testing/conformance.js +12 -6
  69. package/dist/testing/conformance.js.map +1 -1
  70. package/examples/README.md +13 -13
  71. package/examples/connectors/LocalModelTemplate/LocalModelTemplate.ts +138 -0
  72. package/examples/connectors/LocalModelTemplate/README.md +134 -0
  73. package/examples/connectors/McpConnectorTemplate/McpConnectorTemplate.ts +200 -0
  74. package/examples/connectors/McpConnectorTemplate/README.md +153 -0
  75. package/examples/connectors/MemoryStoreTemplate/MemoryStoreTemplate.ts +155 -0
  76. package/examples/connectors/MemoryStoreTemplate/README.md +169 -0
  77. package/examples/connectors/README.md +47 -0
  78. package/examples/connectors/SkillStoreTemplate/README.md +143 -0
  79. package/examples/connectors/SkillStoreTemplate/SkillStoreTemplate.ts +199 -0
  80. package/examples/{hello.skill.provenance.json → skillscripts/hello.skill.provenance.json} +1 -1
  81. package/package.json +1 -1
  82. package/scaffold/config.toml +8 -12
  83. package/scaffold/connectors.json +31 -3
  84. /package/examples/{classify-support-ticket.skill.md → skillscripts/classify-support-ticket.skill.md} +0 -0
  85. /package/examples/{cut-release-tag.skill.md → skillscripts/cut-release-tag.skill.md} +0 -0
  86. /package/examples/{doc-qa-with-citations.skill.md → skillscripts/doc-qa-with-citations.skill.md} +0 -0
  87. /package/examples/{feedback-sentiment-scan.skill.md → skillscripts/feedback-sentiment-scan.skill.md} +0 -0
  88. /package/examples/{hello.skill.md → skillscripts/hello.skill.md} +0 -0
  89. /package/examples/{morning-brief.skill.md → skillscripts/morning-brief.skill.md} +0 -0
  90. /package/examples/{queue-length-monitor.skill.md → skillscripts/queue-length-monitor.skill.md} +0 -0
  91. /package/examples/{service-health-watch.skill.md → skillscripts/service-health-watch.skill.md} +0 -0
  92. /package/examples/{youtrack-morning-sweep.skill.md → skillscripts/youtrack-morning-sweep.skill.md} +0 -0
@@ -1,18 +1,18 @@
1
1
  # Examples
2
2
 
3
- Curated production-quality skills demonstrating distinct language patterns. Each compiles + lints clean and can be run directly via `skillfile run examples/<name>.skill.md` (some require connectors — see the per-skill notes below).
3
+ Curated production-quality skills demonstrating distinct language patterns. The `.skill.md` files live in [`skillscripts/`](./skillscripts/); each compiles + lints clean and can be run directly via `skillfile run examples/skillscripts/<name>.skill.md` (some require connectors — see the per-skill notes below).
4
4
 
5
5
  For full language semantics, see [`../docs/language-reference.md`](../docs/language-reference.md).
6
6
 
7
7
  | Example | Patterns demonstrated |
8
8
  |---|---|
9
- | [`hello.skill.md`](./hello.skill.md) | Three-command first run; `! emit`; default target; `# Vars:` declared inputs |
10
- | [`morning-brief.skill.md`](./morning-brief.skill.md) | Multi-target with `needs:`; cron-fired with `EVENT.fired_at_*`; `# OnError:` fallback; dual `# Output:` (slack + prompt-context); `# Requires:` user-var cascade with fallback; LocalModel + retrieval composition |
11
- | [`doc-qa-with-citations.skill.md`](./doc-qa-with-citations.skill.md) | Single-target retrieval; `(fallback: [])` op-level fallback; LLM-with-citation pattern; pipe filter `\|json` for prompt embedding |
12
- | [`classify-support-ticket.skill.md`](./classify-support-ticket.skill.md) | `if`/`elif`/`else` multi-branch routing; classifier-cascade pattern; MemoryStore `$` writes with structured `domain_tags`; `$set` literal binding |
13
- | [`cut-release-tag.skill.md`](./cut-release-tag.skill.md) | `??` interactive ask-for-input; `else:` short-circuit on multiple targets; `@` shell ops with per-op error fallback; rollback-on-failure flow |
14
- | [`service-health-watch.skill.md`](./service-health-watch.skill.md) | `foreach` iteration; cron-fired with `expires_at=$(EVENT.fired_at_plus_1d_unix)` TTL math; classifier-gated MemoryStore writes; pipe filter `\|url` for path-safe interpolation |
15
- | [`feedback-sentiment-scan.skill.md`](./feedback-sentiment-scan.skill.md) | `in` / `not in` set-membership ops; dedupe-via-seen-markers idiom; nested `if`/`elif` classification cascade; TTL on bookkeeping writes |
9
+ | [`hello.skill.md`](./skillscripts/hello.skill.md) | Three-command first run; `! emit`; default target; `# Vars:` declared inputs |
10
+ | [`morning-brief.skill.md`](./skillscripts/morning-brief.skill.md) | Multi-target with `needs:`; cron-fired with `EVENT.fired_at_*`; `# OnError:` fallback; dual `# Output:` (slack + prompt-context); `# Requires:` user-var cascade with fallback; LocalModel + retrieval composition |
11
+ | [`doc-qa-with-citations.skill.md`](./skillscripts/doc-qa-with-citations.skill.md) | Single-target retrieval; `(fallback: [])` op-level fallback; LLM-with-citation pattern; pipe filter `\|json` for prompt embedding |
12
+ | [`classify-support-ticket.skill.md`](./skillscripts/classify-support-ticket.skill.md) | `if`/`elif`/`else` multi-branch routing; classifier-cascade pattern; MemoryStore `$` writes with structured `domain_tags`; `$set` literal binding |
13
+ | [`cut-release-tag.skill.md`](./skillscripts/cut-release-tag.skill.md) | `??` interactive ask-for-input; `else:` short-circuit on multiple targets; `@` shell ops with per-op error fallback; rollback-on-failure flow |
14
+ | [`service-health-watch.skill.md`](./skillscripts/service-health-watch.skill.md) | `foreach` iteration; cron-fired with `expires_at=$(EVENT.fired_at_plus_1d_unix)` TTL math; classifier-gated MemoryStore writes; pipe filter `\|url` for path-safe interpolation |
15
+ | [`feedback-sentiment-scan.skill.md`](./skillscripts/feedback-sentiment-scan.skill.md) | `in` / `not in` set-membership ops; dedupe-via-seen-markers idiom; nested `if`/`elif` classification cascade; TTL on bookkeeping writes |
16
16
 
17
17
  ## Connector requirements
18
18
 
@@ -31,20 +31,20 @@ Skills that talk to MCP tools (like `calendar.list_events`) need that tool wired
31
31
 
32
32
  ```sh
33
33
  # Cold install — no Ollama, no MCP, no external state
34
- skillfile run examples/hello.skill.md
34
+ skillfile run examples/skillscripts/hello.skill.md
35
35
 
36
36
  # With overrides
37
- skillfile run examples/hello.skill.md --input WHO=Scott
37
+ skillfile run examples/skillscripts/hello.skill.md --input WHO=Scott
38
38
 
39
39
  # Preview the compiled artifact without execution
40
- skillfile compile examples/hello.skill.md
40
+ skillfile compile examples/skillscripts/hello.skill.md
41
41
 
42
42
  # Mechanical preview — $/~/> ops short-circuit (useful for examining flow
43
43
  # without firing real LLM calls)
44
- skillfile run examples/morning-brief.skill.md --mechanical
44
+ skillfile run examples/skillscripts/morning-brief.skill.md --mechanical
45
45
 
46
46
  # Render the control-flow graph
47
- skillfile diagram examples/cut-release-tag.skill.md
47
+ skillfile diagram examples/skillscripts/cut-release-tag.skill.md
48
48
  ```
49
49
 
50
50
  ## Programmatic example
@@ -0,0 +1,138 @@
1
+ /**
2
+ * LocalModelTemplate — fork-me skeleton for writing your own LocalModel impl.
3
+ *
4
+ * This is NOT a runnable connector. Every method throws a "TODO" error.
5
+ * Adopters writing OpenAI-compat / Anthropic-compat / vLLM / TGI / SGLang /
6
+ * hosted-LLM / any-other LocalModel substrate fork this directory.
7
+ *
8
+ * The bundled `OllamaLocalModel` (in `src/connectors/local-model.ts`) is
9
+ * Ollama-specific. When that doesn't fit, fork from here.
10
+ *
11
+ * Forking workflow:
12
+ * 1. Copy this directory: `cp -r examples/connectors/LocalModelTemplate examples/connectors/MyLocalModel`
13
+ * 2. Rename the class — typically `<Substrate>LocalModel` (e.g., `OpenAICompatLocalModel`, `AnthropicLocalModel`, `VllmLocalModel`)
14
+ * 3. Implement `run()` against your transport
15
+ * 4. Implement `manifest()` returning substrate metadata
16
+ * 5. Update `staticCapabilities()` to declare what your impl supports
17
+ * 6. Register from your adopter bootstrap:
18
+ * `registry.registerLocalModel("default", new MyLocalModel({ ... }))`
19
+ * 7. Validate via the conformance suite:
20
+ * `LocalModelConformance.buildTests({ build: () => new MyLocalModel(...), ctor: MyLocalModel })`
21
+ *
22
+ * See `src/connectors/local-model.ts` (`OllamaLocalModel`) for a working
23
+ * reference implementation against the Ollama HTTP API. The full contract
24
+ * spec lives in `src/connectors/types.ts` (`LocalModel` interface).
25
+ *
26
+ * Runtime hosts (MCP server + web dashboard) honor whichever LocalModel impl
27
+ * you register. The auto-wired `$ llm` MCP bridge wraps your impl
28
+ * transparently — once registered, `$ llm prompt="..."` dispatches through
29
+ * your fork without any additional wiring.
30
+ *
31
+ * **Note on declarative wiring**: LocalModel is intrinsically *singleton* per
32
+ * deployment (one default per runtime), unlike McpConnector which is
33
+ * multi-instance. There's no `registerLocalModelClass()` equivalent of
34
+ * `registerConnectorClass()` — adopters wire their LocalModel
35
+ * programmatically via `registry.registerLocalModel()` from their bootstrap.
36
+ * Declarative custom-LocalModel via `connectors.json` substrate `{type:
37
+ * "custom", module, export, config}` form is deferred until async-bootstrap
38
+ * support lands (cross-cutting limitation across all three substrate slots).
39
+ */
40
+
41
+ import type {
42
+ LocalModel,
43
+ LocalModelCapabilities,
44
+ ManifestInfo,
45
+ } from "../../../src/connectors/types.js";
46
+
47
+ /** Replace with your substrate's connection config (endpoint, auth, model selection, etc.). */
48
+ export interface LocalModelTemplateConfig {
49
+ // TODO — declare the fields your substrate needs to connect.
50
+ // Examples:
51
+ // endpoint?: string; // OpenAI-compat / vLLM / TGI URL
52
+ // apiKey?: string; // hosted services
53
+ // defaultModel?: string; // model name / tag
54
+ // timeoutMs?: number;
55
+ exampleConfigField?: string;
56
+ }
57
+
58
+ export class LocalModelTemplate implements LocalModel {
59
+ /**
60
+ * Declare what your impl supports. The runtime + lint consult these flags.
61
+ * Set conservatively — overclaiming triggers cryptic downstream failures;
62
+ * underclaiming hides usable features.
63
+ */
64
+ static staticCapabilities(): LocalModelCapabilities {
65
+ return {
66
+ connector_type: "local_model",
67
+ implementation: "LocalModelTemplate", // ← rename to your class name
68
+ contract_version: "1.0.0",
69
+ features: {
70
+ // TODO — set each flag based on what your substrate can actually do.
71
+ supports_max_tokens: false, // can opts.maxTokens cap output?
72
+ supports_timeout: false, // do you honor a per-call timeout?
73
+ supports_streaming: false, // does your run() stream tokens? (v1 contract is non-streaming — flag for forward-compat)
74
+ supports_embedding: false, // does your substrate also expose embeddings?
75
+ },
76
+ };
77
+ }
78
+
79
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
80
+ constructor(_config: LocalModelTemplateConfig) {
81
+ // TODO — establish your substrate connection. For HTTP: store base URL
82
+ // + auth headers + default model. For SDK: cache the client instance.
83
+ throw new Error("LocalModelTemplate is a fork-me skeleton; replace with your impl.");
84
+ }
85
+
86
+ /**
87
+ * Dispatch a prompt and return the model's response text.
88
+ *
89
+ * `prompt` — the substituted prompt body (template substitution already
90
+ * resolved by the runtime; you get the final string).
91
+ *
92
+ * `opts`:
93
+ * - `maxTokens` — optional cap on output length. Honor if `supports_max_tokens: true`.
94
+ * - `model` — optional override of the configured default. Honor if your
95
+ * substrate supports multiple models per instance.
96
+ *
97
+ * Return the response text directly (not wrapped in an envelope). The
98
+ * `LocalModelMcpConnector` bridge exposes this as `$ llm prompt="..." -> R`
99
+ * where R binds to your string return value.
100
+ *
101
+ * On dispatch failure: throw. Don't return error envelopes silently — the
102
+ * runtime's op-level `(fallback: ...)` machinery catches throws cleanly.
103
+ */
104
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
105
+ async run(_prompt: string, _opts: { maxTokens?: number; model?: string }): Promise<string> {
106
+ // TODO — dispatch via your transport.
107
+ // - Map prompt + opts to your substrate's wire format (chat completion / completion / etc.)
108
+ // - Apply your per-call timeout
109
+ // - Parse the response; extract the text
110
+ // - Throw on dispatch failure (don't return error envelope silently)
111
+ throw new Error("TODO: run() — dispatch prompt + return response text.");
112
+ }
113
+
114
+ /**
115
+ * Capability snapshot for `runtime_capabilities` discovery. Return free-form
116
+ * substrate-specific metadata.
117
+ *
118
+ * Per `LocalModelManifest` in `src/connectors/types.ts`, known curated fields:
119
+ * - `kind`: string tag for substrate flavor ("openai-compat", "vllm", etc.)
120
+ * - `default_model`: configured default model name
121
+ * - `endpoint`: URL the impl connects to
122
+ * - `models_available`: list when introspectable (e.g., `/v1/models` for OpenAI-compat)
123
+ * - `fetch_error`: set when introspection failed (don't silently cache empty array)
124
+ * Plus `[key: string]: unknown` for substrate extensions.
125
+ *
126
+ * The bundled `OllamaLocalModel.manifest()` returns:
127
+ * { capabilities_version: "1", manifest: { kind: "ollama",
128
+ * endpoint: "...", default_model: "gemma2:9b", models_available: [...] } }
129
+ */
130
+ async manifest(): Promise<ManifestInfo<"local_model">> {
131
+ // TODO — return a snapshot of your substrate's metadata.
132
+ // - kind: identify the substrate
133
+ // - endpoint: where you connect
134
+ // - default_model: what model you'll dispatch to
135
+ // - models_available: list if you can introspect; surface fetch_error if introspection fails
136
+ throw new Error("TODO: manifest() — return substrate metadata snapshot.");
137
+ }
138
+ }
@@ -0,0 +1,134 @@
1
+ # LocalModelTemplate — fork-me skeleton
2
+
3
+ A skeleton `LocalModel` implementation for adopters writing their own. Not runnable; every method throws a `TODO` error. Copy this directory, rename, fill in the substrate-specific work.
4
+
5
+ **Use this when you want LocalModel inference backed by:**
6
+ - An OpenAI-compat HTTP endpoint (vLLM, TGI, SGLang, llama.cpp server)
7
+ - A hosted LLM API (OpenAI, Anthropic, Cohere — though most adopters wire those via `RemoteMcpConnector` + the provider's MCP server instead)
8
+ - A custom transport (gRPC, WebSocket, in-process inference)
9
+ - A multi-model gateway
10
+
11
+ The bundled `OllamaLocalModel` (in `src/connectors/local-model.ts`) is Ollama-specific. Fork from here when that doesn't fit.
12
+
13
+ ## The two legs
14
+
15
+ ```
16
+ LocalModel (choose which connector)
17
+ ├── OllamaLocalModel (bundled, Ollama HTTP — src/connectors/local-model.ts)
18
+ └── Your fork from this template
19
+ ```
20
+
21
+ LocalModel is the narrowest contract — just 2 methods + staticCapabilities. Most of the work in a fork is substrate-specific (auth, wire format, response parsing), not contract conformance.
22
+
23
+ ## Forking workflow
24
+
25
+ ```bash
26
+ cp -r examples/connectors/LocalModelTemplate examples/connectors/MyLocalModel
27
+ ```
28
+
29
+ 1. **Rename the class.** Convention: `<Substrate>LocalModel` (e.g., `OpenAICompatLocalModel`, `AnthropicLocalModel`, `VllmLocalModel`).
30
+ 2. **Define your config interface.** Edit `LocalModelTemplateConfig` to declare what your substrate needs (endpoint URL, API key, default model, timeout, etc.).
31
+ 3. **Implement `run()`** — translate `(prompt, opts)` to your substrate's wire format; dispatch; parse the response text.
32
+ 4. **Implement `manifest()`** — return substrate metadata (kind, endpoint, default model, available models when introspectable).
33
+ 5. **Update `staticCapabilities()`** to declare what your impl supports.
34
+ 6. **Wire from your adopter bootstrap:**
35
+
36
+ ```typescript
37
+ import { Registry } from "skillscript-runtime";
38
+ import { MyLocalModel } from "./MyLocalModel.js";
39
+
40
+ const registry = new Registry();
41
+ registry.registerLocalModel("default", new MyLocalModel({ /* config */ }));
42
+ ```
43
+
44
+ The auto-wired `$ llm` MCP bridge wraps your impl transparently — once registered, `$ llm prompt="..."` dispatches through your fork without additional wiring.
45
+
46
+ 7. **Validate via the conformance suite:**
47
+
48
+ ```typescript
49
+ import { describe, it } from "vitest";
50
+ import { LocalModelConformance } from "skillscript-runtime/testing";
51
+ import { MyLocalModel } from "./MyLocalModel.js";
52
+
53
+ describe("MyLocalModel conformance", () => {
54
+ const tests = LocalModelConformance.buildTests({
55
+ build: () => new MyLocalModel({ /* test config */ }),
56
+ ctor: MyLocalModel,
57
+ });
58
+ for (const t of tests) it(`[${t.category}] ${t.name}`, t.run);
59
+ });
60
+ ```
61
+
62
+ ## Reference implementation
63
+
64
+ The bundled `OllamaLocalModel` at `src/connectors/local-model.ts` is the canonical reference (164 LOC):
65
+
66
+ - Ollama HTTP API (`POST /api/generate`, `GET /api/tags`)
67
+ - AbortController timeout pattern
68
+ - Manifest caches successful introspection but NOT failures (retries on next call)
69
+ - Deduped stderr warning for fetch errors
70
+ - Surfaces `fetch_error` in manifest when `/api/tags` introspection fails
71
+
72
+ The error-handling patterns (don't silently cache failures; surface in manifest; dedupe-log) apply to any LocalModel substrate that introspects.
73
+
74
+ ## Contract surface (2 methods)
75
+
76
+ LocalModel is the narrowest contract surface in skillscript:
77
+
78
+ | Method | What it does | When called |
79
+ |---|---|---|
80
+ | `run(prompt, opts)` | Dispatch a prompt; return response text | Every `$ llm prompt="..." -> R` op (via the LocalModelMcpConnector bridge) |
81
+ | `manifest()` | Substrate metadata for discovery | At startup + on-demand from MCP clients |
82
+
83
+ Plus `staticCapabilities()` (required static).
84
+
85
+ ### `run()` semantics
86
+
87
+ - **`prompt`** — already-substituted prompt body. Template substitution (`${VAR}` resolution) happens before your impl is called; you get the final string.
88
+ - **`opts.maxTokens`** — optional output length cap. Honor if `supports_max_tokens: true`.
89
+ - **`opts.model`** — optional per-call model override. Useful for adopters with multi-model gateways. Honor if your substrate supports it.
90
+ - **Return** — the response text directly. NOT wrapped in an envelope. The bridge passes this through to skill source as `$(R)`.
91
+
92
+ On dispatch failure: **throw**. The runtime's op-level `(fallback: ...)` machinery catches throws cleanly. Don't return error envelopes silently — that's the silent-recovery footgun pattern.
93
+
94
+ ### `manifest()` semantics
95
+
96
+ Surface enough substrate metadata for adopters running `runtime_capabilities` to understand what's wired. Curated fields per `LocalModelManifest`:
97
+
98
+ - `kind` — substrate flavor tag (`"openai-compat"`, `"vllm"`, `"anthropic"`, etc.)
99
+ - `default_model` — configured default model name
100
+ - `endpoint` — URL your impl connects to (helps debugging)
101
+ - `models_available` — list when introspectable
102
+ - `fetch_error` — set when introspection failed; don't silently cache empty array
103
+
104
+ Plus `[key: string]: unknown` for substrate extensions.
105
+
106
+ ## A note on declarative wiring
107
+
108
+ Unlike `McpConnector` (which has `registerConnectorClass()` for adopter-extensible declarative wiring via `connectors.json`), `LocalModel` is intrinsically *singleton* per deployment — one default per runtime. There's no equivalent class registry.
109
+
110
+ Adopters wire their custom LocalModel **programmatically** from their bootstrap:
111
+
112
+ ```typescript
113
+ registry.registerLocalModel("default", new MyLocalModel({ /* config */ }));
114
+ ```
115
+
116
+ Declarative custom-LocalModel via `connectors.json` `substrate.local_model: {type: "custom", module, export, config}` form is **deferred** until async-bootstrap support lands. Same limitation applies to `SkillStore` and `MemoryStore` custom forms — sync `bootstrap()` can't `await import()`. Not LocalModel-specific.
117
+
118
+ ## Wiring against the dashboard / MCP
119
+
120
+ Runtime hosts (MCP server + web dashboard) honor whichever LocalModel impl you register. To make your fork visible through `skillfile dashboard`:
121
+
122
+ - Write a custom bootstrap that constructs the runtime with your LocalModel
123
+ - Or register via `registry.registerLocalModel("default", new MyLocalModel(...))` before `bootstrap()` returns
124
+
125
+ The auto-wired `$ llm` MCP bridge picks up your registration automatically — skills writing `$ llm prompt="..." -> R` get your impl without additional config.
126
+
127
+ ## Further reading
128
+
129
+ - **[`../../../docs/configuration.md`](../../../docs/configuration.md)** — substrate selection via `connectors.json`
130
+ - **[`../../../docs/adopter-playbook.md`](../../../docs/adopter-playbook.md)** — programmatic-bootstrap patterns
131
+ - **`src/connectors/types.ts`** — authoritative `LocalModel` interface + `LocalModelManifest` curated fields
132
+ - **`src/connectors/local-model.ts`** — `OllamaLocalModel` reference impl
133
+ - **`src/connectors/local-model-mcp.ts`** — the `LocalModelMcpConnector` bridge that wraps your impl as `$ llm`
134
+ - **`src/testing/conformance.ts`** — the per-contract conformance test suites
@@ -0,0 +1,200 @@
1
+ /**
2
+ * McpConnectorTemplate — fork-me skeleton for writing your own McpConnector impl.
3
+ *
4
+ * This is NOT a runnable connector. Every method throws a "TODO" error. The
5
+ * purpose is to surface the McpConnector contract surface in a copy-and-customize
6
+ * shape for the exotic cases the four bundled impls don't cover.
7
+ *
8
+ * **When to fork from this template** (vs. using a bundled impl):
9
+ * - `RemoteMcpConnector` — covers the common case: spawn a child process,
10
+ * bridge stdio (LSP or newline framing). YouTrack, GitHub, Linear, most
11
+ * remote MCPs all wire through this. Use it for ANY external MCP server
12
+ * reachable via stdio bridging — JSON-configurable via `connectors.json`.
13
+ * - `CallbackMcpConnector` — wraps a JS function as McpConnector. For test
14
+ * rigs + embedder-wired transports where the dispatch is local code.
15
+ * - `LocalModelMcpConnector` — bridges a registered LocalModel as
16
+ * `$ llm prompt=...` MCP dispatch. Auto-wired when LocalModel substrate
17
+ * is configured.
18
+ * - `MemoryStoreMcpConnector` — bridges a registered MemoryStore as
19
+ * `$ memory mode=...` MCP dispatch. Auto-wired when MemoryStore substrate
20
+ * is configured.
21
+ *
22
+ * Fork from this template when none of those fit — e.g.:
23
+ * - Direct HTTP MCP (JSON-RPC over HTTP, no child process)
24
+ * - WebSocket MCP
25
+ * - In-process MCP (call methods directly without IPC)
26
+ * - Custom protocol that doesn't match stdio framing
27
+ * - Cross-thread / worker-pool dispatch
28
+ *
29
+ * Forking workflow:
30
+ * 1. Copy this directory: `cp -r examples/connectors/McpConnectorTemplate examples/connectors/MyMcpConnector`
31
+ * 2. Rename the class — typically `<Transport>McpConnector` (e.g., `HttpMcpConnector`, `WebSocketMcpConnector`)
32
+ * 3. Implement `call()` against your transport
33
+ * 4. Implement `manifest()` returning transport metadata
34
+ * 5. Update `staticCapabilities()` to declare what your impl supports
35
+ * 6. (Optional) Implement `staticTools()` — returns the closed-set list of
36
+ * tools your connector exposes so lint can validate `$ name.tool` references
37
+ * 7. Register from your adopter bootstrap:
38
+ * `registry.registerMcpConnector("mytool", new MyMcpConnector({ ... }))`
39
+ * 8. (Optional) For `connectors.json` JSON-instantiability, add a static
40
+ * `fromConfig(config)` factory + register via `registerConnectorClass()`:
41
+ * ```typescript
42
+ * import { registerConnectorClass } from "skillscript-runtime/connectors";
43
+ * registerConnectorClass("MyMcpConnector", {
44
+ * ctor: MyMcpConnector,
45
+ * fromConfig: (cfg) => MyMcpConnector.fromConfig(cfg),
46
+ * });
47
+ * ```
48
+ *
49
+ * See `src/connectors/mcp-remote.ts` (`RemoteMcpConnector`) for the most
50
+ * comprehensive reference impl — stdio framing, child process lifecycle,
51
+ * `fromConfig` factory, timeout discipline. See `src/connectors/mcp.ts`
52
+ * (`CallbackMcpConnector`) for the minimal reference impl.
53
+ *
54
+ * Runtime hosts (MCP server + web dashboard) honor whichever McpConnector
55
+ * instances your registry has — bare `$ <name>` or qualified `$ <name>.<tool>`
56
+ * dispatches route through whatever's registered.
57
+ */
58
+
59
+ import type {
60
+ McpConnector,
61
+ McpDispatchCtx,
62
+ McpConnectorCapabilities,
63
+ ManifestInfo,
64
+ } from "../../../src/connectors/types.js";
65
+
66
+ /** Replace with your transport's connection config (URL, auth, timeouts, etc.). */
67
+ export interface McpConnectorTemplateConfig {
68
+ // TODO — declare the fields your transport needs to connect.
69
+ // Examples:
70
+ // httpEndpoint?: string;
71
+ // wsUrl?: string;
72
+ // authToken?: string;
73
+ // timeoutMs?: number;
74
+ exampleConfigField?: string;
75
+ }
76
+
77
+ export class McpConnectorTemplate implements McpConnector {
78
+ /**
79
+ * Declare what your impl supports. The runtime + lint consult these flags.
80
+ * Conservative defaults shown; flip to true once your impl actually supports
81
+ * each capability.
82
+ */
83
+ static staticCapabilities(): McpConnectorCapabilities {
84
+ return {
85
+ connector_type: "mcp_connector",
86
+ implementation: "McpConnectorTemplate", // ← rename to your class name
87
+ contract_version: "1.0.0",
88
+ features: {
89
+ // TODO — set each flag based on what your transport can actually do.
90
+ supports_identity_propagation: false, // can ctxOverrides.agentId thread through to upstream?
91
+ supports_streaming_responses: false, // can `call()` return an async iterable?
92
+ supports_batch: false, // can the upstream batch multiple tool calls?
93
+ },
94
+ };
95
+ }
96
+
97
+ /**
98
+ * (Optional) Declare the closed-set tool surface for lint validation.
99
+ *
100
+ * Return:
101
+ * - `string[]` — the canonical tool names your connector exposes;
102
+ * `$ name.tool` references are validated against this list at lint time
103
+ * - `null` — surface varies at runtime (e.g., `RemoteMcpConnector` wraps
104
+ * an arbitrary upstream MCP server); lint emits a tier-3 advisory
105
+ * instead of green-lighting
106
+ *
107
+ * Omit this method entirely and behavior is the same as `null`.
108
+ */
109
+ // static staticTools(): string[] | null {
110
+ // return ["my_tool_a", "my_tool_b"];
111
+ // }
112
+
113
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
114
+ constructor(_config: McpConnectorTemplateConfig) {
115
+ // TODO — establish your transport. For HTTP: store base URL + auth headers.
116
+ // For WebSocket: open the connection (or defer to first dispatch).
117
+ // For in-process: cache references to the dispatch targets.
118
+ throw new Error("McpConnectorTemplate is a fork-me skeleton; replace with your impl.");
119
+ }
120
+
121
+ /**
122
+ * (Optional, for `connectors.json` JSON-instantiability)
123
+ *
124
+ * Factory that constructs an instance from a JSON `config` block.
125
+ * Used by `loadConnectorsConfig()` when adopters declare your class in
126
+ * `connectors.json`:
127
+ *
128
+ * { "mytool": { "class": "MyMcpConnector", "config": { ... } } }
129
+ *
130
+ * Validates the config shape; throws a clear error on missing/wrong fields.
131
+ *
132
+ * Adopters register the class via:
133
+ * ```
134
+ * import { registerConnectorClass } from "skillscript-runtime/connectors";
135
+ * registerConnectorClass("MyMcpConnector", {
136
+ * ctor: MyMcpConnector,
137
+ * fromConfig: (cfg) => MyMcpConnector.fromConfig(cfg),
138
+ * });
139
+ * ```
140
+ * BEFORE `loadConnectorsConfig` runs in their bootstrap.
141
+ *
142
+ * Omit `fromConfig` if your connector can't be configured via JSON (e.g.,
143
+ * it needs a runtime instance the way `CallbackMcpConnector` needs a
144
+ * dispatch function). Adopters then wire it via embedder code only.
145
+ */
146
+ // static fromConfig(_config: Record<string, unknown>): McpConnectorTemplate {
147
+ // // TODO — validate config + construct.
148
+ // throw new Error("TODO: fromConfig() — validate JSON config + instantiate.");
149
+ // }
150
+
151
+ /**
152
+ * Dispatch a tool call to your transport.
153
+ *
154
+ * `toolName` — the tool the caller wants (`$ youtrack.search_issues` →
155
+ * `toolName = "search_issues"`; bare `$ youtrack args` → `toolName` = the
156
+ * unqualified op name).
157
+ *
158
+ * `args` — the kwargs from the skill source (`$ youtrack.search_issues
159
+ * query="..." limit=10` → `{ query: "...", limit: 10 }`).
160
+ *
161
+ * `ctxOverrides` — optional identity overrides threaded through dispatch
162
+ * (`agentId`, `isAdmin`). Bundled connectors that honor identity propagation
163
+ * forward these to the upstream substrate.
164
+ *
165
+ * Return whatever the upstream MCP returns. Skills that bind via `-> R`
166
+ * get the raw result; skills that descend into it (`$(R.field)`) get
167
+ * tier-3 advisory if your impl's return shape isn't statically discoverable.
168
+ *
169
+ * On dispatch failure, throw — the runtime's op-level `(fallback: ...)`
170
+ * machinery surfaces it cleanly. Don't return error envelopes silently;
171
+ * the v0.5.0+ contract surfaces inner-tool errors via throw.
172
+ */
173
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
174
+ async call(
175
+ _toolName: string,
176
+ _args: Record<string, unknown>,
177
+ _ctxOverrides?: McpDispatchCtx,
178
+ ): Promise<unknown> {
179
+ // TODO — dispatch via your transport.
180
+ // - Map toolName + args to your transport's wire format
181
+ // - Apply ctxOverrides if you support identity propagation
182
+ // - Apply your transport-specific timeout
183
+ // - Throw on dispatch failure (don't return an error envelope silently)
184
+ // - Return the raw response from upstream
185
+ throw new Error("TODO: call() — dispatch tool call via your transport.");
186
+ }
187
+
188
+ /**
189
+ * Capability snapshot for `runtime_capabilities` discovery. Return free-form
190
+ * transport-specific metadata.
191
+ *
192
+ * The bundled `RemoteMcpConnector.manifest()` returns:
193
+ * { capabilities_version: "1", manifest: { kind: "remote-mcp",
194
+ * command: "...", framing: "lsp", tools_available: [...] } }
195
+ */
196
+ async manifest(): Promise<ManifestInfo<"mcp_connector">> {
197
+ // TODO — return a snapshot of your transport's metadata.
198
+ throw new Error("TODO: manifest() — return transport-specific capability snapshot.");
199
+ }
200
+ }