cherry-works 0.1.0 → 0.2.0

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.0",
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
  );
@@ -1345,7 +1528,7 @@ var InitCommand = class {
1345
1528
  async function whichAgents(charterAuthoringApp2, typed) {
1346
1529
  if (typed !== void 0) {
1347
1530
  if (!isAgentProvider(typed))
1348
- throw new Fault(
1531
+ throw new DomainFault(
1349
1532
  `This engine compiles for no agent called "${typed}".`,
1350
1533
  `Name any of: ${AGENT_PROVIDERS.join(", ")}.`
1351
1534
  );
@@ -1354,7 +1537,7 @@ async function whichAgents(charterAuthoringApp2, typed) {
1354
1537
  const agentsInSettings = (await charterAuthoringApp2.settings()).data.agents.filter(isAgentProvider);
1355
1538
  if (!process.stdin.isTTY) {
1356
1539
  if (agentsInSettings.length === 0)
1357
- throw new Fault(
1540
+ throw new DomainFault(
1358
1541
  "Nobody is at the terminal to choose an agent, and this repository has none chosen yet.",
1359
1542
  `Run "cw init --agent <name>", naming one of: ${AGENT_PROVIDERS.join(", ")}.`
1360
1543
  );
@@ -1374,7 +1557,7 @@ async function whichAgents(charterAuthoringApp2, typed) {
1374
1557
  min: 1
1375
1558
  });
1376
1559
  if (chosen === void 0)
1377
- throw new Fault("Setup was stopped before it had its answers.", 'Run "cw init" again.');
1560
+ throw new DomainFault("Setup was stopped before it had its answers.", 'Run "cw init" again.');
1378
1561
  return chosen;
1379
1562
  }
1380
1563
 
@@ -1472,6 +1655,133 @@ function inFull(entry) {
1472
1655
  return [surveyed(entry), ` ${file}`, ...declared.length === 0 ? [] : [` ${declared.join(", ")}`]].join("\n");
1473
1656
  }
1474
1657
 
1658
+ // src/driver/cli/commands/McpAuthCommand.ts
1659
+ import prompts3 from "prompts";
1660
+ var OPTIONS6 = {
1661
+ identity: {
1662
+ type: "string",
1663
+ describe: 'One mcp whose address to sign in to again, whatever you were signed in as: "mcp:github/billing"'
1664
+ },
1665
+ status: {
1666
+ type: "boolean",
1667
+ default: false,
1668
+ describe: "List each address, the mcps at it and whether you are signed in there, and change nothing"
1669
+ }
1670
+ };
1671
+ var McpAuthCommand = class {
1672
+ name = "mcp auth [identity]";
1673
+ summary = "Sign in, as yourself, to every place the charter reaches";
1674
+ options = OPTIONS6;
1675
+ async run({ mcpConnectingApp: mcpConnectingApp2 }, { identity, status }) {
1676
+ const signInStatuses = (await mcpConnectingApp2.signInStatus()).map((one) => one.data);
1677
+ if (status)
1678
+ return {
1679
+ code: EXIT_OK,
1680
+ result: signInStatuses.length === 0 ? "No place the last build listed takes a sign-in.\n" : signInStatuses.map(
1681
+ ({ address, identities, signedIn, method }) => [address, ` ${identities.join(", ")}`, ` ${signedIn ? `signed in by ${method}` : "not signed in"}`, ""].join("\n")
1682
+ ).join("")
1683
+ };
1684
+ let unsignedStatuses;
1685
+ if (identity === void 0) unsignedStatuses = signInStatuses.filter((one) => !one.signedIn);
1686
+ else {
1687
+ const identityStatus = signInStatuses.find((one) => one.identities.includes(identity));
1688
+ if (identityStatus === void 0)
1689
+ throw new DomainFault(
1690
+ `No place the last build listed is declared by ${identity}.`,
1691
+ 'Run "cw mcp auth --status" to see every mcp, or "cw build" if the charter changed.'
1692
+ );
1693
+ unsignedStatuses = [identityStatus];
1694
+ }
1695
+ if (unsignedStatuses.length === 0)
1696
+ return {
1697
+ code: EXIT_OK,
1698
+ result: signInStatuses.length === 0 ? "No place the last build listed takes a sign-in.\n" : `You are signed in at every address: ${signInStatuses.length}.
1699
+ `
1700
+ };
1701
+ if (!process.stdin.isTTY)
1702
+ return {
1703
+ code: EXIT_FAILURE,
1704
+ problem: [
1705
+ "Not signed in at:",
1706
+ ...unsignedStatuses.map(({ address }) => ` ${address}`),
1707
+ ' Run "cw mcp auth" at a terminal to sign in; nothing is asked without one.',
1708
+ ""
1709
+ ].join("\n")
1710
+ };
1711
+ for (const { address, identities, auth: auth2 } of unsignedStatuses) {
1712
+ process.stderr.write(`${address}
1713
+ ${identities.join(", ")}
1714
+ `);
1715
+ const { method } = auth2.length > 1 ? await prompts3({
1716
+ type: "select",
1717
+ name: "method",
1718
+ message: "Sign in how?",
1719
+ choices: auth2.map((one) => ({ title: one === "oauth" ? "in the browser (OAuth)" : "with a token", value: one }))
1720
+ }) : { method: auth2[0] };
1721
+ if (method === "oauth")
1722
+ await mcpConnectingApp2.signInWithOAuth(
1723
+ address,
1724
+ (authorizationUrl) => process.stderr.write(` Open this address to sign in, then come back here:
1725
+ ${authorizationUrl}
1726
+ `)
1727
+ );
1728
+ else if (method === "token") {
1729
+ const { token } = await prompts3({ type: "invisible", name: "token", message: "Token?" });
1730
+ if (token === void 0) throwInterruptFault(address);
1731
+ await mcpConnectingApp2.signInWithToken(address, token);
1732
+ } else throwInterruptFault(address);
1733
+ }
1734
+ return {
1735
+ code: EXIT_OK,
1736
+ result: `Signed in at ${unsignedStatuses.map(({ address }) => address).join(", ")}.
1737
+ `
1738
+ };
1739
+ }
1740
+ };
1741
+ function throwInterruptFault(address) {
1742
+ throw new DomainFault(`This was stopped before signing in at ${address}; what was signed in before it is kept.`, 'Run "cw mcp auth" again.');
1743
+ }
1744
+
1745
+ // src/driver/cli/commands/McpServeCommand.ts
1746
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1747
+
1748
+ // src/driver/mcp/server.ts
1749
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
1750
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
1751
+ async function startMcpServer(mcpConnectingApp2, transport, version = "0.0.0") {
1752
+ const server = new Server({ name: "cw", version }, { capabilities: { tools: {} } });
1753
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
1754
+ tools: (await mcpConnectingApp2.served()).data.tools.map(({ data }) => data)
1755
+ }));
1756
+ server.setRequestHandler(
1757
+ CallToolRequestSchema,
1758
+ async ({ params }) => (await mcpConnectingApp2.call(params.name, params.arguments ?? {})).data
1759
+ );
1760
+ const serverClosed = new Promise((resolve) => server.onclose = resolve);
1761
+ await server.connect(transport);
1762
+ return { serverClosed };
1763
+ }
1764
+
1765
+ // src/driver/cli/commands/McpServeCommand.ts
1766
+ var McpServeCommand = class {
1767
+ name = "mcp serve";
1768
+ summary = "Serve every place the charter reaches to your agent, over stdio";
1769
+ async run({ mcpConnectingApp: mcpConnectingApp2, version }) {
1770
+ const servedTools2 = await mcpConnectingApp2.served();
1771
+ for (const problem of servedTools2.data.problems) process.stderr.write(`${problem}
1772
+ `);
1773
+ const transport = new StdioServerTransport();
1774
+ const { serverClosed } = await startMcpServer(mcpConnectingApp2, transport, version);
1775
+ const closeTransport = () => void transport.close();
1776
+ process.stdin.once("end", closeTransport);
1777
+ process.once("SIGTERM", closeTransport);
1778
+ process.once("SIGINT", closeTransport);
1779
+ await serverClosed;
1780
+ await mcpConnectingApp2.stopServing();
1781
+ return { code: EXIT_OK };
1782
+ }
1783
+ };
1784
+
1475
1785
  // src/driver/portal/server.ts
1476
1786
  import { randomBytes } from "crypto";
1477
1787
  import { access } from "fs/promises";
@@ -1484,87 +1794,87 @@ import { serveStatic } from "@hono/node-server/serve-static";
1484
1794
  // src/driver/portal/routes.ts
1485
1795
  import { createRoute, OpenAPIHono } from "@hono/zod-openapi";
1486
1796
  import { HTTPException } from "hono/http-exception";
1487
- import { z as z10 } from "zod";
1797
+ import { z as z11 } from "zod";
1488
1798
 
1489
1799
  // 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 });
1800
+ import { z as z6 } from "zod";
1801
+ var strings = z6.array(z6.string()).readonly();
1802
+ var dto = (type, data) => z6.object({ type: z6.literal(type), data });
1493
1803
  function byType(...schemas) {
1494
1804
  return Object.fromEntries(schemas.map((one) => [one.shape.type.value, one]));
1495
1805
  }
1496
1806
 
1497
1807
  // src/hexagon/port/driver/dtos/data.ts
1498
- import { z as z6 } from "zod";
1808
+ import { z as z7 } from "zod";
1499
1809
  var fault = dto(
1500
1810
  "Fault",
1501
- z6.object({
1502
- message: z6.string(),
1503
- fix: z6.string(),
1504
- severity: z6.enum(["error", "warn"])
1811
+ z7.object({
1812
+ message: z7.string(),
1813
+ fix: z7.string(),
1814
+ severity: z7.enum(["error", "warn"])
1505
1815
  })
1506
1816
  );
1507
1817
  var faultsByFile = dto(
1508
1818
  "FaultsByFile",
1509
- z6.object({
1510
- files: z6.record(z6.string(), z6.array(fault).readonly())
1819
+ z7.object({
1820
+ files: z7.record(z7.string(), z7.array(fault).readonly())
1511
1821
  })
1512
1822
  );
1513
1823
  var faults = dto(
1514
1824
  "Faults",
1515
- z6.object({
1516
- faults: z6.array(fault).readonly()
1825
+ z7.object({
1826
+ faults: z7.array(fault).readonly()
1517
1827
  })
1518
1828
  );
1519
- var primitiveKinds = dto("PrimitiveKinds", z6.record(z6.string(), z6.string()));
1829
+ var primitiveKinds = dto("PrimitiveKinds", z7.record(z7.string(), z7.string()));
1520
1830
  var catalogueEntry = dto(
1521
1831
  "CatalogueEntry",
1522
- z6.object({
1523
- identity: z6.string(),
1524
- kind: z6.string(),
1525
- id: z6.string(),
1526
- description: z6.string(),
1527
- file: z6.string(),
1832
+ z7.object({
1833
+ identity: z7.string(),
1834
+ kind: z7.string(),
1835
+ id: z7.string(),
1836
+ description: z7.string(),
1837
+ file: z7.string(),
1528
1838
  tags: strings.optional(),
1529
1839
  globs: strings.optional(),
1530
- rationale: z6.string().optional(),
1840
+ rationale: z7.string().optional(),
1531
1841
  mixins: strings.optional()
1532
1842
  })
1533
1843
  );
1534
1844
  var catalogue = dto(
1535
1845
  "Catalogue",
1536
- z6.object({
1537
- entries: z6.array(catalogueEntry).readonly()
1846
+ z7.object({
1847
+ entries: z7.array(catalogueEntry).readonly()
1538
1848
  })
1539
1849
  );
1540
1850
  var scopedPrimitive = dto(
1541
1851
  "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]))
1852
+ z7.object({
1853
+ identity: z7.string(),
1854
+ kind: z7.string(),
1855
+ description: z7.string(),
1856
+ file: z7.string(),
1857
+ scope: z7.enum(["repo", "vendor", "builtin"]),
1858
+ headers: z7.record(z7.string(), z7.union([z7.string(), strings]))
1549
1859
  })
1550
1860
  );
1551
1861
  var scopedPrimitives = dto(
1552
1862
  "ScopedPrimitives",
1553
- z6.object({
1554
- primitives: z6.array(scopedPrimitive).readonly()
1863
+ z7.object({
1864
+ primitives: z7.array(scopedPrimitive).readonly()
1555
1865
  })
1556
1866
  );
1557
1867
  var primitiveSnapshot = dto(
1558
1868
  "PrimitiveSnapshot",
1559
- z6.object({
1869
+ z7.object({
1560
1870
  scopedPrimitive,
1561
- body: z6.string(),
1562
- revision: z6.string()
1871
+ body: z7.string(),
1872
+ revision: z7.string()
1563
1873
  })
1564
1874
  );
1565
1875
  var planSummary = dto(
1566
1876
  "PlanSummary",
1567
- z6.object({
1877
+ z7.object({
1568
1878
  added: strings,
1569
1879
  edited: strings,
1570
1880
  deleted: strings,
@@ -1573,63 +1883,89 @@ var planSummary = dto(
1573
1883
  );
1574
1884
  var primitiveHeader = dto(
1575
1885
  "PrimitiveHeader",
1576
- z6.object({
1577
- field: z6.string(),
1578
- shape: z6.enum(["line", "list"]),
1579
- required: z6.boolean(),
1886
+ z7.object({
1887
+ field: z7.string(),
1888
+ shape: z7.enum(["line", "list"]),
1889
+ required: z7.boolean(),
1580
1890
  allowedValues: strings.optional()
1581
1891
  })
1582
1892
  );
1583
1893
  var primitiveRequirements = dto(
1584
1894
  "PrimitiveRequirements",
1585
- z6.object({
1586
- headers: z6.array(primitiveHeader).readonly(),
1587
- sample: z6.string()
1895
+ z7.object({
1896
+ headers: z7.array(primitiveHeader).readonly(),
1897
+ sample: z7.string()
1588
1898
  })
1589
1899
  );
1590
- var testCasesByFile = dto("TestCasesByFile", z6.record(z6.string(), strings));
1900
+ var testCasesByFile = dto("TestCasesByFile", z7.record(z7.string(), strings));
1591
1901
  var testCaseReport = dto(
1592
1902
  "TestCaseReport",
1593
- z6.object({
1594
- suiteName: z6.string(),
1595
- situation: z6.string(),
1596
- passed: z6.boolean(),
1903
+ z7.object({
1904
+ suiteName: z7.string(),
1905
+ situation: z7.string(),
1906
+ passed: z7.boolean(),
1597
1907
  unmet: fault.optional()
1598
1908
  })
1599
1909
  );
1600
1910
  var testRunReport = dto(
1601
1911
  "TestRunReport",
1602
- z6.object({
1603
- testCaseReports: z6.array(testCaseReport).readonly()
1912
+ z7.object({
1913
+ testCaseReports: z7.array(testCaseReport).readonly()
1604
1914
  })
1605
1915
  );
1606
1916
  var testCase = dto(
1607
1917
  "TestCase",
1608
- z6.object({
1609
- situation: z6.string(),
1610
- expectation: z6.string(),
1611
- identity: z6.string().optional()
1918
+ z7.object({
1919
+ situation: z7.string(),
1920
+ expectation: z7.string(),
1921
+ identity: z7.string().optional()
1612
1922
  })
1613
1923
  );
1614
1924
  var testSuite = dto(
1615
1925
  "TestSuite",
1616
- z6.object({
1617
- name: z6.string(),
1618
- text: z6.string().optional(),
1619
- description: z6.string().optional(),
1620
- cases: z6.array(testCase).readonly(),
1926
+ z7.object({
1927
+ name: z7.string(),
1928
+ text: z7.string().optional(),
1929
+ description: z7.string().optional(),
1930
+ cases: z7.array(testCase).readonly(),
1621
1931
  fault: fault.optional()
1622
1932
  })
1623
1933
  );
1624
1934
  var testSuites = dto(
1625
1935
  "TestSuites",
1626
- z6.object({
1627
- testSuites: z6.array(testSuite).readonly()
1936
+ z7.object({
1937
+ testSuites: z7.array(testSuite).readonly()
1938
+ })
1939
+ );
1940
+ var signInStatus = dto(
1941
+ "SignInStatus",
1942
+ z7.object({
1943
+ address: z7.string(),
1944
+ identities: strings,
1945
+ auth: z7.array(z7.enum(["oauth", "token"])).readonly(),
1946
+ signedIn: z7.boolean(),
1947
+ method: z7.enum(["oauth", "token"]).optional()
1628
1948
  })
1629
1949
  );
1950
+ var servedTool = dto(
1951
+ "ServedTool",
1952
+ z7.object({
1953
+ name: z7.string(),
1954
+ description: z7.string(),
1955
+ inputSchema: z7.record(z7.string(), z7.unknown())
1956
+ })
1957
+ );
1958
+ var servedTools = dto(
1959
+ "ServedTools",
1960
+ z7.object({
1961
+ tools: z7.array(servedTool).readonly(),
1962
+ problems: strings
1963
+ })
1964
+ );
1965
+ var toolAnswer = dto("ToolAnswer", z7.record(z7.string(), z7.unknown()));
1630
1966
  var workspaceSettings = dto(
1631
1967
  "WorkspaceSettings",
1632
- z6.object({
1968
+ z7.object({
1633
1969
  agents: strings
1634
1970
  })
1635
1971
  );
@@ -1646,59 +1982,63 @@ var DataDTOs = byType(
1646
1982
  primitiveSnapshot,
1647
1983
  scopedPrimitive,
1648
1984
  scopedPrimitives,
1985
+ servedTool,
1986
+ servedTools,
1987
+ signInStatus,
1649
1988
  testCase,
1650
1989
  testCaseReport,
1651
1990
  testCasesByFile,
1652
1991
  testRunReport,
1653
1992
  testSuite,
1654
1993
  testSuites,
1994
+ toolAnswer,
1655
1995
  workspaceSettings
1656
1996
  );
1657
1997
 
1658
1998
  // src/hexagon/port/driver/dtos/outcome.ts
1659
- import { z as z7 } from "zod";
1999
+ import { z as z8 } from "zod";
1660
2000
  var doctorOutcome = dto(
1661
2001
  "DoctorOutcome",
1662
- z7.object({
2002
+ z8.object({
1663
2003
  agents: strings,
1664
- errorCount: z7.number(),
1665
- warnCount: z7.number(),
1666
- pendingCount: z7.number().nullable(),
2004
+ errorCount: z8.number(),
2005
+ warnCount: z8.number(),
2006
+ pendingCount: z8.number().nullable(),
1667
2007
  faultsByFile: DataDTOs.FaultsByFile,
1668
2008
  driftedVendors: strings,
1669
- problemCount: z7.number()
2009
+ problemCount: z8.number()
1670
2010
  })
1671
2011
  );
1672
2012
  var explanationOutcome = dto(
1673
2013
  "ExplanationOutcome",
1674
- z7.object({
2014
+ z8.object({
1675
2015
  scopedPrimitive: DataDTOs.ScopedPrimitive,
1676
- activatesWhen: z7.string(),
1677
- useMixins: z7.array(DataDTOs.ScopedPrimitive).readonly(),
2016
+ activatesWhen: z8.string(),
2017
+ useMixins: z8.array(DataDTOs.ScopedPrimitive).readonly(),
1678
2018
  rationale: DataDTOs.ScopedPrimitive.optional(),
1679
- hosts: z7.array(DataDTOs.ScopedPrimitive).readonly(),
1680
- citers: z7.array(DataDTOs.ScopedPrimitive).readonly(),
2019
+ hosts: z8.array(DataDTOs.ScopedPrimitive).readonly(),
2020
+ citers: z8.array(DataDTOs.ScopedPrimitive).readonly(),
1681
2021
  testCasesByFile: DataDTOs.TestCasesByFile
1682
2022
  })
1683
2023
  );
1684
2024
  var OutcomeDTOs = byType(doctorOutcome, explanationOutcome);
1685
2025
 
1686
2026
  // src/hexagon/domain/models/test/TestSuite.ts
1687
- import { z as z9 } from "zod";
2027
+ import { z as z10 } from "zod";
1688
2028
 
1689
2029
  // 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() })
2030
+ import { z as z9 } from "zod";
2031
+ var TEST_CASE_SCHEMA = z9.union([
2032
+ z9.strictObject({
2033
+ do: z9.strictObject({ touchFile: z9.string().min(1) }),
2034
+ expect: z9.union([
2035
+ z9.strictObject({ activate: PRIMITIVE_IDENTITY_SCHEMA }),
2036
+ z9.strictObject({ allow: z9.boolean() })
1697
2037
  ])
1698
2038
  }),
1699
- z8.strictObject({
1700
- when: z8.enum(SIGNALS),
1701
- expect: z8.strictObject({ run: PRIMITIVE_IDENTITY_SCHEMA })
2039
+ z9.strictObject({
2040
+ when: z9.enum(SIGNALS),
2041
+ expect: z9.strictObject({ run: PRIMITIVE_IDENTITY_SCHEMA })
1702
2042
  })
1703
2043
  ]);
1704
2044
  var TestCase = class {
@@ -1740,9 +2080,9 @@ var TestCase = class {
1740
2080
  };
1741
2081
 
1742
2082
  // 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)
2083
+ var TEST_SUITE_SCHEMA = z10.strictObject({
2084
+ description: z10.string().min(1).optional(),
2085
+ cases: z10.array(TEST_CASE_SCHEMA.transform((written) => new TestCase(written))).min(1)
1746
2086
  });
1747
2087
  var TestSuite = class {
1748
2088
  constructor(description, cases, body) {
@@ -1809,7 +2149,7 @@ function testRootOf(testSuiteFiles) {
1809
2149
  try {
1810
2150
  suitesByFile[path] = testSuiteOf(contents);
1811
2151
  } catch (raised) {
1812
- if (!(raised instanceof Fault)) throw raised;
2152
+ if (!(raised instanceof DomainFault)) throw raised;
1813
2153
  faultsByFiles[path] = [raised];
1814
2154
  }
1815
2155
  }
@@ -1962,7 +2302,7 @@ var json = (schema, description) => ({
1962
2302
  function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
1963
2303
  const app = new OpenAPIHono();
1964
2304
  app.onError((raised, c) => {
1965
- if (raised instanceof Fault) return c.json(faultDTO(raised), 422);
2305
+ if (raised instanceof DomainFault) return c.json(faultDTO(raised), 422);
1966
2306
  if (raised instanceof HTTPException) return raised.getResponse();
1967
2307
  console.error(raised);
1968
2308
  return c.text(raised.message, 500);
@@ -1978,10 +2318,10 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
1978
2318
  createRoute({
1979
2319
  method: "get",
1980
2320
  path: "/charter/root/primitives",
1981
- request: { query: z10.object({ matching: z10.string().optional() }) },
2321
+ request: { query: z11.object({ matching: z11.string().optional() }) },
1982
2322
  responses: {
1983
2323
  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")
2324
+ 422: json(z11.union([DataDTOs.FaultsByFile, DataDTOs.Fault]), "The charter does not hold")
1985
2325
  }
1986
2326
  }),
1987
2327
  async (c) => {
@@ -1992,10 +2332,10 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
1992
2332
  createRoute({
1993
2333
  method: "get",
1994
2334
  path: "/charter/root/primitives/{identity}/explanation",
1995
- request: { params: z10.object({ identity: z10.string() }) },
2335
+ request: { params: z11.object({ identity: z11.string() }) },
1996
2336
  responses: {
1997
2337
  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")
2338
+ 422: json(z11.union([DataDTOs.FaultsByFile, DataDTOs.Fault]), "The charter does not hold, or holds nothing of it")
1999
2339
  }
2000
2340
  }),
2001
2341
  async (c) => {
@@ -2009,11 +2349,11 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2009
2349
  path: "/charter/root/primitives",
2010
2350
  request: {
2011
2351
  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()
2352
+ z11.object({
2353
+ kind: z11.string(),
2354
+ id: z11.string(),
2355
+ headers: z11.record(z11.string(), z11.union([z11.string(), z11.array(z11.string())])),
2356
+ body: z11.string()
2017
2357
  }),
2018
2358
  "A new primitive: its kind and id, a line or a list under each header, and its body"
2019
2359
  )
@@ -2021,26 +2361,26 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2021
2361
  responses: {
2022
2362
  201: {
2023
2363
  ...json(DataDTOs.ScopedPrimitive, "The primitive written"),
2024
- headers: z10.object({ Location: z10.string() })
2364
+ headers: z11.object({ Location: z11.string() })
2025
2365
  },
2026
- 422: json(z10.union([DataDTOs.Faults, DataDTOs.Fault]), "The answers the kind refused, or an identity already claimed")
2366
+ 422: json(z11.union([DataDTOs.Faults, DataDTOs.Fault]), "The answers the kind refused, or an identity already claimed")
2027
2367
  }
2028
2368
  }),
2029
2369
  async (c) => {
2030
2370
  const { kind, id, headers, body } = c.req.valid("json");
2031
2371
  const scopedPrimitiveDTO2 = await charterAuthoringApp2.add(kind, id, headers, body);
2032
2372
  if (scopedPrimitiveDTO2.type === "Faults") return c.json(scopedPrimitiveDTO2, 422);
2033
- return c.json(scopedPrimitiveDTO2, 201, { Location: `/api/charter/root/primitives/${scopedPrimitiveDTO2.data.identity}` });
2373
+ return c.json(scopedPrimitiveDTO2, 201, { Location: `/api/charter/root/primitives/${encodeURIComponent(scopedPrimitiveDTO2.data.identity)}` });
2034
2374
  }
2035
2375
  ).openapi(
2036
2376
  createRoute({
2037
2377
  method: "get",
2038
2378
  path: "/charter/root/primitives/{identity}",
2039
- request: { params: z10.object({ identity: z10.string() }) },
2379
+ request: { params: z11.object({ identity: z11.string() }) },
2040
2380
  responses: {
2041
2381
  200: {
2042
2382
  ...json(DataDTOs.PrimitiveSnapshot, "One primitive as the charter read it"),
2043
- headers: z10.object({ ETag: z10.string() })
2383
+ headers: z11.object({ ETag: z11.string() })
2044
2384
  },
2045
2385
  422: json(DataDTOs.Fault, "The charter holds nothing of it")
2046
2386
  }
@@ -2055,12 +2395,12 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2055
2395
  method: "put",
2056
2396
  path: "/charter/root/primitives/{identity}",
2057
2397
  request: {
2058
- params: z10.object({ identity: z10.string() }),
2059
- headers: z10.object({ "if-match": z10.string().optional() }),
2398
+ params: z11.object({ identity: z11.string() }),
2399
+ headers: z11.object({ "if-match": z11.string().optional() }),
2060
2400
  body: json(
2061
- z10.object({
2062
- headers: z10.record(z10.string(), z10.union([z10.string(), z10.array(z10.string())])),
2063
- body: z10.string()
2401
+ z11.object({
2402
+ headers: z11.record(z11.string(), z11.union([z11.string(), z11.array(z11.string())])),
2403
+ body: z11.string()
2064
2404
  }),
2065
2405
  "A line or a list under each header, and the body, written over the primitive's own"
2066
2406
  )
@@ -2068,7 +2408,7 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2068
2408
  responses: {
2069
2409
  200: json(DataDTOs.ScopedPrimitive, "The primitive written"),
2070
2410
  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")
2411
+ 422: json(z11.union([DataDTOs.Faults, DataDTOs.Fault]), "The answers the kind refused, or a primitive this repository did not author")
2072
2412
  }
2073
2413
  }),
2074
2414
  async (c) => {
@@ -2077,7 +2417,7 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2077
2417
  if (c.req.valid("header")["if-match"] !== `"${revision}"`)
2078
2418
  return c.json(
2079
2419
  faultDTO(
2080
- new Fault(
2420
+ new DomainFault(
2081
2421
  `${scopedPrimitive2.data.file} changed on disk after it was opened, and saving would write over that change.`,
2082
2422
  "Open it again to see what changed, then make your edit there."
2083
2423
  )
@@ -2092,7 +2432,7 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2092
2432
  createRoute({
2093
2433
  method: "delete",
2094
2434
  path: "/charter/root/primitives/{identity}",
2095
- request: { params: z10.object({ identity: z10.string() }) },
2435
+ request: { params: z11.object({ identity: z11.string() }) },
2096
2436
  responses: {
2097
2437
  204: { description: "The file is gone" },
2098
2438
  422: json(DataDTOs.Fault, "A primitive this repository did not author, or none at all")
@@ -2114,7 +2454,7 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2114
2454
  createRoute({
2115
2455
  method: "get",
2116
2456
  path: "/definitions/kinds/{kind}/requirements",
2117
- request: { params: z10.object({ kind: z10.string() }) },
2457
+ request: { params: z11.object({ kind: z11.string() }) },
2118
2458
  responses: {
2119
2459
  200: json(DataDTOs.PrimitiveRequirements, "Every header the kind takes, and its sample"),
2120
2460
  422: json(DataDTOs.Fault, "A word that is no kind")
@@ -2167,8 +2507,8 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2167
2507
  path: "/test-suites",
2168
2508
  responses: {
2169
2509
  201: {
2170
- ...json(z10.string(), "The name of the test file written"),
2171
- headers: z10.object({ Location: z10.string() })
2510
+ ...json(z11.string(), "The name of the test file written"),
2511
+ headers: z11.object({ Location: z11.string() })
2172
2512
  }
2173
2513
  }
2174
2514
  }),
@@ -2181,11 +2521,11 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2181
2521
  method: "put",
2182
2522
  path: "/test-suites/{name}",
2183
2523
  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")
2524
+ params: z11.object({ name: z11.string() }),
2525
+ body: json(z11.object({ text: z11.string() }), "The whole text the test file is written over with")
2186
2526
  },
2187
2527
  responses: {
2188
- 200: json(z10.string(), "The name of the test file written"),
2528
+ 200: json(z11.string(), "The name of the test file written"),
2189
2529
  422: json(DataDTOs.Fault, "Text that is not a suite, with its sample, or a name that is no test file")
2190
2530
  }
2191
2531
  }),
@@ -2194,7 +2534,7 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2194
2534
  createRoute({
2195
2535
  method: "delete",
2196
2536
  path: "/test-suites/{name}",
2197
- request: { params: z10.object({ name: z10.string() }) },
2537
+ request: { params: z11.object({ name: z11.string() }) },
2198
2538
  responses: {
2199
2539
  204: { description: "The test file is gone" },
2200
2540
  422: json(DataDTOs.Fault, "A name that is no test file")
@@ -2221,7 +2561,7 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2221
2561
  createRoute({
2222
2562
  method: "get",
2223
2563
  path: "/vendors",
2224
- responses: { 200: json(z10.array(z10.string()).readonly(), "Every folder a vendor source was installed as") }
2564
+ responses: { 200: json(z11.array(z11.string()).readonly(), "Every folder a vendor source was installed as") }
2225
2565
  }),
2226
2566
  async (c) => c.json(await charterVendoringApp2.installed(), 200)
2227
2567
  ).openapi(
@@ -2230,7 +2570,7 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2230
2570
  path: "/vendors",
2231
2571
  request: {
2232
2572
  body: json(
2233
- z10.object({ source: z10.string(), version: z10.string().optional() }),
2573
+ z11.object({ source: z11.string(), version: z11.string().optional() }),
2234
2574
  "A source as git fetches it, and the version to pin it to where one is named"
2235
2575
  )
2236
2576
  },
@@ -2248,7 +2588,7 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2248
2588
  createRoute({
2249
2589
  method: "delete",
2250
2590
  path: "/vendors/{name}",
2251
- request: { params: z10.object({ name: z10.string() }) },
2591
+ request: { params: z11.object({ name: z11.string() }) },
2252
2592
  responses: {
2253
2593
  204: { description: "The folder is gone, and that it is gone is committed" },
2254
2594
  422: json(DataDTOs.Fault, "No repository, work in hand, or what git refused")
@@ -2265,7 +2605,7 @@ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
2265
2605
  var DEFAULT_PORT = 9927;
2266
2606
  async function startPortal(page, charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2, port = DEFAULT_PORT) {
2267
2607
  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.');
2608
+ throw new DomainFault("The portal's page is not built.", 'Run "pnpm build", then start the portal again.');
2269
2609
  });
2270
2610
  const token = randomBytes(32).toString("base64url");
2271
2611
  for (let next = port; ; next++) {
@@ -2300,7 +2640,7 @@ function listen(app, port) {
2300
2640
  }
2301
2641
 
2302
2642
  // src/driver/cli/commands/PortalCommand.ts
2303
- var OPTIONS6 = {
2643
+ var OPTIONS7 = {
2304
2644
  port: {
2305
2645
  type: "number",
2306
2646
  describe: "Which port to serve on, the next free one when it is taken",
@@ -2310,7 +2650,7 @@ var OPTIONS6 = {
2310
2650
  var PortalCommand = class {
2311
2651
  name = "portal";
2312
2652
  summary = "Serve this repository's charter to a browser on this machine";
2313
- options = OPTIONS6;
2653
+ options = OPTIONS7;
2314
2654
  async run({ charterAuthoringApp: charterAuthoringApp2, charterVendoringApp: charterVendoringApp2, testAuthoringApp: testAuthoringApp2 }, { port }) {
2315
2655
  await charterAuthoringApp2.ensureRepoReady();
2316
2656
  const { address } = await startPortal(
@@ -2327,8 +2667,8 @@ Press Ctrl+C to stop it.
2327
2667
  };
2328
2668
 
2329
2669
  // src/driver/cli/commands/RemoveCommand.ts
2330
- import prompts3 from "prompts";
2331
- var OPTIONS7 = {
2670
+ import prompts4 from "prompts";
2671
+ var OPTIONS8 = {
2332
2672
  identity: {
2333
2673
  type: "string",
2334
2674
  describe: 'What the primitive is named by across the whole charter: "guide:no-any"'
@@ -2343,16 +2683,16 @@ var OPTIONS7 = {
2343
2683
  var RemoveCommand = class {
2344
2684
  name = "remove <identity>";
2345
2685
  summary = "Delete the file of one primitive this repository authored";
2346
- options = OPTIONS7;
2686
+ options = OPTIONS8;
2347
2687
  async run({ charterAuthoringApp: charterAuthoringApp2 }, { identity, yes }) {
2348
2688
  const { scopedPrimitive: scopedPrimitive2 } = (await charterAuthoringApp2.open(identity ?? "")).data;
2349
2689
  if (!yes && scopedPrimitive2.data.scope === REPO_SCOPE) {
2350
2690
  if (!process.stdin.isTTY)
2351
- throw new Fault(
2691
+ throw new DomainFault(
2352
2692
  `Removing ${scopedPrimitive2.data.file} is asked first, and nobody is at the terminal to answer.`,
2353
2693
  `Run "cw remove ${scopedPrimitive2.data.identity} --yes" to remove it without asking.`
2354
2694
  );
2355
- const { confirmed } = await prompts3({
2695
+ const { confirmed } = await prompts4({
2356
2696
  type: "confirm",
2357
2697
  name: "confirmed",
2358
2698
  message: `Remove ${scopedPrimitive2.data.file}?`,
@@ -2386,7 +2726,7 @@ var SuiteAddCommand = class {
2386
2726
  };
2387
2727
 
2388
2728
  // src/driver/cli/commands/SuiteEditCommand.ts
2389
- var OPTIONS8 = {
2729
+ var OPTIONS9 = {
2390
2730
  name: {
2391
2731
  type: "string",
2392
2732
  describe: `The test file's name under ${TEST_DIRECTORY}/: "untitled-1.json"`
@@ -2395,11 +2735,11 @@ var OPTIONS8 = {
2395
2735
  var SuiteEditCommand = class {
2396
2736
  name = "suite edit <name>";
2397
2737
  summary = "Open one test file in your editor";
2398
- options = OPTIONS8;
2738
+ options = OPTIONS9;
2399
2739
  async run({ cwd, testAuthoringApp: testAuthoringApp2 }, { name }) {
2400
2740
  const { testSuites: testSuites2 } = (await testAuthoringApp2.suites()).data;
2401
2741
  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.');
2742
+ throw new DomainFault(`${TEST_DIRECTORY}/ holds no test file called "${name ?? ""}".`, 'Run "cw suite add" to write a new one.');
2403
2743
  const file = `${TEST_DIRECTORY}/${name}`;
2404
2744
  const problem = await editInEditor(cwd, file);
2405
2745
  if (problem !== void 0) return { code: EXIT_FAILURE, problem };
@@ -2408,7 +2748,7 @@ var SuiteEditCommand = class {
2408
2748
  };
2409
2749
 
2410
2750
  // src/driver/cli/commands/SuiteRemoveCommand.ts
2411
- var OPTIONS9 = {
2751
+ var OPTIONS10 = {
2412
2752
  name: {
2413
2753
  type: "string",
2414
2754
  describe: `The test file's name under ${TEST_DIRECTORY}/: "untitled-1.json"`
@@ -2417,7 +2757,7 @@ var OPTIONS9 = {
2417
2757
  var SuiteRemoveCommand = class {
2418
2758
  name = "suite remove <name>";
2419
2759
  summary = "Delete one test file";
2420
- options = OPTIONS9;
2760
+ options = OPTIONS10;
2421
2761
  async run({ testAuthoringApp: testAuthoringApp2 }, { name }) {
2422
2762
  const removedName = await testAuthoringApp2.removeSuite(name ?? "");
2423
2763
  return { code: EXIT_OK, result: `Removed ${TEST_DIRECTORY}/${removedName}.
@@ -2464,7 +2804,7 @@ function lines2(one) {
2464
2804
  }
2465
2805
 
2466
2806
  // src/driver/cli/commands/VendorAddCommand.ts
2467
- var OPTIONS10 = {
2807
+ var OPTIONS11 = {
2468
2808
  source: {
2469
2809
  type: "string",
2470
2810
  describe: "What git clones the charter from: an address over any transport, or a path on disk"
@@ -2477,7 +2817,7 @@ var OPTIONS10 = {
2477
2817
  var VendorAddCommand = class {
2478
2818
  name = "vendor add <source>";
2479
2819
  summary = "Install a charter from a git source, or bring an installed one up to date";
2480
- options = OPTIONS10;
2820
+ options = OPTIONS11;
2481
2821
  async run({ charterVendoringApp: charterVendoringApp2 }, { source, ref }) {
2482
2822
  const folder = await charterVendoringApp2.add(source ?? "", ref);
2483
2823
  return {
@@ -2505,7 +2845,7 @@ var VendorListCommand = class {
2505
2845
  };
2506
2846
 
2507
2847
  // src/driver/cli/commands/VendorRemoveCommand.ts
2508
- var OPTIONS11 = {
2848
+ var OPTIONS12 = {
2509
2849
  name: {
2510
2850
  type: "string",
2511
2851
  describe: "The folder this charter was installed as, as it reads under .cw/vendor/"
@@ -2514,7 +2854,7 @@ var OPTIONS11 = {
2514
2854
  var VendorRemoveCommand = class {
2515
2855
  name = "vendor remove <name>";
2516
2856
  summary = "Take an installed charter away";
2517
- options = OPTIONS11;
2857
+ options = OPTIONS12;
2518
2858
  async run({ charterVendoringApp: charterVendoringApp2 }, { name }) {
2519
2859
  const folder = await charterVendoringApp2.remove(name ?? "");
2520
2860
  return {
@@ -2546,7 +2886,9 @@ var COMMANDS = [
2546
2886
  new VendorAddCommand(),
2547
2887
  new VendorRemoveCommand(),
2548
2888
  new VendorListCommand(),
2549
- new PortalCommand()
2889
+ new PortalCommand(),
2890
+ new McpAuthCommand(),
2891
+ new McpServeCommand()
2550
2892
  ];
2551
2893
 
2552
2894
  // src/driver/cli/Commander.ts
@@ -2603,7 +2945,7 @@ var Commander = class {
2603
2945
  try {
2604
2946
  collect(await command.run(this.#context, options));
2605
2947
  } catch (raised) {
2606
- if (!(raised instanceof Fault)) throw raised;
2948
+ if (!(raised instanceof DomainFault)) throw raised;
2607
2949
  collect({ code: EXIT_FAILURE, problem: `${raised.message}
2608
2950
  ${raised.fix}
2609
2951
  ` });
@@ -2642,7 +2984,6 @@ var CwAuthorSkill = class extends SkillPrimitive {
2642
2984
  "write a rule",
2643
2985
  "add a standard",
2644
2986
  "write a skill",
2645
- "add a command",
2646
2987
  "author a primitive of the charter"
2647
2988
  ]
2648
2989
  },
@@ -2707,7 +3048,7 @@ async function writeCharter(repo, primitive, fileReader2, fileWriter2) {
2707
3048
  const path = `${CHARTER_DIRECTORY}/${under2}`;
2708
3049
  const file = new URL(under2, charterFolderIn(repo));
2709
3050
  if (await fileReader2.readIfThere(file) !== void 0)
2710
- throw new Fault(
3051
+ throw new DomainFault(
2711
3052
  `${path} is already there, and writing this one would write over what it holds.`,
2712
3053
  `Open it, or run this again with an identity this charter has not got.`
2713
3054
  );
@@ -2716,9 +3057,9 @@ async function writeCharter(repo, primitive, fileReader2, fileWriter2) {
2716
3057
  }
2717
3058
 
2718
3059
  // 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))
3060
+ import { z as z12 } from "zod";
3061
+ var SETTINGS = z12.object({
3062
+ agents: z12.array(z12.enum(AGENT_PROVIDERS))
2722
3063
  });
2723
3064
  var WorkspaceSettings = class {
2724
3065
  constructor(agents) {
@@ -2811,12 +3152,12 @@ async function loadVendorNames(repo, fileReaders) {
2811
3152
  }
2812
3153
  async function ensureVCSReady(repo, vcs2, because) {
2813
3154
  if (!await vcs2.isInstalled(repo))
2814
- throw new Fault(
3155
+ throw new DomainFault(
2815
3156
  "This folder is not inside a git repository, and a vendor lives in one.",
2816
3157
  'Run "git init" here, or run this again where your repository is.'
2817
3158
  );
2818
3159
  if (!await vcs2.isClean(repo))
2819
- throw new Fault(
3160
+ throw new DomainFault(
2820
3161
  `This repository has work in hand, and ${because}.`,
2821
3162
  "Commit or stash what you are holding, then run this again."
2822
3163
  );
@@ -2966,19 +3307,7 @@ async function contentHashOf(content) {
2966
3307
  return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
2967
3308
  }
2968
3309
 
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
3310
+ // src/hexagon/domain/models/output/common/Catalogue.ts
2982
3311
  var Catalogue = class _Catalogue {
2983
3312
  constructor(entries) {
2984
3313
  this.entries = entries;
@@ -3020,6 +3349,20 @@ var Catalogue = class _Catalogue {
3020
3349
  return this.entries.map(({ identity, kind, id, description }) => ({ identity, kind, id, description }));
3021
3350
  }
3022
3351
  };
3352
+
3353
+ // src/hexagon/domain/models/output/StampedDocument.ts
3354
+ var GENERATED_STAMP = "Generated by cherry-works";
3355
+ function stamp(document) {
3356
+ return `${document.replace(/\n+$/, "")}
3357
+
3358
+ <!-- ${GENERATED_STAMP}. Do not edit; edit the charter and build again. -->
3359
+ `;
3360
+ }
3361
+ function isStamped(contents) {
3362
+ return contents.includes(GENERATED_STAMP);
3363
+ }
3364
+
3365
+ // src/hexagon/domain/models/output/common/CharterMd.ts
3023
3366
  var CharterMd = class _CharterMd {
3024
3367
  constructor(body) {
3025
3368
  this.body = body;
@@ -3050,34 +3393,26 @@ var CharterMd = class _CharterMd {
3050
3393
  }
3051
3394
  };
3052
3395
 
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;
3396
+ // src/hexagon/domain/models/output/common/CompiledPrimitive.ts
3397
+ var CompiledPrimitive = class _CompiledPrimitive {
3398
+ constructor(identity, file, document) {
3399
+ this.identity = identity;
3400
+ this.file = file;
3401
+ this.document = document;
3071
3402
  }
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);
3403
+ identity;
3404
+ file;
3405
+ document;
3406
+ static of(one, document) {
3407
+ return new _CompiledPrimitive(one.identity, `${OUT_DIRECTORY}/${one.primitive.kind}/${one.primitive.headers.id}.md`, document);
3408
+ }
3409
+ /** Generated, and the charter's alone: written whole over whatever is there,
3410
+ * and gone with the primitive it was compiled from. */
3411
+ projection = "replace";
3412
+ /** This document as a reader opens it, with the stamp saying where to change
3413
+ * it instead. */
3414
+ toStampedDocument() {
3415
+ return stamp(this.document);
3081
3416
  }
3082
3417
  };
3083
3418
 
@@ -3128,15 +3463,6 @@ var ClaudeAgentComponent = class _ClaudeAgentComponent extends DocumentBasedComp
3128
3463
  }
3129
3464
  };
3130
3465
 
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
3466
  // src/hexagon/domain/models/output/providers/claude/document-based-components/ClaudeRuleComponent.ts
3141
3467
  var ClaudeRuleComponent = class _ClaudeRuleComponent extends DocumentBasedComponent {
3142
3468
  static kind = "rule";
@@ -3146,59 +3472,134 @@ var ClaudeRuleComponent = class _ClaudeRuleComponent extends DocumentBasedCompon
3146
3472
  }
3147
3473
  };
3148
3474
 
3149
- // src/hexagon/domain/models/output/providers/claude/document-based-components/ClaudeSkillComponent.ts
3150
- var ClaudeSkillComponent = class _ClaudeSkillComponent extends DocumentBasedComponent {
3151
- static kind = "skill";
3152
- kind = _ClaudeSkillComponent.kind;
3153
- static of(name, declared, body) {
3154
- return new _ClaudeSkillComponent(name, { name, ...declared }, body);
3475
+ // src/hexagon/domain/models/output/providers/claude/ClaudeMcpConfigComponent.ts
3476
+ var ClaudeMcpConfigComponent = class _ClaudeMcpConfigComponent {
3477
+ static kind = "mcp-config";
3478
+ kind = _ClaudeMcpConfigComponent.kind;
3479
+ /** What this host names the server, and so what it puts before each tool it
3480
+ * serves: `mcp__cw__<tool>`. */
3481
+ static cwMcpName = "cw";
3482
+ /** That file is the repository's too — every server it wrote itself is
3483
+ * there — so the charter's one entry is written into it, and it is never
3484
+ * taken away, as the settings are not (FR-146). */
3485
+ projection = "mergeJSON";
3486
+ /** The one entry the charter speaks for, under `mcpServers`. */
3487
+ mcpServers = { [_ClaudeMcpConfigComponent.cwMcpName]: { command: "cw", args: ["mcp", "serve"] } };
3488
+ static of() {
3489
+ return new _ClaudeMcpConfigComponent();
3490
+ }
3491
+ constructor() {
3155
3492
  }
3156
3493
  };
3157
3494
 
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
- };
3495
+ // src/hexagon/domain/models/output/providers/claude/ClaudeSettingsComponent.ts
3496
+ var CLAUDE_HOOK_EVENTS = [
3497
+ "PreToolUse",
3498
+ "PostToolUse",
3499
+ "UserPromptSubmit",
3500
+ "Notification",
3501
+ "Stop",
3502
+ "SubagentStop",
3503
+ "PreCompact",
3504
+ "SessionStart",
3505
+ "SessionEnd"
3506
+ ];
3507
+ function isClaudeHookEvent(name) {
3508
+ return CLAUDE_HOOK_EVENTS.includes(name);
3184
3509
  }
3185
- function forAgent(agent, charter) {
3186
- switch (agent) {
3187
- case "claude":
3188
- return compileClaude(charter);
3510
+ var ClaudeSettingsComponent = class _ClaudeSettingsComponent {
3511
+ constructor(settings) {
3512
+ this.settings = settings;
3513
+ }
3514
+ settings;
3515
+ static kind = "settings";
3516
+ kind = _ClaudeSettingsComponent.kind;
3517
+ /** The settings file this host reads is shared with whatever the repository set
3518
+ * for itself, so what is put down there is written into what is there rather
3519
+ * than in place of it (FR-018). */
3520
+ projection = "mergeJSON";
3521
+ static of(settings) {
3522
+ return new _ClaudeSettingsComponent(settings);
3523
+ }
3524
+ };
3525
+
3526
+ // src/hexagon/domain/models/output/providers/claude/document-based-components/ClaudeSkillComponent.ts
3527
+ var ClaudeSkillComponent = class _ClaudeSkillComponent extends DocumentBasedComponent {
3528
+ static kind = "skill";
3529
+ kind = _ClaudeSkillComponent.kind;
3530
+ static of(name, declared, body) {
3531
+ return new _ClaudeSkillComponent(name, { name, ...declared }, body);
3532
+ }
3533
+ };
3534
+
3535
+ // src/hexagon/domain/models/output/providers/claude/ClaudeComponent.ts
3536
+ var CLAUDE_COMPONENT_CLASSES = [
3537
+ ClaudeAgentComponent,
3538
+ ClaudeSkillComponent,
3539
+ ClaudeSettingsComponent,
3540
+ ClaudeMcpConfigComponent,
3541
+ ClaudeRuleComponent
3542
+ ];
3543
+ var CLAUDE_COMPONENT_KINDS = Object.freeze(CLAUDE_COMPONENT_CLASSES.map((one) => one.kind));
3544
+ function claudeDocumentComponentOf(sc, compiledFile, agentTools) {
3545
+ const name = sc.normIdentity;
3546
+ const compiledFromClaudeFolder = `../../${compiledFile}`;
3547
+ const pointerTo = (path) => `Read and follow @${path}.
3548
+ `;
3549
+ switch (sc.primitive.kind) {
3550
+ // A guide is a rule this host loads into context whole: when a file it names
3551
+ // is touched, which is what `paths` on a rule does, and at the start of every
3552
+ // session where it names none (FR-013). Under the identity it is changed by,
3553
+ // so a reader who wants it changed is sent to the primitive rather than
3554
+ // editing what the next build overwrites (FR-017, FR-020).
3555
+ case GuidePrimitive.kind: {
3556
+ const { globs = [] } = sc.primitive.headers;
3557
+ return ClaudeRuleComponent.of(name, globs.length === 0 ? {} : { paths: globs }, `@${compiledFromClaudeFolder}
3558
+ `);
3559
+ }
3560
+ case AgentPrimitive.kind: {
3561
+ const { tools } = sc.primitive.headers;
3562
+ return ClaudeAgentComponent.of(name, { description: sc.primitive.description(), tools: (agentTools ?? tools).join(", ") }, pointerTo(compiledFromClaudeFolder));
3563
+ }
3564
+ // One kind of this host for the two the charter loads when the request calls
3565
+ // for them: what decides that the body is worth opening is the description,
3566
+ // because that line is all this host reads before deciding (FR-013), and
3567
+ // composing it is the primitive's.
3568
+ case SkillPrimitive.kind:
3569
+ case PlaybookPrimitive.kind:
3570
+ return ClaudeSkillComponent.of(name, { description: sc.primitive.description() }, pointerTo(`../${compiledFromClaudeFolder}`));
3571
+ default:
3572
+ return void 0;
3189
3573
  }
3190
3574
  }
3191
- function compileClaude(charter) {
3575
+
3576
+ // src/hexagon/domain/services/providers/claude.ts
3577
+ function compileForClaude(charter, compiledPrimitives, shortStrings) {
3578
+ const isSettingComponent = (one) => one.primitive.kind === PosturePrimitive.kind || one.primitive.kind === SensorPrimitive.kind;
3192
3579
  const asSettings = charter.primitives.filter(isSettingComponent);
3193
3580
  const asDocuments = charter.primitives.filter((one) => !isSettingComponent(one));
3581
+ const toolsByMcpIdentity = new Map(
3582
+ charter.primitives.flatMap(({ identity, primitive }) => primitive instanceof McpPrimitive ? [[identity, primitive.headers.tools]] : [])
3583
+ );
3584
+ const agentToolsOf = (agent) => agent.headers.tools.flatMap((tool) => {
3585
+ const [, mcpIdentity, mcpTool] = tool.match(MCP_TOOL_REFERENCE) ?? [];
3586
+ if (mcpIdentity === void 0) return [tool];
3587
+ return (mcpTool === void 0 ? toolsByMcpIdentity.get(mcpIdentity) ?? [] : [mcpTool]).map(
3588
+ (oneTool) => `mcp__${ClaudeMcpConfigComponent.cwMcpName}__${shortStrings[mcpIdentity]}__${oneTool}`
3589
+ );
3590
+ });
3194
3591
  return [
3195
3592
  ...asSettings.length === 0 ? [] : [ClaudeSettingsComponent.of(claudeSettingsOf(asSettings))],
3196
- ...asDocuments.flatMap((one) => claudeDocumentComponentOf(charter, one) ?? [])
3593
+ ClaudeMcpConfigComponent.of(),
3594
+ ...asDocuments.flatMap(
3595
+ (one) => claudeDocumentComponentOf(
3596
+ one,
3597
+ compiledPrimitives.find((compiled) => compiled.identity === one.identity).file,
3598
+ one.primitive instanceof AgentPrimitive ? agentToolsOf(one.primitive) : void 0
3599
+ ) ?? []
3600
+ )
3197
3601
  ];
3198
3602
  }
3199
- function isSettingComponent(one) {
3200
- return one.primitive.kind === PosturePrimitive.kind || one.primitive.kind === SensorPrimitive.kind;
3201
- }
3202
3603
  function claudeSettingsOf(ones) {
3203
3604
  const allow = /* @__PURE__ */ new Set();
3204
3605
  const deny = /* @__PURE__ */ new Set();
@@ -3220,46 +3621,126 @@ function claudeSettingsOf(ones) {
3220
3621
  ...Object.keys(hooks).length === 0 ? {} : { hooks }
3221
3622
  };
3222
3623
  }
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;
3624
+ function claudeComponentProjection(one) {
3625
+ const projectionPolicy = one.projection;
3626
+ switch (one.kind) {
3627
+ case ClaudeAgentComponent.kind:
3628
+ return { path: `${CLAUDE_DIRECTORY}/agents/${one.name}.md`, contents: one.toStampedDocument(), projectionPolicy };
3629
+ // A skill is a directory holding one file of that name, which is where this
3630
+ // host looks for it.
3631
+ case ClaudeSkillComponent.kind:
3632
+ return {
3633
+ path: `${CLAUDE_DIRECTORY}/skills/${one.name}/SKILL.md`,
3634
+ contents: one.toStampedDocument(),
3635
+ projectionPolicy
3636
+ };
3637
+ // A rule of its own file, under the directory this host loads every one of:
3638
+ // at the start of a session where it declares no `paths`, and when one of
3639
+ // them is touched where it does.
3640
+ case ClaudeRuleComponent.kind:
3641
+ return { path: `${CLAUDE_DIRECTORY}/rules/${one.name}.md`, contents: one.toStampedDocument(), projectionPolicy };
3642
+ // Every posture and every sensor of the charter lands in the one file this
3643
+ // host reads its settings from.
3644
+ case ClaudeSettingsComponent.kind:
3645
+ return {
3646
+ path: `${CLAUDE_DIRECTORY}/settings.json`,
3647
+ contents: `${JSON.stringify(one.settings, void 0, 2)}
3648
+ `,
3649
+ projectionPolicy
3650
+ };
3651
+ // The one server reaching every place, in the file this host starts a
3652
+ // project's servers from (FR-146).
3653
+ case ClaudeMcpConfigComponent.kind:
3654
+ return {
3655
+ path: CLAUDE_MCP_CONFIG_FILE,
3656
+ contents: `${JSON.stringify({ mcpServers: one.mcpServers }, void 0, 2)}
3657
+ `,
3658
+ projectionPolicy
3659
+ };
3259
3660
  }
3260
3661
  }
3261
- function claudeNameOf(one) {
3262
- return one.identity.replace(/[:/]/g, "-");
3662
+
3663
+ // src/hexagon/domain/services/compileService.ts
3664
+ function compile(charter, agents) {
3665
+ const shortStrings = shortenStringsOf(
3666
+ charter.primitives.filter(({ primitive }) => primitive.kind === McpPrimitive.kind).map((one) => one.identity)
3667
+ );
3668
+ const compiledPrimitives = charter.primitives.map(
3669
+ (one) => CompiledPrimitive.of(
3670
+ one,
3671
+ one.primitive.toMarkdown({
3672
+ // A mixin nothing answers to is left out: the charter already names
3673
+ // that file as an error, and a build refused for it never reaches
3674
+ // here (FR-009).
3675
+ mixins: charter.mixinsOf(one).flatMap(({ primitive }) => primitive.kind === MixinPrimitive.kind ? [primitive] : []),
3676
+ // Each place the body names as `mcp:<id>` written as the name its
3677
+ // tools are served under, `<name>__<tool>`, which every host's name for
3678
+ // them holds whatever it puts before it (FR-147). A name no mcp
3679
+ // answers to has none and is left as written; the charter has warned
3680
+ // about it (FR-143).
3681
+ idReplacer: (body) => body.replace(MCP_MENTION, (identity) => shortStrings[identity] ?? identity)
3682
+ })
3683
+ )
3684
+ );
3685
+ return {
3686
+ // What an agent opens from the catalogue is the primitive as it compiled,
3687
+ // not the file its author wrote: the whole body in one place, under the
3688
+ // output folder whichever layer brought it (FR-140). The catalogue is where
3689
+ // this path is said; the projection reads it from there.
3690
+ catalogue: catalogueOf(charter, (one) => compiledPrimitives.find((compiled) => compiled.identity === one.identity).file),
3691
+ charterMd: CharterMd.of(PRIMITIVE_CLASSES),
3692
+ compiledPrimitives,
3693
+ mcpOrigins: mcpOriginsOf(charter, shortStrings),
3694
+ providerComponents: agents.flatMap((agent) => compileForAgent(agent, charter, compiledPrimitives, shortStrings))
3695
+ };
3696
+ }
3697
+ function catalogueOf(charter, fileOf) {
3698
+ return Catalogue.of(
3699
+ charter.primitives.map((one) => {
3700
+ const { id, description, tags, globs, rationale, mixins } = one.primitive.headers;
3701
+ return {
3702
+ identity: one.identity,
3703
+ kind: one.primitive.kind,
3704
+ id,
3705
+ description,
3706
+ file: fileOf(one),
3707
+ ...tags === void 0 ? {} : { tags },
3708
+ ...globs === void 0 ? {} : { globs },
3709
+ ...rationale === void 0 ? {} : { rationale },
3710
+ ...mixins === void 0 ? {} : { mixins }
3711
+ };
3712
+ })
3713
+ );
3714
+ }
3715
+ function mcpOriginsOf(charter, shortStrings) {
3716
+ const mcpsByKey = /* @__PURE__ */ new Map();
3717
+ for (const { identity, primitive } of [...charter.primitives].sort((one, another) => one.identity < another.identity ? -1 : 1)) {
3718
+ if (primitive.kind !== McpPrimitive.kind) continue;
3719
+ const key = `${primitive.address}
3720
+ ${primitive.headers.path ?? ""}`;
3721
+ mcpsByKey.set(key, [...mcpsByKey.get(key) ?? [], { identity, primitive }]);
3722
+ }
3723
+ return [...mcpsByKey.entries()].sort(([oneKey], [anotherKey]) => oneKey < anotherKey ? -1 : 1).map(([, mcpsAtOrigin]) => {
3724
+ const [firstMcp] = mcpsAtOrigin;
3725
+ const { endpoint, command, args = [], path } = firstMcp.primitive.headers;
3726
+ const tokenEnv = mcpsAtOrigin.find((one) => one.primitive.headers.tokenEnv !== void 0)?.primitive.headers.tokenEnv;
3727
+ const auths = new Set(mcpsAtOrigin.flatMap((one) => one.primitive.headers.auth ?? []));
3728
+ return {
3729
+ identities: mcpsAtOrigin.map((one) => one.identity),
3730
+ names: Object.fromEntries(mcpsAtOrigin.map((one) => [one.identity, shortStrings[one.identity]])),
3731
+ address: firstMcp.primitive.address,
3732
+ ...endpoint === void 0 ? {} : { endpoint },
3733
+ ...command === void 0 ? {} : { command: { command, args, ...tokenEnv === void 0 ? {} : { tokenEnv } } },
3734
+ ...path === void 0 ? {} : { path },
3735
+ auth: MCP_AUTHS.filter((auth2) => auths.has(auth2))
3736
+ };
3737
+ });
3738
+ }
3739
+ function compileForAgent(agent, charter, compiledPrimitives, shortStrings) {
3740
+ switch (agent) {
3741
+ case "claude":
3742
+ return compileForClaude(charter, compiledPrimitives, shortStrings);
3743
+ }
3263
3744
  }
3264
3745
 
3265
3746
  // src/hexagon/domain/services/projectionService.ts
@@ -3282,13 +3763,27 @@ function charterOutputProjection(output, agentProviders) {
3282
3763
  `,
3283
3764
  projectionPolicy: output.catalogue.projection
3284
3765
  },
3766
+ // Indented for the reason the full listing is: committed, and read in
3767
+ // diffs when a place moves (SC-038).
3768
+ {
3769
+ path: `${OUT_DIRECTORY}/mcp-origins.json`,
3770
+ contents: `${JSON.stringify({ origins: output.mcpOrigins }, void 0, 2)}
3771
+ `,
3772
+ projectionPolicy: "replace"
3773
+ },
3285
3774
  {
3286
3775
  path: `${OUT_DIRECTORY}/CHARTER.md`,
3287
3776
  contents: output.charterMd.toStampedDocument(),
3288
3777
  projectionPolicy: output.charterMd.projection
3289
3778
  },
3779
+ // Each primitive lands where the catalogue sends an agent for it, so the
3780
+ // file named and the file written are one (FR-140).
3781
+ ...output.catalogue.full.flatMap(({ identity, file }) => {
3782
+ const compiledPrimitive = output.compiledPrimitives.find((one) => one.identity === identity);
3783
+ return compiledPrimitive === void 0 ? [] : [{ path: file, contents: compiledPrimitive.toStampedDocument(), projectionPolicy: compiledPrimitive.projection }];
3784
+ }),
3290
3785
  ...agentProviders.map(entryFileProjection),
3291
- ...output.providerComponents.map(componentProjection)
3786
+ ...output.providerComponents.map(claudeComponentProjection)
3292
3787
  ];
3293
3788
  }
3294
3789
  function entryFileProjection(provider) {
@@ -3308,37 +3803,6 @@ ${[
3308
3803
  }
3309
3804
  return { path, contents, projectionPolicy: "upsertWithMarker" };
3310
3805
  }
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
3806
 
3343
3807
  // src/hexagon/service/buildService.ts
3344
3808
  async function plan(repo, charter, agentProviders, fileReaders) {
@@ -3360,10 +3824,19 @@ async function planForProjection(repo, output, agentProviders, fileReaders) {
3360
3824
  break;
3361
3825
  // What the repository set for itself is read and written back: the fields
3362
3826
  // this charter speaks for are put over the top of it, and every other one
3363
- // is theirs and comes through untouched (FR-018).
3827
+ // is theirs and comes through untouched (FR-018). Field by field at every
3828
+ // depth, so an entry of the repository's beside the charter's under one
3829
+ // key — a server of its own under `mcpServers` — stays (FR-146); a list
3830
+ // or a value is the charter's whole, so what it no longer asks for goes.
3364
3831
  case "mergeJSON": {
3365
- const already = JSON.parse(held?.trim() || "{}");
3366
- contents = `${JSON.stringify({ ...already, ...JSON.parse(one.contents) }, void 0, 2)}
3832
+ const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3833
+ const mergedJSON = (already, written) => isPlainObject(already) && isPlainObject(written) ? Object.fromEntries(
3834
+ [.../* @__PURE__ */ new Set([...Object.keys(already), ...Object.keys(written)])].map((field) => [
3835
+ field,
3836
+ field in written ? mergedJSON(already[field], written[field]) : already[field]
3837
+ ])
3838
+ ) : written;
3839
+ contents = `${JSON.stringify(mergedJSON(JSON.parse(held?.trim() || "{}"), JSON.parse(one.contents)), void 0, 2)}
3367
3840
  `;
3368
3841
  break;
3369
3842
  }
@@ -3426,7 +3899,7 @@ async function executePlan(cleanupPlan, projectionPlan, fileWriter2) {
3426
3899
  await Promise.all(cleanupPlan.map((one) => fileWriter2.delete(one.file)));
3427
3900
  for (const one of projectionPlan) await fileWriter2.write(one.file, one.contents ?? "");
3428
3901
  } catch (raised) {
3429
- throw new Fault(
3902
+ throw new DomainFault(
3430
3903
  `This build could not be written: ${raised instanceof Error ? raised.message : String(raised)}`,
3431
3904
  "Check that the files this build writes can be written, then build again."
3432
3905
  );
@@ -3499,11 +3972,10 @@ var CharterAuthoring = class {
3499
3972
  * What the charter holds, listed, narrowed to one kind where one is named
3500
3973
  * (FR-011, FR-012).
3501
3974
  *
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.
3975
+ * Read off the charter, not off what it compiles to: a person is shown the
3976
+ * file each primitive was authored in, which is the one they would edit and
3977
+ * what says which layer it came from, where the catalogue a build writes sends
3978
+ * an agent to the compiled one (FR-140).
3507
3979
  *
3508
3980
  * Whether the word narrowing it is a kind at all is asked here, of the
3509
3981
  * domain that holds the set. A caller passes on what was typed and knows
@@ -3514,10 +3986,10 @@ var CharterAuthoring = class {
3514
3986
  */
3515
3987
  async list(kind) {
3516
3988
  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(", ")}.`);
3989
+ throw new DomainFault(`"${kind}" is not a kind the charter knows.`, `Ask for one the charter knows: ${KINDS.join(", ")}.`);
3518
3990
  const [charter, , faultsByFiles] = await this.#read();
3519
3991
  if (charter === void 0) return faultsByFileDTO(faultsByFiles.errors(), this.#repoPath);
3520
- const { catalogue: catalogue2 } = compile(charter, []);
3992
+ const catalogue2 = catalogueOf(charter, (one) => one.file);
3521
3993
  return catalogueDTO(kind === void 0 ? catalogue2 : catalogue2.filterByKind(kind));
3522
3994
  }
3523
3995
  /** Every primitive the charter read, or those mentioning a word, each as the
@@ -3552,7 +4024,7 @@ var CharterAuthoring = class {
3552
4024
  if (charter === void 0) return faultsByFileDTO(faultsByFiles.errors(), this.#repoPath);
3553
4025
  const declared = charter.primitiveById.get(primitiveIdentity);
3554
4026
  if (declared === void 0)
3555
- throw new Fault(
4027
+ throw new DomainFault(
3556
4028
  `This charter holds no "${identity}".`,
3557
4029
  'Run "cw list --min" to see every identity it does hold.'
3558
4030
  );
@@ -3692,7 +4164,7 @@ var CharterAuthoring = class {
3692
4164
  const charter = await loadCharterRoot(this.#repoPath, this.#fileReader, this.#yamlParser);
3693
4165
  const claimingPrimitive = charter.primitiveById.get(identityOf({ kind, id }));
3694
4166
  if (claimingPrimitive !== void 0)
3695
- throw new Fault(
4167
+ throw new DomainFault(
3696
4168
  `"${identityOf({ kind, id })}" is already there, declared by ${claimingPrimitive.file}, and one identity names one primitive in the whole charter.`,
3697
4169
  `Open ${claimingPrimitive.file}, or run this again with an id this charter has not got.`
3698
4170
  );
@@ -3729,12 +4201,12 @@ var CharterAuthoring = class {
3729
4201
  async rewrite(identity, headers, body, revision) {
3730
4202
  const scopedPrimitive2 = await this.#scopedPrimitiveOf(identity);
3731
4203
  if (!RepoScopedPrimitive.isSatisfiedBy(scopedPrimitive2))
3732
- throw new Fault(
4204
+ throw new DomainFault(
3733
4205
  `${identity} was not authored in this repository, and ${scopedPrimitive2.file} is read-only here.`,
3734
4206
  `To differ from it, author a primitive of your own under an identity of its own.`
3735
4207
  );
3736
4208
  if (await contentHashOf(scopedPrimitive2.primitive.toMarkdown()) !== revision)
3737
- throw new Fault(
4209
+ throw new DomainFault(
3738
4210
  `${scopedPrimitive2.file} changed on disk after it was opened, and saving would write over that change.`,
3739
4211
  `Open it again to see what changed, then make your edit there.`
3740
4212
  );
@@ -3762,7 +4234,7 @@ var CharterAuthoring = class {
3762
4234
  async remove(identity) {
3763
4235
  const scopedPrimitive2 = await this.#scopedPrimitiveOf(identity);
3764
4236
  if (!RepoScopedPrimitive.isSatisfiedBy(scopedPrimitive2))
3765
- throw new Fault(
4237
+ throw new DomainFault(
3766
4238
  `${identity} was not authored in this repository, and ${scopedPrimitive2.file} is read-only here.`,
3767
4239
  `To differ from it, author a primitive of your own under an identity of its own.`
3768
4240
  );
@@ -3777,7 +4249,7 @@ var CharterAuthoring = class {
3777
4249
  const charter = await loadCharterRoot(this.#repoPath, this.#fileReader, this.#yamlParser);
3778
4250
  const scopedPrimitive2 = charter.primitiveById.get(primitiveIdentity);
3779
4251
  if (scopedPrimitive2 === void 0)
3780
- throw new Fault(
4252
+ throw new DomainFault(
3781
4253
  `This charter holds no "${identity}".`,
3782
4254
  'Run "cw list --min" to see every identity it does hold.'
3783
4255
  );
@@ -3788,7 +4260,7 @@ var CharterAuthoring = class {
3788
4260
  * every kind there is. */
3789
4261
  #kindOf(kind) {
3790
4262
  if (!isKind(kind))
3791
- throw new Fault(`"${kind}" is not a kind the charter knows.`, `Ask for one the charter knows: ${KINDS.join(", ")}.`);
4263
+ throw new DomainFault(`"${kind}" is not a kind the charter knows.`, `Ask for one the charter knows: ${KINDS.join(", ")}.`);
3792
4264
  return kind;
3793
4265
  }
3794
4266
  /** What this repository configured itself with, read the one way settings are
@@ -3804,12 +4276,12 @@ var CharterAuthoring = class {
3804
4276
  */
3805
4277
  async ensureRepoReady() {
3806
4278
  if (!await this.#vcs.isInstalled(this.#repoPath))
3807
- throw new Fault(
4279
+ throw new DomainFault(
3808
4280
  "This folder is not inside a git repository, and a charter is authored inside one.",
3809
4281
  'Run "git init" here, then "cw init", or run this again where your repository is.'
3810
4282
  );
3811
4283
  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.');
4284
+ throw new DomainFault("This repository was never set up under a charter.", 'Run "cw init" first.');
3813
4285
  }
3814
4286
  /**
3815
4287
  * One repository, set up under a charter (FR-032, FR-037).
@@ -3839,12 +4311,12 @@ var CharterAuthoring = class {
3839
4311
  */
3840
4312
  async init({ agents }) {
3841
4313
  if (!await this.#vcs.isInstalled(this.#repoPath))
3842
- throw new Fault(
4314
+ throw new DomainFault(
3843
4315
  "This folder is not inside a git repository, and a charter is authored inside one.",
3844
4316
  'Run "git init" here, or run this again where your repository is.'
3845
4317
  );
3846
4318
  if (agents.length === 0)
3847
- throw new Fault(
4319
+ throw new DomainFault(
3848
4320
  "Setup was given no agent to compile for, and a charter compiles for at least one.",
3849
4321
  `Choose at least one of: ${AGENT_PROVIDERS.join(", ")}.`
3850
4322
  );
@@ -3903,6 +4375,275 @@ var TestAuthoring = class {
3903
4375
  }
3904
4376
  };
3905
4377
 
4378
+ // src/hexagon/service/credentialRepo.ts
4379
+ async function readCredential(secrets2, address) {
4380
+ const secret = await secrets2.readSecret(address);
4381
+ if (secret === void 0) return void 0;
4382
+ try {
4383
+ const credential = JSON.parse(secret);
4384
+ return typeof credential?.accessToken === "string" ? credential : void 0;
4385
+ } catch {
4386
+ return void 0;
4387
+ }
4388
+ }
4389
+ async function writeCredential(secrets2, credential) {
4390
+ await secrets2.writeSecret(credential.address, JSON.stringify(credential));
4391
+ }
4392
+ async function credentialFor(secrets2, authorizing2, origin, { refused = false } = {}) {
4393
+ if (origin.endpoint !== void 0 && !isSecureEndpoint(origin.endpoint))
4394
+ 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".');
4395
+ const signInAgain = `Run "cw mcp auth ${origin.identities[0] ?? ""}" at a terminal to sign in there.`;
4396
+ const credential = await readCredential(secrets2, origin.address);
4397
+ if (credential === void 0) throw new DomainFault(`You are not signed in to ${origin.address}.`, signInAgain);
4398
+ const expired = refused || credential.expiresAt !== void 0 && Date.parse(credential.expiresAt) <= Date.now();
4399
+ if (!expired) return credential.accessToken;
4400
+ const { address, method, client, ...tokens } = credential;
4401
+ const renewedGrant = origin.endpoint !== void 0 && client !== void 0 && tokens.refreshToken !== void 0 ? await authorizing2.renew(origin.endpoint, { ...tokens, client }) : void 0;
4402
+ if (renewedGrant === void 0)
4403
+ throw new DomainFault(`Your sign-in to ${address} has expired and could not be renewed.`, signInAgain);
4404
+ await writeCredential(secrets2, { address, method, ...renewedGrant });
4405
+ return renewedGrant.accessToken;
4406
+ }
4407
+
4408
+ // src/hexagon/service/mcpOriginsRepo.ts
4409
+ async function loadMcpOrigins(repo, fileReader2) {
4410
+ const file = `${OUT_DIRECTORY}/mcp-origins.json`;
4411
+ const contents = await fileReader2.readIfThere(new URL("mcp-origins.json", outFolderIn(repo)));
4412
+ if (contents === void 0)
4413
+ throw new DomainFault(`There is no ${file}, so no place has been listed yet.`, 'Run "cw build" first.');
4414
+ try {
4415
+ const mcpOriginsJson = JSON.parse(contents);
4416
+ const origins = mcpOriginsJson.origins;
4417
+ if (Array.isArray(origins)) return origins;
4418
+ } catch {
4419
+ }
4420
+ throw new DomainFault(`${file} does not hold a list of places.`, 'Run "cw build" to write it again.');
4421
+ }
4422
+ async function loadMcpTools(repo, fileReader2, yamlParser2) {
4423
+ const contents = await fileReader2.readIfThere(new URL("catalog.json", outFolderIn(repo)));
4424
+ let catalogEntries = [];
4425
+ try {
4426
+ const catalogJson = JSON.parse(contents ?? "[]");
4427
+ if (Array.isArray(catalogJson)) catalogEntries = catalogJson;
4428
+ } catch {
4429
+ }
4430
+ const toolsByIdentity = /* @__PURE__ */ new Map();
4431
+ for (const { identity, kind, file } of catalogEntries) {
4432
+ if (kind !== McpPrimitive.kind || typeof identity !== "string" || typeof file !== "string") continue;
4433
+ const compiledDocument = await fileReader2.readIfThere(new URL(file, repo));
4434
+ if (compiledDocument === void 0) continue;
4435
+ try {
4436
+ const primitive = primitiveOf(compiledDocument, yamlParser2);
4437
+ if (primitive instanceof McpPrimitive) toolsByIdentity.set(identity, primitive.headers.tools);
4438
+ } catch {
4439
+ }
4440
+ }
4441
+ return toolsByIdentity;
4442
+ }
4443
+
4444
+ // src/hexagon/port/zdriven/DrivenFault.ts
4445
+ var DrivenFault = class extends DomainFault {
4446
+ };
4447
+
4448
+ // src/hexagon/port/zdriven/ForCallingMcpServers.ts
4449
+ var UnauthorizedMcpFault = class extends DrivenFault {
4450
+ };
4451
+
4452
+ // src/hexagon/application/McpConnecting.ts
4453
+ var CONNECT_TIMEOUT_MS = 1e4;
4454
+ var errorAnswerOf = (text) => ({
4455
+ type: "ToolAnswer",
4456
+ data: { content: [{ type: "text", text }], isError: true }
4457
+ });
4458
+ var McpConnecting = class {
4459
+ #repoPath;
4460
+ #fileReader;
4461
+ #secrets;
4462
+ #authorizing;
4463
+ #yamlParser;
4464
+ #mcpServers;
4465
+ #mcpServing;
4466
+ constructor(repoPath2, fileReader2, secrets2, authorizing2, yamlParser2, mcpServers2) {
4467
+ this.#repoPath = repoPath2;
4468
+ this.#fileReader = fileReader2;
4469
+ this.#secrets = secrets2;
4470
+ this.#authorizing = authorizing2;
4471
+ this.#yamlParser = yamlParser2;
4472
+ this.#mcpServers = mcpServers2;
4473
+ }
4474
+ async signInStatus() {
4475
+ return Promise.all(
4476
+ (await this.#signInAddresses()).map(async ({ address, identities, auth: auth2 }) => {
4477
+ const credential = await readCredential(this.#secrets, address);
4478
+ return {
4479
+ type: "SignInStatus",
4480
+ data: { address, identities, auth: auth2, signedIn: credential !== void 0, ...credential && { method: credential.method } }
4481
+ };
4482
+ })
4483
+ );
4484
+ }
4485
+ async signInWithToken(address, token) {
4486
+ await this.#addressTaking(address, "token");
4487
+ 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.');
4488
+ await writeCredential(this.#secrets, { address, method: "token", accessToken: token });
4489
+ }
4490
+ async signInWithOAuth(address, showAuthorizationUrl) {
4491
+ await this.#addressTaking(address, "oauth");
4492
+ const oauthGrant = await this.#authorizing.authorize(address, showAuthorizationUrl);
4493
+ await writeCredential(this.#secrets, { address, method: "oauth", ...oauthGrant });
4494
+ }
4495
+ async served() {
4496
+ return (await this.#serving()).servedTools;
4497
+ }
4498
+ async call(name, args) {
4499
+ const { toolRoutes, unreachableTools } = await this.#serving();
4500
+ const toolRoute = toolRoutes.get(name);
4501
+ if (toolRoute === void 0)
4502
+ return errorAnswerOf(unreachableTools.get(name) ?? `"${name}" is not a tool this server serves; call one it listed.`);
4503
+ const { origin, identity, upstream, connection } = toolRoute;
4504
+ const signInAgain = `${origin.address} refused your credential. Run "cw mcp auth ${identity}" at a terminal to sign in there again.`;
4505
+ const accessTokenOf = (refused) => origin.endpoint === void 0 ? Promise.resolve(void 0) : credentialFor(this.#secrets, this.#authorizing, origin, { refused });
4506
+ try {
4507
+ try {
4508
+ return { type: "ToolAnswer", data: await connection.callTool(upstream, args, await accessTokenOf(false)) };
4509
+ } catch (raised) {
4510
+ if (!(raised instanceof UnauthorizedMcpFault)) throw raised;
4511
+ const renewedToken = await accessTokenOf(true).catch(() => void 0);
4512
+ if (renewedToken === void 0) return errorAnswerOf(signInAgain);
4513
+ return { type: "ToolAnswer", data: await connection.callTool(upstream, args, renewedToken) };
4514
+ }
4515
+ } catch (raised) {
4516
+ if (raised instanceof UnauthorizedMcpFault) return errorAnswerOf(signInAgain);
4517
+ if (raised instanceof DomainFault) return errorAnswerOf(`${raised.message} ${raised.fix}`);
4518
+ throw raised;
4519
+ }
4520
+ }
4521
+ async stopServing() {
4522
+ if (this.#mcpServing === void 0) return;
4523
+ const { connections } = await this.#mcpServing;
4524
+ await Promise.all(connections.map((connection) => connection.close()));
4525
+ }
4526
+ /**
4527
+ * Every place the last build listed, reached at once and once per run
4528
+ * (FR-152). What each lists is kept where one of its identities
4529
+ * declares it, renamed `<prefix>__<tool>`; a place down or not signed in to,
4530
+ * and a declared tool its place lacks, is said and left out, and the rest
4531
+ * are served (FR-153, FR-154, SC-039).
4532
+ */
4533
+ #serving() {
4534
+ this.#mcpServing ??= (async () => {
4535
+ const origins = await loadMcpOrigins(this.#repoPath, this.#fileReader);
4536
+ const toolsByIdentity = await loadMcpTools(this.#repoPath, this.#fileReader, this.#yamlParser);
4537
+ const problems = [];
4538
+ const servedTools2 = [];
4539
+ const toolRoutes = /* @__PURE__ */ new Map();
4540
+ const unreachableTools = /* @__PURE__ */ new Map();
4541
+ const connections = [];
4542
+ const reachedPlaces = await Promise.all(
4543
+ origins.map(async (origin) => {
4544
+ try {
4545
+ const accessToken = origin.auth.length > 0 ? await credentialFor(this.#secrets, this.#authorizing, origin) : void 0;
4546
+ return { origin, connection: await this.#mcpServers.connect(origin, accessToken, CONNECT_TIMEOUT_MS) };
4547
+ } catch (raised) {
4548
+ if (!(raised instanceof DomainFault)) throw raised;
4549
+ return { origin, reason: `${raised.message} ${raised.fix}` };
4550
+ }
4551
+ })
4552
+ );
4553
+ for (const { origin, connection, reason } of reachedPlaces) {
4554
+ if (connection !== void 0) connections.push(connection);
4555
+ else problems.push(`${origin.identities.join(", ")}: ${reason}`);
4556
+ for (const identity of origin.identities) {
4557
+ const declaredTools = toolsByIdentity.get(identity);
4558
+ if (declaredTools === void 0) {
4559
+ problems.push(`${identity} has no compiled document under .cw/out/, so none of its tools is served. Run "cw build".`);
4560
+ continue;
4561
+ }
4562
+ const identityName = origin.names?.[identity];
4563
+ if (identityName === void 0) {
4564
+ problems.push(`${identity} has no served name in .cw/out/mcp-origins.json, so none of its tools is served. Run "cw build" again.`);
4565
+ continue;
4566
+ }
4567
+ for (const tool of declaredTools) {
4568
+ const servedName = `${identityName}__${tool}`;
4569
+ if (connection === void 0) {
4570
+ unreachableTools.set(servedName, reason ?? "");
4571
+ continue;
4572
+ }
4573
+ const upstreamTool = connection.tools.find(({ name }) => name === tool);
4574
+ if (upstreamTool === void 0) {
4575
+ problems.push(`${identity} declares "${tool}", which ${origin.address} does not have; it is not served.`);
4576
+ continue;
4577
+ }
4578
+ const placeName = origin.path === void 0 ? identity : `${identity} \u2014 ${origin.path}`;
4579
+ servedTools2.push({
4580
+ type: "ServedTool",
4581
+ data: {
4582
+ name: servedName,
4583
+ description: `[${placeName}] ${upstreamTool.description ?? ""}`.trimEnd(),
4584
+ inputSchema: upstreamTool.inputSchema
4585
+ }
4586
+ });
4587
+ toolRoutes.set(servedName, { origin, identity, upstream: tool, connection });
4588
+ }
4589
+ }
4590
+ }
4591
+ return {
4592
+ servedTools: {
4593
+ type: "ServedTools",
4594
+ data: { tools: servedTools2.sort((one, other) => one.data.name.localeCompare(other.data.name)), problems }
4595
+ },
4596
+ toolRoutes,
4597
+ unreachableTools,
4598
+ connections
4599
+ };
4600
+ })();
4601
+ return this.#mcpServing;
4602
+ }
4603
+ /**
4604
+ * Every address a developer signs in to, once, ordered by address (plan
4605
+ * §19.5): one address is one account at one service, whatever path each
4606
+ * place there names. A local command taking no token signs in to nothing and
4607
+ * is left out.
4608
+ */
4609
+ async #signInAddresses() {
4610
+ const origins = await loadMcpOrigins(this.#repoPath, this.#fileReader);
4611
+ const addresses = /* @__PURE__ */ new Map();
4612
+ for (const { address, endpoint, identities, auth: auth2 } of origins.filter((one) => one.auth.length > 0)) {
4613
+ const signInAddress = addresses.get(address) ?? { ...endpoint !== void 0 && { endpoint }, identities: /* @__PURE__ */ new Set(), auth: /* @__PURE__ */ new Set() };
4614
+ identities.forEach((identity) => signInAddress.identities.add(identity));
4615
+ auth2.forEach((method) => signInAddress.auth.add(method));
4616
+ addresses.set(address, signInAddress);
4617
+ }
4618
+ return [...addresses].sort(([one], [other]) => one.localeCompare(other)).map(([address, { endpoint, identities, auth: auth2 }]) => ({
4619
+ address,
4620
+ ...endpoint !== void 0 && { endpoint },
4621
+ identities: [...identities].sort(),
4622
+ auth: ["oauth", "token"].filter((method) => auth2.has(method))
4623
+ }));
4624
+ }
4625
+ /** Refused where no place is at this address, or none there allows signing
4626
+ * in this way: a credential nothing would use is not kept. Refused too where
4627
+ * the address is plain `http` to another machine, whatever the build said:
4628
+ * the list is a committed file, and a credential sent there travels in the
4629
+ * clear. */
4630
+ async #addressTaking(address, method) {
4631
+ const signInAddress = (await this.#signInAddresses()).find((one) => one.address === address);
4632
+ if (signInAddress === void 0)
4633
+ 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.');
4634
+ if (signInAddress.endpoint !== void 0 && !isSecureEndpoint(signInAddress.endpoint))
4635
+ throw new DomainFault(
4636
+ `${address} is plain http to another machine, so no credential is sent there.`,
4637
+ 'Declare its endpoint as https://, then run "cw build".'
4638
+ );
4639
+ if (!signInAddress.auth.includes(method))
4640
+ throw new DomainFault(
4641
+ `${address} is not signed in to by ${method}; it allows ${signInAddress.auth.join(" or ")}.`,
4642
+ `Sign in there by ${signInAddress.auth.join(" or ")}.`
4643
+ );
4644
+ }
4645
+ };
4646
+
3906
4647
  // src/zdriven/FileReaders.ts
3907
4648
  import { readdir, readFile } from "fs/promises";
3908
4649
  var FileReaders = class {
@@ -4053,7 +4794,7 @@ var Git = class {
4053
4794
  { cwd }
4054
4795
  );
4055
4796
  } catch (raised) {
4056
- throw new Fault(
4797
+ throw new DrivenFault(
4057
4798
  `${raised}`,
4058
4799
  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
4800
  );
@@ -4068,19 +4809,286 @@ var Git = class {
4068
4809
  await run("git", ["rm", "-r", "--quiet", "--", subFolder], { cwd });
4069
4810
  await run("git", ["commit", "-qm", `Remove ${subFolder}`], { cwd });
4070
4811
  } catch (raised) {
4071
- throw new Fault(`${raised}`, `Check that "${subFolder}" is a folder this repository holds.`);
4812
+ throw new DrivenFault(`${raised}`, `Check that "${subFolder}" is a folder this repository holds.`);
4072
4813
  }
4073
4814
  }
4074
4815
  };
4075
4816
 
4817
+ // src/zdriven/OsSecrets.ts
4818
+ import { spawn as spawn2 } from "child_process";
4819
+ var SERVICE = "cherry-works";
4820
+ var MACOS_NOT_FOUND = 44;
4821
+ function commandOutput(command, args, stdin = "") {
4822
+ return new Promise((resolve) => {
4823
+ const child = spawn2(command, args, { stdio: ["pipe", "pipe", "pipe"] });
4824
+ let stdout = "";
4825
+ let stderr = "";
4826
+ child.stdout.on("data", (chunk) => stdout += chunk);
4827
+ child.stderr.on("data", (chunk) => stderr += chunk);
4828
+ child.on("error", (raised) => resolve({ code: -1, stdout, stderr: `${raised.message}` }));
4829
+ child.on("close", (code) => resolve({ code: code ?? -1, stdout, stderr }));
4830
+ child.stdin.on("error", () => {
4831
+ });
4832
+ child.stdin.end(stdin);
4833
+ });
4834
+ }
4835
+ var OsSecrets = class {
4836
+ /** Which system's store to use: this process's, unless a test names
4837
+ * another. */
4838
+ constructor(platform = process.platform) {
4839
+ this.platform = platform;
4840
+ }
4841
+ platform;
4842
+ async readSecret(key) {
4843
+ this.refuseUnsupportedPlatform();
4844
+ 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]);
4845
+ if (commandOutputOfRead.code === 0) return commandOutputOfRead.stdout.replace(/\n$/, "");
4846
+ const nothingThere = this.platform === "darwin" ? commandOutputOfRead.code === MACOS_NOT_FOUND : commandOutputOfRead.stderr.trim() === "";
4847
+ if (nothingThere) return void 0;
4848
+ this.throwFault("read", key, commandOutputOfRead.stderr);
4849
+ }
4850
+ async writeSecret(key, secret) {
4851
+ this.refuseUnsupportedPlatform();
4852
+ if (this.platform === "darwin" && /["\\\n]/.test(key)) {
4853
+ throw new DrivenFault(`The key "${key}" cannot be kept in the keychain.`, "Name the place by an address without quotes, backslashes or line breaks.");
4854
+ }
4855
+ const commandOutputOfWrite = this.platform === "darwin" ? await commandOutput(
4856
+ "security",
4857
+ ["-i"],
4858
+ `add-generic-password -U -s ${SERVICE} -a "${key}" -X ${Buffer.from(secret).toString("hex")}
4859
+ `
4860
+ ) : await commandOutput(
4861
+ "secret-tool",
4862
+ ["store", `--label=${SERVICE} ${key}`, "service", SERVICE, "account", key],
4863
+ secret
4864
+ );
4865
+ if (commandOutputOfWrite.code !== 0) this.throwFault("write", key);
4866
+ }
4867
+ async removeSecret(key) {
4868
+ this.refuseUnsupportedPlatform();
4869
+ const commandOutputOfRemove = this.platform === "darwin" ? await commandOutput("security", ["delete-generic-password", "-s", SERVICE, "-a", key]) : await commandOutput("secret-tool", ["clear", "service", SERVICE, "account", key]);
4870
+ if (commandOutputOfRemove.code === 0) return;
4871
+ const nothingThere = this.platform === "darwin" ? commandOutputOfRemove.code === MACOS_NOT_FOUND : commandOutputOfRemove.stderr.trim() === "";
4872
+ if (!nothingThere) this.throwFault("remove", key, commandOutputOfRemove.stderr);
4873
+ }
4874
+ /** macOS and Linux have a store this knows; anything else is refused
4875
+ * before a command runs. */
4876
+ refuseUnsupportedPlatform() {
4877
+ if (this.platform !== "darwin" && this.platform !== "linux") {
4878
+ throw new DrivenFault(
4879
+ `Keeping a secret is not supported on "${this.platform}".`,
4880
+ "Sign in from macOS or Linux, whose credential stores cw uses."
4881
+ );
4882
+ }
4883
+ }
4884
+ throwFault(operation, key, stderr = "") {
4885
+ const storeName = this.platform === "darwin" ? "The keychain" : "The secret service";
4886
+ throw new DrivenFault(
4887
+ `${storeName} refused to ${operation} the secret for "${key}".${stderr.trim() === "" ? "" : ` It said: ${stderr.trim()}`}`,
4888
+ this.platform === "darwin" ? "Check that the login keychain is unlocked." : "Check that secret-tool is installed and a secret service is running."
4889
+ );
4890
+ }
4891
+ };
4892
+
4893
+ // src/zdriven/OAuth.ts
4894
+ import { randomUUID } from "crypto";
4895
+ import { createServer } from "http";
4896
+ import { auth } from "@modelcontextprotocol/sdk/client/auth.js";
4897
+ import { OAuthError } from "@modelcontextprotocol/sdk/server/auth/errors.js";
4898
+ var CALLBACK_TIMEOUT_MS = 5 * 6e4;
4899
+ var OAuth = class {
4900
+ #callbackTimeoutMs;
4901
+ constructor(callbackTimeoutMs = CALLBACK_TIMEOUT_MS) {
4902
+ this.#callbackTimeoutMs = callbackTimeoutMs;
4903
+ }
4904
+ async authorize(endpoint, showAuthorizationUrl) {
4905
+ const state = randomUUID();
4906
+ const callbackServer = createServer();
4907
+ await new Promise((resolve, reject) => callbackServer.once("error", reject).listen(0, "127.0.0.1", resolve));
4908
+ const redirectUrl = `http://127.0.0.1:${callbackServer.address().port}/callback`;
4909
+ let callbackTimeout;
4910
+ const authorizationCode = new Promise((resolve, reject) => {
4911
+ callbackTimeout = setTimeout(
4912
+ () => reject(new DrivenFault(`Nobody came back from signing in to ${endpoint} in time.`, 'Run "cw mcp auth" again, and open the address it prints.')),
4913
+ this.#callbackTimeoutMs
4914
+ );
4915
+ callbackServer.on("request", (request, response) => {
4916
+ const callbackUrl = new URL(request.url ?? "/", redirectUrl);
4917
+ if (callbackUrl.pathname !== "/callback") return void response.writeHead(404).end();
4918
+ const callbackCode = callbackUrl.searchParams.get("code");
4919
+ const signedIn = callbackCode !== null && callbackUrl.searchParams.get("state") === state;
4920
+ 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");
4921
+ if (signedIn) return resolve(callbackCode);
4922
+ const callbackError = callbackUrl.searchParams.get("error");
4923
+ reject(new DrivenFault(`Signing in to ${endpoint} came back without an authorization${callbackError ? `: ${callbackError}` : ""}.`, 'Run "cw mcp auth" again.'));
4924
+ });
4925
+ });
4926
+ authorizationCode.catch(() => void 0);
4927
+ try {
4928
+ const oauthSession = {};
4929
+ const provider = this.#providerOver(oauthSession, redirectUrl, (authorizationUrl) => showAuthorizationUrl(authorizationUrl.href), state);
4930
+ if (await auth(provider, { serverUrl: endpoint }) === "REDIRECT")
4931
+ await auth(provider, { serverUrl: endpoint, authorizationCode: await authorizationCode });
4932
+ return oauthGrantOf(endpoint, oauthSession);
4933
+ } catch (raised) {
4934
+ if (raised instanceof DrivenFault) throw raised;
4935
+ throw new DrivenFault(
4936
+ `Signing in to ${endpoint} failed: ${raised instanceof Error ? raised.message : String(raised)}`,
4937
+ 'Check the address is reachable and offers an OAuth sign-in, then run "cw mcp auth" again.'
4938
+ );
4939
+ } finally {
4940
+ clearTimeout(callbackTimeout);
4941
+ callbackServer.closeAllConnections();
4942
+ callbackServer.close();
4943
+ }
4944
+ }
4945
+ async renew(endpoint, oauthGrant) {
4946
+ const { clientId, clientSecret, issuer } = oauthGrant.client;
4947
+ const oauthSession = {
4948
+ clientInformation: { client_id: clientId, ...clientSecret !== void 0 && { client_secret: clientSecret }, issuer },
4949
+ tokens: {
4950
+ access_token: oauthGrant.accessToken,
4951
+ token_type: "Bearer",
4952
+ ...oauthGrant.refreshToken !== void 0 && { refresh_token: oauthGrant.refreshToken },
4953
+ issuer
4954
+ }
4955
+ };
4956
+ const provider = this.#providerOver(oauthSession, "http://127.0.0.1/callback", () => void 0);
4957
+ try {
4958
+ if (await auth(provider, { serverUrl: endpoint }) === "REDIRECT") return void 0;
4959
+ } catch (raised) {
4960
+ if (raised instanceof OAuthError) return void 0;
4961
+ throw new DrivenFault(
4962
+ `Renewing the sign-in to ${endpoint} failed: ${raised instanceof Error ? raised.message : String(raised)}`,
4963
+ "Check the address is reachable, then try again."
4964
+ );
4965
+ }
4966
+ const renewedGrant = oauthGrantOf(endpoint, oauthSession);
4967
+ return renewedGrant.refreshToken !== void 0 || oauthGrant.refreshToken === void 0 ? renewedGrant : { ...renewedGrant, refreshToken: oauthGrant.refreshToken };
4968
+ }
4969
+ /**
4970
+ * What the SDK's flow is run with: `cw` described as a public client that
4971
+ * signs in at `redirectUrl`, and everything the flow saves kept in this
4972
+ * session. Registering is offered to a sign-in only, where a `state` is
4973
+ * checked on the way back; a renewal presents what the grant was issued to.
4974
+ */
4975
+ #providerOver(oauthSession, redirectUrl, redirectToAuthorization, state) {
4976
+ return {
4977
+ redirectUrl,
4978
+ clientMetadata: {
4979
+ client_name: "cherry-works",
4980
+ redirect_uris: [redirectUrl],
4981
+ grant_types: ["authorization_code", "refresh_token"],
4982
+ response_types: ["code"],
4983
+ token_endpoint_auth_method: "none"
4984
+ },
4985
+ ...state !== void 0 && {
4986
+ state: () => state,
4987
+ saveClientInformation: (clientInformation) => {
4988
+ oauthSession.clientInformation = clientInformation;
4989
+ }
4990
+ },
4991
+ clientInformation: () => oauthSession.clientInformation,
4992
+ tokens: () => oauthSession.tokens,
4993
+ saveTokens: (tokens) => {
4994
+ oauthSession.tokens = tokens;
4995
+ },
4996
+ redirectToAuthorization,
4997
+ saveCodeVerifier: (codeVerifier) => {
4998
+ oauthSession.codeVerifier = codeVerifier;
4999
+ },
5000
+ codeVerifier: () => oauthSession.codeVerifier ?? ""
5001
+ };
5002
+ }
5003
+ };
5004
+ function oauthGrantOf(endpoint, { clientInformation, tokens }) {
5005
+ if (clientInformation === void 0 || tokens === void 0)
5006
+ throw new DrivenFault(`Signing in to ${endpoint} ended without a token.`, 'Run "cw mcp auth" again.');
5007
+ return {
5008
+ accessToken: tokens.access_token,
5009
+ ...tokens.refresh_token !== void 0 && { refreshToken: tokens.refresh_token },
5010
+ ...tokens.expires_in !== void 0 && { expiresAt: new Date(Date.now() + tokens.expires_in * 1e3).toISOString() },
5011
+ client: {
5012
+ clientId: clientInformation.client_id,
5013
+ ...clientInformation.client_secret !== void 0 && { clientSecret: clientInformation.client_secret },
5014
+ issuer: clientInformation.issuer ?? tokens.issuer ?? endpoint
5015
+ }
5016
+ };
5017
+ }
5018
+
5019
+ // src/zdriven/McpClients.ts
5020
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
5021
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
5022
+ import { StreamableHTTPClientTransport, StreamableHTTPError } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
5023
+ var McpClients = class {
5024
+ async connect({ address, endpoint, command }, accessToken, timeoutMs) {
5025
+ let bearerToken = accessToken;
5026
+ const transport = endpoint !== void 0 ? new StreamableHTTPClientTransport(new URL(endpoint), {
5027
+ fetch: (url, init) => {
5028
+ const headers = new Headers(init?.headers);
5029
+ if (bearerToken !== void 0) headers.set("authorization", `Bearer ${bearerToken}`);
5030
+ return fetch(url, { ...init, headers });
5031
+ }
5032
+ }) : new StdioClientTransport({
5033
+ command: command?.command ?? "",
5034
+ args: [...command?.args ?? []],
5035
+ env: {
5036
+ ...Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== void 0)),
5037
+ ...command?.tokenEnv !== void 0 && accessToken !== void 0 && { [command.tokenEnv]: accessToken }
5038
+ },
5039
+ stderr: "inherit"
5040
+ });
5041
+ const client = new Client({ name: "cherry-works", version: "1" });
5042
+ let connectTimeout;
5043
+ try {
5044
+ const { tools } = await Promise.race([
5045
+ client.connect(transport).then(() => client.listTools()),
5046
+ new Promise((_resolve, reject) => {
5047
+ connectTimeout = setTimeout(
5048
+ () => reject(new DrivenFault(`${address} did not answer within ${timeoutMs / 1e3} seconds.`, "Check the place is up, then start the server again.")),
5049
+ timeoutMs
5050
+ );
5051
+ })
5052
+ ]);
5053
+ return {
5054
+ tools: tools.map(({ name, description, inputSchema }) => ({ name, ...description !== void 0 && { description }, inputSchema })),
5055
+ callTool: async (name, args, callToken) => {
5056
+ if (endpoint !== void 0) bearerToken = callToken;
5057
+ try {
5058
+ return await client.callTool({ name, arguments: { ...args } });
5059
+ } catch (raised) {
5060
+ throw faultOf(raised, `Calling "${name}" at ${address} failed`, "Check the place is up, then try again.");
5061
+ }
5062
+ },
5063
+ close: () => client.close()
5064
+ };
5065
+ } catch (raised) {
5066
+ await client.close().catch(() => void 0);
5067
+ throw faultOf(raised, `${address} could not be reached`, "Check the place is up, or its command installed, then start the server again.");
5068
+ } finally {
5069
+ clearTimeout(connectTimeout);
5070
+ }
5071
+ }
5072
+ };
5073
+ function faultOf(raised, failed, fix) {
5074
+ if (raised instanceof DrivenFault) return raised;
5075
+ if (raised instanceof StreamableHTTPError && raised.code === 401)
5076
+ return new UnauthorizedMcpFault(`${failed}: the place refused your credential.`, 'Run "cw mcp auth" at a terminal to sign in there again.');
5077
+ return new DrivenFault(`${failed}: ${raised instanceof Error ? raised.message : String(raised)}`, fix);
5078
+ }
5079
+
4076
5080
  // main.ts
4077
5081
  var fileReader = new FileReaders();
4078
5082
  var yamlParser = new YamlParser();
4079
5083
  var fileWriter = new FileOutput();
4080
5084
  var vcs = new Git(fileReader);
5085
+ var secrets = new OsSecrets();
5086
+ var authorizing = new OAuth();
5087
+ var mcpServers = new McpClients();
4081
5088
  var repoPath = pathToFileURL2(`${process.cwd()}/`);
4082
5089
  var charterAuthoringApp = new CharterAuthoring(repoPath, fileReader, yamlParser, fileWriter, vcs);
4083
5090
  var charterVendoringApp = new CharterVendoring(repoPath, fileReader, vcs);
4084
5091
  var testAuthoringApp = new TestAuthoring(repoPath, fileReader, fileWriter);
4085
- var cli = new Commander({ cwd: process.cwd(), version: package_default.version, charterAuthoringApp, charterVendoringApp, testAuthoringApp });
5092
+ var mcpConnectingApp = new McpConnecting(repoPath, fileReader, secrets, authorizing, yamlParser, mcpServers);
5093
+ var cli = new Commander({ cwd: process.cwd(), version: package_default.version, charterAuthoringApp, charterVendoringApp, testAuthoringApp, mcpConnectingApp });
4086
5094
  process.exitCode = await cli.run(process.argv.slice(2));