@cat-factory/local-server 0.7.2 → 0.7.3

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 (57) hide show
  1. package/dist/LocalContainerRunnerTransport.d.ts +83 -0
  2. package/dist/LocalContainerRunnerTransport.d.ts.map +1 -0
  3. package/dist/LocalContainerRunnerTransport.js +247 -0
  4. package/dist/LocalContainerRunnerTransport.js.map +1 -0
  5. package/dist/config.d.ts +9 -0
  6. package/dist/config.d.ts.map +1 -0
  7. package/dist/config.js +47 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/container.d.ts +4 -0
  10. package/dist/container.d.ts.map +1 -0
  11. package/dist/container.js +94 -0
  12. package/dist/container.js.map +1 -0
  13. package/dist/github.d.ts +36 -0
  14. package/dist/github.d.ts.map +1 -0
  15. package/dist/github.js +174 -0
  16. package/dist/github.js.map +1 -0
  17. package/dist/index.d.ts +10 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +23 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/installations.d.ts +31 -0
  22. package/dist/installations.d.ts.map +1 -0
  23. package/dist/installations.js +76 -0
  24. package/dist/installations.js.map +1 -0
  25. package/dist/link-repo.d.ts +2 -0
  26. package/dist/link-repo.d.ts.map +1 -0
  27. package/dist/link-repo.js +21 -0
  28. package/dist/link-repo.js.map +1 -0
  29. package/dist/linkRepo.d.ts +31 -0
  30. package/dist/linkRepo.d.ts.map +1 -0
  31. package/dist/linkRepo.js +113 -0
  32. package/dist/linkRepo.js.map +1 -0
  33. package/dist/main.d.ts +2 -0
  34. package/dist/main.d.ts.map +1 -0
  35. package/dist/main.js +10 -0
  36. package/dist/main.js.map +1 -0
  37. package/dist/runtimes/appleContainerRuntime.d.ts +21 -0
  38. package/dist/runtimes/appleContainerRuntime.d.ts.map +1 -0
  39. package/dist/runtimes/appleContainerRuntime.js +191 -0
  40. package/dist/runtimes/appleContainerRuntime.js.map +1 -0
  41. package/dist/runtimes/containerRuntime.d.ts +96 -0
  42. package/dist/runtimes/containerRuntime.d.ts.map +1 -0
  43. package/dist/runtimes/containerRuntime.js +99 -0
  44. package/dist/runtimes/containerRuntime.js.map +1 -0
  45. package/dist/runtimes/dockerRuntime.d.ts +27 -0
  46. package/dist/runtimes/dockerRuntime.d.ts.map +1 -0
  47. package/dist/runtimes/dockerRuntime.js +124 -0
  48. package/dist/runtimes/dockerRuntime.js.map +1 -0
  49. package/dist/runtimes/index.d.ts +13 -0
  50. package/dist/runtimes/index.d.ts.map +1 -0
  51. package/dist/runtimes/index.js +33 -0
  52. package/dist/runtimes/index.js.map +1 -0
  53. package/dist/server.d.ts +8 -0
  54. package/dist/server.d.ts.map +1 -0
  55. package/dist/server.js +100 -0
  56. package/dist/server.js.map +1 -0
  57. package/package.json +2 -2
package/dist/github.js ADDED
@@ -0,0 +1,174 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { FetchGitHubClient } from '@cat-factory/server';
3
+ // PAT-backed GitHub access for local mode. The shared FetchGitHubClient normally mints
4
+ // per-installation tokens via the GitHub App registry; here we feed it a static-token
5
+ // source returning the developer's PAT, so the CI gate + merge / mergeability
6
+ // providers (wired in container.ts from this client) hit real GitHub with the PAT.
7
+ //
8
+ // The app-JWT paths (installation discovery / listing) are never reached in local
9
+ // mode — those are the GitHub-App connect flow, which local mode replaces with the
10
+ // linkRepo helper — so they throw rather than pretend to work.
11
+ // The scopes a local-mode PAT needs. Agent containers clone/push branches and open PRs
12
+ // (`repo`, which also covers reading the PR head's Actions check runs for the CI gate and
13
+ // merging the PR), and the coder/ci-fixer may touch `.github/workflows/*` files (`workflow`).
14
+ const LOCAL_PAT_SCOPES = ['repo', 'workflow'];
15
+ /**
16
+ * A GitHub "new personal access token (classic)" URL with the scopes local mode needs
17
+ * pre-selected, so a developer without a PAT can click straight through to create one.
18
+ * Classic tokens are used (not fine-grained) because only the classic form accepts the
19
+ * `scopes` query param for pre-selection.
20
+ */
21
+ export function githubPatCreationUrl() {
22
+ const params = new URLSearchParams({
23
+ description: 'cat-factory local mode',
24
+ scopes: LOCAL_PAT_SCOPES.join(','),
25
+ });
26
+ return `https://github.com/settings/tokens/new?${params.toString()}`;
27
+ }
28
+ /** An {@link AppTokenSource} that returns a fixed PAT for every installation call. */
29
+ export class StaticTokenAppRegistry {
30
+ token;
31
+ defaultAppId = '';
32
+ constructor(token) {
33
+ this.token = token;
34
+ }
35
+ apps() {
36
+ return [{ appId: '' }];
37
+ }
38
+ authForApp() {
39
+ return {
40
+ appJwt: () => Promise.reject(new Error('GitHub App JWT is not available in local (PAT) mode')),
41
+ };
42
+ }
43
+ installationToken() {
44
+ return Promise.resolve(this.token);
45
+ }
46
+ }
47
+ /** Rate-limit accounting is best-effort telemetry the local facade simply drops. */
48
+ class NoopRateLimitRepository {
49
+ record(_snapshot) {
50
+ return Promise.resolve();
51
+ }
52
+ deleteOlderThan(_epochMs) {
53
+ return Promise.resolve(0);
54
+ }
55
+ }
56
+ const localIdGenerator = {
57
+ next: (prefix) => (prefix ? `${prefix}_${randomUUID()}` : randomUUID()),
58
+ };
59
+ const localClock = { now: () => Date.now() };
60
+ const PER_PAGE = 100;
61
+ const MAX_PAGES = 20;
62
+ /** Parse the `rel="next"` URL out of a GitHub `Link` response header, if present. */
63
+ function nextLink(header) {
64
+ if (!header)
65
+ return null;
66
+ for (const part of header.split(',')) {
67
+ const m = part.match(/<([^>]+)>\s*;\s*rel="next"/);
68
+ if (m)
69
+ return m[1] ?? null;
70
+ }
71
+ return null;
72
+ }
73
+ function toRepoProjection(p, installationId, syncedAt) {
74
+ return {
75
+ githubId: p.id,
76
+ installationId,
77
+ owner: p.owner?.login ?? '',
78
+ name: p.name,
79
+ defaultBranch: p.default_branch ?? null,
80
+ private: p.private ?? false,
81
+ blockId: null,
82
+ syncedAt,
83
+ };
84
+ }
85
+ function patHeaders(pat) {
86
+ return {
87
+ authorization: `Bearer ${pat}`,
88
+ accept: 'application/vnd.github+json',
89
+ 'x-github-api-version': '2022-11-28',
90
+ 'user-agent': 'cat-factory',
91
+ };
92
+ }
93
+ /**
94
+ * A {@link FetchGitHubClient} that lists repos a PAT can access via `/user/repos`, the
95
+ * personal-token analogue of the App-only `/installation/repositories` the base client
96
+ * uses (which 403s for a PAT). The board's "Add from existing repo" picker, the
97
+ * link-a-repo flow and the monorepo browser all enumerate repos through
98
+ * `listInstallationRepos`, so overriding this one method makes them work under a PAT.
99
+ * Every other call (repo/branch/PR/issue reads, merges) already works with the PAT via
100
+ * the installation-token paths, so they fall through to the base implementation.
101
+ */
102
+ class PatGitHubClient extends FetchGitHubClient {
103
+ pat;
104
+ apiBase;
105
+ clock;
106
+ constructor(deps, pat, apiBase, clock) {
107
+ super(deps);
108
+ this.pat = pat;
109
+ this.apiBase = apiBase;
110
+ this.clock = clock;
111
+ }
112
+ async listInstallationRepos(installationId) {
113
+ const syncedAt = this.clock.now();
114
+ const items = [];
115
+ let url = `${this.apiBase}/user/repos?per_page=${PER_PAGE}&sort=full_name&affiliation=owner,collaborator,organization_member`;
116
+ for (let page = 0; url && page < MAX_PAGES; page++) {
117
+ const res = await fetch(url, { headers: patHeaders(this.pat) });
118
+ if (!res.ok) {
119
+ const text = await res.text().catch(() => '');
120
+ throw new Error(`GitHub /user/repos failed (HTTP ${res.status}): ${text.slice(0, 200)}`);
121
+ }
122
+ const payload = (await res.json());
123
+ for (const repo of payload)
124
+ items.push(toRepoProjection(repo, installationId, syncedAt));
125
+ url = nextLink(res.headers.get('link'));
126
+ }
127
+ return { items };
128
+ }
129
+ }
130
+ /**
131
+ * Read the PAT's own account (`GET /user`) so a synthetic installation can be attributed
132
+ * to it in the connect UI. Best-effort: a failed/forbidden call falls back to an empty
133
+ * login (the link flow only needs the installation row to exist, not its account label).
134
+ */
135
+ export async function fetchPatAccount(env) {
136
+ const fallback = { accountId: null, accountLogin: '', targetType: 'User' };
137
+ const pat = env.GITHUB_PAT?.trim();
138
+ if (!pat)
139
+ return fallback;
140
+ const apiBase = (env.GITHUB_API_BASE?.trim() || 'https://api.github.com').replace(/\/+$/, '');
141
+ try {
142
+ const res = await fetch(`${apiBase}/user`, { headers: patHeaders(pat) });
143
+ if (!res.ok)
144
+ return fallback;
145
+ const user = (await res.json());
146
+ return {
147
+ accountId: user.id != null ? String(user.id) : null,
148
+ accountLogin: user.login ?? '',
149
+ targetType: user.type === 'Organization' ? 'Organization' : 'User',
150
+ };
151
+ }
152
+ catch {
153
+ return fallback;
154
+ }
155
+ }
156
+ /**
157
+ * Build a {@link GitHubClient} that authenticates with the PAT, for the CI / merge /
158
+ * mergeability gates AND the repo-link / board "add from repo" flows. Returns undefined
159
+ * when no PAT is configured (the gates then pass through, like the Node default).
160
+ */
161
+ export function createLocalGitHubClient(env) {
162
+ const pat = env.GITHUB_PAT?.trim();
163
+ if (!pat)
164
+ return undefined;
165
+ const apiBase = env.GITHUB_API_BASE?.trim() || 'https://api.github.com';
166
+ return new PatGitHubClient({
167
+ registry: new StaticTokenAppRegistry(pat),
168
+ rateLimitRepository: new NoopRateLimitRepository(),
169
+ idGenerator: localIdGenerator,
170
+ clock: localClock,
171
+ apiBase,
172
+ }, pat, apiBase, localClock);
173
+ }
174
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.js","sourceRoot":"","sources":["../src/github.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export { startLocal } from './server.js';
2
+ export { buildLocalContainer } from './container.js';
3
+ export { loadLocalConfig, applyLocalDefaults } from './config.js';
4
+ export { LocalContainerRunnerTransport, createLocalContainerTransportFromEnv, type LocalContainerRunnerTransportOptions, } from './LocalContainerRunnerTransport.js';
5
+ export { type ContainerRuntimeAdapter, type ContainerExec, type RuntimeId, createRuntimeAdapter, resolveRuntimeId, runtimeProfile, resolveHostAlias, DockerRuntimeAdapter, AppleContainerRuntimeAdapter, } from './runtimes/index.js';
6
+ export { linkRepo, type LinkRepoOptions, type LinkedRepo } from './linkRepo.js';
7
+ export { createLocalGitHubClient, StaticTokenAppRegistry } from './github.js';
8
+ export { registerAgentKind, registerAgentKinds, clearRegisteredAgentKinds, type AgentKindDefinition, } from '@cat-factory/agents';
9
+ export { registerPipeline, registerPipelines, clearRegisteredPipelines } from '@cat-factory/kernel';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AACpD,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AACjE,OAAO,EACL,6BAA6B,EAC7B,oCAAoC,EACpC,KAAK,oCAAoC,GAC1C,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,oBAAoB,EACpB,4BAA4B,GAC7B,MAAM,qBAAqB,CAAA;AAI5B,OAAO,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,MAAM,eAAe,CAAA;AAE/E,OAAO,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAI7E,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,yBAAyB,EACzB,KAAK,mBAAmB,GACzB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ // @cat-factory/local-server — the local-mode runtime facade. It is the Node.js
2
+ // facade (@cat-factory/node-server: shared Hono app + Drizzle/Postgres + pg-boss)
3
+ // with two differentiators so a developer can run the whole product on their own
4
+ // machine: agent jobs run as per-run local containers (Docker/Podman/OrbStack/Colima/
5
+ // Apple `container`, selected by LOCAL_CONTAINER_RUNTIME), and GitHub is
6
+ // reached via a personal access token (no GitHub App). `startLocal()` boots the
7
+ // service; `buildLocalContainer()` is the composition root.
8
+ export { startLocal } from './server.js';
9
+ export { buildLocalContainer } from './container.js';
10
+ export { loadLocalConfig, applyLocalDefaults } from './config.js';
11
+ export { LocalContainerRunnerTransport, createLocalContainerTransportFromEnv, } from './LocalContainerRunnerTransport.js';
12
+ export { createRuntimeAdapter, resolveRuntimeId, runtimeProfile, resolveHostAlias, DockerRuntimeAdapter, AppleContainerRuntimeAdapter, } from './runtimes/index.js';
13
+ // Seed the github_installations/github_repos projection so container agent steps can
14
+ // resolve a target repo in local mode (no GitHub App connect flow). Also a CLI:
15
+ // `node dist/link-repo.js <workspaceId> <frameBlockId> <owner/repo>`.
16
+ export { linkRepo } from './linkRepo.js';
17
+ // PAT-backed GitHub access for the CI gate + merge / mergeability providers.
18
+ export { createLocalGitHubClient, StaticTokenAppRegistry } from './github.js';
19
+ // Installation-level extension points, re-exported for parity with the Node facade so
20
+ // a local deployment can register custom agent kinds / pipelines before `startLocal()`.
21
+ export { registerAgentKind, registerAgentKinds, clearRegisteredAgentKinds, } from '@cat-factory/agents';
22
+ export { registerPipeline, registerPipelines, clearRegisteredPipelines } from '@cat-factory/kernel';
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,kFAAkF;AAClF,iFAAiF;AACjF,sFAAsF;AACtF,yEAAyE;AACzE,gFAAgF;AAChF,4DAA4D;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AACpD,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AACjE,OAAO,EACL,6BAA6B,EAC7B,oCAAoC,GAErC,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EAIL,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,oBAAoB,EACpB,4BAA4B,GAC7B,MAAM,qBAAqB,CAAA;AAC5B,qFAAqF;AACrF,gFAAgF;AAChF,sEAAsE;AACtE,OAAO,EAAE,QAAQ,EAAyC,MAAM,eAAe,CAAA;AAC/E,6EAA6E;AAC7E,OAAO,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAE7E,sFAAsF;AACtF,wFAAwF;AACxF,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,yBAAyB,GAE1B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAA"}
@@ -0,0 +1,31 @@
1
+ import type { GitHubInstallation, GitHubInstallationRepository } from '@cat-factory/kernel';
2
+ /** A stable, positive, safe-integer installation id derived from the workspace id. */
3
+ export declare function syntheticInstallationId(workspaceId: string): number;
4
+ /** The PAT account a synthetic installation is attributed to (shown in the connect UI). */
5
+ export interface PatAccount {
6
+ accountId: string | null;
7
+ accountLogin: string;
8
+ targetType: 'Organization' | 'User';
9
+ }
10
+ /**
11
+ * Wraps a real {@link GitHubInstallationRepository} so that, in local PAT mode, a
12
+ * workspace's installation is conjured on first read instead of requiring a connect flow.
13
+ * Every method delegates to the inner repository; only {@link getByWorkspace} adds the
14
+ * lazy provision (and only when no live row exists — a CLI-seeded or already-provisioned
15
+ * row is returned untouched). The provisioned row carries the synthetic id, so a repo
16
+ * later linked via the CLI lands under the same installation.
17
+ */
18
+ export declare class AutoProvisioningInstallationRepository implements GitHubInstallationRepository {
19
+ private readonly inner;
20
+ private readonly resolveAccount;
21
+ private readonly now;
22
+ constructor(inner: GitHubInstallationRepository, resolveAccount: () => Promise<PatAccount>, now?: () => number);
23
+ getByWorkspace(workspaceId: string): Promise<GitHubInstallation | null>;
24
+ getByInstallationId(installationId: number): Promise<GitHubInstallation | null>;
25
+ listWorkspacesForInstallation(installationId: number): Promise<string[]>;
26
+ listActive(): Promise<GitHubInstallation[]>;
27
+ upsert(installation: GitHubInstallation): Promise<void>;
28
+ updateCachedToken(installationId: number, token: string, expiresAt: number): Promise<void>;
29
+ softDelete(installationId: number, at: number): Promise<void>;
30
+ }
31
+ //# sourceMappingURL=installations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installations.d.ts","sourceRoot":"","sources":["../src/installations.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAA;AAa3F,sFAAsF;AACtF,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAMnE;AAED,2FAA2F;AAC3F,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,cAAc,GAAG,MAAM,CAAA;CACpC;AAED;;;;;;;GAOG;AACH,qBAAa,sCAAuC,YAAW,4BAA4B;IAEvF,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,GAAG;IAHtB,YACmB,KAAK,EAAE,4BAA4B,EACnC,cAAc,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,EACzC,GAAG,GAAE,MAAM,MAAyB,EACnD;IAEE,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAkB5E;IAED,mBAAmB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAE9E;IAED,6BAA6B,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAEvE;IAED,UAAU,IAAI,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAE1C;IAED,MAAM,CAAC,YAAY,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtD;IAED,iBAAiB,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEzF;IAED,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5D;CACF"}
@@ -0,0 +1,76 @@
1
+ import { createHash } from 'node:crypto';
2
+ // Local mode has no GitHub-App connect flow: a single developer runs the whole product
3
+ // against one PAT. So instead of binding a real App installation, every workspace is
4
+ // implicitly "connected" to the PAT. This module supplies the two pieces that makes the
5
+ // shared GitHub integration work that way:
6
+ // - `syntheticInstallationId`: a stable per-workspace id (the projection rows the CLI
7
+ // `linkRepo` helper and the in-UI link flow both write key off it, so they agree);
8
+ // - `AutoProvisioningInstallationRepository`: a decorator that lazily materialises that
9
+ // synthetic `github_installations` row the first time a workspace's installation is
10
+ // read, so `getConnection` reports connected and the sync service has an
11
+ // installation id to list/link repos under — no manual connect step.
12
+ /** A stable, positive, safe-integer installation id derived from the workspace id. */
13
+ export function syntheticInstallationId(workspaceId) {
14
+ // 48 bits keeps it well inside Number.MAX_SAFE_INTEGER and the bigint column; the value
15
+ // is per-workspace (the table's workspace_id is unique) so two workspaces never collide,
16
+ // and re-provisioning a workspace reuses the same id (upsert, not a new row).
17
+ const hex = createHash('sha1').update(workspaceId).digest('hex').slice(0, 12);
18
+ return Number.parseInt(hex, 16);
19
+ }
20
+ /**
21
+ * Wraps a real {@link GitHubInstallationRepository} so that, in local PAT mode, a
22
+ * workspace's installation is conjured on first read instead of requiring a connect flow.
23
+ * Every method delegates to the inner repository; only {@link getByWorkspace} adds the
24
+ * lazy provision (and only when no live row exists — a CLI-seeded or already-provisioned
25
+ * row is returned untouched). The provisioned row carries the synthetic id, so a repo
26
+ * later linked via the CLI lands under the same installation.
27
+ */
28
+ export class AutoProvisioningInstallationRepository {
29
+ inner;
30
+ resolveAccount;
31
+ now;
32
+ constructor(inner, resolveAccount, now = () => Date.now()) {
33
+ this.inner = inner;
34
+ this.resolveAccount = resolveAccount;
35
+ this.now = now;
36
+ }
37
+ async getByWorkspace(workspaceId) {
38
+ const existing = await this.inner.getByWorkspace(workspaceId);
39
+ if (existing && !existing.deletedAt)
40
+ return existing;
41
+ const account = await this.resolveAccount();
42
+ const installation = {
43
+ installationId: syntheticInstallationId(workspaceId),
44
+ workspaceId,
45
+ accountId: account.accountId,
46
+ accountLogin: account.accountLogin,
47
+ targetType: account.targetType,
48
+ appId: null,
49
+ cachedToken: null,
50
+ tokenExpiresAt: null,
51
+ createdAt: this.now(),
52
+ deletedAt: null,
53
+ };
54
+ await this.inner.upsert(installation);
55
+ return installation;
56
+ }
57
+ getByInstallationId(installationId) {
58
+ return this.inner.getByInstallationId(installationId);
59
+ }
60
+ listWorkspacesForInstallation(installationId) {
61
+ return this.inner.listWorkspacesForInstallation(installationId);
62
+ }
63
+ listActive() {
64
+ return this.inner.listActive();
65
+ }
66
+ upsert(installation) {
67
+ return this.inner.upsert(installation);
68
+ }
69
+ updateCachedToken(installationId, token, expiresAt) {
70
+ return this.inner.updateCachedToken(installationId, token, expiresAt);
71
+ }
72
+ softDelete(installationId, at) {
73
+ return this.inner.softDelete(installationId, at);
74
+ }
75
+ }
76
+ //# sourceMappingURL=installations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installations.js","sourceRoot":"","sources":["../src/installations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAGxC,uFAAuF;AACvF,qFAAqF;AACrF,wFAAwF;AACxF,2CAA2C;AAC3C,wFAAwF;AACxF,uFAAuF;AACvF,0FAA0F;AAC1F,wFAAwF;AACxF,6EAA6E;AAC7E,yEAAyE;AAEzE,sFAAsF;AACtF,MAAM,UAAU,uBAAuB,CAAC,WAAmB;IACzD,wFAAwF;IACxF,yFAAyF;IACzF,8EAA8E;IAC9E,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;IAC7E,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;AACjC,CAAC;AASD;;;;;;;GAOG;AACH,MAAM,OAAO,sCAAsC;IAE9B,KAAK;IACL,cAAc;IACd,GAAG;IAHtB,YACmB,KAAmC,EACnC,cAAyC,EACzC,GAAG,GAAiB,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;qBAFpC,KAAK;8BACL,cAAc;mBACd,GAAG;IACnB,CAAC;IAEJ,KAAK,CAAC,cAAc,CAAC,WAAmB;QACtC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC,CAAA;QAC7D,IAAI,QAAQ,IAAI,CAAC,QAAQ,CAAC,SAAS;YAAE,OAAO,QAAQ,CAAA;QACpD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,cAAc,EAAE,CAAA;QAC3C,MAAM,YAAY,GAAuB;YACvC,cAAc,EAAE,uBAAuB,CAAC,WAAW,CAAC;YACpD,WAAW;YACX,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,KAAK,EAAE,IAAI;YACX,WAAW,EAAE,IAAI;YACjB,cAAc,EAAE,IAAI;YACpB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,SAAS,EAAE,IAAI;SAChB,CAAA;QACD,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;QACrC,OAAO,YAAY,CAAA;IACrB,CAAC;IAED,mBAAmB,CAAC,cAAsB;QACxC,OAAO,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,cAAc,CAAC,CAAA;IACvD,CAAC;IAED,6BAA6B,CAAC,cAAsB;QAClD,OAAO,IAAI,CAAC,KAAK,CAAC,6BAA6B,CAAC,cAAc,CAAC,CAAA;IACjE,CAAC;IAED,UAAU;QACR,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAA;IAChC,CAAC;IAED,MAAM,CAAC,YAAgC;QACrC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;IACxC,CAAC;IAED,iBAAiB,CAAC,cAAsB,EAAE,KAAa,EAAE,SAAiB;QACxE,OAAO,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,cAAc,EAAE,KAAK,EAAE,SAAS,CAAC,CAAA;IACvE,CAAC;IAED,UAAU,CAAC,cAAsB,EAAE,EAAU;QAC3C,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,cAAc,EAAE,EAAE,CAAC,CAAA;IAClD,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=link-repo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-repo.d.ts","sourceRoot":"","sources":["../src/link-repo.ts"],"names":[],"mappings":""}
@@ -0,0 +1,21 @@
1
+ import { linkRepo } from './linkRepo.js';
2
+ // Small CLI to link a real GitHub repo to a board service frame in local mode:
3
+ // node dist/link-repo.js <workspaceId> <frameBlockId> <owner/repo>
4
+ // Reads GITHUB_PAT + DATABASE_URL from the environment (load a .env via Node's
5
+ // `--env-file-if-exists`). Seeds the github_installations + github_repos rows the
6
+ // container executor resolves a run's target repo from.
7
+ const [workspaceId, frameBlockId, repo] = process.argv.slice(2);
8
+ if (!workspaceId || !frameBlockId || !repo) {
9
+ console.error('usage: link-repo <workspaceId> <frameBlockId> <owner/repo>');
10
+ process.exit(2);
11
+ }
12
+ linkRepo({ workspaceId, frameBlockId, repo })
13
+ .then((r) => {
14
+ console.log(`linked ${r.owner}/${r.name} (#${r.githubId}) → frame ${frameBlockId} ` +
15
+ `[default branch: ${r.defaultBranch}, installation ${r.installationId}]`);
16
+ })
17
+ .catch((err) => {
18
+ console.error('link failed:', err instanceof Error ? err.message : String(err));
19
+ process.exit(1);
20
+ });
21
+ //# sourceMappingURL=link-repo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-repo.js","sourceRoot":"","sources":["../src/link-repo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC,+EAA+E;AAC/E,qEAAqE;AACrE,+EAA+E;AAC/E,kFAAkF;AAClF,wDAAwD;AACxD,MAAM,CAAC,WAAW,EAAE,YAAY,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAE/D,IAAI,CAAC,WAAW,IAAI,CAAC,YAAY,IAAI,CAAC,IAAI,EAAE,CAAC;IAC3C,OAAO,CAAC,KAAK,CAAC,4DAA4D,CAAC,CAAA;IAC3E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC;AAED,QAAQ,CAAC,EAAE,WAAW,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;KAC1C,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;IACV,OAAO,CAAC,GAAG,CACT,UAAU,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,QAAQ,aAAa,YAAY,GAAG;QACrE,oBAAoB,CAAC,CAAC,aAAa,kBAAkB,CAAC,CAAC,cAAc,GAAG,CAC3E,CAAA;AACH,CAAC,CAAC;KACD,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IACtB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;IAC/E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAC,CAAA"}
@@ -0,0 +1,31 @@
1
+ import { type DrizzleDb } from '@cat-factory/node-server';
2
+ export interface LinkRepoOptions {
3
+ /** The board workspace id. */
4
+ workspaceId: string;
5
+ /** The service-frame block id tasks will sit under (the repo links to this). */
6
+ frameBlockId: string;
7
+ /** The repository, as `owner/name`. */
8
+ repo: string;
9
+ /** Pre-built Drizzle client; else one is opened from {@link databaseUrl}. */
10
+ db?: DrizzleDb;
11
+ /** `DATABASE_URL`; required when {@link db} is not supplied. */
12
+ databaseUrl?: string;
13
+ /** The PAT; defaults to `env.GITHUB_PAT`. */
14
+ pat?: string;
15
+ /** GitHub API base; defaults to `env.GITHUB_API_BASE` or api.github.com. */
16
+ apiBase?: string;
17
+ /** Environment source for the PAT / API base defaults. */
18
+ env?: NodeJS.ProcessEnv;
19
+ /** Injectable fetch (tests). */
20
+ fetchImpl?: typeof fetch;
21
+ }
22
+ export interface LinkedRepo {
23
+ owner: string;
24
+ name: string;
25
+ githubId: number;
26
+ installationId: number;
27
+ defaultBranch: string;
28
+ private: boolean;
29
+ }
30
+ export declare function linkRepo(options: LinkRepoOptions): Promise<LinkedRepo>;
31
+ //# sourceMappingURL=linkRepo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linkRepo.d.ts","sourceRoot":"","sources":["../src/linkRepo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAA0B,MAAM,0BAA0B,CAAA;AAkBjF,MAAM,WAAW,eAAe;IAC9B,8BAA8B;IAC9B,WAAW,EAAE,MAAM,CAAA;IACnB,gFAAgF;IAChF,YAAY,EAAE,MAAM,CAAA;IACpB,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,6EAA6E;IAC7E,EAAE,CAAC,EAAE,SAAS,CAAA;IACd,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6CAA6C;IAC7C,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IACvB,gCAAgC;IAChC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;IACtB,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAgH5E"}
@@ -0,0 +1,113 @@
1
+ import { createDbClient, schema } from '@cat-factory/node-server';
2
+ import { and, eq, ne } from 'drizzle-orm';
3
+ import { syntheticInstallationId } from './installations.js';
4
+ // Link a real GitHub repo to a board service frame for LOCAL mode. Container agent
5
+ // steps resolve which repo to operate on from the `github_repos` /
6
+ // `github_installations` projection (`buildResolveRepoTarget`). The cloud facades
7
+ // populate those rows from the GitHub App connect/sync flow; local mode has no App, so
8
+ // this helper seeds them directly from the repo's public metadata read with the PAT:
9
+ // - a synthetic per-workspace `github_installations` row (the executor's PAT token
10
+ // source ignores the installation id, but `resolveRepoTarget` requires a row to
11
+ // exist + reads its id back), and
12
+ // - a `github_repos` row linked to the frame via `block_id` (the legacy projection
13
+ // link `resolveRepoTarget` walks the block ancestry to find).
14
+ // Idempotent: re-linking the same repo/frame updates the rows in place.
15
+ const GITHUB_API_BASE = 'https://api.github.com';
16
+ export async function linkRepo(options) {
17
+ const env = options.env ?? process.env;
18
+ const pat = options.pat ?? env.GITHUB_PAT?.trim();
19
+ if (!pat)
20
+ throw new Error('A GitHub PAT is required (set GITHUB_PAT or pass options.pat)');
21
+ const apiBase = (options.apiBase ?? env.GITHUB_API_BASE?.trim() ?? GITHUB_API_BASE).replace(/\/+$/, '');
22
+ const slash = options.repo.indexOf('/');
23
+ if (slash <= 0 || slash === options.repo.length - 1) {
24
+ throw new Error(`Invalid repo '${options.repo}' — expected 'owner/name'`);
25
+ }
26
+ const owner = options.repo.slice(0, slash);
27
+ const name = options.repo.slice(slash + 1);
28
+ const fetchImpl = options.fetchImpl ?? fetch;
29
+ const res = await fetchImpl(`${apiBase}/repos/${owner}/${name}`, {
30
+ headers: {
31
+ authorization: `Bearer ${pat}`,
32
+ accept: 'application/vnd.github+json',
33
+ 'user-agent': 'cat-factory',
34
+ },
35
+ });
36
+ if (!res.ok) {
37
+ const text = await res.text().catch(() => '');
38
+ throw new Error(`GitHub repo lookup failed (HTTP ${res.status}): ${text.slice(0, 200)}`);
39
+ }
40
+ const meta = (await res.json());
41
+ const installationId = syntheticInstallationId(options.workspaceId);
42
+ const defaultBranch = meta.default_branch ?? 'main';
43
+ const now = Date.now();
44
+ let db = options.db;
45
+ let close;
46
+ if (!db) {
47
+ const url = options.databaseUrl ?? env.DATABASE_URL;
48
+ if (!url)
49
+ throw new Error('DATABASE_URL is required to link a repo (or pass options.db)');
50
+ const client = createDbClient(url);
51
+ db = client.db;
52
+ close = () => client.pool.end();
53
+ }
54
+ try {
55
+ const installationValues = {
56
+ installation_id: installationId,
57
+ workspace_id: options.workspaceId,
58
+ account_id: meta.owner?.id != null ? String(meta.owner.id) : null,
59
+ account_login: meta.owner?.login ?? owner,
60
+ target_type: meta.owner?.type === 'Organization' ? 'Organization' : 'User',
61
+ app_id: null,
62
+ cached_token: null,
63
+ token_expires_at: null,
64
+ created_at: now,
65
+ deleted_at: null,
66
+ };
67
+ // `github_installations` has a partial UNIQUE index on (workspace_id) WHERE
68
+ // deleted_at IS NULL, so a pre-existing live row for this workspace under a DIFFERENT
69
+ // id (e.g. from a real GitHub-App connect) would collide with our synthetic-id
70
+ // insert — and the upsert below keys on installation_id, not workspace_id, so it
71
+ // wouldn't catch it. Clear any such row first so re-linking is robust.
72
+ await db
73
+ .delete(schema.githubInstallations)
74
+ .where(and(eq(schema.githubInstallations.workspace_id, options.workspaceId), ne(schema.githubInstallations.installation_id, installationId)));
75
+ await db.insert(schema.githubInstallations).values(installationValues).onConflictDoUpdate({
76
+ target: schema.githubInstallations.installation_id,
77
+ set: installationValues,
78
+ });
79
+ const repoValues = {
80
+ workspace_id: options.workspaceId,
81
+ github_id: meta.id,
82
+ installation_id: installationId,
83
+ owner,
84
+ name,
85
+ default_branch: defaultBranch,
86
+ private: meta.private ? 1 : 0,
87
+ block_id: options.frameBlockId,
88
+ is_monorepo: 0,
89
+ etag: null,
90
+ synced_at: now,
91
+ deleted_at: null,
92
+ };
93
+ await db
94
+ .insert(schema.githubRepos)
95
+ .values(repoValues)
96
+ .onConflictDoUpdate({
97
+ target: [schema.githubRepos.workspace_id, schema.githubRepos.github_id],
98
+ set: repoValues,
99
+ });
100
+ }
101
+ finally {
102
+ await close?.();
103
+ }
104
+ return {
105
+ owner,
106
+ name,
107
+ githubId: meta.id,
108
+ installationId,
109
+ defaultBranch,
110
+ private: Boolean(meta.private),
111
+ };
112
+ }
113
+ //# sourceMappingURL=linkRepo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linkRepo.js","sourceRoot":"","sources":["../src/linkRepo.ts"],"names":[],"mappings":"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"}
package/dist/main.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":""}
package/dist/main.js ADDED
@@ -0,0 +1,10 @@
1
+ import { logger } from '@cat-factory/server';
2
+ import { startLocal } from './server.js';
3
+ // Default entrypoint: `pnpm build` then `node dist/main.js`. Requires DATABASE_URL
4
+ // (the local Postgres) and LOCAL_HARNESS_IMAGE (the executor-harness image run per
5
+ // job). Set PORT to override the listen port (PUBLIC_URL defaults from it).
6
+ startLocal().catch((err) => {
7
+ logger.error({ err: err instanceof Error ? err.message : String(err) }, 'failed to start');
8
+ process.exit(1);
9
+ });
10
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC,mFAAmF;AACnF,mFAAmF;AACnF,4EAA4E;AAC5E,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAClC,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,iBAAiB,CAAC,CAAA;IAC1F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAC,CAAA"}
@@ -0,0 +1,21 @@
1
+ import { type ContainerEndpoint, type ContainerExec, type ContainerRuntimeAdapter, type RunContainerSpec } from './containerRuntime.js';
2
+ export declare class AppleContainerRuntimeAdapter implements ContainerRuntimeAdapter {
3
+ readonly id: 'apple';
4
+ readonly binary: string;
5
+ readonly hostAlias: string;
6
+ readonly capabilities: {
7
+ localDind: boolean;
8
+ };
9
+ constructor(options: {
10
+ binary?: string;
11
+ hostAlias: string;
12
+ });
13
+ run(exec: ContainerExec, spec: RunContainerSpec): Promise<string>;
14
+ find(exec: ContainerExec, runId: string): Promise<string | undefined>;
15
+ endpoint(exec: ContainerExec, containerId: string): Promise<ContainerEndpoint | undefined>;
16
+ isRunning(exec: ContainerExec, containerId: string): Promise<boolean>;
17
+ remove(exec: ContainerExec, containerId: string): Promise<void>;
18
+ removeRun(exec: ContainerExec, runId: string): Promise<void>;
19
+ reapExited(exec: ContainerExec): Promise<number>;
20
+ }
21
+ //# sourceMappingURL=appleContainerRuntime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appleContainerRuntime.d.ts","sourceRoot":"","sources":["../../src/runtimes/appleContainerRuntime.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,uBAAuB,EAE5B,KAAK,gBAAgB,EACtB,MAAM,uBAAuB,CAAA;AA0H9B,qBAAa,4BAA6B,YAAW,uBAAuB;IAC1E,QAAQ,CAAC,EAAE,EAAG,OAAO,CAAS;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,YAAY;QAAK,SAAS;MAAS;IAE5C,YAAY,OAAO,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAG1D;IAEK,GAAG,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAqBtE;IAEK,IAAI,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAQ1E;IAEK,QAAQ,CAAC,IAAI,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAI/F;IAEK,SAAS,CAAC,IAAI,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAM1E;IAEK,MAAM,CAAC,IAAI,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEpE;IAEK,SAAS,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEjE;IAEK,UAAU,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAYrD;CACF"}