cherry-works 0.1.0 → 0.2.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/main.js CHANGED
@@ -6,7 +6,7 @@ import { pathToFileURL as pathToFileURL2 } from "url";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "cherry-works",
9
- version: "0.1.0",
9
+ version: "0.2.1",
10
10
  description: "The charter engine: resolves a charter and compiles it into what a coding agent reads.",
11
11
  keywords: [
12
12
  "claude-code",
@@ -93,6 +93,7 @@ var package_default = {
93
93
  dependencies: {
94
94
  "@hono/node-server": "^2.1.1",
95
95
  "@hono/zod-openapi": "^1.6.3",
96
+ "@modelcontextprotocol/sdk": "^1.31.0",
96
97
  hono: "^4.13.7",
97
98
  picomatch: "^4.0.7",
98
99
  prompts: "^2.4.2",
@@ -105,8 +106,8 @@ var package_default = {
105
106
  // src/driver/cli/Commander.ts
106
107
  import yargs from "yargs";
107
108
 
108
- // src/hexagon/domain/models/Fault.ts
109
- var Fault = class extends Error {
109
+ // src/hexagon/domain/models/DomainFault.ts
110
+ var DomainFault = class extends Error {
110
111
  constructor(message, fix, severity = "error") {
111
112
  super(message);
112
113
  this.fix = fix;
@@ -116,17 +117,17 @@ var Fault = class extends Error {
116
117
  fix;
117
118
  severity;
118
119
  };
119
- var CharterRootFault = class extends Fault {
120
+ var CharterRootFault = class extends DomainFault {
120
121
  };
121
- var SettingsFault = class extends Fault {
122
+ var SettingsFault = class extends DomainFault {
122
123
  };
123
- var CharterPrimitiveFault = class extends Fault {
124
+ var CharterPrimitiveFault = class extends DomainFault {
124
125
  };
125
- var TestSuiteFault = class extends Fault {
126
+ var TestSuiteFault = class extends DomainFault {
126
127
  };
127
- var TestCaseFault = class extends Fault {
128
+ var TestCaseFault = class extends DomainFault {
128
129
  };
129
- var VendorFault = class extends Fault {
130
+ var VendorFault = class extends DomainFault {
130
131
  };
131
132
  function throwAggregateError(faults2) {
132
133
  throw new AggregateError(faults2, faults2.map((one) => one.message).join(" "));
@@ -197,14 +198,32 @@ function matches(glob, path) {
197
198
  }
198
199
 
199
200
  // src/hexagon/domain/models/charter/primitive/Primitive.ts
200
- import { z as z4 } from "zod";
201
+ import { z as z5 } from "zod";
201
202
 
202
203
  // src/hexagon/domain/models/charter/primitive/BasePrimitive.ts
203
204
  import { z } from "zod";
204
205
 
205
206
  // src/hexagon/domain/models/helper.ts
206
207
  function formatFrontmatterValue(value) {
207
- return Array.isArray(value) ? `[${value.map((one) => JSON.stringify(one)).join(", ")}]` : String(value);
208
+ if (Array.isArray(value)) return `[${value.map((one) => JSON.stringify(one)).join(", ")}]`;
209
+ const line = String(value);
210
+ return YAML_READS_OTHERWISE.test(line) ? JSON.stringify(line) : line;
211
+ }
212
+ var YAML_READS_OTHERWISE = /^[-?:,[\]{}#&*!|>'"%@`]|: | #|:$|^\s|\s$|^$|^(true|false|yes|no|on|off|null|~)$|^[-+]?(\d[\d_]*)?\.?\d+([eE][-+]?\d+)?$/i;
213
+ function shortenStringsOf(strings2) {
214
+ const shortStrings = {};
215
+ const takenShortStrings = /* @__PURE__ */ new Set();
216
+ for (const string of [...new Set(strings2)].sort()) {
217
+ const firstSegment = string.slice(string.indexOf(":") + 1).split("/")[0];
218
+ let hash = 2166136261;
219
+ for (const char of string) hash = Math.imul(hash ^ char.charCodeAt(0), 16777619);
220
+ const hashedString = `${firstSegment}_${(hash >>> 0).toString(16).padStart(8, "0").slice(0, 4)}`;
221
+ let shortString = hashedString;
222
+ for (let count = 2; takenShortStrings.has(shortString); count++) shortString = `${hashedString}_${count}`;
223
+ takenShortStrings.add(shortString);
224
+ shortStrings[string] = shortString;
225
+ }
226
+ return shortStrings;
208
227
  }
209
228
 
210
229
  // src/hexagon/domain/models/charter/primitive/BasePrimitive.ts
@@ -212,7 +231,12 @@ var DELIMITER = "---";
212
231
  var goodLine = z.string().regex(/\S/);
213
232
  var goodArray = z.array(goodLine).min(1).readonly();
214
233
  var CommonHeaders = z.object({
215
- id: z.string().regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/),
234
+ /** Slugs joined by `/`, so identities can be grouped by team or domain
235
+ * (FR-141). The `/` puts the file in folders and nothing else: nothing is
236
+ * read from where a file sits. At most 40 characters, so every name a host
237
+ * is given for it — an mcp's tools among them, `mcp__cw__mcp-<id>__<tool>` —
238
+ * stays within what the host takes (FR-157). */
239
+ id: z.string().regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\/[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/).refine((id) => id.length <= 40, { message: `"id" is at most 40 characters.` }),
216
240
  /** All an agent reads of a primitive before opening its body. */
217
241
  description: goodLine,
218
242
  tags: z.array(goodLine).readonly().optional(),
@@ -256,14 +280,24 @@ var BasePrimitive = class {
256
280
  * The kind leads, since it is what decides how the rest is read (FR-003). A
257
281
  * list is written the way the parser gives it back, so an author opening the
258
282
  * file finds what they declared rather than a second spelling of it.
283
+ *
284
+ * Written as its author wrote it unless the compiler says otherwise (FR-139):
285
+ * `mixins` lend their bodies, before this one's own so it reads as the point
286
+ * and theirs as the setting — nothing is merged and no header moves (FR-006);
287
+ * and `idReplacer` rewrites the body as a host reads it, a place named as
288
+ * `mcp:<id>` as the host calls its tools (FR-147).
259
289
  */
260
- toMarkdown() {
290
+ toMarkdown({
291
+ mixins = [],
292
+ idReplacer = (body) => body
293
+ } = {}) {
294
+ const body = [...mixins.map((mixin) => mixin.body), this.body].filter((one) => one.trim() !== "").join("\n\n");
261
295
  return [
262
296
  DELIMITER,
263
297
  ...Object.entries({ kind: this.kind, ...this.headers }).map(([field, value]) => `${field}: ${formatFrontmatterValue(value)}`),
264
298
  DELIMITER,
265
299
  "",
266
- this.body
300
+ idReplacer(body)
267
301
  ].join("\n").replace(/\n*$/, "\n");
268
302
  }
269
303
  /**
@@ -284,10 +318,11 @@ function headersOf(sampledKind, headerSchema, record) {
284
318
  const parsed = headerSchema.safeParse(record);
285
319
  if (parsed.success) return parsed.data;
286
320
  const { kind, sample } = sampledKind;
321
+ const refinementMessages = parsed.error.issues.filter((issue) => issue.code === "custom").map((issue) => issue.message);
287
322
  const sampleLines = Object.entries({ kind, ...sample }).map(([field, value]) => `${field}: ${formatFrontmatterValue(value)}`);
288
323
  return throwAggregateError([
289
324
  new CharterPrimitiveFault(
290
- `These headers are not what a ${kind} holds.`,
325
+ refinementMessages.length > 0 ? refinementMessages.join(" ") : `These headers are not what a ${kind} holds.`,
291
326
  `Write them the way this ${kind} does:
292
327
  ${sampleLines.join("\n")}`
293
328
  )
@@ -312,12 +347,13 @@ var AgentPrimitive = class _AgentPrimitive extends BasePrimitive {
312
347
  /** When a reader of this charter is to open this kind at all, said
313
348
  * where the kind's contract is: the neutral surface lists one
314
349
  * line per kind and none of them is written down twice (FR-002). */
315
- static activatesWhen = "it is spawned by identity, holding the `tools` it lists and nothing else";
316
- /** An agent as an author writes one: what a refused header is fixed with. */
350
+ static activatesWhen = "it is spawned by identity, holding the `tools` it lists and nothing else \u2014 a place as `mcp:<id>`, or one of its tools as `mcp:<id>:<tool>`";
351
+ /** An agent as an author writes one: what a refused header is fixed with.
352
+ * It holds one tool of a place the way an author writes it (FR-156). */
317
353
  static sample = {
318
354
  id: "reviewer",
319
355
  description: "Review a diff and name what is wrong with it.",
320
- tools: ["Read", "Grep"]
356
+ tools: ["Read", "Grep", "mcp:mfbs/billing:search_code"]
321
357
  };
322
358
  /** One agent, or every fault its headers have (FR-004). */
323
359
  static of(record, body) {
@@ -325,34 +361,6 @@ var AgentPrimitive = class _AgentPrimitive extends BasePrimitive {
325
361
  }
326
362
  };
327
363
 
328
- // src/hexagon/domain/models/charter/primitive/CommandPrimitive.ts
329
- var CommandPrimitive = class _CommandPrimitive extends BasePrimitive {
330
- static kind = "command";
331
- kind = _CommandPrimitive.kind;
332
- /** What this kind requires beyond the common headers: nothing. Declared all
333
- * the same, since what a file is scaffolded with is read off this (FR-039). */
334
- static requires = {};
335
- /** The schema its headers are read by: what `headersOf` refuses a file
336
- * against, and what says the shape of every header this kind takes, required
337
- * or not (FR-004). */
338
- static schema = CommonHeaders;
339
- /** When a reader of this charter is to open this kind at all, said
340
- * where the kind's contract is: the neutral surface lists one
341
- * line per kind and none of them is written down twice (FR-002). */
342
- static activatesWhen = "it is asked for by name, or a request matches what it describes itself as being for";
343
- /** A command as an author writes one: what a refused header is fixed with. */
344
- static sample = {
345
- id: "release",
346
- description: "Cut a release from the main branch."
347
- };
348
- /** One command, or every fault its headers have. Nothing beyond what every
349
- * primitive declares: a command is asked for by name, and the one line it
350
- * describes itself with is what decides that it is the one being asked for. */
351
- static of(record, body) {
352
- return new _CommandPrimitive(headersOf(_CommandPrimitive, CommonHeaders, record), body);
353
- }
354
- };
355
-
356
364
  // src/hexagon/domain/models/charter/primitive/CorpusPrimitive.ts
357
365
  var CorpusHeaders = CommonHeaders.extend({});
358
366
  var CorpusPrimitive = class _CorpusPrimitive extends BasePrimitive {
@@ -410,11 +418,109 @@ var GuidePrimitive = class _GuidePrimitive extends BasePrimitive {
410
418
  }
411
419
  };
412
420
 
413
- // src/hexagon/domain/models/charter/primitive/MixinPrimitive.ts
421
+ // src/hexagon/domain/models/charter/primitive/McpPrimitive.ts
414
422
  import { z as z2 } from "zod";
423
+ var MCP_AUTHS = ["oauth", "token"];
424
+ function isSecureEndpoint(endpoint) {
425
+ if (!URL.canParse(endpoint)) return false;
426
+ const endpointURL = new URL(endpoint);
427
+ return endpointURL.protocol === "https:" || endpointURL.protocol === "http:" && ["127.0.0.1", "localhost"].includes(endpointURL.hostname);
428
+ }
429
+ var MCP_MENTION = /\bmcp:([a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\/[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*)/g;
430
+ var MCP_TOOL_REFERENCE = /^(mcp:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\/[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*)(?::([^:\s]+))?$/;
431
+ var McpHeaders = CommonHeaders.extend({
432
+ /** Reached over HTTP. Plain `http` only to this machine, for a server under
433
+ * test: anything else would send a developer's credential in the clear. */
434
+ endpoint: goodLine.refine(isSecureEndpoint, {
435
+ message: `"endpoint" is an https:// URL, or http:// to 127.0.0.1 or localhost for a server under test.`
436
+ }).optional(),
437
+ /** Started as a local process, spoken to over its standard input and output. */
438
+ command: goodLine.optional(),
439
+ args: z2.array(goodLine).readonly().optional(),
440
+ auth: z2.array(z2.enum(MCP_AUTHS)).min(1).readonly().optional(),
441
+ /** The environment variable a command reads its token from. */
442
+ tokenEnv: z2.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).optional(),
443
+ /** The place inside that server: a repository, a database, a channel. */
444
+ path: goodLine.optional(),
445
+ /** The server's tools the agent may use there, by the server's own names. */
446
+ tools: goodArray
447
+ }).superRefine((headers, context) => {
448
+ const addFault = (message) => context.addIssue({ code: "custom", message });
449
+ const { endpoint, command, args, auth: auth2, tokenEnv } = headers;
450
+ if (endpoint !== void 0 && command !== void 0)
451
+ return addFault(`This declares both "endpoint" and "command"; an mcp is one server, reached one way.`);
452
+ if (endpoint === void 0 && command === void 0)
453
+ return addFault(`This declares neither "endpoint" nor "command", so there is no server to reach.`);
454
+ if (endpoint !== void 0) {
455
+ if (auth2 === void 0) addFault(`An endpoint says how a developer signs in to it: "auth" of oauth, token or both.`);
456
+ if (args !== void 0) addFault(`"args" are a command's; an endpoint takes none.`);
457
+ if (tokenEnv !== void 0) addFault(`"tokenEnv" is where a command reads its token; an endpoint takes none.`);
458
+ return;
459
+ }
460
+ const takesToken = auth2 !== void 0;
461
+ if (takesToken && (auth2.length !== 1 || auth2[0] !== "token"))
462
+ addFault(`A command signs in by token alone: "auth: [token]", or no "auth" for one that takes none.`);
463
+ if (takesToken && tokenEnv === void 0) addFault(`A command taking a token names where it reads it: "tokenEnv".`);
464
+ if (!takesToken && tokenEnv !== void 0) addFault(`"tokenEnv" is read only by a command taking a token: add "auth: [token]", or drop it.`);
465
+ });
466
+ var McpPrimitive = class _McpPrimitive extends BasePrimitive {
467
+ static kind = "mcp";
468
+ kind = _McpPrimitive.kind;
469
+ /** What this kind requires beyond the common headers. `endpoint` or
470
+ * `command` is required too, but which depends on the shape, so the schema's
471
+ * refinement says it rather than this (FR-004, FR-039). */
472
+ static requires = { tools: "list" };
473
+ /** The schema its headers are read by: what `headersOf` refuses a file
474
+ * against, and what says the shape of every header this kind takes, required
475
+ * or not (FR-004). */
476
+ static schema = McpHeaders;
477
+ /** When a reader of this charter is to open this kind at all, said
478
+ * where the kind's contract is: the neutral surface lists one
479
+ * line per kind and none of them is written down twice (FR-002). */
480
+ static activatesWhen = "a primitive's body names it as `mcp:<id>` \u2014 a place outside the repository, reached through `cw mcp serve`";
481
+ /** An mcp reached at an endpoint, as an author writes one: what a refused
482
+ * header is fixed with. */
483
+ static sample = {
484
+ id: "mfbs/billing",
485
+ description: "Billing service code and pull requests.",
486
+ endpoint: "https://api.githubcopilot.com/mcp/",
487
+ path: "moneyforward/billing-service",
488
+ auth: ["oauth", "token"],
489
+ tools: ["get_file_contents", "search_code", "list_pull_requests"]
490
+ };
491
+ /** The same place started as a local process: what a refused file declaring
492
+ * `command` is held up against, since that is the shape it was nearer. */
493
+ static commandSample = {
494
+ id: "mfbs/billing",
495
+ description: "Billing service code and pull requests.",
496
+ command: "npx",
497
+ args: ["-y", "@modelcontextprotocol/server-github"],
498
+ tokenEnv: "GITHUB_PERSONAL_ACCESS_TOKEN",
499
+ auth: ["token"],
500
+ path: "moneyforward/billing-service",
501
+ tools: ["get_file_contents", "search_code", "list_pull_requests"]
502
+ };
503
+ /** What a developer signs in to, and what places are told apart by beside
504
+ * their `path`: the endpoint, or the command followed by each of its
505
+ * arguments. The refinement has already held that one of the two is here
506
+ * (FR-142). */
507
+ get address() {
508
+ const { endpoint, command, args = [] } = this.headers;
509
+ return endpoint ?? [command, ...args].join(" ");
510
+ }
511
+ /** One mcp, or every fault its headers have, shown the sample of the shape
512
+ * it declared (FR-004, FR-142). */
513
+ static of(record, body) {
514
+ const nearerSample = record.command === void 0 ? _McpPrimitive.sample : _McpPrimitive.commandSample;
515
+ return new _McpPrimitive(headersOf({ kind: _McpPrimitive.kind, sample: nearerSample }, McpHeaders, record), body);
516
+ }
517
+ };
518
+
519
+ // src/hexagon/domain/models/charter/primitive/MixinPrimitive.ts
520
+ import { z as z3 } from "zod";
415
521
  var MixinHeaders = CommonHeaders.extend({
416
522
  /** Never: reading refuses a mixin that declares one. */
417
- mixins: z2.undefined().optional()
523
+ mixins: z3.undefined().optional()
418
524
  });
419
525
  var MixinPrimitive = class _MixinPrimitive extends BasePrimitive {
420
526
  static kind = "mixin";
@@ -460,7 +566,7 @@ var PlaybookPrimitive = class _PlaybookPrimitive extends BasePrimitive {
460
566
  /** When a reader of this charter is to open this kind at all, said
461
567
  * where the kind's contract is: the neutral surface lists one
462
568
  * line per kind and none of them is written down twice (FR-002). */
463
- static activatesWhen = "one of its `triggers` matches; the body is a sequence of commands";
569
+ static activatesWhen = "one of its `triggers` matches; the body is a sequence of skills";
464
570
  /** A playbook as an author writes one: what a refused header is fixed with. */
465
571
  static sample = {
466
572
  id: "ship-a-feature",
@@ -512,7 +618,7 @@ var PosturePrimitive = class _PosturePrimitive extends BasePrimitive {
512
618
  };
513
619
 
514
620
  // src/hexagon/domain/models/charter/primitive/SensorPrimitive.ts
515
- import { z as z3 } from "zod";
621
+ import { z as z4 } from "zod";
516
622
  var SIGNALS = [
517
623
  "PreToolUse",
518
624
  "PostToolUse",
@@ -526,7 +632,7 @@ var SIGNALS = [
526
632
  ];
527
633
  var SensorHeaders = CommonHeaders.extend({
528
634
  /** What raises it: one of the events every host of this engine speaks in. */
529
- signal: z3.enum(SIGNALS),
635
+ signal: z4.enum(SIGNALS),
530
636
  /** What is run when it does. A sensor that names no command is a sensor
531
637
  * nothing can fire, so this is required. */
532
638
  run: goodLine
@@ -599,16 +705,16 @@ var SkillPrimitive = class _SkillPrimitive extends BasePrimitive {
599
705
  var PRIMITIVE_CLASSES = [
600
706
  GuidePrimitive,
601
707
  SensorPrimitive,
602
- CommandPrimitive,
603
708
  SkillPrimitive,
604
709
  PlaybookPrimitive,
605
710
  AgentPrimitive,
606
711
  PosturePrimitive,
607
712
  CorpusPrimitive,
608
- MixinPrimitive
713
+ MixinPrimitive,
714
+ McpPrimitive
609
715
  ];
610
716
  var KINDS = Object.freeze(PRIMITIVE_CLASSES.map((one) => one.kind));
611
- var PRIMITIVE_IDENTITY_SCHEMA = z4.string().regex(/^[a-z0-9-]+:[a-z0-9-]+$/).transform((identity) => identity);
717
+ var PRIMITIVE_IDENTITY_SCHEMA = z5.string().regex(/^[a-z0-9-]+:[a-z0-9-]+(\/[a-z0-9-]+)*$/).transform((identity) => identity);
612
718
  function identityOf(kindAndId) {
613
719
  if (typeof kindAndId !== "string") return `${kindAndId.kind}:${kindAndId.id}`;
614
720
  const parsed = PRIMITIVE_IDENTITY_SCHEMA.safeParse(kindAndId);
@@ -650,13 +756,13 @@ function primitiveHeadersOf(kind) {
650
756
  const required = { ...BasePrimitive.requires, ...kindClass.requires };
651
757
  return Object.entries(kindClass.schema.shape).filter(([field]) => field !== "id").map(([field, type]) => {
652
758
  let heldType = type;
653
- while (!(heldType instanceof z4.ZodArray) && typeof heldType?.unwrap === "function")
759
+ while (!(heldType instanceof z5.ZodArray) && typeof heldType?.unwrap === "function")
654
760
  heldType = heldType.unwrap();
655
761
  return new PrimitiveHeader(
656
762
  field,
657
- heldType instanceof z4.ZodArray ? "list" : "line",
763
+ heldType instanceof z5.ZodArray ? "list" : "line",
658
764
  field in required,
659
- heldType instanceof z4.ZodEnum ? heldType.options : void 0
765
+ heldType instanceof z5.ZodEnum ? heldType.options : void 0
660
766
  );
661
767
  });
662
768
  }
@@ -731,11 +837,16 @@ var ScopedPrimitive = class {
731
837
  this.scope = scope;
732
838
  this.file = file;
733
839
  this.primitive = primitive;
840
+ this.normIdentity = identity.replace(/[:/]/g, "-");
734
841
  }
735
842
  identity;
736
843
  scope;
737
844
  file;
738
845
  primitive;
846
+ /** The identity as every name given to a host writes it, `:` and `/` as `-`:
847
+ * `guide-mfbs-no-any` for `guide:mfbs/no-any` (FR-141). Made here, once, so
848
+ * nothing that names a host's file or a place's tools spells it again. */
849
+ normIdentity;
739
850
  /** Whether a word someone searched for is anywhere a reader would look for
740
851
  * it: the identity, the kind, what it is for, when its kind comes up, its
741
852
  * file, or any header it declared — ignoring case, since whoever typed it
@@ -772,26 +883,6 @@ var CharterRoot = class {
772
883
  this.primitives.filter(({ primitive }) => primitive.kind === "mixin").map((one) => [one.primitive.headers.id, one.primitive])
773
884
  );
774
885
  }
775
- /**
776
- * What one primitive says once its mixins are in it: the body of every mixin
777
- * it pulls in, in the order it named them, and then its own (FR-006).
778
- *
779
- * The one place a mixin is applied at all. Nothing is merged and no header
780
- * moves — a mixin lends text, so what comes back is text, and the host's own
781
- * words come last so they read as the point and the lent ones as the setting.
782
- *
783
- * Asked of the charter because the charter is what already read the mixins
784
- * and knows which identity a name means across layers. A mixin nothing
785
- * answers to is left out rather than raised: `allFaultsByFiles` already names
786
- * that file, and a build refused for it never reaches here (FR-009).
787
- */
788
- bodyOf(one) {
789
- const mixins = this.mixins;
790
- return [
791
- ...(one.primitive.headers.mixins ?? []).map((named) => mixins.get(named)?.body),
792
- one.primitive.body
793
- ].filter((body) => body !== void 0 && body.trim() !== "").join("\n\n");
794
- }
795
886
  /**
796
887
  * Every primitive under the identity the whole charter names it by (FR-017).
797
888
  *
@@ -864,20 +955,36 @@ var CharterRoot = class {
864
955
  * rationale no corpus answers to, which is a warning rather than an error:
865
956
  * the rule holds without its reasoning, and the author is told the reasoning
866
957
  * is gone (FR-005). A corpus nobody cites and a mixin nobody pulls in are
867
- * warnings too: nothing breaks, and nothing reads them either (FR-014).
958
+ * warnings too: nothing breaks, and nothing reads them either (FR-014). So
959
+ * are a body naming an mcp nothing holds (FR-143) and an mcp no body names
960
+ * (FR-144). An agent holding an mcp nothing holds, or a tool it does not
961
+ * declare, is an error: the role would be promised a tool it cannot have
962
+ * (FR-156).
868
963
  */
869
964
  get compositeFaultsByFiles() {
870
965
  const everyPrimitive = this.primitives;
871
966
  const mixinsByIdentity = this.mixins;
872
967
  const corpusIdentities = this.corpora;
968
+ const toolsByMcpIdentity = new Map(
969
+ everyPrimitive.flatMap(({ identity, primitive }) => primitive instanceof McpPrimitive ? [[identity, primitive.headers.tools]] : [])
970
+ );
971
+ const mcpIdentities = new Set(toolsByMcpIdentity.keys());
972
+ const mcpsNamedBy = (one) => [...one.primitive.body.matchAll(MCP_MENTION)].map(([, id]) => `mcp:${id}`);
973
+ const namedMcps = new Set(
974
+ everyPrimitive.flatMap((one) => [
975
+ ...mcpsNamedBy(one),
976
+ ...one.primitive instanceof AgentPrimitive ? one.primitive.headers.tools.flatMap((tool) => tool.match(MCP_TOOL_REFERENCE)?.[1] ?? []) : []
977
+ ])
978
+ );
873
979
  const fileByIdentity = /* @__PURE__ */ new Map();
980
+ const identityByNormalizedIdentity = /* @__PURE__ */ new Map();
874
981
  const faultsByFiles = {};
875
982
  const addFault = (file, found) => {
876
983
  faultsByFiles[file] = [...faultsByFiles[file] ?? [], found];
877
984
  };
878
985
  for (const one of everyPrimitive) {
879
986
  const { file, primitive } = one;
880
- const { identity } = one;
987
+ const { identity, normIdentity } = one;
881
988
  const declaredIn = fileByIdentity.get(identity);
882
989
  if (declaredIn === void 0) fileByIdentity.set(identity, file);
883
990
  else
@@ -888,6 +995,16 @@ var CharterRoot = class {
888
995
  `Give this one an id of its own, or delete it if the other says the same thing. A vendor claiming an id you authored is one to raise with whoever publishes it. An id ${BUILTIN_PATH_PREFIX} claims is the engine's own and nobody can change it, so the one to rename is yours.`
889
996
  )
890
997
  );
998
+ const normalizedIdentityClaimedBy = identityByNormalizedIdentity.get(normIdentity);
999
+ if (normalizedIdentityClaimedBy === void 0) identityByNormalizedIdentity.set(normIdentity, identity);
1000
+ else if (normalizedIdentityClaimedBy !== identity)
1001
+ addFault(
1002
+ file,
1003
+ new CharterRootFault(
1004
+ `"${identity}" and "${normalizedIdentityClaimedBy}" are both named "${normIdentity}" in an agent's files, where "/" is written as "-". Only one of them would be written there.`,
1005
+ `Give this one an id that stays its own once "/" is written as "-".`
1006
+ )
1007
+ );
891
1008
  for (const namedMixin of primitive.headers.mixins ?? []) {
892
1009
  const mixin = mixinsByIdentity.get(namedMixin);
893
1010
  if (mixin === void 0) {
@@ -921,6 +1038,45 @@ var CharterRoot = class {
921
1038
  )
922
1039
  );
923
1040
  }
1041
+ for (const namedMcp of new Set(mcpsNamedBy(one)))
1042
+ if (!mcpIdentities.has(namedMcp))
1043
+ addFault(
1044
+ file,
1045
+ new CharterRootFault(
1046
+ `This names "${namedMcp}", and this charter holds no mcp of that identity. The agent would be told of a place it cannot reach.`,
1047
+ `Author that mcp, or correct the name. An mcp is named as "mcp:<id>", whichever layer authored it.`,
1048
+ "warn"
1049
+ )
1050
+ );
1051
+ if (primitive instanceof AgentPrimitive)
1052
+ for (const tool of primitive.headers.tools.filter((one2) => one2.startsWith("mcp:"))) {
1053
+ const [, mcpIdentity, mcpTool] = tool.match(MCP_TOOL_REFERENCE) ?? [];
1054
+ const declaredTools = mcpIdentity === void 0 ? void 0 : toolsByMcpIdentity.get(mcpIdentity);
1055
+ if (mcpIdentity === void 0)
1056
+ addFault(
1057
+ file,
1058
+ new CharterRootFault(
1059
+ `"${tool}" under "tools" is not a place this role can hold.`,
1060
+ `Write a place as "mcp:<id>" for every tool it declares, or "mcp:<id>:<tool>" for one.`
1061
+ )
1062
+ );
1063
+ else if (declaredTools === void 0)
1064
+ addFault(
1065
+ file,
1066
+ new CharterRootFault(
1067
+ `"${tool}" under "tools" names "${mcpIdentity}", and this charter holds no mcp of that identity.`,
1068
+ `Author that mcp, or correct the name. An mcp is named as "mcp:<id>", whichever layer authored it.`
1069
+ )
1070
+ );
1071
+ else if (mcpTool !== void 0 && !declaredTools.includes(mcpTool))
1072
+ addFault(
1073
+ file,
1074
+ new CharterRootFault(
1075
+ `"${tool}" under "tools" names a tool "${mcpIdentity}" does not declare; it declares ${declaredTools.join(", ")}.`,
1076
+ `Name one of those, or add "${mcpTool}" to the tools of "${mcpIdentity}".`
1077
+ )
1078
+ );
1079
+ }
924
1080
  if (primitive.kind === "corpus" && this.citersOf(one).length === 0)
925
1081
  addFault(
926
1082
  file,
@@ -939,6 +1095,32 @@ var CharterRoot = class {
939
1095
  "warn"
940
1096
  )
941
1097
  );
1098
+ if (primitive.kind === "mcp" && !namedMcps.has(identity))
1099
+ addFault(
1100
+ file,
1101
+ new CharterRootFault(
1102
+ `No primitive names "${identity}" in its body, so nothing an agent reads leads to it.`,
1103
+ `Name it where it is used, as "${identity}", or delete it.`,
1104
+ "warn"
1105
+ )
1106
+ );
1107
+ }
1108
+ const mcpPrimitives = everyPrimitive.flatMap(
1109
+ ({ identity, file, primitive }) => primitive.kind === McpPrimitive.kind ? [{ identity, file, primitive }] : []
1110
+ ).sort((one, another) => one.identity < another.identity ? -1 : 1);
1111
+ for (const { file, primitive } of mcpPrimitives) {
1112
+ const { tokenEnv } = primitive.headers;
1113
+ const conflictingMcp = mcpPrimitives.find(
1114
+ (other) => other.primitive.address === primitive.address && other.primitive.headers.tokenEnv !== void 0 && other.primitive.headers.tokenEnv !== tokenEnv
1115
+ );
1116
+ if (tokenEnv !== void 0 && conflictingMcp !== void 0)
1117
+ addFault(
1118
+ file,
1119
+ new CharterRootFault(
1120
+ `This hands "${primitive.address}" its token in "${tokenEnv}", and "${conflictingMcp.identity}" in "${conflictingMcp.primitive.headers.tokenEnv}". One process reads its token from one variable.`,
1121
+ `Name the variable that command reads in both, or drop "tokenEnv" and "auth" from one of them.`
1122
+ )
1123
+ );
942
1124
  }
943
1125
  return new FaultsByFile(faultsByFiles);
944
1126
  }
@@ -970,7 +1152,7 @@ function charterRootOf({
970
1152
  function folderURL(at) {
971
1153
  const url = new URL(at);
972
1154
  if (!url.pathname.endsWith("/"))
973
- throw new Fault(
1155
+ throw new DomainFault(
974
1156
  `"${url.href}" names a file, and a folder is what is wanted here.`,
975
1157
  `Write it with the separator a folder ends in, as in "${url.href}/".`
976
1158
  );
@@ -990,6 +1172,7 @@ function outFolderIn(repo) {
990
1172
  }
991
1173
  var CLAUDE_DIRECTORY = ".claude";
992
1174
  var CLAUDE_ENTRY_FILE = "CLAUDE.md";
1175
+ var CLAUDE_MCP_CONFIG_FILE = ".mcp.json";
993
1176
  function agentProviderFolderIn(repo, provider) {
994
1177
  switch (provider) {
995
1178
  case "claude":
@@ -1025,7 +1208,7 @@ function parseKVParams(typed, paramName) {
1025
1208
  for (const one of typed) {
1026
1209
  const at = one.indexOf("=");
1027
1210
  if (at <= 0)
1028
- throw new Fault(
1211
+ throw new DomainFault(
1029
1212
  `"--${paramName} ${one}" names nothing to put a value under.`,
1030
1213
  `Type each one as --${paramName} name=value, once per name.`
1031
1214
  );
@@ -1042,7 +1225,7 @@ function lines({ data: { severity, message, fix } }) {
1042
1225
  async function editInEditor(cwd, file) {
1043
1226
  const editorCommand = process.env.VISUAL || process.env.EDITOR;
1044
1227
  if (!editorCommand)
1045
- throw new Fault("Neither $VISUAL nor $EDITOR is set, so there is no editor to open it in.", 'Set one, as in "export EDITOR=vim".');
1228
+ throw new DomainFault("Neither $VISUAL nor $EDITOR is set, so there is no editor to open it in.", 'Set one, as in "export EDITOR=vim".');
1046
1229
  const exitCode = await new Promise(
1047
1230
  (resolve, reject) => spawn("sh", ["-c", `${editorCommand} "$1"`, editorCommand, `${cwd}/${file}`], { stdio: "inherit" }).once("error", reject).once("exit", resolve)
1048
1231
  );
@@ -1077,7 +1260,7 @@ var AddCommand = class {
1077
1260
  Object.entries(parseKVParams(header, "header")).map(([field, values]) => {
1078
1261
  if (lists.has(field)) return [field, values];
1079
1262
  if (values.length > 1)
1080
- throw new Fault(
1263
+ throw new DomainFault(
1081
1264
  `"${field}" holds one line, and was typed ${values.length} times.`,
1082
1265
  `Type "--header ${field}=..." once.`
1083
1266
  );
@@ -1114,7 +1297,7 @@ var AddCommand = class {
1114
1297
  */
1115
1298
  async #answered(required, kind) {
1116
1299
  if (!process.stdin.isTTY)
1117
- throw new Fault(
1300
+ throw new DomainFault(
1118
1301
  `A ${kind} requires ${required.map((one) => `"${one.field}"`).join(", ")}, and nobody is at the terminal to answer.`,
1119
1302
  `Run "cw add" where you can answer, or author the file under the charter yourself.`
1120
1303
  );
@@ -1130,7 +1313,7 @@ var AddCommand = class {
1130
1313
  )
1131
1314
  );
1132
1315
  if (required.some((one) => answers[one.field] === void 0))
1133
- throw new Fault("This was stopped before it had its answers, so nothing was written.", 'Run "cw add" again.');
1316
+ throw new DomainFault("This was stopped before it had its answers, so nothing was written.", 'Run "cw add" again.');
1134
1317
  return answers;
1135
1318
  }
1136
1319
  };
@@ -1249,7 +1432,7 @@ var EditCommand = class {
1249
1432
  async run({ cwd, charterAuthoringApp: charterAuthoringApp2 }, { identity }) {
1250
1433
  const { scopedPrimitive: scopedPrimitive2 } = (await charterAuthoringApp2.open(identity ?? "")).data;
1251
1434
  if (scopedPrimitive2.data.scope === BUILTIN_SCOPE)
1252
- throw new Fault(
1435
+ throw new DomainFault(
1253
1436
  `${scopedPrimitive2.data.identity} is built into cw, and there is no file of it in this repository to edit.`,
1254
1437
  `To differ from it, run "cw add" for a primitive of your own under an identity of its own.`
1255
1438
  );
@@ -1330,13 +1513,26 @@ var InitCommand = class {
1330
1513
  options = OPTIONS4;
1331
1514
  async run({ charterAuthoringApp: charterAuthoringApp2 }, { agent }) {
1332
1515
  const agents = await whichAgents(charterAuthoringApp2, agent);
1333
- await charterAuthoringApp2.init({ agents });
1516
+ const planSummaryDTO2 = await charterAuthoringApp2.init({ agents });
1517
+ const setUp = ["Set up this repository under a charter.", `Compiling for: ${agents.join(", ")}.`];
1518
+ if (planSummaryDTO2.type === "FaultsByFile") {
1519
+ const noOfFiles = Object.keys(planSummaryDTO2.data.files).length;
1520
+ return {
1521
+ code: EXIT_FAILURE,
1522
+ result: [...setUp, ""].join("\n"),
1523
+ problem: `Nothing was built: ${noOfFiles} file${noOfFiles === 1 ? " has" : "s have"} errors.
1524
+ Run "cw doctor" to see what is wrong with them.
1525
+ `
1526
+ };
1527
+ }
1528
+ const { added, edited } = planSummaryDTO2.data;
1529
+ const wrote = added.length + edited.length;
1334
1530
  return {
1335
1531
  code: EXIT_OK,
1336
1532
  result: [
1337
- "Set up this repository under a charter.",
1338
- `Compiling for: ${agents.join(", ")}.`,
1339
- 'Author your first rule under .cw/charter/guide/, then run "cw build".',
1533
+ ...setUp,
1534
+ `Built ${wrote} file${wrote === 1 ? "" : "s"}: ${added.length} added, ${edited.length} changed.`,
1535
+ 'Author your first rule under .cw/charter/guide/, or ask your agent to write one, then run "cw build".',
1340
1536
  ""
1341
1537
  ].join("\n")
1342
1538
  };
@@ -1345,20 +1541,21 @@ var InitCommand = class {
1345
1541
  async function whichAgents(charterAuthoringApp2, typed) {
1346
1542
  if (typed !== void 0) {
1347
1543
  if (!isAgentProvider(typed))
1348
- throw new Fault(
1544
+ throw new DomainFault(
1349
1545
  `This engine compiles for no agent called "${typed}".`,
1350
1546
  `Name any of: ${AGENT_PROVIDERS.join(", ")}.`
1351
1547
  );
1352
1548
  return [typed];
1353
1549
  }
1354
1550
  const agentsInSettings = (await charterAuthoringApp2.settings()).data.agents.filter(isAgentProvider);
1551
+ const defaultAgents = agentsInSettings.length > 0 ? agentsInSettings : AGENT_PROVIDERS.length === 1 ? AGENT_PROVIDERS : [];
1355
1552
  if (!process.stdin.isTTY) {
1356
- if (agentsInSettings.length === 0)
1357
- throw new Fault(
1553
+ if (defaultAgents.length === 0)
1554
+ throw new DomainFault(
1358
1555
  "Nobody is at the terminal to choose an agent, and this repository has none chosen yet.",
1359
1556
  `Run "cw init --agent <name>", naming one of: ${AGENT_PROVIDERS.join(", ")}.`
1360
1557
  );
1361
- return agentsInSettings;
1558
+ return defaultAgents;
1362
1559
  }
1363
1560
  const { chosen } = await prompts2({
1364
1561
  type: "multiselect",
@@ -1367,14 +1564,15 @@ async function whichAgents(charterAuthoringApp2, typed) {
1367
1564
  // What the settings already name arrives ticked, so a return keeps what this
1368
1565
  // repository is already running under and a second run is rechoosing rather
1369
1566
  // than starting over (FR-038). Nothing named yet — a repository being set up
1370
- // for the first time — arrives with nothing ticked.
1371
- choices: AGENT_PROVIDERS.map((one) => ({ title: one, value: one, selected: agentsInSettings.includes(one) })),
1567
+ // for the first time — arrives with the one agent ticked where this engine
1568
+ // compiles for one, and with nothing ticked otherwise (FR-055).
1569
+ choices: AGENT_PROVIDERS.map((one) => ({ title: one, value: one, selected: defaultAgents.includes(one) })),
1372
1570
  // Ticking none of them is not an answer: a charter compiles for at least one
1373
1571
  // agent, so the prompt holds the submit until one is ticked (FR-033).
1374
1572
  min: 1
1375
1573
  });
1376
1574
  if (chosen === void 0)
1377
- throw new Fault("Setup was stopped before it had its answers.", 'Run "cw init" again.');
1575
+ throw new DomainFault("Setup was stopped before it had its answers.", 'Run "cw init" again.');
1378
1576
  return chosen;
1379
1577
  }
1380
1578
 
@@ -1472,6 +1670,133 @@ function inFull(entry) {
1472
1670
  return [surveyed(entry), ` ${file}`, ...declared.length === 0 ? [] : [` ${declared.join(", ")}`]].join("\n");
1473
1671
  }
1474
1672
 
1673
+ // src/driver/cli/commands/McpAuthCommand.ts
1674
+ import prompts3 from "prompts";
1675
+ var OPTIONS6 = {
1676
+ identity: {
1677
+ type: "string",
1678
+ describe: 'One mcp whose address to sign in to again, whatever you were signed in as: "mcp:github/billing"'
1679
+ },
1680
+ status: {
1681
+ type: "boolean",
1682
+ default: false,
1683
+ describe: "List each address, the mcps at it and whether you are signed in there, and change nothing"
1684
+ }
1685
+ };
1686
+ var McpAuthCommand = class {
1687
+ name = "mcp auth [identity]";
1688
+ summary = "Sign in, as yourself, to every place the charter reaches";
1689
+ options = OPTIONS6;
1690
+ async run({ mcpConnectingApp: mcpConnectingApp2 }, { identity, status }) {
1691
+ const signInStatuses = (await mcpConnectingApp2.signInStatus()).map((one) => one.data);
1692
+ if (status)
1693
+ return {
1694
+ code: EXIT_OK,
1695
+ result: signInStatuses.length === 0 ? "No place the last build listed takes a sign-in.\n" : signInStatuses.map(
1696
+ ({ address, identities, signedIn, method }) => [address, ` ${identities.join(", ")}`, ` ${signedIn ? `signed in by ${method}` : "not signed in"}`, ""].join("\n")
1697
+ ).join("")
1698
+ };
1699
+ let unsignedStatuses;
1700
+ if (identity === void 0) unsignedStatuses = signInStatuses.filter((one) => !one.signedIn);
1701
+ else {
1702
+ const identityStatus = signInStatuses.find((one) => one.identities.includes(identity));
1703
+ if (identityStatus === void 0)
1704
+ throw new DomainFault(
1705
+ `No place the last build listed is declared by ${identity}.`,
1706
+ 'Run "cw mcp auth --status" to see every mcp, or "cw build" if the charter changed.'
1707
+ );
1708
+ unsignedStatuses = [identityStatus];
1709
+ }
1710
+ if (unsignedStatuses.length === 0)
1711
+ return {
1712
+ code: EXIT_OK,
1713
+ result: signInStatuses.length === 0 ? "No place the last build listed takes a sign-in.\n" : `You are signed in at every address: ${signInStatuses.length}.
1714
+ `
1715
+ };
1716
+ if (!process.stdin.isTTY)
1717
+ return {
1718
+ code: EXIT_FAILURE,
1719
+ problem: [
1720
+ "Not signed in at:",
1721
+ ...unsignedStatuses.map(({ address }) => ` ${address}`),
1722
+ ' Run "cw mcp auth" at a terminal to sign in; nothing is asked without one.',
1723
+ ""
1724
+ ].join("\n")
1725
+ };
1726
+ for (const { address, identities, auth: auth2 } of unsignedStatuses) {
1727
+ process.stderr.write(`${address}
1728
+ ${identities.join(", ")}
1729
+ `);
1730
+ const { method } = auth2.length > 1 ? await prompts3({
1731
+ type: "select",
1732
+ name: "method",
1733
+ message: "Sign in how?",
1734
+ choices: auth2.map((one) => ({ title: one === "oauth" ? "in the browser (OAuth)" : "with a token", value: one }))
1735
+ }) : { method: auth2[0] };
1736
+ if (method === "oauth")
1737
+ await mcpConnectingApp2.signInWithOAuth(
1738
+ address,
1739
+ (authorizationUrl) => process.stderr.write(` Open this address to sign in, then come back here:
1740
+ ${authorizationUrl}
1741
+ `)
1742
+ );
1743
+ else if (method === "token") {
1744
+ const { token } = await prompts3({ type: "invisible", name: "token", message: "Token?" });
1745
+ if (token === void 0) throwInterruptFault(address);
1746
+ await mcpConnectingApp2.signInWithToken(address, token);
1747
+ } else throwInterruptFault(address);
1748
+ }
1749
+ return {
1750
+ code: EXIT_OK,
1751
+ result: `Signed in at ${unsignedStatuses.map(({ address }) => address).join(", ")}.
1752
+ `
1753
+ };
1754
+ }
1755
+ };
1756
+ function throwInterruptFault(address) {
1757
+ throw new DomainFault(`This was stopped before signing in at ${address}; what was signed in before it is kept.`, 'Run "cw mcp auth" again.');
1758
+ }
1759
+
1760
+ // src/driver/cli/commands/McpServeCommand.ts
1761
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1762
+
1763
+ // src/driver/mcp/server.ts
1764
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
1765
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
1766
+ async function startMcpServer(mcpConnectingApp2, transport, version = "0.0.0") {
1767
+ const server = new Server({ name: "cw", version }, { capabilities: { tools: {} } });
1768
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
1769
+ tools: (await mcpConnectingApp2.served()).data.tools.map(({ data }) => data)
1770
+ }));
1771
+ server.setRequestHandler(
1772
+ CallToolRequestSchema,
1773
+ async ({ params }) => (await mcpConnectingApp2.call(params.name, params.arguments ?? {})).data
1774
+ );
1775
+ const serverClosed = new Promise((resolve) => server.onclose = resolve);
1776
+ await server.connect(transport);
1777
+ return { serverClosed };
1778
+ }
1779
+
1780
+ // src/driver/cli/commands/McpServeCommand.ts
1781
+ var McpServeCommand = class {
1782
+ name = "mcp serve";
1783
+ summary = "Serve every place the charter reaches to your agent, over stdio";
1784
+ async run({ mcpConnectingApp: mcpConnectingApp2, version }) {
1785
+ const servedTools2 = await mcpConnectingApp2.served();
1786
+ for (const problem of servedTools2.data.problems) process.stderr.write(`${problem}
1787
+ `);
1788
+ const transport = new StdioServerTransport();
1789
+ const { serverClosed } = await startMcpServer(mcpConnectingApp2, transport, version);
1790
+ const closeTransport = () => void transport.close();
1791
+ process.stdin.once("end", closeTransport);
1792
+ process.once("SIGTERM", closeTransport);
1793
+ process.once("SIGINT", closeTransport);
1794
+ await serverClosed;
1795
+ await mcpConnectingApp2.stopServing();
1796
+ return { code: EXIT_OK };
1797
+ }
1798
+ };
1799
+
1475
1800
  // src/driver/portal/server.ts
1476
1801
  import { randomBytes } from "crypto";
1477
1802
  import { access } from "fs/promises";
@@ -1484,87 +1809,87 @@ import { serveStatic } from "@hono/node-server/serve-static";
1484
1809
  // src/driver/portal/routes.ts
1485
1810
  import { createRoute, OpenAPIHono } from "@hono/zod-openapi";
1486
1811
  import { HTTPException } from "hono/http-exception";
1487
- import { z as z10 } from "zod";
1812
+ import { z as z11 } from "zod";
1488
1813
 
1489
1814
  // src/hexagon/port/driver/dtos/dto.ts
1490
- import { z as z5 } from "zod";
1491
- var strings = z5.array(z5.string()).readonly();
1492
- var dto = (type, data) => z5.object({ type: z5.literal(type), data });
1815
+ import { z as z6 } from "zod";
1816
+ var strings = z6.array(z6.string()).readonly();
1817
+ var dto = (type, data) => z6.object({ type: z6.literal(type), data });
1493
1818
  function byType(...schemas) {
1494
1819
  return Object.fromEntries(schemas.map((one) => [one.shape.type.value, one]));
1495
1820
  }
1496
1821
 
1497
1822
  // src/hexagon/port/driver/dtos/data.ts
1498
- import { z as z6 } from "zod";
1823
+ import { z as z7 } from "zod";
1499
1824
  var fault = dto(
1500
1825
  "Fault",
1501
- z6.object({
1502
- message: z6.string(),
1503
- fix: z6.string(),
1504
- severity: z6.enum(["error", "warn"])
1826
+ z7.object({
1827
+ message: z7.string(),
1828
+ fix: z7.string(),
1829
+ severity: z7.enum(["error", "warn"])
1505
1830
  })
1506
1831
  );
1507
1832
  var faultsByFile = dto(
1508
1833
  "FaultsByFile",
1509
- z6.object({
1510
- files: z6.record(z6.string(), z6.array(fault).readonly())
1834
+ z7.object({
1835
+ files: z7.record(z7.string(), z7.array(fault).readonly())
1511
1836
  })
1512
1837
  );
1513
1838
  var faults = dto(
1514
1839
  "Faults",
1515
- z6.object({
1516
- faults: z6.array(fault).readonly()
1840
+ z7.object({
1841
+ faults: z7.array(fault).readonly()
1517
1842
  })
1518
1843
  );
1519
- var primitiveKinds = dto("PrimitiveKinds", z6.record(z6.string(), z6.string()));
1844
+ var primitiveKinds = dto("PrimitiveKinds", z7.record(z7.string(), z7.string()));
1520
1845
  var catalogueEntry = dto(
1521
1846
  "CatalogueEntry",
1522
- z6.object({
1523
- identity: z6.string(),
1524
- kind: z6.string(),
1525
- id: z6.string(),
1526
- description: z6.string(),
1527
- file: z6.string(),
1847
+ z7.object({
1848
+ identity: z7.string(),
1849
+ kind: z7.string(),
1850
+ id: z7.string(),
1851
+ description: z7.string(),
1852
+ file: z7.string(),
1528
1853
  tags: strings.optional(),
1529
1854
  globs: strings.optional(),
1530
- rationale: z6.string().optional(),
1855
+ rationale: z7.string().optional(),
1531
1856
  mixins: strings.optional()
1532
1857
  })
1533
1858
  );
1534
1859
  var catalogue = dto(
1535
1860
  "Catalogue",
1536
- z6.object({
1537
- entries: z6.array(catalogueEntry).readonly()
1861
+ z7.object({
1862
+ entries: z7.array(catalogueEntry).readonly()
1538
1863
  })
1539
1864
  );
1540
1865
  var scopedPrimitive = dto(
1541
1866
  "ScopedPrimitive",
1542
- z6.object({
1543
- identity: z6.string(),
1544
- kind: z6.string(),
1545
- description: z6.string(),
1546
- file: z6.string(),
1547
- scope: z6.enum(["repo", "vendor", "builtin"]),
1548
- headers: z6.record(z6.string(), z6.union([z6.string(), strings]))
1867
+ z7.object({
1868
+ identity: z7.string(),
1869
+ kind: z7.string(),
1870
+ description: z7.string(),
1871
+ file: z7.string(),
1872
+ scope: z7.enum(["repo", "vendor", "builtin"]),
1873
+ headers: z7.record(z7.string(), z7.union([z7.string(), strings]))
1549
1874
  })
1550
1875
  );
1551
1876
  var scopedPrimitives = dto(
1552
1877
  "ScopedPrimitives",
1553
- z6.object({
1554
- primitives: z6.array(scopedPrimitive).readonly()
1878
+ z7.object({
1879
+ primitives: z7.array(scopedPrimitive).readonly()
1555
1880
  })
1556
1881
  );
1557
1882
  var primitiveSnapshot = dto(
1558
1883
  "PrimitiveSnapshot",
1559
- z6.object({
1884
+ z7.object({
1560
1885
  scopedPrimitive,
1561
- body: z6.string(),
1562
- revision: z6.string()
1886
+ body: z7.string(),
1887
+ revision: z7.string()
1563
1888
  })
1564
1889
  );
1565
1890
  var planSummary = dto(
1566
1891
  "PlanSummary",
1567
- z6.object({
1892
+ z7.object({
1568
1893
  added: strings,
1569
1894
  edited: strings,
1570
1895
  deleted: strings,
@@ -1573,63 +1898,89 @@ var planSummary = dto(
1573
1898
  );
1574
1899
  var primitiveHeader = dto(
1575
1900
  "PrimitiveHeader",
1576
- z6.object({
1577
- field: z6.string(),
1578
- shape: z6.enum(["line", "list"]),
1579
- required: z6.boolean(),
1901
+ z7.object({
1902
+ field: z7.string(),
1903
+ shape: z7.enum(["line", "list"]),
1904
+ required: z7.boolean(),
1580
1905
  allowedValues: strings.optional()
1581
1906
  })
1582
1907
  );
1583
1908
  var primitiveRequirements = dto(
1584
1909
  "PrimitiveRequirements",
1585
- z6.object({
1586
- headers: z6.array(primitiveHeader).readonly(),
1587
- sample: z6.string()
1910
+ z7.object({
1911
+ headers: z7.array(primitiveHeader).readonly(),
1912
+ sample: z7.string()
1588
1913
  })
1589
1914
  );
1590
- var testCasesByFile = dto("TestCasesByFile", z6.record(z6.string(), strings));
1915
+ var testCasesByFile = dto("TestCasesByFile", z7.record(z7.string(), strings));
1591
1916
  var testCaseReport = dto(
1592
1917
  "TestCaseReport",
1593
- z6.object({
1594
- suiteName: z6.string(),
1595
- situation: z6.string(),
1596
- passed: z6.boolean(),
1918
+ z7.object({
1919
+ suiteName: z7.string(),
1920
+ situation: z7.string(),
1921
+ passed: z7.boolean(),
1597
1922
  unmet: fault.optional()
1598
1923
  })
1599
1924
  );
1600
1925
  var testRunReport = dto(
1601
1926
  "TestRunReport",
1602
- z6.object({
1603
- testCaseReports: z6.array(testCaseReport).readonly()
1927
+ z7.object({
1928
+ testCaseReports: z7.array(testCaseReport).readonly()
1604
1929
  })
1605
1930
  );
1606
1931
  var testCase = dto(
1607
1932
  "TestCase",
1608
- z6.object({
1609
- situation: z6.string(),
1610
- expectation: z6.string(),
1611
- identity: z6.string().optional()
1933
+ z7.object({
1934
+ situation: z7.string(),
1935
+ expectation: z7.string(),
1936
+ identity: z7.string().optional()
1612
1937
  })
1613
1938
  );
1614
1939
  var testSuite = dto(
1615
1940
  "TestSuite",
1616
- z6.object({
1617
- name: z6.string(),
1618
- text: z6.string().optional(),
1619
- description: z6.string().optional(),
1620
- cases: z6.array(testCase).readonly(),
1941
+ z7.object({
1942
+ name: z7.string(),
1943
+ text: z7.string().optional(),
1944
+ description: z7.string().optional(),
1945
+ cases: z7.array(testCase).readonly(),
1621
1946
  fault: fault.optional()
1622
1947
  })
1623
1948
  );
1624
1949
  var testSuites = dto(
1625
1950
  "TestSuites",
1626
- z6.object({
1627
- testSuites: z6.array(testSuite).readonly()
1951
+ z7.object({
1952
+ testSuites: z7.array(testSuite).readonly()
1953
+ })
1954
+ );
1955
+ var signInStatus = dto(
1956
+ "SignInStatus",
1957
+ z7.object({
1958
+ address: z7.string(),
1959
+ identities: strings,
1960
+ auth: z7.array(z7.enum(["oauth", "token"])).readonly(),
1961
+ signedIn: z7.boolean(),
1962
+ method: z7.enum(["oauth", "token"]).optional()
1963
+ })
1964
+ );
1965
+ var servedTool = dto(
1966
+ "ServedTool",
1967
+ z7.object({
1968
+ name: z7.string(),
1969
+ description: z7.string(),
1970
+ inputSchema: z7.record(z7.string(), z7.unknown())
1971
+ })
1972
+ );
1973
+ var servedTools = dto(
1974
+ "ServedTools",
1975
+ z7.object({
1976
+ tools: z7.array(servedTool).readonly(),
1977
+ problems: strings
1628
1978
  })
1629
1979
  );
1980
+ var toolAnswer = dto("ToolAnswer", z7.record(z7.string(), z7.unknown()));
1630
1981
  var workspaceSettings = dto(
1631
1982
  "WorkspaceSettings",
1632
- z6.object({
1983
+ z7.object({
1633
1984
  agents: strings
1634
1985
  })
1635
1986
  );
@@ -1646,59 +1997,63 @@ var DataDTOs = byType(
1646
1997
  primitiveSnapshot,
1647
1998
  scopedPrimitive,
1648
1999
  scopedPrimitives,
2000
+ servedTool,
2001
+ servedTools,
2002
+ signInStatus,
1649
2003
  testCase,
1650
2004
  testCaseReport,
1651
2005
  testCasesByFile,
1652
2006
  testRunReport,
1653
2007
  testSuite,
1654
2008
  testSuites,
2009
+ toolAnswer,
1655
2010
  workspaceSettings
1656
2011
  );
1657
2012
 
1658
2013
  // src/hexagon/port/driver/dtos/outcome.ts
1659
- import { z as z7 } from "zod";
2014
+ import { z as z8 } from "zod";
1660
2015
  var doctorOutcome = dto(
1661
2016
  "DoctorOutcome",
1662
- z7.object({
2017
+ z8.object({
1663
2018
  agents: strings,
1664
- errorCount: z7.number(),
1665
- warnCount: z7.number(),
1666
- pendingCount: z7.number().nullable(),
2019
+ errorCount: z8.number(),
2020
+ warnCount: z8.number(),
2021
+ pendingCount: z8.number().nullable(),
1667
2022
  faultsByFile: DataDTOs.FaultsByFile,
1668
2023
  driftedVendors: strings,
1669
- problemCount: z7.number()
2024
+ problemCount: z8.number()
1670
2025
  })
1671
2026
  );
1672
2027
  var explanationOutcome = dto(
1673
2028
  "ExplanationOutcome",
1674
- z7.object({
2029
+ z8.object({
1675
2030
  scopedPrimitive: DataDTOs.ScopedPrimitive,
1676
- activatesWhen: z7.string(),
1677
- useMixins: z7.array(DataDTOs.ScopedPrimitive).readonly(),
2031
+ activatesWhen: z8.string(),
2032
+ useMixins: z8.array(DataDTOs.ScopedPrimitive).readonly(),
1678
2033
  rationale: DataDTOs.ScopedPrimitive.optional(),
1679
- hosts: z7.array(DataDTOs.ScopedPrimitive).readonly(),
1680
- citers: z7.array(DataDTOs.ScopedPrimitive).readonly(),
2034
+ hosts: z8.array(DataDTOs.ScopedPrimitive).readonly(),
2035
+ citers: z8.array(DataDTOs.ScopedPrimitive).readonly(),
1681
2036
  testCasesByFile: DataDTOs.TestCasesByFile
1682
2037
  })
1683
2038
  );
1684
2039
  var OutcomeDTOs = byType(doctorOutcome, explanationOutcome);
1685
2040
 
1686
2041
  // src/hexagon/domain/models/test/TestSuite.ts
1687
- import { z as z9 } from "zod";
2042
+ import { z as z10 } from "zod";
1688
2043
 
1689
2044
  // src/hexagon/domain/models/test/TestCase.ts
1690
- import { z as z8 } from "zod";
1691
- var TEST_CASE_SCHEMA = z8.union([
1692
- z8.strictObject({
1693
- do: z8.strictObject({ touchFile: z8.string().min(1) }),
1694
- expect: z8.union([
1695
- z8.strictObject({ activate: PRIMITIVE_IDENTITY_SCHEMA }),
1696
- z8.strictObject({ allow: z8.boolean() })
2045
+ import { z as z9 } from "zod";
2046
+ var TEST_CASE_SCHEMA = z9.union([
2047
+ z9.strictObject({
2048
+ do: z9.strictObject({ touchFile: z9.string().min(1) }),
2049
+ expect: z9.union([
2050
+ z9.strictObject({ activate: PRIMITIVE_IDENTITY_SCHEMA }),
2051
+ z9.strictObject({ allow: z9.boolean() })
1697
2052
  ])
1698
2053
  }),
1699
- z8.strictObject({
1700
- when: z8.enum(SIGNALS),
1701
- expect: z8.strictObject({ run: PRIMITIVE_IDENTITY_SCHEMA })
2054
+ z9.strictObject({
2055
+ when: z9.enum(SIGNALS),
2056
+ expect: z9.strictObject({ run: PRIMITIVE_IDENTITY_SCHEMA })
1702
2057
  })
1703
2058
  ]);
1704
2059
  var TestCase = class {
@@ -1740,9 +2095,9 @@ var TestCase = class {
1740
2095
  };
1741
2096
 
1742
2097
  // src/hexagon/domain/models/test/TestSuite.ts
1743
- var TEST_SUITE_SCHEMA = z9.strictObject({
1744
- description: z9.string().min(1).optional(),
1745
- cases: z9.array(TEST_CASE_SCHEMA.transform((written) => new TestCase(written))).min(1)
2098
+ var TEST_SUITE_SCHEMA = z10.strictObject({
2099
+ description: z10.string().min(1).optional(),
2100
+ cases: z10.array(TEST_CASE_SCHEMA.transform((written) => new TestCase(written))).min(1)
1746
2101
  });
1747
2102
  var TestSuite = class {
1748
2103
  constructor(description, cases, body) {
@@ -1809,7 +2164,7 @@ function testRootOf(testSuiteFiles) {
1809
2164
  try {
1810
2165
  suitesByFile[path] = testSuiteOf(contents);
1811
2166
  } catch (raised) {
1812
- if (!(raised instanceof Fault)) throw raised;
2167
+ if (!(raised instanceof DomainFault)) throw raised;
1813
2168
  faultsByFiles[path] = [raised];
1814
2169
  }
1815
2170
  }
@@ -1962,7 +2317,7 @@ var json = (schema, description) => ({
1962
2317
  function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
1963
2318
  const app = new OpenAPIHono();
1964
2319
  app.onError((raised, c) => {
1965
- if (raised instanceof Fault) return c.json(faultDTO(raised), 422);
2320
+ if (raised instanceof DomainFault) return c.json(faultDTO(raised), 422);
1966
2321
  if (raised instanceof HTTPException) return raised.getResponse();
1967
2322
  console.error(raised);
1968
2323
  return c.text(raised.message, 500);
@@ -1978,10 +2333,10 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
1978
2333
  createRoute({
1979
2334
  method: "get",
1980
2335
  path: "/charter/root/primitives",
1981
- request: { query: z10.object({ matching: z10.string().optional() }) },
2336
+ request: { query: z11.object({ matching: z11.string().optional() }) },
1982
2337
  responses: {
1983
2338
  200: json(DataDTOs.ScopedPrimitives, "Every primitive of every layer, or those mentioning the word"),
1984
- 422: json(z10.union([DataDTOs.FaultsByFile, DataDTOs.Fault]), "The charter does not hold")
2339
+ 422: json(z11.union([DataDTOs.FaultsByFile, DataDTOs.Fault]), "The charter does not hold")
1985
2340
  }
1986
2341
  }),
1987
2342
  async (c) => {
@@ -1992,10 +2347,10 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
1992
2347
  createRoute({
1993
2348
  method: "get",
1994
2349
  path: "/charter/root/primitives/{identity}/explanation",
1995
- request: { params: z10.object({ identity: z10.string() }) },
2350
+ request: { params: z11.object({ identity: z11.string() }) },
1996
2351
  responses: {
1997
2352
  200: json(OutcomeDTOs.ExplanationOutcome, "What the engine says of one primitive"),
1998
- 422: json(z10.union([DataDTOs.FaultsByFile, DataDTOs.Fault]), "The charter does not hold, or holds nothing of it")
2353
+ 422: json(z11.union([DataDTOs.FaultsByFile, DataDTOs.Fault]), "The charter does not hold, or holds nothing of it")
1999
2354
  }
2000
2355
  }),
2001
2356
  async (c) => {
@@ -2009,11 +2364,11 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2009
2364
  path: "/charter/root/primitives",
2010
2365
  request: {
2011
2366
  body: json(
2012
- z10.object({
2013
- kind: z10.string(),
2014
- id: z10.string(),
2015
- headers: z10.record(z10.string(), z10.union([z10.string(), z10.array(z10.string())])),
2016
- body: z10.string()
2367
+ z11.object({
2368
+ kind: z11.string(),
2369
+ id: z11.string(),
2370
+ headers: z11.record(z11.string(), z11.union([z11.string(), z11.array(z11.string())])),
2371
+ body: z11.string()
2017
2372
  }),
2018
2373
  "A new primitive: its kind and id, a line or a list under each header, and its body"
2019
2374
  )
@@ -2021,26 +2376,26 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2021
2376
  responses: {
2022
2377
  201: {
2023
2378
  ...json(DataDTOs.ScopedPrimitive, "The primitive written"),
2024
- headers: z10.object({ Location: z10.string() })
2379
+ headers: z11.object({ Location: z11.string() })
2025
2380
  },
2026
- 422: json(z10.union([DataDTOs.Faults, DataDTOs.Fault]), "The answers the kind refused, or an identity already claimed")
2381
+ 422: json(z11.union([DataDTOs.Faults, DataDTOs.Fault]), "The answers the kind refused, or an identity already claimed")
2027
2382
  }
2028
2383
  }),
2029
2384
  async (c) => {
2030
2385
  const { kind, id, headers, body } = c.req.valid("json");
2031
2386
  const scopedPrimitiveDTO2 = await charterAuthoringApp2.add(kind, id, headers, body);
2032
2387
  if (scopedPrimitiveDTO2.type === "Faults") return c.json(scopedPrimitiveDTO2, 422);
2033
- return c.json(scopedPrimitiveDTO2, 201, { Location: `/api/charter/root/primitives/${scopedPrimitiveDTO2.data.identity}` });
2388
+ return c.json(scopedPrimitiveDTO2, 201, { Location: `/api/charter/root/primitives/${encodeURIComponent(scopedPrimitiveDTO2.data.identity)}` });
2034
2389
  }
2035
2390
  ).openapi(
2036
2391
  createRoute({
2037
2392
  method: "get",
2038
2393
  path: "/charter/root/primitives/{identity}",
2039
- request: { params: z10.object({ identity: z10.string() }) },
2394
+ request: { params: z11.object({ identity: z11.string() }) },
2040
2395
  responses: {
2041
2396
  200: {
2042
2397
  ...json(DataDTOs.PrimitiveSnapshot, "One primitive as the charter read it"),
2043
- headers: z10.object({ ETag: z10.string() })
2398
+ headers: z11.object({ ETag: z11.string() })
2044
2399
  },
2045
2400
  422: json(DataDTOs.Fault, "The charter holds nothing of it")
2046
2401
  }
@@ -2055,12 +2410,12 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2055
2410
  method: "put",
2056
2411
  path: "/charter/root/primitives/{identity}",
2057
2412
  request: {
2058
- params: z10.object({ identity: z10.string() }),
2059
- headers: z10.object({ "if-match": z10.string().optional() }),
2413
+ params: z11.object({ identity: z11.string() }),
2414
+ headers: z11.object({ "if-match": z11.string().optional() }),
2060
2415
  body: json(
2061
- z10.object({
2062
- headers: z10.record(z10.string(), z10.union([z10.string(), z10.array(z10.string())])),
2063
- body: z10.string()
2416
+ z11.object({
2417
+ headers: z11.record(z11.string(), z11.union([z11.string(), z11.array(z11.string())])),
2418
+ body: z11.string()
2064
2419
  }),
2065
2420
  "A line or a list under each header, and the body, written over the primitive's own"
2066
2421
  )
@@ -2068,7 +2423,7 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2068
2423
  responses: {
2069
2424
  200: json(DataDTOs.ScopedPrimitive, "The primitive written"),
2070
2425
  412: json(DataDTOs.Fault, "The file changed on disk after it was opened"),
2071
- 422: json(z10.union([DataDTOs.Faults, DataDTOs.Fault]), "The answers the kind refused, or a primitive this repository did not author")
2426
+ 422: json(z11.union([DataDTOs.Faults, DataDTOs.Fault]), "The answers the kind refused, or a primitive this repository did not author")
2072
2427
  }
2073
2428
  }),
2074
2429
  async (c) => {
@@ -2077,7 +2432,7 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2077
2432
  if (c.req.valid("header")["if-match"] !== `"${revision}"`)
2078
2433
  return c.json(
2079
2434
  faultDTO(
2080
- new Fault(
2435
+ new DomainFault(
2081
2436
  `${scopedPrimitive2.data.file} changed on disk after it was opened, and saving would write over that change.`,
2082
2437
  "Open it again to see what changed, then make your edit there."
2083
2438
  )
@@ -2092,7 +2447,7 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2092
2447
  createRoute({
2093
2448
  method: "delete",
2094
2449
  path: "/charter/root/primitives/{identity}",
2095
- request: { params: z10.object({ identity: z10.string() }) },
2450
+ request: { params: z11.object({ identity: z11.string() }) },
2096
2451
  responses: {
2097
2452
  204: { description: "The file is gone" },
2098
2453
  422: json(DataDTOs.Fault, "A primitive this repository did not author, or none at all")
@@ -2114,7 +2469,7 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2114
2469
  createRoute({
2115
2470
  method: "get",
2116
2471
  path: "/definitions/kinds/{kind}/requirements",
2117
- request: { params: z10.object({ kind: z10.string() }) },
2472
+ request: { params: z11.object({ kind: z11.string() }) },
2118
2473
  responses: {
2119
2474
  200: json(DataDTOs.PrimitiveRequirements, "Every header the kind takes, and its sample"),
2120
2475
  422: json(DataDTOs.Fault, "A word that is no kind")
@@ -2167,8 +2522,8 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2167
2522
  path: "/test-suites",
2168
2523
  responses: {
2169
2524
  201: {
2170
- ...json(z10.string(), "The name of the test file written"),
2171
- headers: z10.object({ Location: z10.string() })
2525
+ ...json(z11.string(), "The name of the test file written"),
2526
+ headers: z11.object({ Location: z11.string() })
2172
2527
  }
2173
2528
  }
2174
2529
  }),
@@ -2181,11 +2536,11 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2181
2536
  method: "put",
2182
2537
  path: "/test-suites/{name}",
2183
2538
  request: {
2184
- params: z10.object({ name: z10.string() }),
2185
- body: json(z10.object({ text: z10.string() }), "The whole text the test file is written over with")
2539
+ params: z11.object({ name: z11.string() }),
2540
+ body: json(z11.object({ text: z11.string() }), "The whole text the test file is written over with")
2186
2541
  },
2187
2542
  responses: {
2188
- 200: json(z10.string(), "The name of the test file written"),
2543
+ 200: json(z11.string(), "The name of the test file written"),
2189
2544
  422: json(DataDTOs.Fault, "Text that is not a suite, with its sample, or a name that is no test file")
2190
2545
  }
2191
2546
  }),
@@ -2194,7 +2549,7 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2194
2549
  createRoute({
2195
2550
  method: "delete",
2196
2551
  path: "/test-suites/{name}",
2197
- request: { params: z10.object({ name: z10.string() }) },
2552
+ request: { params: z11.object({ name: z11.string() }) },
2198
2553
  responses: {
2199
2554
  204: { description: "The test file is gone" },
2200
2555
  422: json(DataDTOs.Fault, "A name that is no test file")
@@ -2221,7 +2576,7 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2221
2576
  createRoute({
2222
2577
  method: "get",
2223
2578
  path: "/vendors",
2224
- responses: { 200: json(z10.array(z10.string()).readonly(), "Every folder a vendor source was installed as") }
2579
+ responses: { 200: json(z11.array(z11.string()).readonly(), "Every folder a vendor source was installed as") }
2225
2580
  }),
2226
2581
  async (c) => c.json(await charterVendoringApp2.installed(), 200)
2227
2582
  ).openapi(
@@ -2230,7 +2585,7 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2230
2585
  path: "/vendors",
2231
2586
  request: {
2232
2587
  body: json(
2233
- z10.object({ source: z10.string(), version: z10.string().optional() }),
2588
+ z11.object({ source: z11.string(), version: z11.string().optional() }),
2234
2589
  "A source as git fetches it, and the version to pin it to where one is named"
2235
2590
  )
2236
2591
  },
@@ -2248,7 +2603,7 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2248
2603
  createRoute({
2249
2604
  method: "delete",
2250
2605
  path: "/vendors/{name}",
2251
- request: { params: z10.object({ name: z10.string() }) },
2606
+ request: { params: z11.object({ name: z11.string() }) },
2252
2607
  responses: {
2253
2608
  204: { description: "The folder is gone, and that it is gone is committed" },
2254
2609
  422: json(DataDTOs.Fault, "No repository, work in hand, or what git refused")
@@ -2265,7 +2620,7 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2265
2620
  var DEFAULT_PORT = 9927;
2266
2621
  async function startPortal(page, charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2, port = DEFAULT_PORT) {
2267
2622
  await access(new URL("index.html", page)).catch(() => {
2268
- throw new Fault("The portal's page is not built.", 'Run "pnpm build", then start the portal again.');
2623
+ throw new DomainFault("The portal's page is not built.", 'Run "pnpm build", then start the portal again.');
2269
2624
  });
2270
2625
  const token = randomBytes(32).toString("base64url");
2271
2626
  for (let next = port; ; next++) {
@@ -2300,7 +2655,7 @@ function listen(app, port) {
2300
2655
  }
2301
2656
 
2302
2657
  // src/driver/cli/commands/PortalCommand.ts
2303
- var OPTIONS6 = {
2658
+ var OPTIONS7 = {
2304
2659
  port: {
2305
2660
  type: "number",
2306
2661
  describe: "Which port to serve on, the next free one when it is taken",
@@ -2310,7 +2665,7 @@ var OPTIONS6 = {
2310
2665
  var PortalCommand = class {
2311
2666
  name = "portal";
2312
2667
  summary = "Serve this repository's charter to a browser on this machine";
2313
- options = OPTIONS6;
2668
+ options = OPTIONS7;
2314
2669
  async run({ charterAuthoringApp: charterAuthoringApp2, charterVendoringApp: charterVendoringApp2, testAuthoringApp: testAuthoringApp2 }, { port }) {
2315
2670
  await charterAuthoringApp2.ensureRepoReady();
2316
2671
  const { address } = await startPortal(
@@ -2327,8 +2682,8 @@ Press Ctrl+C to stop it.
2327
2682
  };
2328
2683
 
2329
2684
  // src/driver/cli/commands/RemoveCommand.ts
2330
- import prompts3 from "prompts";
2331
- var OPTIONS7 = {
2685
+ import prompts4 from "prompts";
2686
+ var OPTIONS8 = {
2332
2687
  identity: {
2333
2688
  type: "string",
2334
2689
  describe: 'What the primitive is named by across the whole charter: "guide:no-any"'
@@ -2343,16 +2698,16 @@ var OPTIONS7 = {
2343
2698
  var RemoveCommand = class {
2344
2699
  name = "remove <identity>";
2345
2700
  summary = "Delete the file of one primitive this repository authored";
2346
- options = OPTIONS7;
2701
+ options = OPTIONS8;
2347
2702
  async run({ charterAuthoringApp: charterAuthoringApp2 }, { identity, yes }) {
2348
2703
  const { scopedPrimitive: scopedPrimitive2 } = (await charterAuthoringApp2.open(identity ?? "")).data;
2349
2704
  if (!yes && scopedPrimitive2.data.scope === REPO_SCOPE) {
2350
2705
  if (!process.stdin.isTTY)
2351
- throw new Fault(
2706
+ throw new DomainFault(
2352
2707
  `Removing ${scopedPrimitive2.data.file} is asked first, and nobody is at the terminal to answer.`,
2353
2708
  `Run "cw remove ${scopedPrimitive2.data.identity} --yes" to remove it without asking.`
2354
2709
  );
2355
- const { confirmed } = await prompts3({
2710
+ const { confirmed } = await prompts4({
2356
2711
  type: "confirm",
2357
2712
  name: "confirmed",
2358
2713
  message: `Remove ${scopedPrimitive2.data.file}?`,
@@ -2386,7 +2741,7 @@ var SuiteAddCommand = class {
2386
2741
  };
2387
2742
 
2388
2743
  // src/driver/cli/commands/SuiteEditCommand.ts
2389
- var OPTIONS8 = {
2744
+ var OPTIONS9 = {
2390
2745
  name: {
2391
2746
  type: "string",
2392
2747
  describe: `The test file's name under ${TEST_DIRECTORY}/: "untitled-1.json"`
@@ -2395,11 +2750,11 @@ var OPTIONS8 = {
2395
2750
  var SuiteEditCommand = class {
2396
2751
  name = "suite edit <name>";
2397
2752
  summary = "Open one test file in your editor";
2398
- options = OPTIONS8;
2753
+ options = OPTIONS9;
2399
2754
  async run({ cwd, testAuthoringApp: testAuthoringApp2 }, { name }) {
2400
2755
  const { testSuites: testSuites2 } = (await testAuthoringApp2.suites()).data;
2401
2756
  if (!testSuites2.some((one) => one.data.name === name))
2402
- throw new Fault(`${TEST_DIRECTORY}/ holds no test file called "${name ?? ""}".`, 'Run "cw suite add" to write a new one.');
2757
+ throw new DomainFault(`${TEST_DIRECTORY}/ holds no test file called "${name ?? ""}".`, 'Run "cw suite add" to write a new one.');
2403
2758
  const file = `${TEST_DIRECTORY}/${name}`;
2404
2759
  const problem = await editInEditor(cwd, file);
2405
2760
  if (problem !== void 0) return { code: EXIT_FAILURE, problem };
@@ -2408,7 +2763,7 @@ var SuiteEditCommand = class {
2408
2763
  };
2409
2764
 
2410
2765
  // src/driver/cli/commands/SuiteRemoveCommand.ts
2411
- var OPTIONS9 = {
2766
+ var OPTIONS10 = {
2412
2767
  name: {
2413
2768
  type: "string",
2414
2769
  describe: `The test file's name under ${TEST_DIRECTORY}/: "untitled-1.json"`
@@ -2417,7 +2772,7 @@ var OPTIONS9 = {
2417
2772
  var SuiteRemoveCommand = class {
2418
2773
  name = "suite remove <name>";
2419
2774
  summary = "Delete one test file";
2420
- options = OPTIONS9;
2775
+ options = OPTIONS10;
2421
2776
  async run({ testAuthoringApp: testAuthoringApp2 }, { name }) {
2422
2777
  const removedName = await testAuthoringApp2.removeSuite(name ?? "");
2423
2778
  return { code: EXIT_OK, result: `Removed ${TEST_DIRECTORY}/${removedName}.
@@ -2464,7 +2819,7 @@ function lines2(one) {
2464
2819
  }
2465
2820
 
2466
2821
  // src/driver/cli/commands/VendorAddCommand.ts
2467
- var OPTIONS10 = {
2822
+ var OPTIONS11 = {
2468
2823
  source: {
2469
2824
  type: "string",
2470
2825
  describe: "What git clones the charter from: an address over any transport, or a path on disk"
@@ -2477,7 +2832,7 @@ var OPTIONS10 = {
2477
2832
  var VendorAddCommand = class {
2478
2833
  name = "vendor add <source>";
2479
2834
  summary = "Install a charter from a git source, or bring an installed one up to date";
2480
- options = OPTIONS10;
2835
+ options = OPTIONS11;
2481
2836
  async run({ charterVendoringApp: charterVendoringApp2 }, { source, ref }) {
2482
2837
  const folder = await charterVendoringApp2.add(source ?? "", ref);
2483
2838
  return {
@@ -2505,7 +2860,7 @@ var VendorListCommand = class {
2505
2860
  };
2506
2861
 
2507
2862
  // src/driver/cli/commands/VendorRemoveCommand.ts
2508
- var OPTIONS11 = {
2863
+ var OPTIONS12 = {
2509
2864
  name: {
2510
2865
  type: "string",
2511
2866
  describe: "The folder this charter was installed as, as it reads under .cw/vendor/"
@@ -2514,7 +2869,7 @@ var OPTIONS11 = {
2514
2869
  var VendorRemoveCommand = class {
2515
2870
  name = "vendor remove <name>";
2516
2871
  summary = "Take an installed charter away";
2517
- options = OPTIONS11;
2872
+ options = OPTIONS12;
2518
2873
  async run({ charterVendoringApp: charterVendoringApp2 }, { name }) {
2519
2874
  const folder = await charterVendoringApp2.remove(name ?? "");
2520
2875
  return {
@@ -2546,7 +2901,9 @@ var COMMANDS = [
2546
2901
  new VendorAddCommand(),
2547
2902
  new VendorRemoveCommand(),
2548
2903
  new VendorListCommand(),
2549
- new PortalCommand()
2904
+ new PortalCommand(),
2905
+ new McpAuthCommand(),
2906
+ new McpServeCommand()
2550
2907
  ];
2551
2908
 
2552
2909
  // src/driver/cli/Commander.ts
@@ -2603,7 +2960,7 @@ var Commander = class {
2603
2960
  try {
2604
2961
  collect(await command.run(this.#context, options));
2605
2962
  } catch (raised) {
2606
- if (!(raised instanceof Fault)) throw raised;
2963
+ if (!(raised instanceof DomainFault)) throw raised;
2607
2964
  collect({ code: EXIT_FAILURE, problem: `${raised.message}
2608
2965
  ${raised.fix}
2609
2966
  ` });
@@ -2642,7 +2999,6 @@ var CwAuthorSkill = class extends SkillPrimitive {
2642
2999
  "write a rule",
2643
3000
  "add a standard",
2644
3001
  "write a skill",
2645
- "add a command",
2646
3002
  "author a primitive of the charter"
2647
3003
  ]
2648
3004
  },
@@ -2707,7 +3063,7 @@ async function writeCharter(repo, primitive, fileReader2, fileWriter2) {
2707
3063
  const path = `${CHARTER_DIRECTORY}/${under2}`;
2708
3064
  const file = new URL(under2, charterFolderIn(repo));
2709
3065
  if (await fileReader2.readIfThere(file) !== void 0)
2710
- throw new Fault(
3066
+ throw new DomainFault(
2711
3067
  `${path} is already there, and writing this one would write over what it holds.`,
2712
3068
  `Open it, or run this again with an identity this charter has not got.`
2713
3069
  );
@@ -2716,9 +3072,9 @@ async function writeCharter(repo, primitive, fileReader2, fileWriter2) {
2716
3072
  }
2717
3073
 
2718
3074
  // src/hexagon/domain/models/Settings.ts
2719
- import { z as z11 } from "zod";
2720
- var SETTINGS = z11.object({
2721
- agents: z11.array(z11.enum(AGENT_PROVIDERS))
3075
+ import { z as z12 } from "zod";
3076
+ var SETTINGS = z12.object({
3077
+ agents: z12.array(z12.enum(AGENT_PROVIDERS))
2722
3078
  });
2723
3079
  var WorkspaceSettings = class {
2724
3080
  constructor(agents) {
@@ -2811,12 +3167,12 @@ async function loadVendorNames(repo, fileReaders) {
2811
3167
  }
2812
3168
  async function ensureVCSReady(repo, vcs2, because) {
2813
3169
  if (!await vcs2.isInstalled(repo))
2814
- throw new Fault(
3170
+ throw new DomainFault(
2815
3171
  "This folder is not inside a git repository, and a vendor lives in one.",
2816
3172
  'Run "git init" here, or run this again where your repository is.'
2817
3173
  );
2818
3174
  if (!await vcs2.isClean(repo))
2819
- throw new Fault(
3175
+ throw new DomainFault(
2820
3176
  `This repository has work in hand, and ${because}.`,
2821
3177
  "Commit or stash what you are holding, then run this again."
2822
3178
  );
@@ -2966,19 +3322,7 @@ async function contentHashOf(content) {
2966
3322
  return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
2967
3323
  }
2968
3324
 
2969
- // src/hexagon/domain/models/output/StampedDocument.ts
2970
- var GENERATED_STAMP = "Generated by cherry-works";
2971
- function stamp(document) {
2972
- return `${document.replace(/\n+$/, "")}
2973
-
2974
- <!-- ${GENERATED_STAMP}. Do not edit; edit the charter and build again. -->
2975
- `;
2976
- }
2977
- function isStamped(contents) {
2978
- return contents.includes(GENERATED_STAMP);
2979
- }
2980
-
2981
- // src/hexagon/domain/models/output/CharterOutput.ts
3325
+ // src/hexagon/domain/models/output/common/Catalogue.ts
2982
3326
  var Catalogue = class _Catalogue {
2983
3327
  constructor(entries) {
2984
3328
  this.entries = entries;
@@ -3020,6 +3364,20 @@ var Catalogue = class _Catalogue {
3020
3364
  return this.entries.map(({ identity, kind, id, description }) => ({ identity, kind, id, description }));
3021
3365
  }
3022
3366
  };
3367
+
3368
+ // src/hexagon/domain/models/output/StampedDocument.ts
3369
+ var GENERATED_STAMP = "Generated by cherry-works";
3370
+ function stamp(document) {
3371
+ return `${document.replace(/\n+$/, "")}
3372
+
3373
+ <!-- ${GENERATED_STAMP}. Do not edit; edit the charter and build again. -->
3374
+ `;
3375
+ }
3376
+ function isStamped(contents) {
3377
+ return contents.includes(GENERATED_STAMP);
3378
+ }
3379
+
3380
+ // src/hexagon/domain/models/output/common/CharterMd.ts
3023
3381
  var CharterMd = class _CharterMd {
3024
3382
  constructor(body) {
3025
3383
  this.body = body;
@@ -3050,34 +3408,26 @@ var CharterMd = class _CharterMd {
3050
3408
  }
3051
3409
  };
3052
3410
 
3053
- // src/hexagon/domain/models/output/providers/claude/ClaudeSettingsComponent.ts
3054
- var CLAUDE_HOOK_EVENTS = [
3055
- "PreToolUse",
3056
- "PostToolUse",
3057
- "UserPromptSubmit",
3058
- "Notification",
3059
- "Stop",
3060
- "SubagentStop",
3061
- "PreCompact",
3062
- "SessionStart",
3063
- "SessionEnd"
3064
- ];
3065
- function isClaudeHookEvent(name) {
3066
- return CLAUDE_HOOK_EVENTS.includes(name);
3067
- }
3068
- var ClaudeSettingsComponent = class _ClaudeSettingsComponent {
3069
- constructor(settings) {
3070
- this.settings = settings;
3411
+ // src/hexagon/domain/models/output/common/CompiledPrimitive.ts
3412
+ var CompiledPrimitive = class _CompiledPrimitive {
3413
+ constructor(identity, file, document) {
3414
+ this.identity = identity;
3415
+ this.file = file;
3416
+ this.document = document;
3071
3417
  }
3072
- settings;
3073
- static kind = "settings";
3074
- kind = _ClaudeSettingsComponent.kind;
3075
- /** The settings file this host reads is shared with whatever the repository set
3076
- * for itself, so what is put down there is written into what is there rather
3077
- * than in place of it (FR-018). */
3078
- projection = "mergeJSON";
3079
- static of(settings) {
3080
- return new _ClaudeSettingsComponent(settings);
3418
+ identity;
3419
+ file;
3420
+ document;
3421
+ static of(one, document) {
3422
+ return new _CompiledPrimitive(one.identity, `${OUT_DIRECTORY}/${one.primitive.kind}/${one.primitive.headers.id}.md`, document);
3423
+ }
3424
+ /** Generated, and the charter's alone: written whole over whatever is there,
3425
+ * and gone with the primitive it was compiled from. */
3426
+ projection = "replace";
3427
+ /** This document as a reader opens it, with the stamp saying where to change
3428
+ * it instead. */
3429
+ toStampedDocument() {
3430
+ return stamp(this.document);
3081
3431
  }
3082
3432
  };
3083
3433
 
@@ -3128,15 +3478,6 @@ var ClaudeAgentComponent = class _ClaudeAgentComponent extends DocumentBasedComp
3128
3478
  }
3129
3479
  };
3130
3480
 
3131
- // src/hexagon/domain/models/output/providers/claude/document-based-components/ClaudeCommandComponent.ts
3132
- var ClaudeCommandComponent = class _ClaudeCommandComponent extends DocumentBasedComponent {
3133
- static kind = "command";
3134
- kind = _ClaudeCommandComponent.kind;
3135
- static of(name, headers, body) {
3136
- return new _ClaudeCommandComponent(name, headers, body);
3137
- }
3138
- };
3139
-
3140
3481
  // src/hexagon/domain/models/output/providers/claude/document-based-components/ClaudeRuleComponent.ts
3141
3482
  var ClaudeRuleComponent = class _ClaudeRuleComponent extends DocumentBasedComponent {
3142
3483
  static kind = "rule";
@@ -3146,6 +3487,57 @@ var ClaudeRuleComponent = class _ClaudeRuleComponent extends DocumentBasedCompon
3146
3487
  }
3147
3488
  };
3148
3489
 
3490
+ // src/hexagon/domain/models/output/providers/claude/ClaudeMcpConfigComponent.ts
3491
+ var ClaudeMcpConfigComponent = class _ClaudeMcpConfigComponent {
3492
+ static kind = "mcp-config";
3493
+ kind = _ClaudeMcpConfigComponent.kind;
3494
+ /** What this host names the server, and so what it puts before each tool it
3495
+ * serves: `mcp__cw__<tool>`. */
3496
+ static cwMcpName = "cw";
3497
+ /** That file is the repository's too — every server it wrote itself is
3498
+ * there — so the charter's one entry is written into it, and it is never
3499
+ * taken away, as the settings are not (FR-146). */
3500
+ projection = "mergeJSON";
3501
+ /** The one entry the charter speaks for, under `mcpServers`. */
3502
+ mcpServers = { [_ClaudeMcpConfigComponent.cwMcpName]: { command: "cw", args: ["mcp", "serve"] } };
3503
+ static of() {
3504
+ return new _ClaudeMcpConfigComponent();
3505
+ }
3506
+ constructor() {
3507
+ }
3508
+ };
3509
+
3510
+ // src/hexagon/domain/models/output/providers/claude/ClaudeSettingsComponent.ts
3511
+ var CLAUDE_HOOK_EVENTS = [
3512
+ "PreToolUse",
3513
+ "PostToolUse",
3514
+ "UserPromptSubmit",
3515
+ "Notification",
3516
+ "Stop",
3517
+ "SubagentStop",
3518
+ "PreCompact",
3519
+ "SessionStart",
3520
+ "SessionEnd"
3521
+ ];
3522
+ function isClaudeHookEvent(name) {
3523
+ return CLAUDE_HOOK_EVENTS.includes(name);
3524
+ }
3525
+ var ClaudeSettingsComponent = class _ClaudeSettingsComponent {
3526
+ constructor(settings) {
3527
+ this.settings = settings;
3528
+ }
3529
+ settings;
3530
+ static kind = "settings";
3531
+ kind = _ClaudeSettingsComponent.kind;
3532
+ /** The settings file this host reads is shared with whatever the repository set
3533
+ * for itself, so what is put down there is written into what is there rather
3534
+ * than in place of it (FR-018). */
3535
+ projection = "mergeJSON";
3536
+ static of(settings) {
3537
+ return new _ClaudeSettingsComponent(settings);
3538
+ }
3539
+ };
3540
+
3149
3541
  // src/hexagon/domain/models/output/providers/claude/document-based-components/ClaudeSkillComponent.ts
3150
3542
  var ClaudeSkillComponent = class _ClaudeSkillComponent extends DocumentBasedComponent {
3151
3543
  static kind = "skill";
@@ -3155,50 +3547,74 @@ var ClaudeSkillComponent = class _ClaudeSkillComponent extends DocumentBasedComp
3155
3547
  }
3156
3548
  };
3157
3549
 
3158
- // src/hexagon/domain/services/compileService.ts
3159
- function compile(charter, agents) {
3160
- return {
3161
- // A header nobody wrote is left out rather than listed as nothing: a
3162
- // listing says what the author declared. `file` is where the body sits
3163
- // inside the workspace — a vendored primitive reads as
3164
- // `.cw/vendor/<name>/...`, which is where it is.
3165
- catalogue: Catalogue.of(
3166
- charter.primitives.map((one) => {
3167
- const { id, description, tags, globs, rationale, mixins } = one.primitive.headers;
3168
- return {
3169
- identity: one.identity,
3170
- kind: one.primitive.kind,
3171
- id,
3172
- description,
3173
- file: one.file,
3174
- ...tags === void 0 ? {} : { tags },
3175
- ...globs === void 0 ? {} : { globs },
3176
- ...rationale === void 0 ? {} : { rationale },
3177
- ...mixins === void 0 ? {} : { mixins }
3178
- };
3179
- })
3180
- ),
3181
- charterMd: CharterMd.of(PRIMITIVE_CLASSES),
3182
- providerComponents: agents.flatMap((agent) => forAgent(agent, charter))
3183
- };
3184
- }
3185
- function forAgent(agent, charter) {
3186
- switch (agent) {
3187
- case "claude":
3188
- return compileClaude(charter);
3550
+ // src/hexagon/domain/models/output/providers/claude/ClaudeComponent.ts
3551
+ var CLAUDE_COMPONENT_CLASSES = [
3552
+ ClaudeAgentComponent,
3553
+ ClaudeSkillComponent,
3554
+ ClaudeSettingsComponent,
3555
+ ClaudeMcpConfigComponent,
3556
+ ClaudeRuleComponent
3557
+ ];
3558
+ var CLAUDE_COMPONENT_KINDS = Object.freeze(CLAUDE_COMPONENT_CLASSES.map((one) => one.kind));
3559
+ function claudeDocumentComponentOf(sc, compiledFile, agentTools) {
3560
+ const name = sc.normIdentity;
3561
+ const compiledFromClaudeFolder = `../../${compiledFile}`;
3562
+ const pointerTo = (path) => `Read and follow @${path}.
3563
+ `;
3564
+ switch (sc.primitive.kind) {
3565
+ // A guide is a rule this host loads into context whole: when a file it names
3566
+ // is touched, which is what `paths` on a rule does, and at the start of every
3567
+ // session where it names none (FR-013). Under the identity it is changed by,
3568
+ // so a reader who wants it changed is sent to the primitive rather than
3569
+ // editing what the next build overwrites (FR-017, FR-020).
3570
+ case GuidePrimitive.kind: {
3571
+ const { globs = [] } = sc.primitive.headers;
3572
+ return ClaudeRuleComponent.of(name, globs.length === 0 ? {} : { paths: globs }, `@${compiledFromClaudeFolder}
3573
+ `);
3574
+ }
3575
+ case AgentPrimitive.kind: {
3576
+ const { tools } = sc.primitive.headers;
3577
+ return ClaudeAgentComponent.of(name, { description: sc.primitive.description(), tools: (agentTools ?? tools).join(", ") }, pointerTo(compiledFromClaudeFolder));
3578
+ }
3579
+ // One kind of this host for the two the charter loads when the request calls
3580
+ // for them: what decides that the body is worth opening is the description,
3581
+ // because that line is all this host reads before deciding (FR-013), and
3582
+ // composing it is the primitive's.
3583
+ case SkillPrimitive.kind:
3584
+ case PlaybookPrimitive.kind:
3585
+ return ClaudeSkillComponent.of(name, { description: sc.primitive.description() }, pointerTo(`../${compiledFromClaudeFolder}`));
3586
+ default:
3587
+ return void 0;
3189
3588
  }
3190
3589
  }
3191
- function compileClaude(charter) {
3590
+
3591
+ // src/hexagon/domain/services/providers/claude.ts
3592
+ function compileForClaude(charter, compiledPrimitives, shortStrings) {
3593
+ const isSettingComponent = (one) => one.primitive.kind === PosturePrimitive.kind || one.primitive.kind === SensorPrimitive.kind;
3192
3594
  const asSettings = charter.primitives.filter(isSettingComponent);
3193
3595
  const asDocuments = charter.primitives.filter((one) => !isSettingComponent(one));
3596
+ const toolsByMcpIdentity = new Map(
3597
+ charter.primitives.flatMap(({ identity, primitive }) => primitive instanceof McpPrimitive ? [[identity, primitive.headers.tools]] : [])
3598
+ );
3599
+ const agentToolsOf = (agent) => agent.headers.tools.flatMap((tool) => {
3600
+ const [, mcpIdentity, mcpTool] = tool.match(MCP_TOOL_REFERENCE) ?? [];
3601
+ if (mcpIdentity === void 0) return [tool];
3602
+ return (mcpTool === void 0 ? toolsByMcpIdentity.get(mcpIdentity) ?? [] : [mcpTool]).map(
3603
+ (oneTool) => `mcp__${ClaudeMcpConfigComponent.cwMcpName}__${shortStrings[mcpIdentity]}__${oneTool}`
3604
+ );
3605
+ });
3194
3606
  return [
3195
3607
  ...asSettings.length === 0 ? [] : [ClaudeSettingsComponent.of(claudeSettingsOf(asSettings))],
3196
- ...asDocuments.flatMap((one) => claudeDocumentComponentOf(charter, one) ?? [])
3608
+ ClaudeMcpConfigComponent.of(),
3609
+ ...asDocuments.flatMap(
3610
+ (one) => claudeDocumentComponentOf(
3611
+ one,
3612
+ compiledPrimitives.find((compiled) => compiled.identity === one.identity).file,
3613
+ one.primitive instanceof AgentPrimitive ? agentToolsOf(one.primitive) : void 0
3614
+ ) ?? []
3615
+ )
3197
3616
  ];
3198
3617
  }
3199
- function isSettingComponent(one) {
3200
- return one.primitive.kind === PosturePrimitive.kind || one.primitive.kind === SensorPrimitive.kind;
3201
- }
3202
3618
  function claudeSettingsOf(ones) {
3203
3619
  const allow = /* @__PURE__ */ new Set();
3204
3620
  const deny = /* @__PURE__ */ new Set();
@@ -3220,46 +3636,126 @@ function claudeSettingsOf(ones) {
3220
3636
  ...Object.keys(hooks).length === 0 ? {} : { hooks }
3221
3637
  };
3222
3638
  }
3223
- function claudeDocumentComponentOf(charter, sc) {
3224
- const name = claudeNameOf(sc);
3225
- const body = charter.bodyOf(sc);
3226
- switch (sc.primitive.kind) {
3227
- // A guide is a rule this host loads into context whole: when a file it names
3228
- // is touched, which is what `paths` on a rule does, and at the start of every
3229
- // session where it names none (FR-013). Under the identity it is changed by,
3230
- // so a reader who wants it changed is sent to the primitive rather than
3231
- // editing what the next build overwrites (FR-017, FR-020).
3232
- case GuidePrimitive.kind: {
3233
- const { globs = [] } = sc.primitive.headers;
3234
- return ClaudeRuleComponent.of(
3235
- name,
3236
- globs.length === 0 ? {} : { paths: globs },
3237
- `${[`## ${sc.identity}`, body].join("\n\n")}
3238
- `
3239
- );
3240
- }
3241
- // Named by the identifier alone, because the filename is what someone types
3242
- // after the slash: this host keeps commands in a directory of their own, so
3243
- // no other kind can land beside one and the kind would only be typed twice.
3244
- case CommandPrimitive.kind:
3245
- return ClaudeCommandComponent.of(sc.primitive.headers.id, { description: sc.primitive.description() }, body);
3246
- case AgentPrimitive.kind: {
3247
- const { tools } = sc.primitive.headers;
3248
- return ClaudeAgentComponent.of(name, { description: sc.primitive.description(), tools: tools.join(", ") }, body);
3249
- }
3250
- // One kind of this host for the two the charter loads when the request calls
3251
- // for them: what decides that the body is worth opening is the description,
3252
- // because that line is all this host reads before deciding (FR-013), and
3253
- // composing it is the primitive's.
3254
- case SkillPrimitive.kind:
3255
- case PlaybookPrimitive.kind:
3256
- return ClaudeSkillComponent.of(name, { description: sc.primitive.description() }, body);
3257
- default:
3258
- return void 0;
3639
+ function claudeComponentProjection(one) {
3640
+ const projectionPolicy = one.projection;
3641
+ switch (one.kind) {
3642
+ case ClaudeAgentComponent.kind:
3643
+ return { path: `${CLAUDE_DIRECTORY}/agents/${one.name}.md`, contents: one.toStampedDocument(), projectionPolicy };
3644
+ // A skill is a directory holding one file of that name, which is where this
3645
+ // host looks for it.
3646
+ case ClaudeSkillComponent.kind:
3647
+ return {
3648
+ path: `${CLAUDE_DIRECTORY}/skills/${one.name}/SKILL.md`,
3649
+ contents: one.toStampedDocument(),
3650
+ projectionPolicy
3651
+ };
3652
+ // A rule of its own file, under the directory this host loads every one of:
3653
+ // at the start of a session where it declares no `paths`, and when one of
3654
+ // them is touched where it does.
3655
+ case ClaudeRuleComponent.kind:
3656
+ return { path: `${CLAUDE_DIRECTORY}/rules/${one.name}.md`, contents: one.toStampedDocument(), projectionPolicy };
3657
+ // Every posture and every sensor of the charter lands in the one file this
3658
+ // host reads its settings from.
3659
+ case ClaudeSettingsComponent.kind:
3660
+ return {
3661
+ path: `${CLAUDE_DIRECTORY}/settings.json`,
3662
+ contents: `${JSON.stringify(one.settings, void 0, 2)}
3663
+ `,
3664
+ projectionPolicy
3665
+ };
3666
+ // The one server reaching every place, in the file this host starts a
3667
+ // project's servers from (FR-146).
3668
+ case ClaudeMcpConfigComponent.kind:
3669
+ return {
3670
+ path: CLAUDE_MCP_CONFIG_FILE,
3671
+ contents: `${JSON.stringify({ mcpServers: one.mcpServers }, void 0, 2)}
3672
+ `,
3673
+ projectionPolicy
3674
+ };
3259
3675
  }
3260
3676
  }
3261
- function claudeNameOf(one) {
3262
- return one.identity.replace(/[:/]/g, "-");
3677
+
3678
+ // src/hexagon/domain/services/compileService.ts
3679
+ function compile(charter, agents) {
3680
+ const shortStrings = shortenStringsOf(
3681
+ charter.primitives.filter(({ primitive }) => primitive.kind === McpPrimitive.kind).map((one) => one.identity)
3682
+ );
3683
+ const compiledPrimitives = charter.primitives.map(
3684
+ (one) => CompiledPrimitive.of(
3685
+ one,
3686
+ one.primitive.toMarkdown({
3687
+ // A mixin nothing answers to is left out: the charter already names
3688
+ // that file as an error, and a build refused for it never reaches
3689
+ // here (FR-009).
3690
+ mixins: charter.mixinsOf(one).flatMap(({ primitive }) => primitive.kind === MixinPrimitive.kind ? [primitive] : []),
3691
+ // Each place the body names as `mcp:<id>` written as the name its
3692
+ // tools are served under, `<name>__<tool>`, which every host's name for
3693
+ // them holds whatever it puts before it (FR-147). A name no mcp
3694
+ // answers to has none and is left as written; the charter has warned
3695
+ // about it (FR-143).
3696
+ idReplacer: (body) => body.replace(MCP_MENTION, (identity) => shortStrings[identity] ?? identity)
3697
+ })
3698
+ )
3699
+ );
3700
+ return {
3701
+ // What an agent opens from the catalogue is the primitive as it compiled,
3702
+ // not the file its author wrote: the whole body in one place, under the
3703
+ // output folder whichever layer brought it (FR-140). The catalogue is where
3704
+ // this path is said; the projection reads it from there.
3705
+ catalogue: catalogueOf(charter, (one) => compiledPrimitives.find((compiled) => compiled.identity === one.identity).file),
3706
+ charterMd: CharterMd.of(PRIMITIVE_CLASSES),
3707
+ compiledPrimitives,
3708
+ mcpOrigins: mcpOriginsOf(charter, shortStrings),
3709
+ providerComponents: agents.flatMap((agent) => compileForAgent(agent, charter, compiledPrimitives, shortStrings))
3710
+ };
3711
+ }
3712
+ function catalogueOf(charter, fileOf) {
3713
+ return Catalogue.of(
3714
+ charter.primitives.map((one) => {
3715
+ const { id, description, tags, globs, rationale, mixins } = one.primitive.headers;
3716
+ return {
3717
+ identity: one.identity,
3718
+ kind: one.primitive.kind,
3719
+ id,
3720
+ description,
3721
+ file: fileOf(one),
3722
+ ...tags === void 0 ? {} : { tags },
3723
+ ...globs === void 0 ? {} : { globs },
3724
+ ...rationale === void 0 ? {} : { rationale },
3725
+ ...mixins === void 0 ? {} : { mixins }
3726
+ };
3727
+ })
3728
+ );
3729
+ }
3730
+ function mcpOriginsOf(charter, shortStrings) {
3731
+ const mcpsByKey = /* @__PURE__ */ new Map();
3732
+ for (const { identity, primitive } of [...charter.primitives].sort((one, another) => one.identity < another.identity ? -1 : 1)) {
3733
+ if (primitive.kind !== McpPrimitive.kind) continue;
3734
+ const key = `${primitive.address}
3735
+ ${primitive.headers.path ?? ""}`;
3736
+ mcpsByKey.set(key, [...mcpsByKey.get(key) ?? [], { identity, primitive }]);
3737
+ }
3738
+ return [...mcpsByKey.entries()].sort(([oneKey], [anotherKey]) => oneKey < anotherKey ? -1 : 1).map(([, mcpsAtOrigin]) => {
3739
+ const [firstMcp] = mcpsAtOrigin;
3740
+ const { endpoint, command, args = [], path } = firstMcp.primitive.headers;
3741
+ const tokenEnv = mcpsAtOrigin.find((one) => one.primitive.headers.tokenEnv !== void 0)?.primitive.headers.tokenEnv;
3742
+ const auths = new Set(mcpsAtOrigin.flatMap((one) => one.primitive.headers.auth ?? []));
3743
+ return {
3744
+ identities: mcpsAtOrigin.map((one) => one.identity),
3745
+ names: Object.fromEntries(mcpsAtOrigin.map((one) => [one.identity, shortStrings[one.identity]])),
3746
+ address: firstMcp.primitive.address,
3747
+ ...endpoint === void 0 ? {} : { endpoint },
3748
+ ...command === void 0 ? {} : { command: { command, args, ...tokenEnv === void 0 ? {} : { tokenEnv } } },
3749
+ ...path === void 0 ? {} : { path },
3750
+ auth: MCP_AUTHS.filter((auth2) => auths.has(auth2))
3751
+ };
3752
+ });
3753
+ }
3754
+ function compileForAgent(agent, charter, compiledPrimitives, shortStrings) {
3755
+ switch (agent) {
3756
+ case "claude":
3757
+ return compileForClaude(charter, compiledPrimitives, shortStrings);
3758
+ }
3263
3759
  }
3264
3760
 
3265
3761
  // src/hexagon/domain/services/projectionService.ts
@@ -3282,13 +3778,27 @@ function charterOutputProjection(output, agentProviders) {
3282
3778
  `,
3283
3779
  projectionPolicy: output.catalogue.projection
3284
3780
  },
3781
+ // Indented for the reason the full listing is: committed, and read in
3782
+ // diffs when a place moves (SC-038).
3783
+ {
3784
+ path: `${OUT_DIRECTORY}/mcp-origins.json`,
3785
+ contents: `${JSON.stringify({ origins: output.mcpOrigins }, void 0, 2)}
3786
+ `,
3787
+ projectionPolicy: "replace"
3788
+ },
3285
3789
  {
3286
3790
  path: `${OUT_DIRECTORY}/CHARTER.md`,
3287
3791
  contents: output.charterMd.toStampedDocument(),
3288
3792
  projectionPolicy: output.charterMd.projection
3289
3793
  },
3794
+ // Each primitive lands where the catalogue sends an agent for it, so the
3795
+ // file named and the file written are one (FR-140).
3796
+ ...output.catalogue.full.flatMap(({ identity, file }) => {
3797
+ const compiledPrimitive = output.compiledPrimitives.find((one) => one.identity === identity);
3798
+ return compiledPrimitive === void 0 ? [] : [{ path: file, contents: compiledPrimitive.toStampedDocument(), projectionPolicy: compiledPrimitive.projection }];
3799
+ }),
3290
3800
  ...agentProviders.map(entryFileProjection),
3291
- ...output.providerComponents.map(componentProjection)
3801
+ ...output.providerComponents.map(claudeComponentProjection)
3292
3802
  ];
3293
3803
  }
3294
3804
  function entryFileProjection(provider) {
@@ -3308,37 +3818,6 @@ ${[
3308
3818
  }
3309
3819
  return { path, contents, projectionPolicy: "upsertWithMarker" };
3310
3820
  }
3311
- function componentProjection(one) {
3312
- const projectionPolicy = one.projection;
3313
- switch (one.kind) {
3314
- case ClaudeCommandComponent.kind:
3315
- return { path: `${CLAUDE_DIRECTORY}/commands/${one.name}.md`, contents: one.toStampedDocument(), projectionPolicy };
3316
- case ClaudeAgentComponent.kind:
3317
- return { path: `${CLAUDE_DIRECTORY}/agents/${one.name}.md`, contents: one.toStampedDocument(), projectionPolicy };
3318
- // A skill is a directory holding one file of that name, which is where this
3319
- // host looks for it.
3320
- case ClaudeSkillComponent.kind:
3321
- return {
3322
- path: `${CLAUDE_DIRECTORY}/skills/${one.name}/SKILL.md`,
3323
- contents: one.toStampedDocument(),
3324
- projectionPolicy
3325
- };
3326
- // A rule of its own file, under the directory this host loads every one of:
3327
- // at the start of a session where it declares no `paths`, and when one of
3328
- // them is touched where it does.
3329
- case ClaudeRuleComponent.kind:
3330
- return { path: `${CLAUDE_DIRECTORY}/rules/${one.name}.md`, contents: one.toStampedDocument(), projectionPolicy };
3331
- // Every posture and every sensor of the charter lands in the one file this
3332
- // host reads its settings from.
3333
- case ClaudeSettingsComponent.kind:
3334
- return {
3335
- path: `${CLAUDE_DIRECTORY}/settings.json`,
3336
- contents: `${JSON.stringify(one.settings, void 0, 2)}
3337
- `,
3338
- projectionPolicy
3339
- };
3340
- }
3341
- }
3342
3821
 
3343
3822
  // src/hexagon/service/buildService.ts
3344
3823
  async function plan(repo, charter, agentProviders, fileReaders) {
@@ -3360,10 +3839,19 @@ async function planForProjection(repo, output, agentProviders, fileReaders) {
3360
3839
  break;
3361
3840
  // What the repository set for itself is read and written back: the fields
3362
3841
  // this charter speaks for are put over the top of it, and every other one
3363
- // is theirs and comes through untouched (FR-018).
3842
+ // is theirs and comes through untouched (FR-018). Field by field at every
3843
+ // depth, so an entry of the repository's beside the charter's under one
3844
+ // key — a server of its own under `mcpServers` — stays (FR-146); a list
3845
+ // or a value is the charter's whole, so what it no longer asks for goes.
3364
3846
  case "mergeJSON": {
3365
- const already = JSON.parse(held?.trim() || "{}");
3366
- contents = `${JSON.stringify({ ...already, ...JSON.parse(one.contents) }, void 0, 2)}
3847
+ const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3848
+ const mergedJSON = (already, written) => isPlainObject(already) && isPlainObject(written) ? Object.fromEntries(
3849
+ [.../* @__PURE__ */ new Set([...Object.keys(already), ...Object.keys(written)])].map((field) => [
3850
+ field,
3851
+ field in written ? mergedJSON(already[field], written[field]) : already[field]
3852
+ ])
3853
+ ) : written;
3854
+ contents = `${JSON.stringify(mergedJSON(JSON.parse(held?.trim() || "{}"), JSON.parse(one.contents)), void 0, 2)}
3367
3855
  `;
3368
3856
  break;
3369
3857
  }
@@ -3426,7 +3914,7 @@ async function executePlan(cleanupPlan, projectionPlan, fileWriter2) {
3426
3914
  await Promise.all(cleanupPlan.map((one) => fileWriter2.delete(one.file)));
3427
3915
  for (const one of projectionPlan) await fileWriter2.write(one.file, one.contents ?? "");
3428
3916
  } catch (raised) {
3429
- throw new Fault(
3917
+ throw new DomainFault(
3430
3918
  `This build could not be written: ${raised instanceof Error ? raised.message : String(raised)}`,
3431
3919
  "Check that the files this build writes can be written, then build again."
3432
3920
  );
@@ -3499,11 +3987,10 @@ var CharterAuthoring = class {
3499
3987
  * What the charter holds, listed, narrowed to one kind where one is named
3500
3988
  * (FR-011, FR-012).
3501
3989
  *
3502
- * The listing is the one the charter compiles to, asked for the way everything
3503
- * else is: `compile` hands back everything one reading produces, and this
3504
- * takes the listing out of it (SC-004). No agent is named, since what an agent
3505
- * reads is not what is being asked for here — a repository that compiles for
3506
- * none still lists what it holds.
3990
+ * Read off the charter, not off what it compiles to: a person is shown the
3991
+ * file each primitive was authored in, which is the one they would edit and
3992
+ * what says which layer it came from, where the catalogue a build writes sends
3993
+ * an agent to the compiled one (FR-140).
3507
3994
  *
3508
3995
  * Whether the word narrowing it is a kind at all is asked here, of the
3509
3996
  * domain that holds the set. A caller passes on what was typed and knows
@@ -3514,10 +4001,10 @@ var CharterAuthoring = class {
3514
4001
  */
3515
4002
  async list(kind) {
3516
4003
  if (kind !== void 0 && !isKind(kind))
3517
- throw new Fault(`"${kind}" is not a kind the charter knows.`, `Ask for one the charter knows: ${KINDS.join(", ")}.`);
4004
+ throw new DomainFault(`"${kind}" is not a kind the charter knows.`, `Ask for one the charter knows: ${KINDS.join(", ")}.`);
3518
4005
  const [charter, , faultsByFiles] = await this.#read();
3519
4006
  if (charter === void 0) return faultsByFileDTO(faultsByFiles.errors(), this.#repoPath);
3520
- const { catalogue: catalogue2 } = compile(charter, []);
4007
+ const catalogue2 = catalogueOf(charter, (one) => one.file);
3521
4008
  return catalogueDTO(kind === void 0 ? catalogue2 : catalogue2.filterByKind(kind));
3522
4009
  }
3523
4010
  /** Every primitive the charter read, or those mentioning a word, each as the
@@ -3552,7 +4039,7 @@ var CharterAuthoring = class {
3552
4039
  if (charter === void 0) return faultsByFileDTO(faultsByFiles.errors(), this.#repoPath);
3553
4040
  const declared = charter.primitiveById.get(primitiveIdentity);
3554
4041
  if (declared === void 0)
3555
- throw new Fault(
4042
+ throw new DomainFault(
3556
4043
  `This charter holds no "${identity}".`,
3557
4044
  'Run "cw list --min" to see every identity it does hold.'
3558
4045
  );
@@ -3692,7 +4179,7 @@ var CharterAuthoring = class {
3692
4179
  const charter = await loadCharterRoot(this.#repoPath, this.#fileReader, this.#yamlParser);
3693
4180
  const claimingPrimitive = charter.primitiveById.get(identityOf({ kind, id }));
3694
4181
  if (claimingPrimitive !== void 0)
3695
- throw new Fault(
4182
+ throw new DomainFault(
3696
4183
  `"${identityOf({ kind, id })}" is already there, declared by ${claimingPrimitive.file}, and one identity names one primitive in the whole charter.`,
3697
4184
  `Open ${claimingPrimitive.file}, or run this again with an id this charter has not got.`
3698
4185
  );
@@ -3729,12 +4216,12 @@ var CharterAuthoring = class {
3729
4216
  async rewrite(identity, headers, body, revision) {
3730
4217
  const scopedPrimitive2 = await this.#scopedPrimitiveOf(identity);
3731
4218
  if (!RepoScopedPrimitive.isSatisfiedBy(scopedPrimitive2))
3732
- throw new Fault(
4219
+ throw new DomainFault(
3733
4220
  `${identity} was not authored in this repository, and ${scopedPrimitive2.file} is read-only here.`,
3734
4221
  `To differ from it, author a primitive of your own under an identity of its own.`
3735
4222
  );
3736
4223
  if (await contentHashOf(scopedPrimitive2.primitive.toMarkdown()) !== revision)
3737
- throw new Fault(
4224
+ throw new DomainFault(
3738
4225
  `${scopedPrimitive2.file} changed on disk after it was opened, and saving would write over that change.`,
3739
4226
  `Open it again to see what changed, then make your edit there.`
3740
4227
  );
@@ -3762,7 +4249,7 @@ var CharterAuthoring = class {
3762
4249
  async remove(identity) {
3763
4250
  const scopedPrimitive2 = await this.#scopedPrimitiveOf(identity);
3764
4251
  if (!RepoScopedPrimitive.isSatisfiedBy(scopedPrimitive2))
3765
- throw new Fault(
4252
+ throw new DomainFault(
3766
4253
  `${identity} was not authored in this repository, and ${scopedPrimitive2.file} is read-only here.`,
3767
4254
  `To differ from it, author a primitive of your own under an identity of its own.`
3768
4255
  );
@@ -3777,7 +4264,7 @@ var CharterAuthoring = class {
3777
4264
  const charter = await loadCharterRoot(this.#repoPath, this.#fileReader, this.#yamlParser);
3778
4265
  const scopedPrimitive2 = charter.primitiveById.get(primitiveIdentity);
3779
4266
  if (scopedPrimitive2 === void 0)
3780
- throw new Fault(
4267
+ throw new DomainFault(
3781
4268
  `This charter holds no "${identity}".`,
3782
4269
  'Run "cw list --min" to see every identity it does hold.'
3783
4270
  );
@@ -3788,7 +4275,7 @@ var CharterAuthoring = class {
3788
4275
  * every kind there is. */
3789
4276
  #kindOf(kind) {
3790
4277
  if (!isKind(kind))
3791
- throw new Fault(`"${kind}" is not a kind the charter knows.`, `Ask for one the charter knows: ${KINDS.join(", ")}.`);
4278
+ throw new DomainFault(`"${kind}" is not a kind the charter knows.`, `Ask for one the charter knows: ${KINDS.join(", ")}.`);
3792
4279
  return kind;
3793
4280
  }
3794
4281
  /** What this repository configured itself with, read the one way settings are
@@ -3804,12 +4291,12 @@ var CharterAuthoring = class {
3804
4291
  */
3805
4292
  async ensureRepoReady() {
3806
4293
  if (!await this.#vcs.isInstalled(this.#repoPath))
3807
- throw new Fault(
4294
+ throw new DomainFault(
3808
4295
  "This folder is not inside a git repository, and a charter is authored inside one.",
3809
4296
  'Run "git init" here, then "cw init", or run this again where your repository is.'
3810
4297
  );
3811
4298
  if (await this.#fileReader.readIfThere(settingsFileIn(this.#repoPath)) === void 0)
3812
- throw new Fault("This repository was never set up under a charter.", 'Run "cw init" first.');
4299
+ throw new DomainFault("This repository was never set up under a charter.", 'Run "cw init" first.');
3813
4300
  }
3814
4301
  /**
3815
4302
  * One repository, set up under a charter (FR-032, FR-037).
@@ -3823,10 +4310,10 @@ var CharterAuthoring = class {
3823
4310
  * agent it runs reads (FR-033).
3824
4311
  *
3825
4312
  * Then the two things setup owns — what the repository configured itself
3826
- * with, and a directory per kind for a charter to be authored in — and
3827
- * nothing more: compiling is `build`'s, run once there is a charter to
3828
- * compile (FR-037). What comes back is that it is set up; what stops it is
3829
- * raised.
4313
+ * with, and a directory per kind for a charter to be authored in — and a
4314
+ * build over them, so the skill the engine brings is in the agent's hands
4315
+ * before anything is authored (FR-057, FR-096). What comes back is what that
4316
+ * build did, or what refused it; what stops setup itself is raised.
3830
4317
  *
3831
4318
  * Nothing is written into an ignore file: everything under the workspace is
3832
4319
  * committed, what a vendor installed included, so a checkout of this
@@ -3839,12 +4326,12 @@ var CharterAuthoring = class {
3839
4326
  */
3840
4327
  async init({ agents }) {
3841
4328
  if (!await this.#vcs.isInstalled(this.#repoPath))
3842
- throw new Fault(
4329
+ throw new DomainFault(
3843
4330
  "This folder is not inside a git repository, and a charter is authored inside one.",
3844
4331
  'Run "git init" here, or run this again where your repository is.'
3845
4332
  );
3846
4333
  if (agents.length === 0)
3847
- throw new Fault(
4334
+ throw new DomainFault(
3848
4335
  "Setup was given no agent to compile for, and a charter compiles for at least one.",
3849
4336
  `Choose at least one of: ${AGENT_PROVIDERS.join(", ")}.`
3850
4337
  );
@@ -3852,7 +4339,7 @@ var CharterAuthoring = class {
3852
4339
  `);
3853
4340
  const kindFolders = KINDS.map((kind) => `${CHARTER_DIRECTORY}/${kind}/.gitkeep`);
3854
4341
  for (const path of kindFolders) await this.#fileWriter.write(new URL(path, this.#repoPath), "");
3855
- return true;
4342
+ return this.build();
3856
4343
  }
3857
4344
  };
3858
4345
 
@@ -3903,6 +4390,275 @@ var TestAuthoring = class {
3903
4390
  }
3904
4391
  };
3905
4392
 
4393
+ // src/hexagon/service/credentialRepo.ts
4394
+ async function readCredential(secrets2, address) {
4395
+ const secret = await secrets2.readSecret(address);
4396
+ if (secret === void 0) return void 0;
4397
+ try {
4398
+ const credential = JSON.parse(secret);
4399
+ return typeof credential?.accessToken === "string" ? credential : void 0;
4400
+ } catch {
4401
+ return void 0;
4402
+ }
4403
+ }
4404
+ async function writeCredential(secrets2, credential) {
4405
+ await secrets2.writeSecret(credential.address, JSON.stringify(credential));
4406
+ }
4407
+ async function credentialFor(secrets2, authorizing2, origin, { refused = false } = {}) {
4408
+ if (origin.endpoint !== void 0 && !isSecureEndpoint(origin.endpoint))
4409
+ throw new DomainFault(`${origin.address} is plain http to another machine, so no credential is sent there.`, 'Declare its endpoint as https://, then run "cw build".');
4410
+ const signInAgain = `Run "cw mcp auth ${origin.identities[0] ?? ""}" at a terminal to sign in there.`;
4411
+ const credential = await readCredential(secrets2, origin.address);
4412
+ if (credential === void 0) throw new DomainFault(`You are not signed in to ${origin.address}.`, signInAgain);
4413
+ const expired = refused || credential.expiresAt !== void 0 && Date.parse(credential.expiresAt) <= Date.now();
4414
+ if (!expired) return credential.accessToken;
4415
+ const { address, method, client, ...tokens } = credential;
4416
+ const renewedGrant = origin.endpoint !== void 0 && client !== void 0 && tokens.refreshToken !== void 0 ? await authorizing2.renew(origin.endpoint, { ...tokens, client }) : void 0;
4417
+ if (renewedGrant === void 0)
4418
+ throw new DomainFault(`Your sign-in to ${address} has expired and could not be renewed.`, signInAgain);
4419
+ await writeCredential(secrets2, { address, method, ...renewedGrant });
4420
+ return renewedGrant.accessToken;
4421
+ }
4422
+
4423
+ // src/hexagon/service/mcpOriginsRepo.ts
4424
+ async function loadMcpOrigins(repo, fileReader2) {
4425
+ const file = `${OUT_DIRECTORY}/mcp-origins.json`;
4426
+ const contents = await fileReader2.readIfThere(new URL("mcp-origins.json", outFolderIn(repo)));
4427
+ if (contents === void 0)
4428
+ throw new DomainFault(`There is no ${file}, so no place has been listed yet.`, 'Run "cw build" first.');
4429
+ try {
4430
+ const mcpOriginsJson = JSON.parse(contents);
4431
+ const origins = mcpOriginsJson.origins;
4432
+ if (Array.isArray(origins)) return origins;
4433
+ } catch {
4434
+ }
4435
+ throw new DomainFault(`${file} does not hold a list of places.`, 'Run "cw build" to write it again.');
4436
+ }
4437
+ async function loadMcpTools(repo, fileReader2, yamlParser2) {
4438
+ const contents = await fileReader2.readIfThere(new URL("catalog.json", outFolderIn(repo)));
4439
+ let catalogEntries = [];
4440
+ try {
4441
+ const catalogJson = JSON.parse(contents ?? "[]");
4442
+ if (Array.isArray(catalogJson)) catalogEntries = catalogJson;
4443
+ } catch {
4444
+ }
4445
+ const toolsByIdentity = /* @__PURE__ */ new Map();
4446
+ for (const { identity, kind, file } of catalogEntries) {
4447
+ if (kind !== McpPrimitive.kind || typeof identity !== "string" || typeof file !== "string") continue;
4448
+ const compiledDocument = await fileReader2.readIfThere(new URL(file, repo));
4449
+ if (compiledDocument === void 0) continue;
4450
+ try {
4451
+ const primitive = primitiveOf(compiledDocument, yamlParser2);
4452
+ if (primitive instanceof McpPrimitive) toolsByIdentity.set(identity, primitive.headers.tools);
4453
+ } catch {
4454
+ }
4455
+ }
4456
+ return toolsByIdentity;
4457
+ }
4458
+
4459
+ // src/hexagon/port/zdriven/DrivenFault.ts
4460
+ var DrivenFault = class extends DomainFault {
4461
+ };
4462
+
4463
+ // src/hexagon/port/zdriven/ForCallingMcpServers.ts
4464
+ var UnauthorizedMcpFault = class extends DrivenFault {
4465
+ };
4466
+
4467
+ // src/hexagon/application/McpConnecting.ts
4468
+ var CONNECT_TIMEOUT_MS = 1e4;
4469
+ var errorAnswerOf = (text) => ({
4470
+ type: "ToolAnswer",
4471
+ data: { content: [{ type: "text", text }], isError: true }
4472
+ });
4473
+ var McpConnecting = class {
4474
+ #repoPath;
4475
+ #fileReader;
4476
+ #secrets;
4477
+ #authorizing;
4478
+ #yamlParser;
4479
+ #mcpServers;
4480
+ #mcpServing;
4481
+ constructor(repoPath2, fileReader2, secrets2, authorizing2, yamlParser2, mcpServers2) {
4482
+ this.#repoPath = repoPath2;
4483
+ this.#fileReader = fileReader2;
4484
+ this.#secrets = secrets2;
4485
+ this.#authorizing = authorizing2;
4486
+ this.#yamlParser = yamlParser2;
4487
+ this.#mcpServers = mcpServers2;
4488
+ }
4489
+ async signInStatus() {
4490
+ return Promise.all(
4491
+ (await this.#signInAddresses()).map(async ({ address, identities, auth: auth2 }) => {
4492
+ const credential = await readCredential(this.#secrets, address);
4493
+ return {
4494
+ type: "SignInStatus",
4495
+ data: { address, identities, auth: auth2, signedIn: credential !== void 0, ...credential && { method: credential.method } }
4496
+ };
4497
+ })
4498
+ );
4499
+ }
4500
+ async signInWithToken(address, token) {
4501
+ await this.#addressTaking(address, "token");
4502
+ if (token.trim() === "") throw new DomainFault(`No token was given for ${address}, so nothing was kept.`, 'Run "cw mcp auth" again and paste the token.');
4503
+ await writeCredential(this.#secrets, { address, method: "token", accessToken: token });
4504
+ }
4505
+ async signInWithOAuth(address, showAuthorizationUrl) {
4506
+ await this.#addressTaking(address, "oauth");
4507
+ const oauthGrant = await this.#authorizing.authorize(address, showAuthorizationUrl);
4508
+ await writeCredential(this.#secrets, { address, method: "oauth", ...oauthGrant });
4509
+ }
4510
+ async served() {
4511
+ return (await this.#serving()).servedTools;
4512
+ }
4513
+ async call(name, args) {
4514
+ const { toolRoutes, unreachableTools } = await this.#serving();
4515
+ const toolRoute = toolRoutes.get(name);
4516
+ if (toolRoute === void 0)
4517
+ return errorAnswerOf(unreachableTools.get(name) ?? `"${name}" is not a tool this server serves; call one it listed.`);
4518
+ const { origin, identity, upstream, connection } = toolRoute;
4519
+ const signInAgain = `${origin.address} refused your credential. Run "cw mcp auth ${identity}" at a terminal to sign in there again.`;
4520
+ const accessTokenOf = (refused) => origin.endpoint === void 0 ? Promise.resolve(void 0) : credentialFor(this.#secrets, this.#authorizing, origin, { refused });
4521
+ try {
4522
+ try {
4523
+ return { type: "ToolAnswer", data: await connection.callTool(upstream, args, await accessTokenOf(false)) };
4524
+ } catch (raised) {
4525
+ if (!(raised instanceof UnauthorizedMcpFault)) throw raised;
4526
+ const renewedToken = await accessTokenOf(true).catch(() => void 0);
4527
+ if (renewedToken === void 0) return errorAnswerOf(signInAgain);
4528
+ return { type: "ToolAnswer", data: await connection.callTool(upstream, args, renewedToken) };
4529
+ }
4530
+ } catch (raised) {
4531
+ if (raised instanceof UnauthorizedMcpFault) return errorAnswerOf(signInAgain);
4532
+ if (raised instanceof DomainFault) return errorAnswerOf(`${raised.message} ${raised.fix}`);
4533
+ throw raised;
4534
+ }
4535
+ }
4536
+ async stopServing() {
4537
+ if (this.#mcpServing === void 0) return;
4538
+ const { connections } = await this.#mcpServing;
4539
+ await Promise.all(connections.map((connection) => connection.close()));
4540
+ }
4541
+ /**
4542
+ * Every place the last build listed, reached at once and once per run
4543
+ * (FR-152). What each lists is kept where one of its identities
4544
+ * declares it, renamed `<prefix>__<tool>`; a place down or not signed in to,
4545
+ * and a declared tool its place lacks, is said and left out, and the rest
4546
+ * are served (FR-153, FR-154, SC-039).
4547
+ */
4548
+ #serving() {
4549
+ this.#mcpServing ??= (async () => {
4550
+ const origins = await loadMcpOrigins(this.#repoPath, this.#fileReader);
4551
+ const toolsByIdentity = await loadMcpTools(this.#repoPath, this.#fileReader, this.#yamlParser);
4552
+ const problems = [];
4553
+ const servedTools2 = [];
4554
+ const toolRoutes = /* @__PURE__ */ new Map();
4555
+ const unreachableTools = /* @__PURE__ */ new Map();
4556
+ const connections = [];
4557
+ const reachedPlaces = await Promise.all(
4558
+ origins.map(async (origin) => {
4559
+ try {
4560
+ const accessToken = origin.auth.length > 0 ? await credentialFor(this.#secrets, this.#authorizing, origin) : void 0;
4561
+ return { origin, connection: await this.#mcpServers.connect(origin, accessToken, CONNECT_TIMEOUT_MS) };
4562
+ } catch (raised) {
4563
+ if (!(raised instanceof DomainFault)) throw raised;
4564
+ return { origin, reason: `${raised.message} ${raised.fix}` };
4565
+ }
4566
+ })
4567
+ );
4568
+ for (const { origin, connection, reason } of reachedPlaces) {
4569
+ if (connection !== void 0) connections.push(connection);
4570
+ else problems.push(`${origin.identities.join(", ")}: ${reason}`);
4571
+ for (const identity of origin.identities) {
4572
+ const declaredTools = toolsByIdentity.get(identity);
4573
+ if (declaredTools === void 0) {
4574
+ problems.push(`${identity} has no compiled document under .cw/out/, so none of its tools is served. Run "cw build".`);
4575
+ continue;
4576
+ }
4577
+ const identityName = origin.names?.[identity];
4578
+ if (identityName === void 0) {
4579
+ problems.push(`${identity} has no served name in .cw/out/mcp-origins.json, so none of its tools is served. Run "cw build" again.`);
4580
+ continue;
4581
+ }
4582
+ for (const tool of declaredTools) {
4583
+ const servedName = `${identityName}__${tool}`;
4584
+ if (connection === void 0) {
4585
+ unreachableTools.set(servedName, reason ?? "");
4586
+ continue;
4587
+ }
4588
+ const upstreamTool = connection.tools.find(({ name }) => name === tool);
4589
+ if (upstreamTool === void 0) {
4590
+ problems.push(`${identity} declares "${tool}", which ${origin.address} does not have; it is not served.`);
4591
+ continue;
4592
+ }
4593
+ const placeName = origin.path === void 0 ? identity : `${identity} \u2014 ${origin.path}`;
4594
+ servedTools2.push({
4595
+ type: "ServedTool",
4596
+ data: {
4597
+ name: servedName,
4598
+ description: `[${placeName}] ${upstreamTool.description ?? ""}`.trimEnd(),
4599
+ inputSchema: upstreamTool.inputSchema
4600
+ }
4601
+ });
4602
+ toolRoutes.set(servedName, { origin, identity, upstream: tool, connection });
4603
+ }
4604
+ }
4605
+ }
4606
+ return {
4607
+ servedTools: {
4608
+ type: "ServedTools",
4609
+ data: { tools: servedTools2.sort((one, other) => one.data.name.localeCompare(other.data.name)), problems }
4610
+ },
4611
+ toolRoutes,
4612
+ unreachableTools,
4613
+ connections
4614
+ };
4615
+ })();
4616
+ return this.#mcpServing;
4617
+ }
4618
+ /**
4619
+ * Every address a developer signs in to, once, ordered by address (plan
4620
+ * §19.5): one address is one account at one service, whatever path each
4621
+ * place there names. A local command taking no token signs in to nothing and
4622
+ * is left out.
4623
+ */
4624
+ async #signInAddresses() {
4625
+ const origins = await loadMcpOrigins(this.#repoPath, this.#fileReader);
4626
+ const addresses = /* @__PURE__ */ new Map();
4627
+ for (const { address, endpoint, identities, auth: auth2 } of origins.filter((one) => one.auth.length > 0)) {
4628
+ const signInAddress = addresses.get(address) ?? { ...endpoint !== void 0 && { endpoint }, identities: /* @__PURE__ */ new Set(), auth: /* @__PURE__ */ new Set() };
4629
+ identities.forEach((identity) => signInAddress.identities.add(identity));
4630
+ auth2.forEach((method) => signInAddress.auth.add(method));
4631
+ addresses.set(address, signInAddress);
4632
+ }
4633
+ return [...addresses].sort(([one], [other]) => one.localeCompare(other)).map(([address, { endpoint, identities, auth: auth2 }]) => ({
4634
+ address,
4635
+ ...endpoint !== void 0 && { endpoint },
4636
+ identities: [...identities].sort(),
4637
+ auth: ["oauth", "token"].filter((method) => auth2.has(method))
4638
+ }));
4639
+ }
4640
+ /** Refused where no place is at this address, or none there allows signing
4641
+ * in this way: a credential nothing would use is not kept. Refused too where
4642
+ * the address is plain `http` to another machine, whatever the build said:
4643
+ * the list is a committed file, and a credential sent there travels in the
4644
+ * clear. */
4645
+ async #addressTaking(address, method) {
4646
+ const signInAddress = (await this.#signInAddresses()).find((one) => one.address === address);
4647
+ if (signInAddress === void 0)
4648
+ throw new DomainFault(`No place the last build listed is at ${address}.`, 'Run "cw mcp auth --status" to see every address, or "cw build" if the charter changed.');
4649
+ if (signInAddress.endpoint !== void 0 && !isSecureEndpoint(signInAddress.endpoint))
4650
+ throw new DomainFault(
4651
+ `${address} is plain http to another machine, so no credential is sent there.`,
4652
+ 'Declare its endpoint as https://, then run "cw build".'
4653
+ );
4654
+ if (!signInAddress.auth.includes(method))
4655
+ throw new DomainFault(
4656
+ `${address} is not signed in to by ${method}; it allows ${signInAddress.auth.join(" or ")}.`,
4657
+ `Sign in there by ${signInAddress.auth.join(" or ")}.`
4658
+ );
4659
+ }
4660
+ };
4661
+
3906
4662
  // src/zdriven/FileReaders.ts
3907
4663
  import { readdir, readFile } from "fs/promises";
3908
4664
  var FileReaders = class {
@@ -4053,7 +4809,7 @@ var Git = class {
4053
4809
  { cwd }
4054
4810
  );
4055
4811
  } catch (raised) {
4056
- throw new Fault(
4812
+ throw new DrivenFault(
4057
4813
  `${raised}`,
4058
4814
  version === void 0 ? "Check the address, and that you can reach it from here." : `Check the address, and that "${version}" is a tag or branch it has.`
4059
4815
  );
@@ -4068,19 +4824,286 @@ var Git = class {
4068
4824
  await run("git", ["rm", "-r", "--quiet", "--", subFolder], { cwd });
4069
4825
  await run("git", ["commit", "-qm", `Remove ${subFolder}`], { cwd });
4070
4826
  } catch (raised) {
4071
- throw new Fault(`${raised}`, `Check that "${subFolder}" is a folder this repository holds.`);
4827
+ throw new DrivenFault(`${raised}`, `Check that "${subFolder}" is a folder this repository holds.`);
4072
4828
  }
4073
4829
  }
4074
4830
  };
4075
4831
 
4832
+ // src/zdriven/OsSecrets.ts
4833
+ import { spawn as spawn2 } from "child_process";
4834
+ var SERVICE = "cherry-works";
4835
+ var MACOS_NOT_FOUND = 44;
4836
+ function commandOutput(command, args, stdin = "") {
4837
+ return new Promise((resolve) => {
4838
+ const child = spawn2(command, args, { stdio: ["pipe", "pipe", "pipe"] });
4839
+ let stdout = "";
4840
+ let stderr = "";
4841
+ child.stdout.on("data", (chunk) => stdout += chunk);
4842
+ child.stderr.on("data", (chunk) => stderr += chunk);
4843
+ child.on("error", (raised) => resolve({ code: -1, stdout, stderr: `${raised.message}` }));
4844
+ child.on("close", (code) => resolve({ code: code ?? -1, stdout, stderr }));
4845
+ child.stdin.on("error", () => {
4846
+ });
4847
+ child.stdin.end(stdin);
4848
+ });
4849
+ }
4850
+ var OsSecrets = class {
4851
+ /** Which system's store to use: this process's, unless a test names
4852
+ * another. */
4853
+ constructor(platform = process.platform) {
4854
+ this.platform = platform;
4855
+ }
4856
+ platform;
4857
+ async readSecret(key) {
4858
+ this.refuseUnsupportedPlatform();
4859
+ const commandOutputOfRead = this.platform === "darwin" ? await commandOutput("security", ["find-generic-password", "-s", SERVICE, "-a", key, "-w"]) : await commandOutput("secret-tool", ["lookup", "service", SERVICE, "account", key]);
4860
+ if (commandOutputOfRead.code === 0) return commandOutputOfRead.stdout.replace(/\n$/, "");
4861
+ const nothingThere = this.platform === "darwin" ? commandOutputOfRead.code === MACOS_NOT_FOUND : commandOutputOfRead.stderr.trim() === "";
4862
+ if (nothingThere) return void 0;
4863
+ this.throwFault("read", key, commandOutputOfRead.stderr);
4864
+ }
4865
+ async writeSecret(key, secret) {
4866
+ this.refuseUnsupportedPlatform();
4867
+ if (this.platform === "darwin" && /["\\\n]/.test(key)) {
4868
+ throw new DrivenFault(`The key "${key}" cannot be kept in the keychain.`, "Name the place by an address without quotes, backslashes or line breaks.");
4869
+ }
4870
+ const commandOutputOfWrite = this.platform === "darwin" ? await commandOutput(
4871
+ "security",
4872
+ ["-i"],
4873
+ `add-generic-password -U -s ${SERVICE} -a "${key}" -X ${Buffer.from(secret).toString("hex")}
4874
+ `
4875
+ ) : await commandOutput(
4876
+ "secret-tool",
4877
+ ["store", `--label=${SERVICE} ${key}`, "service", SERVICE, "account", key],
4878
+ secret
4879
+ );
4880
+ if (commandOutputOfWrite.code !== 0) this.throwFault("write", key);
4881
+ }
4882
+ async removeSecret(key) {
4883
+ this.refuseUnsupportedPlatform();
4884
+ const commandOutputOfRemove = this.platform === "darwin" ? await commandOutput("security", ["delete-generic-password", "-s", SERVICE, "-a", key]) : await commandOutput("secret-tool", ["clear", "service", SERVICE, "account", key]);
4885
+ if (commandOutputOfRemove.code === 0) return;
4886
+ const nothingThere = this.platform === "darwin" ? commandOutputOfRemove.code === MACOS_NOT_FOUND : commandOutputOfRemove.stderr.trim() === "";
4887
+ if (!nothingThere) this.throwFault("remove", key, commandOutputOfRemove.stderr);
4888
+ }
4889
+ /** macOS and Linux have a store this knows; anything else is refused
4890
+ * before a command runs. */
4891
+ refuseUnsupportedPlatform() {
4892
+ if (this.platform !== "darwin" && this.platform !== "linux") {
4893
+ throw new DrivenFault(
4894
+ `Keeping a secret is not supported on "${this.platform}".`,
4895
+ "Sign in from macOS or Linux, whose credential stores cw uses."
4896
+ );
4897
+ }
4898
+ }
4899
+ throwFault(operation, key, stderr = "") {
4900
+ const storeName = this.platform === "darwin" ? "The keychain" : "The secret service";
4901
+ throw new DrivenFault(
4902
+ `${storeName} refused to ${operation} the secret for "${key}".${stderr.trim() === "" ? "" : ` It said: ${stderr.trim()}`}`,
4903
+ this.platform === "darwin" ? "Check that the login keychain is unlocked." : "Check that secret-tool is installed and a secret service is running."
4904
+ );
4905
+ }
4906
+ };
4907
+
4908
+ // src/zdriven/OAuth.ts
4909
+ import { randomUUID } from "crypto";
4910
+ import { createServer } from "http";
4911
+ import { auth } from "@modelcontextprotocol/sdk/client/auth.js";
4912
+ import { OAuthError } from "@modelcontextprotocol/sdk/server/auth/errors.js";
4913
+ var CALLBACK_TIMEOUT_MS = 5 * 6e4;
4914
+ var OAuth = class {
4915
+ #callbackTimeoutMs;
4916
+ constructor(callbackTimeoutMs = CALLBACK_TIMEOUT_MS) {
4917
+ this.#callbackTimeoutMs = callbackTimeoutMs;
4918
+ }
4919
+ async authorize(endpoint, showAuthorizationUrl) {
4920
+ const state = randomUUID();
4921
+ const callbackServer = createServer();
4922
+ await new Promise((resolve, reject) => callbackServer.once("error", reject).listen(0, "127.0.0.1", resolve));
4923
+ const redirectUrl = `http://127.0.0.1:${callbackServer.address().port}/callback`;
4924
+ let callbackTimeout;
4925
+ const authorizationCode = new Promise((resolve, reject) => {
4926
+ callbackTimeout = setTimeout(
4927
+ () => reject(new DrivenFault(`Nobody came back from signing in to ${endpoint} in time.`, 'Run "cw mcp auth" again, and open the address it prints.')),
4928
+ this.#callbackTimeoutMs
4929
+ );
4930
+ callbackServer.on("request", (request, response) => {
4931
+ const callbackUrl = new URL(request.url ?? "/", redirectUrl);
4932
+ if (callbackUrl.pathname !== "/callback") return void response.writeHead(404).end();
4933
+ const callbackCode = callbackUrl.searchParams.get("code");
4934
+ const signedIn = callbackCode !== null && callbackUrl.searchParams.get("state") === state;
4935
+ response.writeHead(200, { "content-type": "text/plain; charset=utf-8" }).end(signedIn ? "Signed in. Go back to the terminal.\n" : "Not signed in. Go back to the terminal.\n");
4936
+ if (signedIn) return resolve(callbackCode);
4937
+ const callbackError = callbackUrl.searchParams.get("error");
4938
+ reject(new DrivenFault(`Signing in to ${endpoint} came back without an authorization${callbackError ? `: ${callbackError}` : ""}.`, 'Run "cw mcp auth" again.'));
4939
+ });
4940
+ });
4941
+ authorizationCode.catch(() => void 0);
4942
+ try {
4943
+ const oauthSession = {};
4944
+ const provider = this.#providerOver(oauthSession, redirectUrl, (authorizationUrl) => showAuthorizationUrl(authorizationUrl.href), state);
4945
+ if (await auth(provider, { serverUrl: endpoint }) === "REDIRECT")
4946
+ await auth(provider, { serverUrl: endpoint, authorizationCode: await authorizationCode });
4947
+ return oauthGrantOf(endpoint, oauthSession);
4948
+ } catch (raised) {
4949
+ if (raised instanceof DrivenFault) throw raised;
4950
+ throw new DrivenFault(
4951
+ `Signing in to ${endpoint} failed: ${raised instanceof Error ? raised.message : String(raised)}`,
4952
+ 'Check the address is reachable and offers an OAuth sign-in, then run "cw mcp auth" again.'
4953
+ );
4954
+ } finally {
4955
+ clearTimeout(callbackTimeout);
4956
+ callbackServer.closeAllConnections();
4957
+ callbackServer.close();
4958
+ }
4959
+ }
4960
+ async renew(endpoint, oauthGrant) {
4961
+ const { clientId, clientSecret, issuer } = oauthGrant.client;
4962
+ const oauthSession = {
4963
+ clientInformation: { client_id: clientId, ...clientSecret !== void 0 && { client_secret: clientSecret }, issuer },
4964
+ tokens: {
4965
+ access_token: oauthGrant.accessToken,
4966
+ token_type: "Bearer",
4967
+ ...oauthGrant.refreshToken !== void 0 && { refresh_token: oauthGrant.refreshToken },
4968
+ issuer
4969
+ }
4970
+ };
4971
+ const provider = this.#providerOver(oauthSession, "http://127.0.0.1/callback", () => void 0);
4972
+ try {
4973
+ if (await auth(provider, { serverUrl: endpoint }) === "REDIRECT") return void 0;
4974
+ } catch (raised) {
4975
+ if (raised instanceof OAuthError) return void 0;
4976
+ throw new DrivenFault(
4977
+ `Renewing the sign-in to ${endpoint} failed: ${raised instanceof Error ? raised.message : String(raised)}`,
4978
+ "Check the address is reachable, then try again."
4979
+ );
4980
+ }
4981
+ const renewedGrant = oauthGrantOf(endpoint, oauthSession);
4982
+ return renewedGrant.refreshToken !== void 0 || oauthGrant.refreshToken === void 0 ? renewedGrant : { ...renewedGrant, refreshToken: oauthGrant.refreshToken };
4983
+ }
4984
+ /**
4985
+ * What the SDK's flow is run with: `cw` described as a public client that
4986
+ * signs in at `redirectUrl`, and everything the flow saves kept in this
4987
+ * session. Registering is offered to a sign-in only, where a `state` is
4988
+ * checked on the way back; a renewal presents what the grant was issued to.
4989
+ */
4990
+ #providerOver(oauthSession, redirectUrl, redirectToAuthorization, state) {
4991
+ return {
4992
+ redirectUrl,
4993
+ clientMetadata: {
4994
+ client_name: "cherry-works",
4995
+ redirect_uris: [redirectUrl],
4996
+ grant_types: ["authorization_code", "refresh_token"],
4997
+ response_types: ["code"],
4998
+ token_endpoint_auth_method: "none"
4999
+ },
5000
+ ...state !== void 0 && {
5001
+ state: () => state,
5002
+ saveClientInformation: (clientInformation) => {
5003
+ oauthSession.clientInformation = clientInformation;
5004
+ }
5005
+ },
5006
+ clientInformation: () => oauthSession.clientInformation,
5007
+ tokens: () => oauthSession.tokens,
5008
+ saveTokens: (tokens) => {
5009
+ oauthSession.tokens = tokens;
5010
+ },
5011
+ redirectToAuthorization,
5012
+ saveCodeVerifier: (codeVerifier) => {
5013
+ oauthSession.codeVerifier = codeVerifier;
5014
+ },
5015
+ codeVerifier: () => oauthSession.codeVerifier ?? ""
5016
+ };
5017
+ }
5018
+ };
5019
+ function oauthGrantOf(endpoint, { clientInformation, tokens }) {
5020
+ if (clientInformation === void 0 || tokens === void 0)
5021
+ throw new DrivenFault(`Signing in to ${endpoint} ended without a token.`, 'Run "cw mcp auth" again.');
5022
+ return {
5023
+ accessToken: tokens.access_token,
5024
+ ...tokens.refresh_token !== void 0 && { refreshToken: tokens.refresh_token },
5025
+ ...tokens.expires_in !== void 0 && { expiresAt: new Date(Date.now() + tokens.expires_in * 1e3).toISOString() },
5026
+ client: {
5027
+ clientId: clientInformation.client_id,
5028
+ ...clientInformation.client_secret !== void 0 && { clientSecret: clientInformation.client_secret },
5029
+ issuer: clientInformation.issuer ?? tokens.issuer ?? endpoint
5030
+ }
5031
+ };
5032
+ }
5033
+
5034
+ // src/zdriven/McpClients.ts
5035
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
5036
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
5037
+ import { StreamableHTTPClientTransport, StreamableHTTPError } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
5038
+ var McpClients = class {
5039
+ async connect({ address, endpoint, command }, accessToken, timeoutMs) {
5040
+ let bearerToken = accessToken;
5041
+ const transport = endpoint !== void 0 ? new StreamableHTTPClientTransport(new URL(endpoint), {
5042
+ fetch: (url, init) => {
5043
+ const headers = new Headers(init?.headers);
5044
+ if (bearerToken !== void 0) headers.set("authorization", `Bearer ${bearerToken}`);
5045
+ return fetch(url, { ...init, headers });
5046
+ }
5047
+ }) : new StdioClientTransport({
5048
+ command: command?.command ?? "",
5049
+ args: [...command?.args ?? []],
5050
+ env: {
5051
+ ...Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== void 0)),
5052
+ ...command?.tokenEnv !== void 0 && accessToken !== void 0 && { [command.tokenEnv]: accessToken }
5053
+ },
5054
+ stderr: "inherit"
5055
+ });
5056
+ const client = new Client({ name: "cherry-works", version: "1" });
5057
+ let connectTimeout;
5058
+ try {
5059
+ const { tools } = await Promise.race([
5060
+ client.connect(transport).then(() => client.listTools()),
5061
+ new Promise((_resolve, reject) => {
5062
+ connectTimeout = setTimeout(
5063
+ () => reject(new DrivenFault(`${address} did not answer within ${timeoutMs / 1e3} seconds.`, "Check the place is up, then start the server again.")),
5064
+ timeoutMs
5065
+ );
5066
+ })
5067
+ ]);
5068
+ return {
5069
+ tools: tools.map(({ name, description, inputSchema }) => ({ name, ...description !== void 0 && { description }, inputSchema })),
5070
+ callTool: async (name, args, callToken) => {
5071
+ if (endpoint !== void 0) bearerToken = callToken;
5072
+ try {
5073
+ return await client.callTool({ name, arguments: { ...args } });
5074
+ } catch (raised) {
5075
+ throw faultOf(raised, `Calling "${name}" at ${address} failed`, "Check the place is up, then try again.");
5076
+ }
5077
+ },
5078
+ close: () => client.close()
5079
+ };
5080
+ } catch (raised) {
5081
+ await client.close().catch(() => void 0);
5082
+ throw faultOf(raised, `${address} could not be reached`, "Check the place is up, or its command installed, then start the server again.");
5083
+ } finally {
5084
+ clearTimeout(connectTimeout);
5085
+ }
5086
+ }
5087
+ };
5088
+ function faultOf(raised, failed, fix) {
5089
+ if (raised instanceof DrivenFault) return raised;
5090
+ if (raised instanceof StreamableHTTPError && raised.code === 401)
5091
+ return new UnauthorizedMcpFault(`${failed}: the place refused your credential.`, 'Run "cw mcp auth" at a terminal to sign in there again.');
5092
+ return new DrivenFault(`${failed}: ${raised instanceof Error ? raised.message : String(raised)}`, fix);
5093
+ }
5094
+
4076
5095
  // main.ts
4077
5096
  var fileReader = new FileReaders();
4078
5097
  var yamlParser = new YamlParser();
4079
5098
  var fileWriter = new FileOutput();
4080
5099
  var vcs = new Git(fileReader);
5100
+ var secrets = new OsSecrets();
5101
+ var authorizing = new OAuth();
5102
+ var mcpServers = new McpClients();
4081
5103
  var repoPath = pathToFileURL2(`${process.cwd()}/`);
4082
5104
  var charterAuthoringApp = new CharterAuthoring(repoPath, fileReader, yamlParser, fileWriter, vcs);
4083
5105
  var charterVendoringApp = new CharterVendoring(repoPath, fileReader, vcs);
4084
5106
  var testAuthoringApp = new TestAuthoring(repoPath, fileReader, fileWriter);
4085
- var cli = new Commander({ cwd: process.cwd(), version: package_default.version, charterAuthoringApp, charterVendoringApp, testAuthoringApp });
5107
+ var mcpConnectingApp = new McpConnecting(repoPath, fileReader, secrets, authorizing, yamlParser, mcpServers);
5108
+ var cli = new Commander({ cwd: process.cwd(), version: package_default.version, charterAuthoringApp, charterVendoringApp, testAuthoringApp, mcpConnectingApp });
4086
5109
  process.exitCode = await cli.run(process.argv.slice(2));