specguard-mcp 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +183 -17
  2. package/dist/bin/specguard-mcp.js +5 -0
  3. package/dist/bin/specguard-mcp.js.map +1 -1
  4. package/dist/src/support/run-command.d.ts +42 -0
  5. package/dist/src/support/run-command.js +124 -8
  6. package/dist/src/support/run-command.js.map +1 -1
  7. package/dist/src/support/specguard-api.d.ts +50 -0
  8. package/dist/src/support/specguard-api.js +148 -8
  9. package/dist/src/support/specguard-api.js.map +1 -1
  10. package/dist/src/support/teardown.d.ts +33 -0
  11. package/dist/src/support/teardown.js +56 -0
  12. package/dist/src/support/teardown.js.map +1 -0
  13. package/dist/src/tools/add-repository.d.ts +55 -0
  14. package/dist/src/tools/add-repository.js +114 -0
  15. package/dist/src/tools/add-repository.js.map +1 -0
  16. package/dist/src/tools/args.d.ts +20 -0
  17. package/dist/src/tools/args.js +30 -0
  18. package/dist/src/tools/args.js.map +1 -1
  19. package/dist/src/tools/create-repository-api-key.d.ts +26 -0
  20. package/dist/src/tools/create-repository-api-key.js +83 -0
  21. package/dist/src/tools/create-repository-api-key.js.map +1 -0
  22. package/dist/src/tools/index.d.ts +61 -7
  23. package/dist/src/tools/index.js +71 -7
  24. package/dist/src/tools/index.js.map +1 -1
  25. package/dist/src/tools/list-repositories.d.ts +14 -7
  26. package/dist/src/tools/list-repositories.js +14 -7
  27. package/dist/src/tools/list-repositories.js.map +1 -1
  28. package/dist/src/tools/registrable-repositories.d.ts +51 -0
  29. package/dist/src/tools/registrable-repositories.js +92 -0
  30. package/dist/src/tools/registrable-repositories.js.map +1 -0
  31. package/dist/src/tools/remove-repository.d.ts +33 -0
  32. package/dist/src/tools/remove-repository.js +81 -0
  33. package/dist/src/tools/remove-repository.js.map +1 -0
  34. package/dist/src/tools/repository-overview.js +33 -8
  35. package/dist/src/tools/repository-overview.js.map +1 -1
  36. package/dist/src/tools/revoke-repository-api-key.d.ts +29 -0
  37. package/dist/src/tools/revoke-repository-api-key.js +85 -0
  38. package/dist/src/tools/revoke-repository-api-key.js.map +1 -0
  39. package/package.json +1 -1
@@ -0,0 +1,83 @@
1
+ import { postJsonObject, requireUserApiConfig } from "../support/specguard-api.js";
2
+ import { optionalString, requireString } from "./args.js";
3
+ /**
4
+ * `POST /api/v1/repositories/:repository_id/api_keys` as a tool — shipped in
5
+ * the platform (`specguard/config/routes.rb:158`,
6
+ * `user_repository_api_keys_controller#create`, SPGD-754).
7
+ *
8
+ * == Reveal-once, again
9
+ *
10
+ * The 201 body carries `api_key.token` — the raw key, the only time it exists
11
+ * anywhere — exactly as `add_repository`'s does. The body is therefore passed
12
+ * through UNRESHAPED in both `text` and `structured` for the same reason that
13
+ * tool states: any reshaping on this hop is a value that cannot be recovered
14
+ * rather than a field that can be re-fetched. Recovery for a dropped token is
15
+ * minting another key — this same tool — because the platform ships no
16
+ * `#regenerate` and no re-serve.
17
+ *
18
+ * == The name is top-level and optional
19
+ *
20
+ * `params[:name]` defaults to `ApiKey::DEFAULT_NAME` server-side; `undefined`
21
+ * here means "let the server name it" and simply omits the key from the POST
22
+ * body. Not re-validated here for the reason `add_repository` states: a second
23
+ * format rule on this side is a rule with no owner, free to drift from the one
24
+ * that actually decides.
25
+ */
26
+ const createRepositoryApiKey = {
27
+ name: "create_repository_api_key",
28
+ title: "Create repository API key",
29
+ description: "Mints a new CI API key (an sgk_… key) for a SpecGuard repository, and returns it " +
30
+ "alongside the repository's existing keys. " +
31
+ "⚠️ `api_key.token` is shown THIS ONCE AND NEVER AGAIN — nothing stores it and no " +
32
+ "endpoint can re-serve it, so hand it to the user in your reply rather than assuming " +
33
+ "it can be fetched later. If it is dropped, the recovery is minting another key with " +
34
+ "this same tool (the platform has no regenerate), then revoking the orphaned one. " +
35
+ "On success the response carries an `api_key` block (`name`, `token`, `hint`, " +
36
+ "`created_at`) — the same reveal-once shape `add_repository` serves. " +
37
+ "Minting does not disturb existing keys: each key on a repository authenticates " +
38
+ "independently until revoked. " +
39
+ "Takes `repository_id` (the numeric id `list_repositories` reports) and an optional " +
40
+ "`name`, which the server defaults when omitted. " +
41
+ "Authorization is the `keys_manage` capability — a member without it is refused 403 " +
42
+ "in SpecGuard's own words. " +
43
+ "Needs SPECGUARD_USER_API_KEY (an sgu_… key), the same credential `add_repository` " +
44
+ "writes with and a DIFFERENT one from the sgk_… repository key " +
45
+ "`get_repository_overview` uses.",
46
+ inputSchema: {
47
+ type: "object",
48
+ properties: {
49
+ repository_id: {
50
+ type: "string",
51
+ description: "The repository to mint the key for — its numeric id, as `add_repository` " +
52
+ "returns and `list_repositories` reports, not the `org/repo` handle.",
53
+ },
54
+ name: {
55
+ type: "string",
56
+ description: "An optional label for the key. Omit it to let SpecGuard use its default name. " +
57
+ "The server validates the name and refuses an unusable one in its own words.",
58
+ },
59
+ },
60
+ required: ["repository_id"],
61
+ // Closed for the reason every tool here states — and on a WRITE, a silently
62
+ // dropped misspelled argument still mints something, just possibly mislabeled.
63
+ additionalProperties: false,
64
+ },
65
+ async run(args, context) {
66
+ const repositoryId = requireString(args["repository_id"], "repository_id");
67
+ const name = optionalString(args["name"], "name");
68
+ const api = requireUserApiConfig(context.config);
69
+ // `name` omitted when absent rather than sent as null, so the server's own
70
+ // `ApiKey::DEFAULT_NAME` default applies — this bridge expresses "no
71
+ // preference", it does not choose on the server's behalf.
72
+ const created = await postJsonObject(api, `/api/v1/repositories/${encodeURIComponent(repositoryId)}/api_keys`, name === undefined ? {} : { name }, context.fetch);
73
+ // Unreshaped, for the reason `add_repository` states at its own return:
74
+ // `api_key.token` exists nowhere else, so any reshaping on this hop is a
75
+ // value that cannot be recovered.
76
+ return {
77
+ text: JSON.stringify(created, null, 2),
78
+ structured: created,
79
+ };
80
+ },
81
+ };
82
+ export default createRepositoryApiKey;
83
+ //# sourceMappingURL=create-repository-api-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-repository-api-key.js","sourceRoot":"","sources":["../../../src/tools/create-repository-api-key.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAG1D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,sBAAsB,GAAmB;IAC7C,IAAI,EAAE,2BAA2B;IACjC,KAAK,EAAE,2BAA2B;IAClC,WAAW,EACT,mFAAmF;QACnF,4CAA4C;QAC5C,mFAAmF;QACnF,sFAAsF;QACtF,sFAAsF;QACtF,mFAAmF;QACnF,+EAA+E;QAC/E,sEAAsE;QACtE,iFAAiF;QACjF,+BAA+B;QAC/B,qFAAqF;QACrF,kDAAkD;QAClD,qFAAqF;QACrF,4BAA4B;QAC5B,oFAAoF;QACpF,gEAAgE;QAChE,iCAAiC;IACnC,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,aAAa,EAAE;gBACb,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,2EAA2E;oBAC3E,qEAAqE;aACxE;YACD,IAAI,EAAE;gBACJ,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,gFAAgF;oBAChF,6EAA6E;aAChF;SACF;QACD,QAAQ,EAAE,CAAC,eAAe,CAAC;QAC3B,4EAA4E;QAC5E,+EAA+E;QAC/E,oBAAoB,EAAE,KAAK;KAC5B;IAED,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO;QACrB,MAAM,YAAY,GAAG,aAAa,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC,CAAC;QAC3E,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;QAElD,MAAM,GAAG,GAAG,oBAAoB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAEjD,2EAA2E;QAC3E,qEAAqE;QACrE,0DAA0D;QAC1D,MAAM,OAAO,GAAG,MAAM,cAAc,CAClC,GAAG,EACH,wBAAwB,kBAAkB,CAAC,YAAY,CAAC,WAAW,EACnE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAClC,OAAO,CAAC,KAAK,CACd,CAAC;QAEF,wEAAwE;QACxE,yEAAyE;QACzE,kCAAkC;QAClC,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YACtC,UAAU,EAAE,OAAO;SACpB,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,eAAe,sBAAsB,CAAC"}
@@ -41,13 +41,67 @@ import type { ToolDefinition } from "./types.js";
41
41
  * and fails on use, which is worse than not offering the tool, because the
42
42
  * agent has already committed to a plan by the time it finds out.
43
43
  *
44
- * The user-scoped WRITE endpoints are absent for a different reason, and it is
45
- * worth stating so nobody re-derives the wrong one: `POST /api/v1/repositories`
46
- * exists on the platform today. What this bridge does not have is a way to call
47
- * it — `support/specguard-api.ts` offers `getJson`, which hardcodes
48
- * `method: "GET"` and takes no body. That transport lands with the first write
49
- * tool, designed against a real request body and a real 4xx surface, rather
50
- * than being invented here for a tool that does not yet exist.
44
+ * == The fourth: the first tool that WRITES
45
+ *
46
+ * - `add_repository` wraps `POST /api/v1/repositories` (shipped:
47
+ * `specguard/config/routes.rb`, `Api::V1::UserRepositoriesController#create`).
48
+ *
49
+ * The endpoint had shipped for a while; what this bridge lacked was a way to
50
+ * CALL it — `support/specguard-api.ts` offered only `getJson`, which hardcoded
51
+ * `method: "GET"` and took no body. The reservation recorded here was that the
52
+ * write transport should land WITH the first write tool, designed against a real
53
+ * request body and a real 4xx surface rather than invented for a caller that did
54
+ * not exist. That is what happened: `postJson`/`postJsonObject` arrived with
55
+ * this entry, sharing `fetchWithTimeout` with the read path rather than standing
56
+ * beside it, and `describeFailure` grew the `400` branch that surfaces
57
+ * SpecGuard's own refusal sentence — the modal answer this endpoint gives.
58
+ *
59
+ * The standing rule is unchanged and still binding. What once kept `DELETE
60
+ * /api/v1/repositories/:id` and the API-key endpoints out under it — "not on
61
+ * `origin/main`, so they may not be wrapped" — stopped being true when SPGD-754
62
+ * shipped them, and the sixth-through-eighth section below records their
63
+ * wrapping. What moved was the platform, not the bar.
64
+ *
65
+ * == The fifth: the read half of the registration gate
66
+ *
67
+ * - `registrable_repositories` wraps `GET /api/v1/repositories/registrable`
68
+ * (shipped: `specguard/config/routes.rb:117`,
69
+ * `Api::V1::UserRepositoriesController#registrable`).
70
+ *
71
+ * `list_repositories` says what IS registered; this says what COULD be — the
72
+ * set the gate would consult, read out in advance, so an agent can pick a
73
+ * `full_name` for `add_repository` from a real answer. Landing it also
74
+ * generalised the 400 branch in `describeFailure` into a status-parameterised
75
+ * extractor, because this endpoint's modal first answer is a 403 (`not_granted`)
76
+ * carrying the same `{error, message}` contract — the identical defect, given
77
+ * the identical remedy.
78
+ *
79
+ * The user-scoped surface as it now stands is therefore three tools: read the
80
+ * list, read the gate's answer, write a registration. The standing rule is
81
+ * unchanged and still binding, which is what keeps the rest out:
82
+ * `DELETE /api/v1/repositories/:id` and the API-key endpoints (SPGD-754) are
83
+ * NOT on `origin/main`, so they may not be wrapped here however useful a tool
84
+ * for them would be. What moved was the platform, not the bar.
85
+ *
86
+ * == The sixth through eighth: removal and the key lifecycle
87
+ *
88
+ * - `remove_repository` wraps `DELETE /api/v1/repositories/:id`, and
89
+ * `create_repository_api_key` / `revoke_repository_api_key` wrap the two
90
+ * `api_keys` endpoints (all shipped: SPGD-754, `specguard@origin/main`).
91
+ *
92
+ * The closing fence the two paragraphs above share — "the DELETE and API-key
93
+ * endpoints are NOT on `origin/main`, so they may not be wrapped" — stopped
94
+ * being true when SPGD-754 landed, and these three entries are what became
95
+ * wrappable the moment it did. They also forced the transport's third verb:
96
+ * both DELETE endpoints answer `204` with NO body, the one response in the
97
+ * `sgu_` surface that is deliberately not JSON, which is why `deleteJson`
98
+ * returns the raw body text instead of routing an empty 204 through
99
+ * `requestJson`'s JSON parse.
100
+ *
101
+ * The standing rule itself is unchanged and still binding — which still keeps
102
+ * out `/check-intent`, duplicate clustering, member management and rename: none
103
+ * of their backing endpoints has shipped, and a tool advertised in `tools/list`
104
+ * remains a promise an agent will act on.
51
105
  */
52
106
  export declare const TOOLS: readonly ToolDefinition[];
53
107
  export type { ToolContext, ToolDefinition, ToolResult } from "./types.js";
@@ -1,6 +1,11 @@
1
+ import addRepository from "./add-repository.js";
2
+ import createRepositoryApiKey from "./create-repository-api-key.js";
1
3
  import lintIntentAnnotations from "./lint-intent-annotations.js";
2
4
  import listRepositories from "./list-repositories.js";
3
5
  import getRepositoryOverview from "./repository-overview.js";
6
+ import registrableRepositories from "./registrable-repositories.js";
7
+ import removeRepository from "./remove-repository.js";
8
+ import revokeRepositoryApiKey from "./revoke-repository-api-key.js";
4
9
  /**
5
10
  * THE REGISTRY — the one file that changes when the toolset grows.
6
11
  *
@@ -43,17 +48,76 @@ import getRepositoryOverview from "./repository-overview.js";
43
48
  * and fails on use, which is worse than not offering the tool, because the
44
49
  * agent has already committed to a plan by the time it finds out.
45
50
  *
46
- * The user-scoped WRITE endpoints are absent for a different reason, and it is
47
- * worth stating so nobody re-derives the wrong one: `POST /api/v1/repositories`
48
- * exists on the platform today. What this bridge does not have is a way to call
49
- * it — `support/specguard-api.ts` offers `getJson`, which hardcodes
50
- * `method: "GET"` and takes no body. That transport lands with the first write
51
- * tool, designed against a real request body and a real 4xx surface, rather
52
- * than being invented here for a tool that does not yet exist.
51
+ * == The fourth: the first tool that WRITES
52
+ *
53
+ * - `add_repository` wraps `POST /api/v1/repositories` (shipped:
54
+ * `specguard/config/routes.rb`, `Api::V1::UserRepositoriesController#create`).
55
+ *
56
+ * The endpoint had shipped for a while; what this bridge lacked was a way to
57
+ * CALL it — `support/specguard-api.ts` offered only `getJson`, which hardcoded
58
+ * `method: "GET"` and took no body. The reservation recorded here was that the
59
+ * write transport should land WITH the first write tool, designed against a real
60
+ * request body and a real 4xx surface rather than invented for a caller that did
61
+ * not exist. That is what happened: `postJson`/`postJsonObject` arrived with
62
+ * this entry, sharing `fetchWithTimeout` with the read path rather than standing
63
+ * beside it, and `describeFailure` grew the `400` branch that surfaces
64
+ * SpecGuard's own refusal sentence — the modal answer this endpoint gives.
65
+ *
66
+ * The standing rule is unchanged and still binding. What once kept `DELETE
67
+ * /api/v1/repositories/:id` and the API-key endpoints out under it — "not on
68
+ * `origin/main`, so they may not be wrapped" — stopped being true when SPGD-754
69
+ * shipped them, and the sixth-through-eighth section below records their
70
+ * wrapping. What moved was the platform, not the bar.
71
+ *
72
+ * == The fifth: the read half of the registration gate
73
+ *
74
+ * - `registrable_repositories` wraps `GET /api/v1/repositories/registrable`
75
+ * (shipped: `specguard/config/routes.rb:117`,
76
+ * `Api::V1::UserRepositoriesController#registrable`).
77
+ *
78
+ * `list_repositories` says what IS registered; this says what COULD be — the
79
+ * set the gate would consult, read out in advance, so an agent can pick a
80
+ * `full_name` for `add_repository` from a real answer. Landing it also
81
+ * generalised the 400 branch in `describeFailure` into a status-parameterised
82
+ * extractor, because this endpoint's modal first answer is a 403 (`not_granted`)
83
+ * carrying the same `{error, message}` contract — the identical defect, given
84
+ * the identical remedy.
85
+ *
86
+ * The user-scoped surface as it now stands is therefore three tools: read the
87
+ * list, read the gate's answer, write a registration. The standing rule is
88
+ * unchanged and still binding, which is what keeps the rest out:
89
+ * `DELETE /api/v1/repositories/:id` and the API-key endpoints (SPGD-754) are
90
+ * NOT on `origin/main`, so they may not be wrapped here however useful a tool
91
+ * for them would be. What moved was the platform, not the bar.
92
+ *
93
+ * == The sixth through eighth: removal and the key lifecycle
94
+ *
95
+ * - `remove_repository` wraps `DELETE /api/v1/repositories/:id`, and
96
+ * `create_repository_api_key` / `revoke_repository_api_key` wrap the two
97
+ * `api_keys` endpoints (all shipped: SPGD-754, `specguard@origin/main`).
98
+ *
99
+ * The closing fence the two paragraphs above share — "the DELETE and API-key
100
+ * endpoints are NOT on `origin/main`, so they may not be wrapped" — stopped
101
+ * being true when SPGD-754 landed, and these three entries are what became
102
+ * wrappable the moment it did. They also forced the transport's third verb:
103
+ * both DELETE endpoints answer `204` with NO body, the one response in the
104
+ * `sgu_` surface that is deliberately not JSON, which is why `deleteJson`
105
+ * returns the raw body text instead of routing an empty 204 through
106
+ * `requestJson`'s JSON parse.
107
+ *
108
+ * The standing rule itself is unchanged and still binding — which still keeps
109
+ * out `/check-intent`, duplicate clustering, member management and rename: none
110
+ * of their backing endpoints has shipped, and a tool advertised in `tools/list`
111
+ * remains a promise an agent will act on.
53
112
  */
54
113
  export const TOOLS = [
55
114
  lintIntentAnnotations,
56
115
  getRepositoryOverview,
57
116
  listRepositories,
117
+ addRepository,
118
+ registrableRepositories,
119
+ removeRepository,
120
+ createRepositoryApiKey,
121
+ revokeRepositoryApiKey,
58
122
  ];
59
123
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,MAAM,8BAA8B,CAAC;AACjE,OAAO,gBAAgB,MAAM,wBAAwB,CAAC;AACtD,OAAO,qBAAqB,MAAM,0BAA0B,CAAC;AAG7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,MAAM,CAAC,MAAM,KAAK,GAA8B;IAC9C,qBAAqB;IACrB,qBAAqB;IACrB,gBAAgB;CACjB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,qBAAqB,CAAC;AAChD,OAAO,sBAAsB,MAAM,gCAAgC,CAAC;AACpE,OAAO,qBAAqB,MAAM,8BAA8B,CAAC;AACjE,OAAO,gBAAgB,MAAM,wBAAwB,CAAC;AACtD,OAAO,qBAAqB,MAAM,0BAA0B,CAAC;AAC7D,OAAO,uBAAuB,MAAM,+BAA+B,CAAC;AACpE,OAAO,gBAAgB,MAAM,wBAAwB,CAAC;AACtD,OAAO,sBAAsB,MAAM,gCAAgC,CAAC;AAGpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuGG;AACH,MAAM,CAAC,MAAM,KAAK,GAA8B;IAC9C,qBAAqB;IACrB,qBAAqB;IACrB,gBAAgB;IAChB,aAAa;IACb,uBAAuB;IACvB,gBAAgB;IAChB,sBAAsB;IACtB,sBAAsB;CACvB,CAAC"}
@@ -13,13 +13,20 @@ import type { ToolDefinition } from "./types.js";
13
13
  * answer, and it is the whole of what this tool does.
14
14
  *
15
15
  * It is also the tool that proves the second credential slot works end to end,
16
- * which is why it ships alone. The registry's standing rule (`tools/index.ts`)
17
- * is that a tool in `tools/list` is a promise an agent acts on, so the other
18
- * user-scoped endpoints wait for the transport they need: `POST /api/v1/repositories`
19
- * EXISTS on the platform today, and `getJson` hardcodes `method: "GET"` and
20
- * takes no body, so registering a repository is blocked on a write transport
21
- * rather than on a missing endpoint. That transport belongs with the first write
22
- * tool, where it can be designed against a real body and a real 4xx surface.
16
+ * which is why it shipped alone. It no longer IS alone: `add_repository`
17
+ * (SPGD-764) reads the same `sgu_` key and wraps `POST /api/v1/repositories`,
18
+ * which arrived once this bridge had a write transport to call it with —
19
+ * `postJson`/`postJsonObject` in `support/specguard-api.ts`, landed with that
20
+ * tool and designed against a real request body and a real 4xx surface, exactly
21
+ * as the reservation recorded here asked. The registry's standing rule
22
+ * (`tools/index.ts`) is unchanged and still binding: a tool in `tools/list` is a
23
+ * promise an agent acts on, so the REST of the user-scoped surface — removing a
24
+ * repository, minting or revoking keys — stays out until those endpoints ship.
25
+ *
26
+ * What this tool still uniquely answers is the question above: which
27
+ * repositories there ARE. `add_repository` extends that surface rather than
28
+ * replacing it, and the two share a handle — the `full_name` reported here is
29
+ * the `full_name` that one takes.
23
30
  *
24
31
  * == It reads the OTHER key, and that is the point
25
32
  *
@@ -13,13 +13,20 @@ import { getJsonObject, requireUserApiConfig } from "../support/specguard-api.js
13
13
  * answer, and it is the whole of what this tool does.
14
14
  *
15
15
  * It is also the tool that proves the second credential slot works end to end,
16
- * which is why it ships alone. The registry's standing rule (`tools/index.ts`)
17
- * is that a tool in `tools/list` is a promise an agent acts on, so the other
18
- * user-scoped endpoints wait for the transport they need: `POST /api/v1/repositories`
19
- * EXISTS on the platform today, and `getJson` hardcodes `method: "GET"` and
20
- * takes no body, so registering a repository is blocked on a write transport
21
- * rather than on a missing endpoint. That transport belongs with the first write
22
- * tool, where it can be designed against a real body and a real 4xx surface.
16
+ * which is why it shipped alone. It no longer IS alone: `add_repository`
17
+ * (SPGD-764) reads the same `sgu_` key and wraps `POST /api/v1/repositories`,
18
+ * which arrived once this bridge had a write transport to call it with —
19
+ * `postJson`/`postJsonObject` in `support/specguard-api.ts`, landed with that
20
+ * tool and designed against a real request body and a real 4xx surface, exactly
21
+ * as the reservation recorded here asked. The registry's standing rule
22
+ * (`tools/index.ts`) is unchanged and still binding: a tool in `tools/list` is a
23
+ * promise an agent acts on, so the REST of the user-scoped surface — removing a
24
+ * repository, minting or revoking keys — stays out until those endpoints ship.
25
+ *
26
+ * What this tool still uniquely answers is the question above: which
27
+ * repositories there ARE. `add_repository` extends that surface rather than
28
+ * replacing it, and the two share a handle — the `full_name` reported here is
29
+ * the `full_name` that one takes.
23
30
  *
24
31
  * == It reads the OTHER key, and that is the point
25
32
  *
@@ -1 +1 @@
1
- {"version":3,"file":"list-repositories.js","sourceRoot":"","sources":["../../../src/tools/list-repositories.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAGlF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AACH,MAAM,gBAAgB,GAAmB;IACvC,IAAI,EAAE,mBAAmB;IACzB,KAAK,EAAE,mBAAmB;IAC1B,WAAW,EACT,2FAA2F;QAC3F,2FAA2F;QAC3F,6DAA6D;QAC7D,6FAA6F;QAC7F,wDAAwD;QACxD,mFAAmF;QACnF,2FAA2F;QAC3F,uDAAuD;QACvD,kEAAkE;QAClE,6FAA6F;QAC7F,8FAA8F;QAC9F,wFAAwF;QACxF,4FAA4F;QAC5F,QAAQ;IACV,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,4EAA4E;QAC5E,oEAAoE;QACpE,wEAAwE;QACxE,sEAAsE;QACtE,2EAA2E;QAC3E,uEAAuE;QACvE,uCAAuC;QACvC,oBAAoB,EAAE,KAAK;KAC5B;IAED,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO;QACtB,MAAM,GAAG,GAAG,oBAAoB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAEjD,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,sBAAsB,EAAE,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QAEpF,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YACtC,UAAU,EAAE,OAAO;SACpB,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"list-repositories.js","sourceRoot":"","sources":["../../../src/tools/list-repositories.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAGlF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;AACH,MAAM,gBAAgB,GAAmB;IACvC,IAAI,EAAE,mBAAmB;IACzB,KAAK,EAAE,mBAAmB;IAC1B,WAAW,EACT,2FAA2F;QAC3F,2FAA2F;QAC3F,6DAA6D;QAC7D,6FAA6F;QAC7F,wDAAwD;QACxD,mFAAmF;QACnF,2FAA2F;QAC3F,uDAAuD;QACvD,kEAAkE;QAClE,6FAA6F;QAC7F,8FAA8F;QAC9F,wFAAwF;QACxF,4FAA4F;QAC5F,QAAQ;IACV,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,4EAA4E;QAC5E,oEAAoE;QACpE,wEAAwE;QACxE,sEAAsE;QACtE,2EAA2E;QAC3E,uEAAuE;QACvE,uCAAuC;QACvC,oBAAoB,EAAE,KAAK;KAC5B;IAED,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO;QACtB,MAAM,GAAG,GAAG,oBAAoB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAEjD,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,sBAAsB,EAAE,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QAEpF,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YACtC,UAAU,EAAE,OAAO;SACpB,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,51 @@
1
+ import type { ToolDefinition } from "./types.js";
2
+ /**
3
+ * `GET /api/v1/repositories/registrable` as a tool — shipped today in the
4
+ * platform (`specguard/config/routes.rb:117`, served by
5
+ * `Api::V1::UserRepositoriesController#registrable`).
6
+ *
7
+ * == What it answers, and why `list_repositories` does not
8
+ *
9
+ * `list_repositories` reports what is already registered; this reports what
10
+ * COULD be — the repositories the registration gate would consult, read out
11
+ * loud in advance. It exists so an agent can pick a `full_name` for
12
+ * `add_repository` from a real answer rather than by guessing, and so a
13
+ * `has already been taken` refusal can be explained rather than merely
14
+ * retried.
15
+ *
16
+ * == Two controller decisions the description carries, because both are
17
+ * counter-intuitive and both are stated in the controller's own comments
18
+ *
19
+ * `registered` is asked of `Repository` GLOBALLY, not of what this person can
20
+ * open. The controller says why: a repository somebody ELSE registered still
21
+ * refuses this person's POST with `has already been taken`, so a reading
22
+ * scoped to what they can see would mark it `registered: false` and send them
23
+ * at a name that cannot be registered by anyone. An entry is therefore
24
+ * MARKED, not excluded — `registered: true` is the answer to "why did my POST
25
+ * say has already been taken".
26
+ *
27
+ * And a name appearing here is NOT a promise the write will succeed. This is
28
+ * the set the gate would consult at the moment of the read; the repository may
29
+ * be registered by someone else between the two calls.
30
+ *
31
+ * == The modal first answer is a 403, and that is why `describeFailure` grew
32
+ *
33
+ * `#registrable` fails closed on the two states `GrantVerifier` refuses on and
34
+ * renders `status: :forbidden` — NOT the 400 path the other refusals use. A
35
+ * nil grant is, in the controller's own words, "an ordinary state and not an
36
+ * error: it is every person who has not opened SpecGuard in a browser since
37
+ * this shipped" — so the 403 is the modal first answer this tool gives, and
38
+ * the sentence it carries (sign in, reconnect GitHub, retry) reaches the agent
39
+ * through the 403 branch in `specguard-api.ts`, which this tool is the reason
40
+ * for. On refusal the body still carries `grant`: `null` when there never was
41
+ * one, populated with `stale: true` when it lapsed — "yours lapsed four days
42
+ * ago" is a different fact from "you never had one", and the tool description
43
+ * is where an agent learns to branch on it.
44
+ *
45
+ * == No arguments, for the same reason `list_repositories` has none
46
+ *
47
+ * The credential is the whole of the scope. The endpoint takes no parameters,
48
+ * and nothing an argument could select reaches this answer.
49
+ */
50
+ declare const registrableRepositories: ToolDefinition;
51
+ export default registrableRepositories;
@@ -0,0 +1,92 @@
1
+ import { getJsonObject, requireUserApiConfig } from "../support/specguard-api.js";
2
+ /**
3
+ * `GET /api/v1/repositories/registrable` as a tool — shipped today in the
4
+ * platform (`specguard/config/routes.rb:117`, served by
5
+ * `Api::V1::UserRepositoriesController#registrable`).
6
+ *
7
+ * == What it answers, and why `list_repositories` does not
8
+ *
9
+ * `list_repositories` reports what is already registered; this reports what
10
+ * COULD be — the repositories the registration gate would consult, read out
11
+ * loud in advance. It exists so an agent can pick a `full_name` for
12
+ * `add_repository` from a real answer rather than by guessing, and so a
13
+ * `has already been taken` refusal can be explained rather than merely
14
+ * retried.
15
+ *
16
+ * == Two controller decisions the description carries, because both are
17
+ * counter-intuitive and both are stated in the controller's own comments
18
+ *
19
+ * `registered` is asked of `Repository` GLOBALLY, not of what this person can
20
+ * open. The controller says why: a repository somebody ELSE registered still
21
+ * refuses this person's POST with `has already been taken`, so a reading
22
+ * scoped to what they can see would mark it `registered: false` and send them
23
+ * at a name that cannot be registered by anyone. An entry is therefore
24
+ * MARKED, not excluded — `registered: true` is the answer to "why did my POST
25
+ * say has already been taken".
26
+ *
27
+ * And a name appearing here is NOT a promise the write will succeed. This is
28
+ * the set the gate would consult at the moment of the read; the repository may
29
+ * be registered by someone else between the two calls.
30
+ *
31
+ * == The modal first answer is a 403, and that is why `describeFailure` grew
32
+ *
33
+ * `#registrable` fails closed on the two states `GrantVerifier` refuses on and
34
+ * renders `status: :forbidden` — NOT the 400 path the other refusals use. A
35
+ * nil grant is, in the controller's own words, "an ordinary state and not an
36
+ * error: it is every person who has not opened SpecGuard in a browser since
37
+ * this shipped" — so the 403 is the modal first answer this tool gives, and
38
+ * the sentence it carries (sign in, reconnect GitHub, retry) reaches the agent
39
+ * through the 403 branch in `specguard-api.ts`, which this tool is the reason
40
+ * for. On refusal the body still carries `grant`: `null` when there never was
41
+ * one, populated with `stale: true` when it lapsed — "yours lapsed four days
42
+ * ago" is a different fact from "you never had one", and the tool description
43
+ * is where an agent learns to branch on it.
44
+ *
45
+ * == No arguments, for the same reason `list_repositories` has none
46
+ *
47
+ * The credential is the whole of the scope. The endpoint takes no parameters,
48
+ * and nothing an argument could select reaches this answer.
49
+ */
50
+ const registrableRepositories = {
51
+ name: "registrable_repositories",
52
+ title: "Registrable repositories",
53
+ description: "Lists the GitHub repositories the person behind this server's user API key could register " +
54
+ "with SpecGuard — the set the registration gate would consult, read out in advance, so an " +
55
+ "agent can pick a `full_name` for `add_repository` from a real answer rather than by " +
56
+ "guessing. Each entry carries `full_name` and `registered`. `registered` is asked GLOBALLY, " +
57
+ "not just of this person's own repositories: an entry marked `registered: true` was " +
58
+ "registered by SOMEBODY — possibly someone else — and a POST naming it will be refused with " +
59
+ "`has already been taken`, which is exactly the question this flag answers. Entries are " +
60
+ "marked, not excluded, for that reason. The response also carries a `grant` block " +
61
+ "(`captured_at`, `expires_at`, `stale`) describing the stored record of this person's GitHub " +
62
+ "permissions. A MISSING or STALE grant is not an error — it is every person who has not " +
63
+ "opened SpecGuard in a browser recently — and the call then answers 403 with SpecGuard's own " +
64
+ "sentence naming the fix: sign in to SpecGuard in a browser and reconnect GitHub, then try " +
65
+ "again. On that refusal the body's `grant` distinguishes the two cases: `grant: null` means " +
66
+ "there never was one (first-time setup), a populated grant with `stale: true` means an " +
67
+ "existing connection lapsed (same remedy, likely faster to complete) — branch on it before " +
68
+ "telling the person what to do. A name appearing in the list is not a promise the write " +
69
+ "will succeed: someone may register it between this read and the POST. Ordered by " +
70
+ "`full_name` ascending, which is stable across calls. Needs SPECGUARD_USER_API_KEY (an " +
71
+ "sgu_… key), the same credential `list_repositories` and `add_repository` read and a " +
72
+ "DIFFERENT one from the sgk_… repository key `get_repository_overview` uses.",
73
+ inputSchema: {
74
+ type: "object",
75
+ // No properties, deliberately — see this file's header. Still CLOSED rather
76
+ // than merely empty, for the reason `list-repositories.ts` gives inline:
77
+ // `server.ts` forwards `arguments` unvalidated and `run` ignores them, so
78
+ // an open schema would have an invented argument silently dropped and the
79
+ // call answered as if it had been honoured.
80
+ additionalProperties: false,
81
+ },
82
+ async run(_args, context) {
83
+ const api = requireUserApiConfig(context.config);
84
+ const listing = await getJsonObject(api, "/api/v1/repositories/registrable", {}, context.fetch);
85
+ return {
86
+ text: JSON.stringify(listing, null, 2),
87
+ structured: listing,
88
+ };
89
+ },
90
+ };
91
+ export default registrableRepositories;
92
+ //# sourceMappingURL=registrable-repositories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registrable-repositories.js","sourceRoot":"","sources":["../../../src/tools/registrable-repositories.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAGlF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,MAAM,uBAAuB,GAAmB;IAC9C,IAAI,EAAE,0BAA0B;IAChC,KAAK,EAAE,0BAA0B;IACjC,WAAW,EACT,4FAA4F;QAC5F,2FAA2F;QAC3F,sFAAsF;QACtF,6FAA6F;QAC7F,qFAAqF;QACrF,6FAA6F;QAC7F,yFAAyF;QACzF,mFAAmF;QACnF,8FAA8F;QAC9F,yFAAyF;QACzF,8FAA8F;QAC9F,4FAA4F;QAC5F,6FAA6F;QAC7F,wFAAwF;QACxF,4FAA4F;QAC5F,yFAAyF;QACzF,mFAAmF;QACnF,wFAAwF;QACxF,sFAAsF;QACtF,6EAA6E;IAC/E,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,4EAA4E;QAC5E,yEAAyE;QACzE,0EAA0E;QAC1E,0EAA0E;QAC1E,4CAA4C;QAC5C,oBAAoB,EAAE,KAAK;KAC5B;IAED,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO;QACtB,MAAM,GAAG,GAAG,oBAAoB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAEjD,MAAM,OAAO,GAAG,MAAM,aAAa,CACjC,GAAG,EACH,kCAAkC,EAClC,EAAE,EACF,OAAO,CAAC,KAAK,CACd,CAAC;QAEF,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YACtC,UAAU,EAAE,OAAO;SACpB,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { ToolDefinition } from "./types.js";
2
+ /**
3
+ * `DELETE /api/v1/repositories/:id` as a tool — shipped in the platform
4
+ * (`specguard/config/routes.rb:152`, `Api::V1::UserRepositoriesController#destroy`,
5
+ * SPGD-754).
6
+ *
7
+ * == The destructive gesture in the whole surface
8
+ *
9
+ * Everything else here reads, registers, or mints. This one removes a
10
+ * repository AND every key, run and intent on it (`memberships_helper.rb`:
11
+ * "Delete the repository, and every key, run and intent on it"), and there is no
12
+ * undo. The description therefore carries the hazard BEFORE the call: it is
13
+ * prompt material, read when the agent is deciding whether to act, not a
14
+ * footnote discovered after.
15
+ *
16
+ * == Authorization is `repo.delete` at either surface
17
+ *
18
+ * The controller authorizes through `RepositoryAuthorization`'s `:repo_delete`
19
+ * fork — deliberately NOT `:owner` — so a member granted `repo.delete` may
20
+ * remove the repository from either surface. The tool does not probe for the
21
+ * capability client-side: the server is the gate, and a member without it
22
+ * receives a 403 whose sentence arrives verbatim through the landed
23
+ * `refusalMessage` branch in `describeFailure`.
24
+ *
25
+ * == 204 with no body
26
+ *
27
+ * This is the one response in the `sgu_` surface that is deliberately not a
28
+ * JSON body — which is why `deleteJson` exists: routing an empty-body 204
29
+ * through `requestJson`'s JSON parse would turn a successful delete into
30
+ * "the body was not JSON". Here, no body IS the success.
31
+ */
32
+ declare const removeRepository: ToolDefinition;
33
+ export default removeRepository;
@@ -0,0 +1,81 @@
1
+ import { deleteJson, requireUserApiConfig } from "../support/specguard-api.js";
2
+ import { requireString } from "./args.js";
3
+ /**
4
+ * `DELETE /api/v1/repositories/:id` as a tool — shipped in the platform
5
+ * (`specguard/config/routes.rb:152`, `Api::V1::UserRepositoriesController#destroy`,
6
+ * SPGD-754).
7
+ *
8
+ * == The destructive gesture in the whole surface
9
+ *
10
+ * Everything else here reads, registers, or mints. This one removes a
11
+ * repository AND every key, run and intent on it (`memberships_helper.rb`:
12
+ * "Delete the repository, and every key, run and intent on it"), and there is no
13
+ * undo. The description therefore carries the hazard BEFORE the call: it is
14
+ * prompt material, read when the agent is deciding whether to act, not a
15
+ * footnote discovered after.
16
+ *
17
+ * == Authorization is `repo.delete` at either surface
18
+ *
19
+ * The controller authorizes through `RepositoryAuthorization`'s `:repo_delete`
20
+ * fork — deliberately NOT `:owner` — so a member granted `repo.delete` may
21
+ * remove the repository from either surface. The tool does not probe for the
22
+ * capability client-side: the server is the gate, and a member without it
23
+ * receives a 403 whose sentence arrives verbatim through the landed
24
+ * `refusalMessage` branch in `describeFailure`.
25
+ *
26
+ * == 204 with no body
27
+ *
28
+ * This is the one response in the `sgu_` surface that is deliberately not a
29
+ * JSON body — which is why `deleteJson` exists: routing an empty-body 204
30
+ * through `requestJson`'s JSON parse would turn a successful delete into
31
+ * "the body was not JSON". Here, no body IS the success.
32
+ */
33
+ const removeRepository = {
34
+ name: "remove_repository",
35
+ title: "Remove repository",
36
+ description: "Removes a repository from SpecGuard, DELETING every key, run and intent on it. " +
37
+ "This is IRREVERSIBLE: the repository's CI keys stop authenticating and its recorded " +
38
+ "runs and intents are gone, with no undo and no way to recover them short of " +
39
+ "re-registering from scratch. A 204 means it is deleted. " +
40
+ "Authorization is the `repo.delete` capability at either surface — an owner, or a " +
41
+ "member granted it, may remove the repository; a member without it is refused 403 in " +
42
+ "SpecGuard's own words. Confirm with the user before calling: this is the destructive " +
43
+ "gesture in this toolset, and once it returns 204 the repository and its history " +
44
+ "cannot be brought back. " +
45
+ "Takes `repository_id` — the numeric id `add_repository` and `list_repositories` " +
46
+ "report, not the `org/repo` handle. " +
47
+ "Needs SPECGUARD_USER_API_KEY (an sgu_… key), the same credential `add_repository` " +
48
+ "writes with and a DIFFERENT one from the sgk_… repository key " +
49
+ "`get_repository_overview` uses.",
50
+ inputSchema: {
51
+ type: "object",
52
+ properties: {
53
+ repository_id: {
54
+ type: "string",
55
+ description: "The id of the repository to remove — the numeric id `add_repository` returns " +
56
+ "and `list_repositories` reports, not the `org/repo` handle. SpecGuard scopes " +
57
+ "the lookup to repositories you may act on and answers 404 otherwise.",
58
+ },
59
+ },
60
+ required: ["repository_id"],
61
+ // Closed for the reason every tool here states: `server.ts` forwards
62
+ // `arguments` unvalidated, and on the DESTRUCTIVE path a silently-dropped
63
+ // argument is the worst case of the write-path argument — the call still
64
+ // deletes something, just not what the agent believed it named.
65
+ additionalProperties: false,
66
+ },
67
+ async run(args, context) {
68
+ const repositoryId = requireString(args["repository_id"], "repository_id");
69
+ const api = requireUserApiConfig(context.config);
70
+ const body = await deleteJson(api, `/api/v1/repositories/${encodeURIComponent(repositoryId)}`, context.fetch);
71
+ // 204 with no body. Nothing upstream to pass through, so the result says
72
+ // what the verb did — in the tool's own words, because the deployment's
73
+ // whole answer was "no content".
74
+ return {
75
+ text: body === "" ? "Repository removed (204). Every key, run and intent on it is deleted." : body,
76
+ structured: { repository_id: repositoryId, deleted: true },
77
+ };
78
+ },
79
+ };
80
+ export default removeRepository;
81
+ //# sourceMappingURL=remove-repository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remove-repository.js","sourceRoot":"","sources":["../../../src/tools/remove-repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAG1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,gBAAgB,GAAmB;IACvC,IAAI,EAAE,mBAAmB;IACzB,KAAK,EAAE,mBAAmB;IAC1B,WAAW,EACT,iFAAiF;QACjF,sFAAsF;QACtF,8EAA8E;QAC9E,0DAA0D;QAC1D,mFAAmF;QACnF,sFAAsF;QACtF,uFAAuF;QACvF,kFAAkF;QAClF,0BAA0B;QAC1B,kFAAkF;QAClF,qCAAqC;QACrC,oFAAoF;QACpF,gEAAgE;QAChE,iCAAiC;IACnC,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,aAAa,EAAE;gBACb,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,+EAA+E;oBAC/E,+EAA+E;oBAC/E,sEAAsE;aACzE;SACF;QACD,QAAQ,EAAE,CAAC,eAAe,CAAC;QAC3B,qEAAqE;QACrE,0EAA0E;QAC1E,yEAAyE;QACzE,gEAAgE;QAChE,oBAAoB,EAAE,KAAK;KAC5B;IAED,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO;QACrB,MAAM,YAAY,GAAG,aAAa,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC,CAAC;QAE3E,MAAM,GAAG,GAAG,oBAAoB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAEjD,MAAM,IAAI,GAAG,MAAM,UAAU,CAC3B,GAAG,EACH,wBAAwB,kBAAkB,CAAC,YAAY,CAAC,EAAE,EAC1D,OAAO,CAAC,KAAK,CACd,CAAC;QAEF,yEAAyE;QACzE,wEAAwE;QACxE,iCAAiC;QACjC,OAAO;YACL,IAAI,EAAE,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,uEAAuE,CAAC,CAAC,CAAC,IAAI;YAClG,UAAU,EAAE,EAAE,aAAa,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE;SAC3D,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,eAAe,gBAAgB,CAAC"}