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/README.md +57 -6
- package/dist/cw.js +2 -1
- package/dist/main.js +1429 -421
- package/dist/portal/main.js +2 -2
- package/dist/portal/styles.css +1 -1
- package/package.json +2 -1
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.
|
|
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/
|
|
109
|
-
var
|
|
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
|
|
120
|
+
var CharterRootFault = class extends DomainFault {
|
|
120
121
|
};
|
|
121
|
-
var SettingsFault = class extends
|
|
122
|
+
var SettingsFault = class extends DomainFault {
|
|
122
123
|
};
|
|
123
|
-
var CharterPrimitiveFault = class extends
|
|
124
|
+
var CharterPrimitiveFault = class extends DomainFault {
|
|
124
125
|
};
|
|
125
|
-
var TestSuiteFault = class extends
|
|
126
|
+
var TestSuiteFault = class extends DomainFault {
|
|
126
127
|
};
|
|
127
|
-
var TestCaseFault = class extends
|
|
128
|
+
var TestCaseFault = class extends DomainFault {
|
|
128
129
|
};
|
|
129
|
-
var VendorFault = class extends
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
|
763
|
+
heldType instanceof z5.ZodArray ? "list" : "line",
|
|
658
764
|
field in required,
|
|
659
|
-
heldType instanceof
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1797
|
+
import { z as z11 } from "zod";
|
|
1488
1798
|
|
|
1489
1799
|
// src/hexagon/port/driver/dtos/dto.ts
|
|
1490
|
-
import { z as
|
|
1491
|
-
var strings =
|
|
1492
|
-
var dto = (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
|
|
1808
|
+
import { z as z7 } from "zod";
|
|
1499
1809
|
var fault = dto(
|
|
1500
1810
|
"Fault",
|
|
1501
|
-
|
|
1502
|
-
message:
|
|
1503
|
-
fix:
|
|
1504
|
-
severity:
|
|
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
|
-
|
|
1510
|
-
files:
|
|
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
|
-
|
|
1516
|
-
faults:
|
|
1825
|
+
z7.object({
|
|
1826
|
+
faults: z7.array(fault).readonly()
|
|
1517
1827
|
})
|
|
1518
1828
|
);
|
|
1519
|
-
var primitiveKinds = dto("PrimitiveKinds",
|
|
1829
|
+
var primitiveKinds = dto("PrimitiveKinds", z7.record(z7.string(), z7.string()));
|
|
1520
1830
|
var catalogueEntry = dto(
|
|
1521
1831
|
"CatalogueEntry",
|
|
1522
|
-
|
|
1523
|
-
identity:
|
|
1524
|
-
kind:
|
|
1525
|
-
id:
|
|
1526
|
-
description:
|
|
1527
|
-
file:
|
|
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:
|
|
1840
|
+
rationale: z7.string().optional(),
|
|
1531
1841
|
mixins: strings.optional()
|
|
1532
1842
|
})
|
|
1533
1843
|
);
|
|
1534
1844
|
var catalogue = dto(
|
|
1535
1845
|
"Catalogue",
|
|
1536
|
-
|
|
1537
|
-
entries:
|
|
1846
|
+
z7.object({
|
|
1847
|
+
entries: z7.array(catalogueEntry).readonly()
|
|
1538
1848
|
})
|
|
1539
1849
|
);
|
|
1540
1850
|
var scopedPrimitive = dto(
|
|
1541
1851
|
"ScopedPrimitive",
|
|
1542
|
-
|
|
1543
|
-
identity:
|
|
1544
|
-
kind:
|
|
1545
|
-
description:
|
|
1546
|
-
file:
|
|
1547
|
-
scope:
|
|
1548
|
-
headers:
|
|
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
|
-
|
|
1554
|
-
primitives:
|
|
1863
|
+
z7.object({
|
|
1864
|
+
primitives: z7.array(scopedPrimitive).readonly()
|
|
1555
1865
|
})
|
|
1556
1866
|
);
|
|
1557
1867
|
var primitiveSnapshot = dto(
|
|
1558
1868
|
"PrimitiveSnapshot",
|
|
1559
|
-
|
|
1869
|
+
z7.object({
|
|
1560
1870
|
scopedPrimitive,
|
|
1561
|
-
body:
|
|
1562
|
-
revision:
|
|
1871
|
+
body: z7.string(),
|
|
1872
|
+
revision: z7.string()
|
|
1563
1873
|
})
|
|
1564
1874
|
);
|
|
1565
1875
|
var planSummary = dto(
|
|
1566
1876
|
"PlanSummary",
|
|
1567
|
-
|
|
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
|
-
|
|
1577
|
-
field:
|
|
1578
|
-
shape:
|
|
1579
|
-
required:
|
|
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
|
-
|
|
1586
|
-
headers:
|
|
1587
|
-
sample:
|
|
1895
|
+
z7.object({
|
|
1896
|
+
headers: z7.array(primitiveHeader).readonly(),
|
|
1897
|
+
sample: z7.string()
|
|
1588
1898
|
})
|
|
1589
1899
|
);
|
|
1590
|
-
var testCasesByFile = dto("TestCasesByFile",
|
|
1900
|
+
var testCasesByFile = dto("TestCasesByFile", z7.record(z7.string(), strings));
|
|
1591
1901
|
var testCaseReport = dto(
|
|
1592
1902
|
"TestCaseReport",
|
|
1593
|
-
|
|
1594
|
-
suiteName:
|
|
1595
|
-
situation:
|
|
1596
|
-
passed:
|
|
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
|
-
|
|
1603
|
-
testCaseReports:
|
|
1912
|
+
z7.object({
|
|
1913
|
+
testCaseReports: z7.array(testCaseReport).readonly()
|
|
1604
1914
|
})
|
|
1605
1915
|
);
|
|
1606
1916
|
var testCase = dto(
|
|
1607
1917
|
"TestCase",
|
|
1608
|
-
|
|
1609
|
-
situation:
|
|
1610
|
-
expectation:
|
|
1611
|
-
identity:
|
|
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
|
-
|
|
1617
|
-
name:
|
|
1618
|
-
text:
|
|
1619
|
-
description:
|
|
1620
|
-
cases:
|
|
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
|
-
|
|
1627
|
-
testSuites:
|
|
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
|
-
|
|
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
|
|
1999
|
+
import { z as z8 } from "zod";
|
|
1660
2000
|
var doctorOutcome = dto(
|
|
1661
2001
|
"DoctorOutcome",
|
|
1662
|
-
|
|
2002
|
+
z8.object({
|
|
1663
2003
|
agents: strings,
|
|
1664
|
-
errorCount:
|
|
1665
|
-
warnCount:
|
|
1666
|
-
pendingCount:
|
|
2004
|
+
errorCount: z8.number(),
|
|
2005
|
+
warnCount: z8.number(),
|
|
2006
|
+
pendingCount: z8.number().nullable(),
|
|
1667
2007
|
faultsByFile: DataDTOs.FaultsByFile,
|
|
1668
2008
|
driftedVendors: strings,
|
|
1669
|
-
problemCount:
|
|
2009
|
+
problemCount: z8.number()
|
|
1670
2010
|
})
|
|
1671
2011
|
);
|
|
1672
2012
|
var explanationOutcome = dto(
|
|
1673
2013
|
"ExplanationOutcome",
|
|
1674
|
-
|
|
2014
|
+
z8.object({
|
|
1675
2015
|
scopedPrimitive: DataDTOs.ScopedPrimitive,
|
|
1676
|
-
activatesWhen:
|
|
1677
|
-
useMixins:
|
|
2016
|
+
activatesWhen: z8.string(),
|
|
2017
|
+
useMixins: z8.array(DataDTOs.ScopedPrimitive).readonly(),
|
|
1678
2018
|
rationale: DataDTOs.ScopedPrimitive.optional(),
|
|
1679
|
-
hosts:
|
|
1680
|
-
citers:
|
|
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
|
|
2027
|
+
import { z as z10 } from "zod";
|
|
1688
2028
|
|
|
1689
2029
|
// src/hexagon/domain/models/test/TestCase.ts
|
|
1690
|
-
import { z as
|
|
1691
|
-
var TEST_CASE_SCHEMA =
|
|
1692
|
-
|
|
1693
|
-
do:
|
|
1694
|
-
expect:
|
|
1695
|
-
|
|
1696
|
-
|
|
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
|
-
|
|
1700
|
-
when:
|
|
1701
|
-
expect:
|
|
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 =
|
|
1744
|
-
description:
|
|
1745
|
-
cases:
|
|
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
|
|
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
|
|
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:
|
|
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(
|
|
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:
|
|
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(
|
|
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
|
-
|
|
2013
|
-
kind:
|
|
2014
|
-
id:
|
|
2015
|
-
headers:
|
|
2016
|
-
body:
|
|
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:
|
|
2364
|
+
headers: z11.object({ Location: z11.string() })
|
|
2025
2365
|
},
|
|
2026
|
-
422: json(
|
|
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:
|
|
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:
|
|
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:
|
|
2059
|
-
headers:
|
|
2398
|
+
params: z11.object({ identity: z11.string() }),
|
|
2399
|
+
headers: z11.object({ "if-match": z11.string().optional() }),
|
|
2060
2400
|
body: json(
|
|
2061
|
-
|
|
2062
|
-
headers:
|
|
2063
|
-
body:
|
|
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(
|
|
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
|
|
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:
|
|
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:
|
|
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(
|
|
2171
|
-
headers:
|
|
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:
|
|
2185
|
-
body: json(
|
|
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(
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
2331
|
-
var
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
2720
|
-
var SETTINGS =
|
|
2721
|
-
agents:
|
|
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
|
|
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
|
|
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/
|
|
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/
|
|
3054
|
-
var
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
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
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
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/
|
|
3150
|
-
var
|
|
3151
|
-
static kind = "
|
|
3152
|
-
kind =
|
|
3153
|
-
|
|
3154
|
-
|
|
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/
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
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
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
3224
|
-
const
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
//
|
|
3229
|
-
//
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
//
|
|
3242
|
-
//
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
//
|
|
3251
|
-
//
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
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
|
-
|
|
3262
|
-
|
|
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(
|
|
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
|
|
3366
|
-
|
|
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
|
|
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
|
-
*
|
|
3503
|
-
*
|
|
3504
|
-
*
|
|
3505
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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));
|