@noir-ai/adapters 1.0.0-beta.1 → 1.2.0-beta.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/dist/index.d.ts +218 -4
- package/dist/index.js +195 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The set of host CLIs Noir knows how to scaffold for. The `host:` config
|
|
3
|
+
* (in @noir-ai/core) + `CompileTarget` (in @noir-ai/skills) widen to this
|
|
4
|
+
* same enum. Defined HERE (in adapters) rather than core/skills so the host
|
|
5
|
+
* list has ONE owner; core/skills carry the enum string literals only (no
|
|
6
|
+
* cross-package dep). See `2026-07-25-s10-multihost-design.md` (A1).
|
|
7
|
+
*
|
|
8
|
+
* - `claude` — Claude Code (the v1 default; the regression anchor).
|
|
9
|
+
* - `agents-md` — the 32-platform universal AGENTS.md standard.
|
|
10
|
+
* - `gemini` — Gemini CLI (GEMINI.md + AGENTS.md).
|
|
11
|
+
* - `cursor` — Cursor (.cursor/rules/*.mdc + AGENTS.md).
|
|
12
|
+
* - `opencode` — OpenCode (AGENTS.md + opencode.json).
|
|
13
|
+
*
|
|
14
|
+
* Add a new host here, extend `resolveAdapter`'s registry, and the schema +
|
|
15
|
+
* compiler widen automatically.
|
|
16
|
+
*/
|
|
17
|
+
type HostId = 'claude' | 'agents-md' | 'gemini' | 'cursor' | 'opencode';
|
|
1
18
|
interface EmitContext {
|
|
2
19
|
root: string;
|
|
3
20
|
}
|
|
@@ -5,18 +22,215 @@ interface McpConfigOptions {
|
|
|
5
22
|
transport: 'stdio' | 'streamable-http';
|
|
6
23
|
url?: string;
|
|
7
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Provider-neutral host MCP server entry an integration can ask the adapter to
|
|
27
|
+
* merge into the host's MCP config (e.g. Claude's `.mcp.json`). Surfaced by
|
|
28
|
+
* `@noir-ai/skills`' `compileIntegration().hostMcp` ONLY when the integration's
|
|
29
|
+
* `runtime ∈ {mcp-stdio, external-mcp}` AND it carries a non-null `mcp`
|
|
30
|
+
* declaration. Defined here (not imported from skills) so adapters does NOT
|
|
31
|
+
* add a skills dependency — the structural shape travels cleanly across the
|
|
32
|
+
* package boundary.
|
|
33
|
+
*/
|
|
34
|
+
interface IntegrationMcpEmission {
|
|
35
|
+
serverName: string;
|
|
36
|
+
command: string;
|
|
37
|
+
args?: string[];
|
|
38
|
+
transport: 'stdio' | 'http';
|
|
39
|
+
url?: string;
|
|
40
|
+
env?: Record<string, string>;
|
|
41
|
+
}
|
|
8
42
|
interface HostAdapter {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
43
|
+
/** The host identifier — must match a `HostId` registry key. Tightened from
|
|
44
|
+
* `string` to `HostId` in S10 so the registry is type-safe end-to-end. */
|
|
45
|
+
readonly id: HostId;
|
|
46
|
+
/** Full contents of the host's MCP config file (e.g. .mcp.json).
|
|
47
|
+
*
|
|
48
|
+
* Two-arg form (the original, used by `noir init`/`sync` today): emits only
|
|
49
|
+
* the host's Noir MCP server entry. Backward-compatible — existing callers
|
|
50
|
+
* keep working unchanged.
|
|
51
|
+
*
|
|
52
|
+
* Three-arg form (Slice X S10-aware overload): when an integration widens
|
|
53
|
+
* emission (`runtime:'external-mcp'` for Claude), merge the integration's
|
|
54
|
+
* server entry alongside the Noir entry. For `gated-write-proxy`/
|
|
55
|
+
* `mcp-stdio`/`none` Claude renders no NEW entry — `mcp-stdio` registers
|
|
56
|
+
* through the existing `noir mcp serve --stdio` entry, and ClickUp writes
|
|
57
|
+
* route through Noir's own MCP tool (no host MCP wiring). */
|
|
58
|
+
emitMcpConfig(ctx: EmitContext, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string;
|
|
12
59
|
/** Managed block to insert into the host's context file (e.g. CLAUDE.md). */
|
|
13
60
|
emitContext(ctx: EmitContext): string;
|
|
61
|
+
/** Managed block inserting the host's AI-rules import (e.g. @.noir/rules/RULES.md). */
|
|
62
|
+
emitRules?(ctx: EmitContext): string;
|
|
14
63
|
/** Host's skill directory (e.g. .claude/skills). Absent ⇒ host has no skill concept. */
|
|
15
64
|
skillsDir?(ctx: EmitContext): string;
|
|
16
65
|
install?(ctx: EmitContext): Promise<void>;
|
|
17
66
|
healthCheck?(ctx: EmitContext): Promise<boolean>;
|
|
67
|
+
/** S10 seam — where the host's MCP config file lives (workspace-level), e.g.
|
|
68
|
+
* `.mcp.json`, `.cursor/mcp.json`, `.gemini/mcp.json`, `opencode.json`. The
|
|
69
|
+
* cli uses this to write the string returned by `emitMcpConfig` to disk.
|
|
70
|
+
* Absent ⇒ the cli falls back to a host-specific default (or skips for
|
|
71
|
+
* hosts with no MCP concept). Optional so existing adapters keep working
|
|
72
|
+
* unchanged; new adapters implement it. */
|
|
73
|
+
mcpConfigPath?(ctx: EmitContext): string;
|
|
74
|
+
/** S10 seam — where the universal AGENTS.md goes. Defaults to root `AGENTS.md`
|
|
75
|
+
* for every host (the 32-platform standard); overridable for hosts that want
|
|
76
|
+
* it elsewhere. The shared `emitAgentsMd(ctx)` helper produces the CONTENT
|
|
77
|
+
* (byte-identical across hosts); the cli writes it to this path. */
|
|
78
|
+
agentsMdPath?(ctx: EmitContext): string;
|
|
18
79
|
}
|
|
19
80
|
|
|
81
|
+
/** The canonical filename for the universal AGENTS.md emitter. Every host
|
|
82
|
+
* writes the SAME content (single source of truth) — adapters may place it at
|
|
83
|
+
* different paths via `HostAdapter#agentsMdPath`, but the filename is constant. */
|
|
84
|
+
declare const AGENTS_MD_FILENAME = "AGENTS.md";
|
|
85
|
+
/**
|
|
86
|
+
* The universal AGENTS.md emitter — a SHARED HELPER composed by every adapter
|
|
87
|
+
* (NOT a HostAdapter itself). AGENTS.md is now the cross-tool standard (32+
|
|
88
|
+
* platforms read it natively — Claude Code, OpenAI Codex, Cursor, Gemini CLI,
|
|
89
|
+
* JetBrains Junie, …), so the SAME file content works for every host: it
|
|
90
|
+
* `@`-imports the canonical `.noir/` sources. Per-host specialization lives in
|
|
91
|
+
* each adapter's OWN native files (CLAUDE.md, GEMINI.md, .cursor/rules/*.mdc,
|
|
92
|
+
* opencode.json); AGENTS.md is the always-emitted universal baseline.
|
|
93
|
+
*
|
|
94
|
+
* Content (byte-identical across hosts):
|
|
95
|
+
*
|
|
96
|
+
* # <projectName> — Noir-managed agent context
|
|
97
|
+
*
|
|
98
|
+
* > Generated by `noir`. Canonical source lives in `.noir/`. Edit there; re-run `noir sync`.
|
|
99
|
+
*
|
|
100
|
+
* > Noir manages this project's context under `.noir/`. Edit `.noir/NOIR.md`
|
|
101
|
+
* > (project brief) + `.noir/rules/RULES.md` (working contract); re-run
|
|
102
|
+
* > `noir sync` to re-emit host files.
|
|
103
|
+
*
|
|
104
|
+
* @.noir/NOIR.md
|
|
105
|
+
* @.noir/rules/RULES.md
|
|
106
|
+
*
|
|
107
|
+
* The inline 3-line fallback (I2) sits BEFORE the `@`-imports so AGENTS.md
|
|
108
|
+
* readers that do NOT resolve `@`-imports (e.g. plain markdown viewers, some
|
|
109
|
+
* GA dashboards) still get a one-glance summary of where to edit. The
|
|
110
|
+
* `@`-imports remain canonical for the hosts that DO resolve them (Cursor,
|
|
111
|
+
* Codex, Junie, …).
|
|
112
|
+
*
|
|
113
|
+
* `<projectName>` is derived from `ctx.root`'s basename — a best-effort label
|
|
114
|
+
* for human readability. If the basename is empty/undeterminable, falls back to
|
|
115
|
+
* the literal `project` so the heading is never malformed.
|
|
116
|
+
*/
|
|
117
|
+
declare function emitAgentsMd(ctx: EmitContext): string;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The `agents-md` host adapter — the 32-platform universal AGENTS.md standard.
|
|
121
|
+
* The SMALLEST surface: a root `AGENTS.md` (context + rules unified — the
|
|
122
|
+
* `@.noir/` imports already cover both NOIR.md and RULES.md), no skill concept
|
|
123
|
+
* (the context IS the surface), and a workspace `.mcp.json` (the Claude shape —
|
|
124
|
+
* broadly compatible; many AGENTS.md readers also read it).
|
|
125
|
+
*
|
|
126
|
+
* This adapter is the fallback / universal baseline — any host that "just reads
|
|
127
|
+
* AGENTS.md" (incl. qwen/agy and other deferred hosts) behaves identically to
|
|
128
|
+
* this. Per-host specialization lives in the other adapters' native files.
|
|
129
|
+
*/
|
|
130
|
+
declare const agentsMdAdapter: HostAdapter;
|
|
131
|
+
|
|
20
132
|
declare const claudeAdapter: HostAdapter;
|
|
21
133
|
|
|
22
|
-
|
|
134
|
+
/**
|
|
135
|
+
* The `cursor` host adapter — Cursor. Cursor reads the universal `AGENTS.md`
|
|
136
|
+
* for context (same content as every other host); that file's
|
|
137
|
+
* `@.noir/rules/RULES.md` import IS the Noir working-rules surface for cursor
|
|
138
|
+
* (NO separate `.cursor/rules/noir-contract.mdc` host-rules pointer — that
|
|
139
|
+
* file was REMOVED: it was `noir-`-prefixed and the C3 cursor flat-skill prune
|
|
140
|
+
* in `emitSkillsToDir` deleted it on every `noir init/create/sync --host
|
|
141
|
+
* cursor`). Skills compile to FLAT `.mdc` in `.cursor/rules/` via
|
|
142
|
+
* `compileSkill(_, 'cursor')` (one file per skill, no per-name subdir). MCP
|
|
143
|
+
* config lands at `.cursor/mcp.json`.
|
|
144
|
+
*
|
|
145
|
+
* `emitContext` returns the universal AGENTS.md content (the single native
|
|
146
|
+
* context surface for cursor). There is NO `emitRules` here — cursor's rules
|
|
147
|
+
* are delivered via AGENTS.md's `@.noir/rules/RULES.md` import, identical to
|
|
148
|
+
* agents-md/opencode.
|
|
149
|
+
*/
|
|
150
|
+
declare const cursorAdapter: HostAdapter;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The `gemini` host adapter — Gemini CLI. Emits `GEMINI.md` (Gemini's native
|
|
154
|
+
* context file) carrying BOTH the context block AND the rules block — Gemini
|
|
155
|
+
* has no separate rules file. Uses Gemini's `@file` import syntax: bare
|
|
156
|
+
* `@.noir/NOIR.md` (no `@import` keyword, no quotes — distinct from Claude's
|
|
157
|
+
* `@import ".noir/..."` form). The cli ALSO emits a root `AGENTS.md` via the
|
|
158
|
+
* shared `emitAgentsMd` helper (Gemini reads AGENTS.md too).
|
|
159
|
+
*
|
|
160
|
+
* Managed-block form (CONTEXT_BLOCK + RULES_BLOCK markers) is used so user
|
|
161
|
+
* content outside the markers survives `noir sync` rewrites — consistent with
|
|
162
|
+
* the claude adapter's contract.
|
|
163
|
+
*
|
|
164
|
+
* MCP config lands at `.gemini/mcp.json` (workspace-level — the portable
|
|
165
|
+
* choice; `~/.gemini/settings.json` is the global alternative, documented).
|
|
166
|
+
*/
|
|
167
|
+
declare const geminiAdapter: HostAdapter;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Build the host MCP config JSON string — the `{mcpServers: {...}}` shape shared
|
|
171
|
+
* by claude (`.mcp.json`), agents-md (`.mcp.json`), gemini (`.gemini/mcp.json`),
|
|
172
|
+
* and cursor (`.cursor/mcp.json`). OpenCode uses a DIFFERENT shape (an `mcp`
|
|
173
|
+
* block with `type`-tagged entries) and does NOT use this helper — see
|
|
174
|
+
* `opencode.ts`.
|
|
175
|
+
*
|
|
176
|
+
* The Noir server entry is always present; an optional integration entry merges
|
|
177
|
+
* alongside it (per the Slice X adapter contract — only `external-mcp`
|
|
178
|
+
* integrations surface a `hostMcp` block by the time it reaches here).
|
|
179
|
+
*
|
|
180
|
+
* Refactored out of `claude.ts` in S10-Adapters so every `{mcpServers}`-shape
|
|
181
|
+
* host emits byte-identical JSON. Claude's `emitMcpConfig` now delegates here
|
|
182
|
+
* (the claude.test.ts + create/scaffold.test.ts parity gates must hold).
|
|
183
|
+
*
|
|
184
|
+
* Stdio entry: `{ command, args }`.
|
|
185
|
+
* HTTP entry : `{ type: 'http', url, [env] }` — env nested under `env:`, never
|
|
186
|
+
* spread at the entry top level (would corrupt the server-field shape).
|
|
187
|
+
*/
|
|
188
|
+
declare function buildMcpServersJson(opts: McpConfigOptions, integration?: IntegrationMcpEmission): string;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* The `opencode` host adapter — OpenCode. Reads the universal `AGENTS.md`
|
|
192
|
+
* (context + rules unified — no separate rules emission, no skill concept).
|
|
193
|
+
*
|
|
194
|
+
* The DIFFERENCE from the `{mcpServers}` family lives in MCP config: OpenCode's
|
|
195
|
+
* root `opencode.json` carries an `mcp` block whose entries are `type`-tagged —
|
|
196
|
+
* `{ type: 'local', command: [...] }` for stdio (note: `command` is an ARRAY,
|
|
197
|
+
* not the claude `{command, args}` split) and `{ type: 'remote', url }` for
|
|
198
|
+
* remote (HTTP/SSE). Server env vars land under the `environment` key (NOT
|
|
199
|
+
* `env`). The `$schema` key pins the opencode config schema. See
|
|
200
|
+
* https://opencode.ai/docs/mcp-servers/ + https://opencode.ai/config.json.
|
|
201
|
+
*
|
|
202
|
+
* Verified verbatim from the opencode docs ("Add remote MCP servers by setting
|
|
203
|
+
* `type` to `\"remote\"`"); the prior `type:'http'` / `env:` shape was wrong.
|
|
204
|
+
*/
|
|
205
|
+
declare const opencodeAdapter: HostAdapter;
|
|
206
|
+
|
|
207
|
+
/** The readonly list of supported hosts — derived from the `HostId` union so it
|
|
208
|
+
* stays in lockstep with the type. Consumers (cli `--host` flag, doctor
|
|
209
|
+
* reporting) use this for "is this host valid?" + iteration. Order is the
|
|
210
|
+
* declaration order in `HostId` (claude first — the default).
|
|
211
|
+
*
|
|
212
|
+
* `Object.freeze` enforces the `readonly` type at runtime — a stray
|
|
213
|
+
* `SUPPORTED_HOSTS.push('qwen')` from a JS caller fails loudly instead of
|
|
214
|
+
* silently corrupting the registry list (TS already prevents it in typed code). */
|
|
215
|
+
declare const SUPPORTED_HOSTS: readonly HostId[];
|
|
216
|
+
/**
|
|
217
|
+
* Resolve a host id to its `HostAdapter`. The registry is a `Record<HostId,
|
|
218
|
+
* HostAdapter>` so the type system enforces completeness — adding a host to
|
|
219
|
+
* `HostId` requires wiring it here (TS errors otherwise). The CLI uses this
|
|
220
|
+
* indirection instead of importing adapters directly so adding a host needs NO
|
|
221
|
+
* CLI edits beyond the `--host` flag's enum.
|
|
222
|
+
*
|
|
223
|
+
* S10-Adapters: all five hosts are now wired —
|
|
224
|
+
* - `claude` — CLAUDE.md + `.claude/skills/` + `.mcp.json` (regression anchor).
|
|
225
|
+
* - `agents-md` — universal AGENTS.md (the 32-platform baseline).
|
|
226
|
+
* - `gemini` — GEMINI.md + AGENTS.md + `.gemini/mcp.json`.
|
|
227
|
+
* - `cursor` — AGENTS.md + `.cursor/rules/*.mdc` + `.cursor/mcp.json`.
|
|
228
|
+
* - `opencode` — AGENTS.md + `opencode.json` (different MCP shape).
|
|
229
|
+
*
|
|
230
|
+
* Unknown/non-`HostId` strings are impossible to pass at compile time (the
|
|
231
|
+
* signature accepts only `HostId`); the runtime fallback is defensive — a
|
|
232
|
+
* JS caller ignoring types still gets a clear error, not a silent `undefined`.
|
|
233
|
+
*/
|
|
234
|
+
declare function resolveAdapter(host: HostId): HostAdapter;
|
|
235
|
+
|
|
236
|
+
export { AGENTS_MD_FILENAME, type EmitContext, type HostAdapter, type HostId, type IntegrationMcpEmission, type McpConfigOptions, SUPPORTED_HOSTS, agentsMdAdapter, buildMcpServersJson, claudeAdapter, cursorAdapter, emitAgentsMd, geminiAdapter, opencodeAdapter, resolveAdapter };
|
package/dist/index.js
CHANGED
|
@@ -1,23 +1,211 @@
|
|
|
1
|
-
// src/
|
|
1
|
+
// src/agents-md-adapter.ts
|
|
2
2
|
import { join } from "path";
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
// src/agents-md.ts
|
|
5
|
+
import { basename } from "path";
|
|
6
|
+
var AGENTS_MD_FILENAME = "AGENTS.md";
|
|
7
|
+
function emitAgentsMd(ctx) {
|
|
8
|
+
const projectName = deriveProjectName(ctx.root);
|
|
9
|
+
return `# ${projectName} \u2014 Noir-managed agent context
|
|
10
|
+
|
|
11
|
+
> Generated by \`noir\`. Canonical source lives in \`.noir/\`. Edit there; re-run \`noir sync\`.
|
|
12
|
+
|
|
13
|
+
> Noir manages this project's context under \`.noir/\`. Edit \`.noir/NOIR.md\` (project brief) + \`.noir/rules/RULES.md\` (working contract); re-run \`noir sync\` to re-emit host files.
|
|
14
|
+
|
|
15
|
+
@.noir/NOIR.md
|
|
16
|
+
@.noir/rules/RULES.md
|
|
17
|
+
`;
|
|
18
|
+
}
|
|
19
|
+
function deriveProjectName(root) {
|
|
20
|
+
const name = basename(root.replace(/\/+$/, "")) || basename(root);
|
|
21
|
+
return name && name.length > 0 ? name : "project";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/mcp.ts
|
|
25
|
+
function buildMcpServersJson(opts, integration) {
|
|
26
|
+
const noirServer = opts.transport === "stdio" ? { command: "noir", args: ["mcp", "serve", "--stdio"] } : { type: "http", url: opts.url ?? "http://127.0.0.1:0/mcp" };
|
|
27
|
+
const mcpServers = { noir: noirServer };
|
|
28
|
+
if (integration) {
|
|
29
|
+
const entry = integration.transport === "http" ? {
|
|
30
|
+
type: "http",
|
|
31
|
+
url: integration.url ?? "",
|
|
32
|
+
// Nest env under `env:` (NOT spread at the entry top level) so the
|
|
33
|
+
// emitted shape matches the stdio branch + Claude's spec. Top-level
|
|
34
|
+
// spread would leak env keys as server fields.
|
|
35
|
+
...integration.env ? { env: integration.env } : {}
|
|
36
|
+
} : {
|
|
37
|
+
command: integration.command,
|
|
38
|
+
...integration.args ? { args: integration.args } : {},
|
|
39
|
+
...integration.env ? { env: integration.env } : {}
|
|
40
|
+
};
|
|
41
|
+
mcpServers[integration.serverName] = entry;
|
|
42
|
+
}
|
|
43
|
+
return JSON.stringify({ mcpServers }, null, 2);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/agents-md-adapter.ts
|
|
47
|
+
var agentsMdAdapter = {
|
|
48
|
+
id: "agents-md",
|
|
49
|
+
emitMcpConfig(_ctx, opts, integration) {
|
|
50
|
+
return buildMcpServersJson(opts, integration);
|
|
51
|
+
},
|
|
52
|
+
emitContext(ctx) {
|
|
53
|
+
return emitAgentsMd(ctx);
|
|
54
|
+
},
|
|
55
|
+
// No `emitRules` — rules live IN the AGENTS.md content already (the
|
|
56
|
+
// `@.noir/rules/RULES.md` import covers them).
|
|
57
|
+
// No `skillsDir` — no skill concept for the universal host.
|
|
58
|
+
mcpConfigPath(ctx) {
|
|
59
|
+
return join(ctx.root, ".mcp.json");
|
|
60
|
+
},
|
|
61
|
+
agentsMdPath(ctx) {
|
|
62
|
+
return join(ctx.root, "AGENTS.md");
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// src/claude.ts
|
|
67
|
+
import { join as join2 } from "path";
|
|
68
|
+
import { CONTEXT_BLOCK_BEGIN, CONTEXT_BLOCK_END, RULES_BLOCK } from "@noir-ai/core";
|
|
4
69
|
var claudeAdapter = {
|
|
5
70
|
id: "claude",
|
|
6
|
-
emitMcpConfig(_ctx, opts) {
|
|
7
|
-
|
|
8
|
-
return JSON.stringify({ mcpServers: { noir: server } }, null, 2);
|
|
71
|
+
emitMcpConfig(_ctx, opts, integration) {
|
|
72
|
+
return buildMcpServersJson(opts, integration);
|
|
9
73
|
},
|
|
10
74
|
emitContext(_ctx) {
|
|
11
75
|
return `${CONTEXT_BLOCK_BEGIN}
|
|
12
76
|
@import ".noir/NOIR.md"
|
|
13
77
|
${CONTEXT_BLOCK_END}
|
|
78
|
+
`;
|
|
79
|
+
},
|
|
80
|
+
emitRules(_ctx) {
|
|
81
|
+
return `${RULES_BLOCK.begin}
|
|
82
|
+
@import ".noir/rules/RULES.md"
|
|
83
|
+
${RULES_BLOCK.end}
|
|
14
84
|
`;
|
|
15
85
|
},
|
|
16
86
|
skillsDir(ctx) {
|
|
17
|
-
return
|
|
87
|
+
return join2(ctx.root, ".claude", "skills");
|
|
18
88
|
}
|
|
19
89
|
};
|
|
90
|
+
|
|
91
|
+
// src/cursor.ts
|
|
92
|
+
import { join as join3 } from "path";
|
|
93
|
+
var cursorAdapter = {
|
|
94
|
+
id: "cursor",
|
|
95
|
+
emitMcpConfig(_ctx, opts, integration) {
|
|
96
|
+
return buildMcpServersJson(opts, integration);
|
|
97
|
+
},
|
|
98
|
+
emitContext(ctx) {
|
|
99
|
+
return emitAgentsMd(ctx);
|
|
100
|
+
},
|
|
101
|
+
skillsDir(ctx) {
|
|
102
|
+
return join3(ctx.root, ".cursor", "rules");
|
|
103
|
+
},
|
|
104
|
+
mcpConfigPath(ctx) {
|
|
105
|
+
return join3(ctx.root, ".cursor", "mcp.json");
|
|
106
|
+
},
|
|
107
|
+
agentsMdPath(ctx) {
|
|
108
|
+
return join3(ctx.root, "AGENTS.md");
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// src/gemini.ts
|
|
113
|
+
import { join as join4 } from "path";
|
|
114
|
+
import { CONTEXT_BLOCK_BEGIN as CONTEXT_BLOCK_BEGIN2, CONTEXT_BLOCK_END as CONTEXT_BLOCK_END2, RULES_BLOCK as RULES_BLOCK2 } from "@noir-ai/core";
|
|
115
|
+
var geminiAdapter = {
|
|
116
|
+
id: "gemini",
|
|
117
|
+
emitMcpConfig(_ctx, opts, integration) {
|
|
118
|
+
return buildMcpServersJson(opts, integration);
|
|
119
|
+
},
|
|
120
|
+
emitContext(_ctx) {
|
|
121
|
+
return `${CONTEXT_BLOCK_BEGIN2}
|
|
122
|
+
@.noir/NOIR.md
|
|
123
|
+
${CONTEXT_BLOCK_END2}
|
|
124
|
+
${RULES_BLOCK2.begin}
|
|
125
|
+
@.noir/rules/RULES.md
|
|
126
|
+
${RULES_BLOCK2.end}
|
|
127
|
+
`;
|
|
128
|
+
},
|
|
129
|
+
// No `emitRules` — rules folded into emitContext (GEMINI.md carries both).
|
|
130
|
+
// No `skillsDir` — no skill concept; GEMINI.md + AGENTS.md are the surface.
|
|
131
|
+
mcpConfigPath(ctx) {
|
|
132
|
+
return join4(ctx.root, ".gemini", "mcp.json");
|
|
133
|
+
},
|
|
134
|
+
agentsMdPath(ctx) {
|
|
135
|
+
return join4(ctx.root, "AGENTS.md");
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// src/opencode.ts
|
|
140
|
+
import { join as join5 } from "path";
|
|
141
|
+
var opencodeAdapter = {
|
|
142
|
+
id: "opencode",
|
|
143
|
+
emitMcpConfig(_ctx, opts, integration) {
|
|
144
|
+
const mcp = {
|
|
145
|
+
noir: opts.transport === "stdio" ? { type: "local", command: ["noir", "mcp", "serve", "--stdio"] } : { type: "remote", url: opts.url ?? "http://127.0.0.1:0/mcp" }
|
|
146
|
+
};
|
|
147
|
+
if (integration) {
|
|
148
|
+
mcp[integration.serverName] = integration.transport === "http" ? {
|
|
149
|
+
type: "remote",
|
|
150
|
+
url: integration.url ?? "",
|
|
151
|
+
...integration.env ? { environment: integration.env } : {}
|
|
152
|
+
} : {
|
|
153
|
+
type: "local",
|
|
154
|
+
command: [integration.command, ...integration.args ?? []],
|
|
155
|
+
...integration.env ? { environment: integration.env } : {}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return JSON.stringify({ $schema: "https://opencode.ai/config.json", mcp }, null, 2);
|
|
159
|
+
},
|
|
160
|
+
emitContext(ctx) {
|
|
161
|
+
return emitAgentsMd(ctx);
|
|
162
|
+
},
|
|
163
|
+
// No `emitRules` — rules live IN AGENTS.md (the @-import covers RULES.md).
|
|
164
|
+
// No `skillsDir` — no skill concept.
|
|
165
|
+
mcpConfigPath(ctx) {
|
|
166
|
+
return join5(ctx.root, "opencode.json");
|
|
167
|
+
},
|
|
168
|
+
agentsMdPath(ctx) {
|
|
169
|
+
return join5(ctx.root, "AGENTS.md");
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// src/index.ts
|
|
174
|
+
var SUPPORTED_HOSTS = Object.freeze([
|
|
175
|
+
"claude",
|
|
176
|
+
"agents-md",
|
|
177
|
+
"gemini",
|
|
178
|
+
"cursor",
|
|
179
|
+
"opencode"
|
|
180
|
+
]);
|
|
181
|
+
function resolveAdapter(host) {
|
|
182
|
+
switch (host) {
|
|
183
|
+
case "claude":
|
|
184
|
+
return claudeAdapter;
|
|
185
|
+
case "agents-md":
|
|
186
|
+
return agentsMdAdapter;
|
|
187
|
+
case "gemini":
|
|
188
|
+
return geminiAdapter;
|
|
189
|
+
case "cursor":
|
|
190
|
+
return cursorAdapter;
|
|
191
|
+
case "opencode":
|
|
192
|
+
return opencodeAdapter;
|
|
193
|
+
default: {
|
|
194
|
+
const _exhaustive = host;
|
|
195
|
+
throw new Error(`Unsupported host: ${String(_exhaustive)} (not in registry)`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
20
199
|
export {
|
|
21
|
-
|
|
200
|
+
AGENTS_MD_FILENAME,
|
|
201
|
+
SUPPORTED_HOSTS,
|
|
202
|
+
agentsMdAdapter,
|
|
203
|
+
buildMcpServersJson,
|
|
204
|
+
claudeAdapter,
|
|
205
|
+
cursorAdapter,
|
|
206
|
+
emitAgentsMd,
|
|
207
|
+
geminiAdapter,
|
|
208
|
+
opencodeAdapter,
|
|
209
|
+
resolveAdapter
|
|
22
210
|
};
|
|
23
211
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/claude.ts"],"sourcesContent":["import { join } from 'node:path';\nimport { CONTEXT_BLOCK_BEGIN, CONTEXT_BLOCK_END } from '@noir-ai/core';\nimport type { EmitContext, HostAdapter, McpConfigOptions } from './types.js';\n\nexport const claudeAdapter: HostAdapter = {\n id: 'claude',\n emitMcpConfig(_ctx, opts: McpConfigOptions): string {\n const server =\n opts.transport === 'stdio'\n ? { command: 'noir', args: ['mcp', 'serve', '--stdio'] }\n : { type: 'http', url: opts.url ?? 'http://127.0.0.1:0/mcp' };\n return JSON.stringify({ mcpServers: { noir: server } }, null, 2);\n },\n emitContext(_ctx: EmitContext): string {\n return `${CONTEXT_BLOCK_BEGIN}\\n@import \".noir/NOIR.md\"\\n${CONTEXT_BLOCK_END}\\n`;\n },\n skillsDir(ctx: EmitContext): string {\n return join(ctx.root, '.claude', 'skills');\n },\n};\n"],"mappings":";AAAA,SAAS,YAAY;AACrB,SAAS,qBAAqB,yBAAyB;AAGhD,IAAM,gBAA6B;AAAA,EACxC,IAAI;AAAA,EACJ,cAAc,MAAM,MAAgC;AAClD,UAAM,SACJ,KAAK,cAAc,UACf,EAAE,SAAS,QAAQ,MAAM,CAAC,OAAO,SAAS,SAAS,EAAE,IACrD,EAAE,MAAM,QAAQ,KAAK,KAAK,OAAO,yBAAyB;AAChE,WAAO,KAAK,UAAU,EAAE,YAAY,EAAE,MAAM,OAAO,EAAE,GAAG,MAAM,CAAC;AAAA,EACjE;AAAA,EACA,YAAY,MAA2B;AACrC,WAAO,GAAG,mBAAmB;AAAA;AAAA,EAA8B,iBAAiB;AAAA;AAAA,EAC9E;AAAA,EACA,UAAU,KAA0B;AAClC,WAAO,KAAK,IAAI,MAAM,WAAW,QAAQ;AAAA,EAC3C;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/agents-md-adapter.ts","../src/agents-md.ts","../src/mcp.ts","../src/claude.ts","../src/cursor.ts","../src/gemini.ts","../src/opencode.ts","../src/index.ts"],"sourcesContent":["import { join } from 'node:path';\nimport { emitAgentsMd } from './agents-md.js';\nimport { buildMcpServersJson } from './mcp.js';\nimport type {\n EmitContext,\n HostAdapter,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/**\n * The `agents-md` host adapter — the 32-platform universal AGENTS.md standard.\n * The SMALLEST surface: a root `AGENTS.md` (context + rules unified — the\n * `@.noir/` imports already cover both NOIR.md and RULES.md), no skill concept\n * (the context IS the surface), and a workspace `.mcp.json` (the Claude shape —\n * broadly compatible; many AGENTS.md readers also read it).\n *\n * This adapter is the fallback / universal baseline — any host that \"just reads\n * AGENTS.md\" (incl. qwen/agy and other deferred hosts) behaves identically to\n * this. Per-host specialization lives in the other adapters' native files.\n */\nexport const agentsMdAdapter: HostAdapter = {\n id: 'agents-md',\n emitMcpConfig(_ctx, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string {\n return buildMcpServersJson(opts, integration);\n },\n emitContext(ctx: EmitContext): string {\n // The universal AGENTS.md content — byte-identical across every adapter\n // that composes `emitAgentsMd` (agents-md / gemini / cursor / opencode).\n return emitAgentsMd(ctx);\n },\n // No `emitRules` — rules live IN the AGENTS.md content already (the\n // `@.noir/rules/RULES.md` import covers them).\n // No `skillsDir` — no skill concept for the universal host.\n mcpConfigPath(ctx: EmitContext): string {\n return join(ctx.root, '.mcp.json');\n },\n agentsMdPath(ctx: EmitContext): string {\n return join(ctx.root, 'AGENTS.md');\n },\n};\n","import { basename } from 'node:path';\nimport type { EmitContext } from './types.js';\n\n/** The canonical filename for the universal AGENTS.md emitter. Every host\n * writes the SAME content (single source of truth) — adapters may place it at\n * different paths via `HostAdapter#agentsMdPath`, but the filename is constant. */\nexport const AGENTS_MD_FILENAME = 'AGENTS.md';\n\n/**\n * The universal AGENTS.md emitter — a SHARED HELPER composed by every adapter\n * (NOT a HostAdapter itself). AGENTS.md is now the cross-tool standard (32+\n * platforms read it natively — Claude Code, OpenAI Codex, Cursor, Gemini CLI,\n * JetBrains Junie, …), so the SAME file content works for every host: it\n * `@`-imports the canonical `.noir/` sources. Per-host specialization lives in\n * each adapter's OWN native files (CLAUDE.md, GEMINI.md, .cursor/rules/*.mdc,\n * opencode.json); AGENTS.md is the always-emitted universal baseline.\n *\n * Content (byte-identical across hosts):\n *\n * # <projectName> — Noir-managed agent context\n *\n * > Generated by `noir`. Canonical source lives in `.noir/`. Edit there; re-run `noir sync`.\n *\n * > Noir manages this project's context under `.noir/`. Edit `.noir/NOIR.md`\n * > (project brief) + `.noir/rules/RULES.md` (working contract); re-run\n * > `noir sync` to re-emit host files.\n *\n * @.noir/NOIR.md\n * @.noir/rules/RULES.md\n *\n * The inline 3-line fallback (I2) sits BEFORE the `@`-imports so AGENTS.md\n * readers that do NOT resolve `@`-imports (e.g. plain markdown viewers, some\n * GA dashboards) still get a one-glance summary of where to edit. The\n * `@`-imports remain canonical for the hosts that DO resolve them (Cursor,\n * Codex, Junie, …).\n *\n * `<projectName>` is derived from `ctx.root`'s basename — a best-effort label\n * for human readability. If the basename is empty/undeterminable, falls back to\n * the literal `project` so the heading is never malformed.\n */\nexport function emitAgentsMd(ctx: EmitContext): string {\n const projectName = deriveProjectName(ctx.root);\n return (\n `# ${projectName} — Noir-managed agent context\\n\\n` +\n `> Generated by \\`noir\\`. Canonical source lives in \\`.noir/\\`. Edit there; re-run \\`noir sync\\`.\\n\\n` +\n `> Noir manages this project's context under \\`.noir/\\`. Edit \\`.noir/NOIR.md\\` ` +\n `(project brief) + \\`.noir/rules/RULES.md\\` (working contract); re-run \\`noir sync\\` ` +\n `to re-emit host files.\\n\\n` +\n `@.noir/NOIR.md\\n` +\n `@.noir/rules/RULES.md\\n`\n );\n}\n\n/** Derive a human-readable project label from the root path's basename.\n * Trims trailing slashes (POSIX) so `/p/demo/` → `demo`; falls back to the\n * literal `project` when the basename is empty (e.g. root was `/` or `''`). */\nfunction deriveProjectName(root: string): string {\n // `basename` already handles trailing slashes on POSIX; guard the empty case.\n const name = basename(root.replace(/\\/+$/, '')) || basename(root);\n return name && name.length > 0 ? name : 'project';\n}\n","import type { IntegrationMcpEmission, McpConfigOptions } from './types.js';\n\n/**\n * Build the host MCP config JSON string — the `{mcpServers: {...}}` shape shared\n * by claude (`.mcp.json`), agents-md (`.mcp.json`), gemini (`.gemini/mcp.json`),\n * and cursor (`.cursor/mcp.json`). OpenCode uses a DIFFERENT shape (an `mcp`\n * block with `type`-tagged entries) and does NOT use this helper — see\n * `opencode.ts`.\n *\n * The Noir server entry is always present; an optional integration entry merges\n * alongside it (per the Slice X adapter contract — only `external-mcp`\n * integrations surface a `hostMcp` block by the time it reaches here).\n *\n * Refactored out of `claude.ts` in S10-Adapters so every `{mcpServers}`-shape\n * host emits byte-identical JSON. Claude's `emitMcpConfig` now delegates here\n * (the claude.test.ts + create/scaffold.test.ts parity gates must hold).\n *\n * Stdio entry: `{ command, args }`.\n * HTTP entry : `{ type: 'http', url, [env] }` — env nested under `env:`, never\n * spread at the entry top level (would corrupt the server-field shape).\n */\nexport function buildMcpServersJson(\n opts: McpConfigOptions,\n integration?: IntegrationMcpEmission,\n): string {\n // Always present: the Noir MCP server — `noir mcp serve --stdio` or the\n // streamable-http endpoint. The placeholder URL (`:0`) is a best-effort hint\n // the user edits; same behavior as the original claude implementation.\n const noirServer =\n opts.transport === 'stdio'\n ? { command: 'noir', args: ['mcp', 'serve', '--stdio'] }\n : { type: 'http', url: opts.url ?? 'http://127.0.0.1:0/mcp' };\n\n const mcpServers: Record<string, unknown> = { noir: noirServer };\n if (integration) {\n const entry =\n integration.transport === 'http'\n ? {\n type: 'http',\n url: integration.url ?? '',\n // Nest env under `env:` (NOT spread at the entry top level) so the\n // emitted shape matches the stdio branch + Claude's spec. Top-level\n // spread would leak env keys as server fields.\n ...(integration.env ? { env: integration.env } : {}),\n }\n : {\n command: integration.command,\n ...(integration.args ? { args: integration.args } : {}),\n ...(integration.env ? { env: integration.env } : {}),\n };\n mcpServers[integration.serverName] = entry;\n }\n return JSON.stringify({ mcpServers }, null, 2);\n}\n","import { join } from 'node:path';\nimport { CONTEXT_BLOCK_BEGIN, CONTEXT_BLOCK_END, RULES_BLOCK } from '@noir-ai/core';\nimport { buildMcpServersJson } from './mcp.js';\nimport type {\n EmitContext,\n HostAdapter,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\nexport const claudeAdapter: HostAdapter = {\n id: 'claude',\n emitMcpConfig(_ctx, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string {\n // Delegates to the shared `{mcpServers}` builder. Behavior-identical to the\n // pre-S10 inline implementation — the claude.test.ts + create/scaffold.test.ts\n // byte-equality parity gates must hold (Slice X integration merge preserved).\n return buildMcpServersJson(opts, integration);\n },\n emitContext(_ctx: EmitContext): string {\n return `${CONTEXT_BLOCK_BEGIN}\\n@import \".noir/NOIR.md\"\\n${CONTEXT_BLOCK_END}\\n`;\n },\n emitRules(_ctx: EmitContext): string {\n return `${RULES_BLOCK.begin}\\n@import \".noir/rules/RULES.md\"\\n${RULES_BLOCK.end}\\n`;\n },\n skillsDir(ctx: EmitContext): string {\n return join(ctx.root, '.claude', 'skills');\n },\n};\n","import { join } from 'node:path';\nimport { emitAgentsMd } from './agents-md.js';\nimport { buildMcpServersJson } from './mcp.js';\nimport type {\n EmitContext,\n HostAdapter,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/**\n * The `cursor` host adapter — Cursor. Cursor reads the universal `AGENTS.md`\n * for context (same content as every other host); that file's\n * `@.noir/rules/RULES.md` import IS the Noir working-rules surface for cursor\n * (NO separate `.cursor/rules/noir-contract.mdc` host-rules pointer — that\n * file was REMOVED: it was `noir-`-prefixed and the C3 cursor flat-skill prune\n * in `emitSkillsToDir` deleted it on every `noir init/create/sync --host\n * cursor`). Skills compile to FLAT `.mdc` in `.cursor/rules/` via\n * `compileSkill(_, 'cursor')` (one file per skill, no per-name subdir). MCP\n * config lands at `.cursor/mcp.json`.\n *\n * `emitContext` returns the universal AGENTS.md content (the single native\n * context surface for cursor). There is NO `emitRules` here — cursor's rules\n * are delivered via AGENTS.md's `@.noir/rules/RULES.md` import, identical to\n * agents-md/opencode.\n */\nexport const cursorAdapter: HostAdapter = {\n id: 'cursor',\n emitMcpConfig(_ctx, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string {\n return buildMcpServersJson(opts, integration);\n },\n emitContext(ctx: EmitContext): string {\n // Cursor reads AGENTS.md — same universal content as every other host.\n return emitAgentsMd(ctx);\n },\n skillsDir(ctx: EmitContext): string {\n // Skills compile to `.mdc` here (via `compileSkill(_, 'cursor')` in skills).\n return join(ctx.root, '.cursor', 'rules');\n },\n mcpConfigPath(ctx: EmitContext): string {\n return join(ctx.root, '.cursor', 'mcp.json');\n },\n agentsMdPath(ctx: EmitContext): string {\n return join(ctx.root, 'AGENTS.md');\n },\n};\n","import { join } from 'node:path';\nimport { CONTEXT_BLOCK_BEGIN, CONTEXT_BLOCK_END, RULES_BLOCK } from '@noir-ai/core';\nimport { buildMcpServersJson } from './mcp.js';\nimport type {\n EmitContext,\n HostAdapter,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/**\n * The `gemini` host adapter — Gemini CLI. Emits `GEMINI.md` (Gemini's native\n * context file) carrying BOTH the context block AND the rules block — Gemini\n * has no separate rules file. Uses Gemini's `@file` import syntax: bare\n * `@.noir/NOIR.md` (no `@import` keyword, no quotes — distinct from Claude's\n * `@import \".noir/...\"` form). The cli ALSO emits a root `AGENTS.md` via the\n * shared `emitAgentsMd` helper (Gemini reads AGENTS.md too).\n *\n * Managed-block form (CONTEXT_BLOCK + RULES_BLOCK markers) is used so user\n * content outside the markers survives `noir sync` rewrites — consistent with\n * the claude adapter's contract.\n *\n * MCP config lands at `.gemini/mcp.json` (workspace-level — the portable\n * choice; `~/.gemini/settings.json` is the global alternative, documented).\n */\nexport const geminiAdapter: HostAdapter = {\n id: 'gemini',\n emitMcpConfig(_ctx, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string {\n return buildMcpServersJson(opts, integration);\n },\n emitContext(_ctx: EmitContext): string {\n // Gemini's `@file` import: bare `@.noir/...` (no `@import`, no quotes).\n // Rules folded into the same GEMINI.md (per spec — Gemini has no separate\n // rules file). Both blocks are marker-wrapped so user content survives sync.\n return (\n `${CONTEXT_BLOCK_BEGIN}\\n@.noir/NOIR.md\\n${CONTEXT_BLOCK_END}\\n` +\n `${RULES_BLOCK.begin}\\n@.noir/rules/RULES.md\\n${RULES_BLOCK.end}\\n`\n );\n },\n // No `emitRules` — rules folded into emitContext (GEMINI.md carries both).\n // No `skillsDir` — no skill concept; GEMINI.md + AGENTS.md are the surface.\n mcpConfigPath(ctx: EmitContext): string {\n return join(ctx.root, '.gemini', 'mcp.json');\n },\n agentsMdPath(ctx: EmitContext): string {\n return join(ctx.root, 'AGENTS.md');\n },\n};\n","import { join } from 'node:path';\nimport { emitAgentsMd } from './agents-md.js';\nimport type {\n EmitContext,\n HostAdapter,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/**\n * The `opencode` host adapter — OpenCode. Reads the universal `AGENTS.md`\n * (context + rules unified — no separate rules emission, no skill concept).\n *\n * The DIFFERENCE from the `{mcpServers}` family lives in MCP config: OpenCode's\n * root `opencode.json` carries an `mcp` block whose entries are `type`-tagged —\n * `{ type: 'local', command: [...] }` for stdio (note: `command` is an ARRAY,\n * not the claude `{command, args}` split) and `{ type: 'remote', url }` for\n * remote (HTTP/SSE). Server env vars land under the `environment` key (NOT\n * `env`). The `$schema` key pins the opencode config schema. See\n * https://opencode.ai/docs/mcp-servers/ + https://opencode.ai/config.json.\n *\n * Verified verbatim from the opencode docs (\"Add remote MCP servers by setting\n * `type` to `\\\"remote\\\"`\"); the prior `type:'http'` / `env:` shape was wrong.\n */\nexport const opencodeAdapter: HostAdapter = {\n id: 'opencode',\n emitMcpConfig(_ctx, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string {\n // OpenCode's `mcp` block — entries carry an explicit `type` tag (verified\n // against https://opencode.ai/docs/mcp-servers/):\n // - stdio → { type: 'local', command: [...] } (command is an ARRAY)\n // - remote → { type: 'remote', url: ... } (HTTP/SSE — NOT 'http')\n // Server env vars land under `environment:` (NOT `env:` — opencode's own\n // spelling). Noir server always present; optional integration merges\n // alongside under its `serverName`. (Does NOT use buildMcpServersJson —\n // different shape; that helper's `env:` is correct for `.mcp.json` /\n // `.cursor/mcp.json` / `.gemini/mcp.json`.)\n const mcp: Record<string, unknown> = {\n noir:\n opts.transport === 'stdio'\n ? { type: 'local', command: ['noir', 'mcp', 'serve', '--stdio'] }\n : { type: 'remote', url: opts.url ?? 'http://127.0.0.1:0/mcp' },\n };\n if (integration) {\n mcp[integration.serverName] =\n integration.transport === 'http'\n ? {\n type: 'remote',\n url: integration.url ?? '',\n ...(integration.env ? { environment: integration.env } : {}),\n }\n : {\n type: 'local',\n command: [integration.command, ...(integration.args ?? [])],\n ...(integration.env ? { environment: integration.env } : {}),\n };\n }\n return JSON.stringify({ $schema: 'https://opencode.ai/config.json', mcp }, null, 2);\n },\n emitContext(ctx: EmitContext): string {\n return emitAgentsMd(ctx);\n },\n // No `emitRules` — rules live IN AGENTS.md (the @-import covers RULES.md).\n // No `skillsDir` — no skill concept.\n mcpConfigPath(ctx: EmitContext): string {\n return join(ctx.root, 'opencode.json');\n },\n agentsMdPath(ctx: EmitContext): string {\n return join(ctx.root, 'AGENTS.md');\n },\n};\n","import { agentsMdAdapter } from './agents-md-adapter.js';\nimport { claudeAdapter } from './claude.js';\nimport { cursorAdapter } from './cursor.js';\nimport { geminiAdapter } from './gemini.js';\nimport { opencodeAdapter } from './opencode.js';\nimport type { HostAdapter, HostId } from './types.js';\n\nexport { AGENTS_MD_FILENAME, emitAgentsMd } from './agents-md.js';\nexport { agentsMdAdapter } from './agents-md-adapter.js';\nexport { claudeAdapter } from './claude.js';\nexport { cursorAdapter } from './cursor.js';\nexport { geminiAdapter } from './gemini.js';\nexport { buildMcpServersJson } from './mcp.js';\nexport { opencodeAdapter } from './opencode.js';\nexport type {\n EmitContext,\n HostAdapter,\n HostId,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/** The readonly list of supported hosts — derived from the `HostId` union so it\n * stays in lockstep with the type. Consumers (cli `--host` flag, doctor\n * reporting) use this for \"is this host valid?\" + iteration. Order is the\n * declaration order in `HostId` (claude first — the default).\n *\n * `Object.freeze` enforces the `readonly` type at runtime — a stray\n * `SUPPORTED_HOSTS.push('qwen')` from a JS caller fails loudly instead of\n * silently corrupting the registry list (TS already prevents it in typed code). */\nexport const SUPPORTED_HOSTS: readonly HostId[] = Object.freeze([\n 'claude',\n 'agents-md',\n 'gemini',\n 'cursor',\n 'opencode',\n]);\n\n/**\n * Resolve a host id to its `HostAdapter`. The registry is a `Record<HostId,\n * HostAdapter>` so the type system enforces completeness — adding a host to\n * `HostId` requires wiring it here (TS errors otherwise). The CLI uses this\n * indirection instead of importing adapters directly so adding a host needs NO\n * CLI edits beyond the `--host` flag's enum.\n *\n * S10-Adapters: all five hosts are now wired —\n * - `claude` — CLAUDE.md + `.claude/skills/` + `.mcp.json` (regression anchor).\n * - `agents-md` — universal AGENTS.md (the 32-platform baseline).\n * - `gemini` — GEMINI.md + AGENTS.md + `.gemini/mcp.json`.\n * - `cursor` — AGENTS.md + `.cursor/rules/*.mdc` + `.cursor/mcp.json`.\n * - `opencode` — AGENTS.md + `opencode.json` (different MCP shape).\n *\n * Unknown/non-`HostId` strings are impossible to pass at compile time (the\n * signature accepts only `HostId`); the runtime fallback is defensive — a\n * JS caller ignoring types still gets a clear error, not a silent `undefined`.\n */\nexport function resolveAdapter(host: HostId): HostAdapter {\n switch (host) {\n case 'claude':\n return claudeAdapter;\n case 'agents-md':\n return agentsMdAdapter;\n case 'gemini':\n return geminiAdapter;\n case 'cursor':\n return cursorAdapter;\n case 'opencode':\n return opencodeAdapter;\n default: {\n // Exhaustiveness guard — if `HostId` gains a member and this switch is\n // not updated, TS narrows `host` to `never` here. At runtime (untyped JS\n // callers) we still surface a clear message.\n const _exhaustive: never = host;\n throw new Error(`Unsupported host: ${String(_exhaustive)} (not in registry)`);\n }\n }\n}\n"],"mappings":"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","names":["join","join","join","join","join","CONTEXT_BLOCK_BEGIN","CONTEXT_BLOCK_END","RULES_BLOCK","CONTEXT_BLOCK_BEGIN","CONTEXT_BLOCK_END","RULES_BLOCK","join","join","join"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noir-ai/adapters",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0-beta.1",
|
|
4
4
|
"description": "Noir adapters — the HostAdapter abstraction and the Claude Code adapter (the v1 host).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "agaaaptr",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"README.md"
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@noir-ai/core": "1.
|
|
46
|
+
"@noir-ai/core": "1.2.0-beta.1"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/node": "^26.1.1"
|