@extenshi/mcp 0.20.0 → 0.21.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 +2 -2
- package/dist/build-map-store.d.ts +123 -0
- package/dist/build-map-store.d.ts.map +1 -0
- package/dist/build-map-store.js +347 -0
- package/dist/build-map-store.js.map +1 -0
- package/dist/build-map-summary.d.ts +29 -0
- package/dist/build-map-summary.d.ts.map +1 -0
- package/dist/build-map-summary.js +30 -0
- package/dist/build-map-summary.js.map +1 -0
- package/dist/development-guide.d.ts +18 -1
- package/dist/development-guide.d.ts.map +1 -1
- package/dist/development-guide.js +26 -0
- package/dist/development-guide.js.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/tools.d.ts +10 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +64 -20
- package/dist/tools.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -102,10 +102,10 @@ stdio or CLI. See the [development workflow](https://docs.extenshi.io/developers
|
|
|
102
102
|
| `record_project_evidence` | Store metadata bound to the exact artifact, input hash, browser and source revision | Requires evidence.write for OAuth |
|
|
103
103
|
| `get_release_readiness` | Explain current, stale and missing release checks by browser and locale. With a linked Pay application, the payment check is derived from Extenshi's payment ledger (source `platform`); a live-mode purchase that reached an installation passes it | Pro project; project read access |
|
|
104
104
|
| `list_my_projects` | Your projects, repository bindings and claimed listings | Free; identity required |
|
|
105
|
-
| `create_project` | Create a project through the Dojo wizard's service; idempotent per key; returns id, next steps and open owner decisions | Requires project.write for OAuth |
|
|
105
|
+
| `create_project` | Create a project through the Dojo wizard's service; idempotent per key; returns id, next steps and open owner decisions; the local server adds `buildMap` | Requires project.write for OAuth |
|
|
106
106
|
| `get_decisions` | Owner decisions (AMO add-on id, license, seller identity, pricing, targets): status, suggested default, what each blocks | Free; identity required |
|
|
107
107
|
| `propose_decision` | Propose a decision value with rationale; the owner decides in Dojo, and decided keys are refused | Requires project.write for OAuth |
|
|
108
|
-
| `get_project_state` | Manifest summaries (imported or generated, digest, keys that differ from the imported files), selected types, saved-state index, hosted URLs and exact integration file; `manifests: "full"` returns the manifest bodies | Free; identity required |
|
|
108
|
+
| `get_project_state` | Manifest summaries (imported or generated, digest, keys that differ from the imported files), selected types, saved-state index, hosted URLs and exact integration file; `manifests: "full"` returns the manifest bodies; the local server adds `buildMap` (whether the build map is open, its address, and whether the author declined it) | Free; identity required |
|
|
109
109
|
| `get_project_scaffold` | Starter files for one project and target browser | Free; identity required |
|
|
110
110
|
| `list_privacy_policy_versions` | Hosted policy version history | Pro project; no read credit |
|
|
111
111
|
| `get_privacy_policy_version` | One hosted policy's markdown and HTML | Pro project; no read credit |
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the build map lives and how every client touches it (plan
|
|
3
|
+
* 2026-11-03-extension-build-map §6.1).
|
|
4
|
+
*
|
|
5
|
+
* ONE MAP PER PROJECT, whoever asks. Agents often work on one extension in
|
|
6
|
+
* parallel, each in its own git worktree, and `.extenshi/` is usually ignored by
|
|
7
|
+
* git, so a per-checkout file would give every agent its own map and the author
|
|
8
|
+
* one browser tab per agent. The map therefore lives in the MAIN checkout: from
|
|
9
|
+
* a linked worktree, `resolveMapHome` maps the directory to the same path in the
|
|
10
|
+
* main working tree. Every agent, from any worktree, reads and writes the same
|
|
11
|
+
* `.extenshi/build-map.json` and finds the same server.
|
|
12
|
+
*
|
|
13
|
+
* Files under `<home>/.extenshi/`:
|
|
14
|
+
* build-map.json the state (BuildMapState); written under build-map.lock
|
|
15
|
+
* build-map.lock/ held for one read-modify-write; stale after a crash is taken over
|
|
16
|
+
* build-map.server.json the running server: pid, port, instance id, shutdown token (0600);
|
|
17
|
+
* a stopped server leaves it with pid 0 so the next start reuses the port
|
|
18
|
+
* build-map.server.lock/ held while a server starts, so two starts cannot both listen
|
|
19
|
+
* build-map.log output of the background server
|
|
20
|
+
*
|
|
21
|
+
* Writes go to a temporary file in the same directory and are renamed over the
|
|
22
|
+
* target, so a reader never sees half a file.
|
|
23
|
+
*
|
|
24
|
+
* The stdio MCP server (`@extenshi/mcp`) is the map's other client and ships a
|
|
25
|
+
* byte-identical copy at tools/extenshi-mcp/src/build-map-store.ts, so both find
|
|
26
|
+
* the same home and the same server; build-map-store-sync.test.ts there fails on
|
|
27
|
+
* any drift. Edit this file, then copy it over.
|
|
28
|
+
*/
|
|
29
|
+
import { type BuildMapState } from '@extenshi/contracts';
|
|
30
|
+
export declare const MAP_DIR = ".extenshi";
|
|
31
|
+
export declare const MAP_STATE_FILE = "build-map.json";
|
|
32
|
+
export declare const MAP_SERVER_FILE = "build-map.server.json";
|
|
33
|
+
export declare const MAP_LOG_FILE = "build-map.log";
|
|
34
|
+
/** Bumped when the page/server protocol changes: a newer CLI replaces an older server instead of reusing it. */
|
|
35
|
+
export declare const MAP_PROTOCOL = 1;
|
|
36
|
+
export type MapHome = {
|
|
37
|
+
/** Where the map's files live: this project directory in the main checkout. */
|
|
38
|
+
home: string;
|
|
39
|
+
/** The directory the command ran in. */
|
|
40
|
+
dir: string;
|
|
41
|
+
/** `dir` is inside a linked git worktree, and the map lives in the main checkout. */
|
|
42
|
+
worktree: boolean;
|
|
43
|
+
};
|
|
44
|
+
export declare const mapPath: (home: string, file: string) => string;
|
|
45
|
+
/**
|
|
46
|
+
* The map home for `dir`: `dir` itself, unless it sits in a linked git worktree
|
|
47
|
+
* whose main working tree has the same directory — then that one. Outside git,
|
|
48
|
+
* in a bare-repository worktree or a submodule, the directory stays its own home.
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveMapHome(dir: string): Promise<MapHome>;
|
|
51
|
+
/** Every checkout of the repository `home` belongs to (main first), mapped to the project directory in each. */
|
|
52
|
+
export declare function projectCheckouts(home: string): Promise<string[]>;
|
|
53
|
+
export declare function pidAlive(pid: number): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Run `work` holding a directory lock. `mkdir` is atomic, so exactly one
|
|
56
|
+
* process creates the directory; its `owner.json` carries a random token. A
|
|
57
|
+
* lock whose owner died on this machine, or that is older than `staleMs`, is
|
|
58
|
+
* taken over — but the verdict is checked again under the breaker
|
|
59
|
+
* (`withBreaker`) before anything is removed, and a release removes the lock
|
|
60
|
+
* only while it still carries this holder's token.
|
|
61
|
+
*/
|
|
62
|
+
export declare function withDirLock<T>(lockDir: string, work: () => Promise<T>, options?: {
|
|
63
|
+
timeoutMs?: number;
|
|
64
|
+
staleMs?: number;
|
|
65
|
+
what?: string;
|
|
66
|
+
}): Promise<T>;
|
|
67
|
+
/** Write JSON so a concurrent reader sees the old file or the new one, never a mix. */
|
|
68
|
+
export declare function writeJsonAtomic(file: string, value: unknown, mode?: number): Promise<void>;
|
|
69
|
+
export type StateRead = {
|
|
70
|
+
state: BuildMapState;
|
|
71
|
+
exists: boolean;
|
|
72
|
+
problem?: string;
|
|
73
|
+
};
|
|
74
|
+
/** Read the state for display: a missing or broken file reads as empty, with the problem named. */
|
|
75
|
+
export declare function readMapState(home: string): Promise<StateRead>;
|
|
76
|
+
/**
|
|
77
|
+
* Read, change and write the state as one step, under the state lock, so two
|
|
78
|
+
* agents reporting at the same moment both land. Refuses to write over a file
|
|
79
|
+
* it cannot read rather than replace someone's edit with an empty map.
|
|
80
|
+
*/
|
|
81
|
+
export declare function updateMapState(home: string, change: (state: BuildMapState) => BuildMapState | undefined): Promise<BuildMapState>;
|
|
82
|
+
export type MapServerRecord = {
|
|
83
|
+
pid: number;
|
|
84
|
+
host: string;
|
|
85
|
+
port: number;
|
|
86
|
+
url: string;
|
|
87
|
+
/** Random per start: tells this server from another that later got the same pid or port. */
|
|
88
|
+
instance: string;
|
|
89
|
+
/** Authorizes /api/shutdown; the file is readable by the owner only. */
|
|
90
|
+
token: string;
|
|
91
|
+
protocol: number;
|
|
92
|
+
version: string;
|
|
93
|
+
home: string;
|
|
94
|
+
startedAt: string;
|
|
95
|
+
};
|
|
96
|
+
export type MapHealth = {
|
|
97
|
+
instance: string;
|
|
98
|
+
home: string;
|
|
99
|
+
protocol: number;
|
|
100
|
+
version: string;
|
|
101
|
+
clients: number;
|
|
102
|
+
};
|
|
103
|
+
export declare function readServerRecord(home: string): Promise<MapServerRecord | undefined>;
|
|
104
|
+
export declare function fetchHealth(port: number, timeoutMs?: number): Promise<MapHealth | undefined>;
|
|
105
|
+
export type RunningServer = MapServerRecord & {
|
|
106
|
+
clients: number;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* The server recorded for `home`, if it is really up: same machine, process
|
|
110
|
+
* alive, and its health answer carries the recorded instance id and home. A
|
|
111
|
+
* record left by a crash, by another machine (a synced folder) or a reused pid
|
|
112
|
+
* reads as no server.
|
|
113
|
+
*/
|
|
114
|
+
export declare function probeServer(home: string): Promise<RunningServer | undefined>;
|
|
115
|
+
export declare const serverLockPath: (home: string) => string;
|
|
116
|
+
/**
|
|
117
|
+
* Mark the server record stopped, but only if it still names `instance` —
|
|
118
|
+
* never a successor's. The port stays in it, so the next start binds the same
|
|
119
|
+
* address and a tab left open reconnects by itself; pid 0 and no token make it
|
|
120
|
+
* read as no server.
|
|
121
|
+
*/
|
|
122
|
+
export declare function retireServerRecord(home: string, instance: string): Promise<void>;
|
|
123
|
+
//# sourceMappingURL=build-map-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-map-store.d.ts","sourceRoot":"","sources":["../src/build-map-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the build map lives and how every client touches it (plan
|
|
3
|
+
* 2026-11-03-extension-build-map §6.1).
|
|
4
|
+
*
|
|
5
|
+
* ONE MAP PER PROJECT, whoever asks. Agents often work on one extension in
|
|
6
|
+
* parallel, each in its own git worktree, and `.extenshi/` is usually ignored by
|
|
7
|
+
* git, so a per-checkout file would give every agent its own map and the author
|
|
8
|
+
* one browser tab per agent. The map therefore lives in the MAIN checkout: from
|
|
9
|
+
* a linked worktree, `resolveMapHome` maps the directory to the same path in the
|
|
10
|
+
* main working tree. Every agent, from any worktree, reads and writes the same
|
|
11
|
+
* `.extenshi/build-map.json` and finds the same server.
|
|
12
|
+
*
|
|
13
|
+
* Files under `<home>/.extenshi/`:
|
|
14
|
+
* build-map.json the state (BuildMapState); written under build-map.lock
|
|
15
|
+
* build-map.lock/ held for one read-modify-write; stale after a crash is taken over
|
|
16
|
+
* build-map.server.json the running server: pid, port, instance id, shutdown token (0600);
|
|
17
|
+
* a stopped server leaves it with pid 0 so the next start reuses the port
|
|
18
|
+
* build-map.server.lock/ held while a server starts, so two starts cannot both listen
|
|
19
|
+
* build-map.log output of the background server
|
|
20
|
+
*
|
|
21
|
+
* Writes go to a temporary file in the same directory and are renamed over the
|
|
22
|
+
* target, so a reader never sees half a file.
|
|
23
|
+
*
|
|
24
|
+
* The stdio MCP server (`@extenshi/mcp`) is the map's other client and ships a
|
|
25
|
+
* byte-identical copy at tools/extenshi-mcp/src/build-map-store.ts, so both find
|
|
26
|
+
* the same home and the same server; build-map-store-sync.test.ts there fails on
|
|
27
|
+
* any drift. Edit this file, then copy it over.
|
|
28
|
+
*/
|
|
29
|
+
import { execFile } from 'node:child_process';
|
|
30
|
+
import { randomUUID } from 'node:crypto';
|
|
31
|
+
import { mkdir, open, readFile, realpath, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
32
|
+
import os from 'node:os';
|
|
33
|
+
import path from 'node:path';
|
|
34
|
+
import { emptyBuildMapState, normalizeBuildMapState } from '@extenshi/contracts';
|
|
35
|
+
export const MAP_DIR = '.extenshi';
|
|
36
|
+
export const MAP_STATE_FILE = 'build-map.json';
|
|
37
|
+
export const MAP_SERVER_FILE = 'build-map.server.json';
|
|
38
|
+
export const MAP_LOG_FILE = 'build-map.log';
|
|
39
|
+
const STATE_LOCK = 'build-map.lock';
|
|
40
|
+
const SERVER_LOCK = 'build-map.server.lock';
|
|
41
|
+
/** Bumped when the page/server protocol changes: a newer CLI replaces an older server instead of reusing it. */
|
|
42
|
+
export const MAP_PROTOCOL = 1;
|
|
43
|
+
export const mapPath = (home, file) => path.join(home, MAP_DIR, file);
|
|
44
|
+
function git(cwd, args) {
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
execFile('git', args, { cwd, timeout: 5_000, windowsHide: true }, (error, stdout) => resolve(error ? undefined : stdout));
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async function isDirectory(location) {
|
|
50
|
+
try {
|
|
51
|
+
return (await stat(location)).isDirectory();
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The map home for `dir`: `dir` itself, unless it sits in a linked git worktree
|
|
59
|
+
* whose main working tree has the same directory — then that one. Outside git,
|
|
60
|
+
* in a bare-repository worktree or a submodule, the directory stays its own home.
|
|
61
|
+
*/
|
|
62
|
+
export async function resolveMapHome(dir) {
|
|
63
|
+
const real = await realpath(path.resolve(dir));
|
|
64
|
+
const own = { home: real, dir: real, worktree: false };
|
|
65
|
+
const out = await git(real, ['rev-parse', '--path-format=absolute', '--show-toplevel', '--git-common-dir']);
|
|
66
|
+
const [top, common] = (out ?? '').trim().split('\n');
|
|
67
|
+
if (!top || !common || path.basename(common) !== '.git')
|
|
68
|
+
return own;
|
|
69
|
+
let mainRoot;
|
|
70
|
+
let topReal;
|
|
71
|
+
try {
|
|
72
|
+
mainRoot = await realpath(path.dirname(common));
|
|
73
|
+
topReal = await realpath(top);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return own;
|
|
77
|
+
}
|
|
78
|
+
if (mainRoot === topReal)
|
|
79
|
+
return own;
|
|
80
|
+
const candidate = path.join(mainRoot, path.relative(topReal, real));
|
|
81
|
+
if (!(await isDirectory(candidate)))
|
|
82
|
+
return own;
|
|
83
|
+
return { home: candidate, dir: real, worktree: true };
|
|
84
|
+
}
|
|
85
|
+
/** Every checkout of the repository `home` belongs to (main first), mapped to the project directory in each. */
|
|
86
|
+
export async function projectCheckouts(home) {
|
|
87
|
+
const out = await git(home, ['worktree', 'list', '--porcelain']);
|
|
88
|
+
const top = (await git(home, ['rev-parse', '--show-toplevel']))?.trim();
|
|
89
|
+
if (!out || !top)
|
|
90
|
+
return [home];
|
|
91
|
+
const relative = path.relative(top, home);
|
|
92
|
+
const roots = out
|
|
93
|
+
.split('\n')
|
|
94
|
+
.filter((line) => line.startsWith('worktree '))
|
|
95
|
+
.map((line) => line.slice('worktree '.length));
|
|
96
|
+
const dirs = [home];
|
|
97
|
+
for (const root of roots) {
|
|
98
|
+
const candidate = path.join(root, relative);
|
|
99
|
+
if (!dirs.includes(candidate) && (await isDirectory(candidate)))
|
|
100
|
+
dirs.push(candidate);
|
|
101
|
+
}
|
|
102
|
+
return dirs;
|
|
103
|
+
}
|
|
104
|
+
export function pidAlive(pid) {
|
|
105
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
106
|
+
return false;
|
|
107
|
+
try {
|
|
108
|
+
process.kill(pid, 0);
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
return error.code === 'EPERM';
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
116
|
+
async function readOwner(lockDir) {
|
|
117
|
+
try {
|
|
118
|
+
return JSON.parse(await readFile(path.join(lockDir, 'owner.json'), 'utf8'));
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return undefined; // created a moment ago and not written yet, or a crash in between
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Whether the lock as it is now may be taken over: its owner died on this
|
|
126
|
+
* machine, or it is older than `staleMs` (the critical sections take
|
|
127
|
+
* milliseconds). `undefined` when the lock is gone.
|
|
128
|
+
*/
|
|
129
|
+
async function lockIsStale(lockDir, staleMs) {
|
|
130
|
+
const owner = await readOwner(lockDir);
|
|
131
|
+
let age;
|
|
132
|
+
try {
|
|
133
|
+
age = Date.now() - (owner?.at ?? (await stat(lockDir)).mtimeMs);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
const dead = owner !== undefined && owner.host === os.hostname() && !pidAlive(owner.pid);
|
|
139
|
+
return dead || age > staleMs;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Hold `<lockDir>.breaker` while removing `lockDir`. Every removal — a release
|
|
143
|
+
* and a takeover alike — happens under it and re-checks the lock it is about to
|
|
144
|
+
* remove, so a waiter that judged a dead owner's lock stale a moment ago cannot
|
|
145
|
+
* delete the fresh lock another waiter has taken since. The breaker is held for
|
|
146
|
+
* a few file operations; one older than 10 s belongs to a process that crashed
|
|
147
|
+
* inside them.
|
|
148
|
+
*/
|
|
149
|
+
async function withBreaker(lockDir, deadline, remove) {
|
|
150
|
+
const breaker = `${lockDir}.breaker`;
|
|
151
|
+
for (;;) {
|
|
152
|
+
try {
|
|
153
|
+
await mkdir(breaker);
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (error.code !== 'EEXIST')
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
if (Date.now() - (await stat(breaker)).mtimeMs > 10_000)
|
|
162
|
+
await rm(breaker, { recursive: true, force: true });
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// released while we looked
|
|
166
|
+
}
|
|
167
|
+
if (Date.now() > deadline)
|
|
168
|
+
throw new Error(`${path.basename(breaker)} is held; remove it if no extenshi command runs.`);
|
|
169
|
+
await sleep(2 + Math.floor(Math.random() * 8));
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
await remove();
|
|
173
|
+
}
|
|
174
|
+
finally {
|
|
175
|
+
await rm(breaker, { recursive: true, force: true });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/** Move the lock directory aside, then delete it: after the rename, `mkdir(lockDir)` is free again. */
|
|
179
|
+
async function removeLockDir(lockDir) {
|
|
180
|
+
const aside = `${lockDir}.gone-${randomUUID()}`;
|
|
181
|
+
try {
|
|
182
|
+
await rename(lockDir, aside);
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return; // already gone
|
|
186
|
+
}
|
|
187
|
+
await rm(aside, { recursive: true, force: true });
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Run `work` holding a directory lock. `mkdir` is atomic, so exactly one
|
|
191
|
+
* process creates the directory; its `owner.json` carries a random token. A
|
|
192
|
+
* lock whose owner died on this machine, or that is older than `staleMs`, is
|
|
193
|
+
* taken over — but the verdict is checked again under the breaker
|
|
194
|
+
* (`withBreaker`) before anything is removed, and a release removes the lock
|
|
195
|
+
* only while it still carries this holder's token.
|
|
196
|
+
*/
|
|
197
|
+
export async function withDirLock(lockDir, work, options = {}) {
|
|
198
|
+
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
199
|
+
const staleMs = options.staleMs ?? 30_000;
|
|
200
|
+
const deadline = Date.now() + timeoutMs;
|
|
201
|
+
await mkdir(path.dirname(lockDir), { recursive: true });
|
|
202
|
+
for (;;) {
|
|
203
|
+
try {
|
|
204
|
+
await mkdir(lockDir);
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
if (error.code !== 'EEXIST')
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
const stale = await lockIsStale(lockDir, staleMs);
|
|
212
|
+
if (stale === undefined)
|
|
213
|
+
continue; // released while we looked
|
|
214
|
+
if (stale) {
|
|
215
|
+
await withBreaker(lockDir, deadline, async () => {
|
|
216
|
+
if (await lockIsStale(lockDir, staleMs))
|
|
217
|
+
await removeLockDir(lockDir);
|
|
218
|
+
});
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (Date.now() > deadline)
|
|
222
|
+
throw new Error(`${options.what ?? path.basename(lockDir)} is held by process ${(await readOwner(lockDir))?.pid ?? 'unknown'}. ` +
|
|
223
|
+
'If no extenshi command is running, remove the lock directory.');
|
|
224
|
+
await sleep(20 + Math.floor(Math.random() * 40));
|
|
225
|
+
}
|
|
226
|
+
const token = randomUUID();
|
|
227
|
+
try {
|
|
228
|
+
const owner = { pid: process.pid, host: os.hostname(), at: Date.now(), token };
|
|
229
|
+
await writeFile(path.join(lockDir, 'owner.json'), JSON.stringify(owner));
|
|
230
|
+
return await work();
|
|
231
|
+
}
|
|
232
|
+
finally {
|
|
233
|
+
await withBreaker(lockDir, Date.now() + 15_000, async () => {
|
|
234
|
+
if ((await readOwner(lockDir))?.token === token)
|
|
235
|
+
await removeLockDir(lockDir);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/** Write JSON so a concurrent reader sees the old file or the new one, never a mix. */
|
|
240
|
+
export async function writeJsonAtomic(file, value, mode = 0o644) {
|
|
241
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
242
|
+
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
243
|
+
try {
|
|
244
|
+
const handle = await open(temporary, 'wx', mode);
|
|
245
|
+
try {
|
|
246
|
+
await handle.writeFile(`${JSON.stringify(value, null, '\t')}\n`);
|
|
247
|
+
await handle.sync();
|
|
248
|
+
}
|
|
249
|
+
finally {
|
|
250
|
+
await handle.close();
|
|
251
|
+
}
|
|
252
|
+
await rename(temporary, file);
|
|
253
|
+
}
|
|
254
|
+
finally {
|
|
255
|
+
await rm(temporary, { force: true });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/** Read the state for display: a missing or broken file reads as empty, with the problem named. */
|
|
259
|
+
export async function readMapState(home) {
|
|
260
|
+
let text;
|
|
261
|
+
try {
|
|
262
|
+
text = await readFile(mapPath(home, MAP_STATE_FILE), 'utf8');
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
if (error.code === 'ENOENT')
|
|
266
|
+
return { state: emptyBuildMapState(), exists: false };
|
|
267
|
+
return { state: emptyBuildMapState(), exists: true, problem: error.message };
|
|
268
|
+
}
|
|
269
|
+
try {
|
|
270
|
+
return { state: normalizeBuildMapState(JSON.parse(text)), exists: true };
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
return {
|
|
274
|
+
state: emptyBuildMapState(),
|
|
275
|
+
exists: true,
|
|
276
|
+
problem: `${MAP_STATE_FILE}: ${error.message}`,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Read, change and write the state as one step, under the state lock, so two
|
|
282
|
+
* agents reporting at the same moment both land. Refuses to write over a file
|
|
283
|
+
* it cannot read rather than replace someone's edit with an empty map.
|
|
284
|
+
*/
|
|
285
|
+
export async function updateMapState(home, change) {
|
|
286
|
+
return withDirLock(mapPath(home, STATE_LOCK), async () => {
|
|
287
|
+
const read = await readMapState(home);
|
|
288
|
+
if (read.problem)
|
|
289
|
+
throw new Error(`Cannot update the build map: ${read.problem}`);
|
|
290
|
+
const next = change(read.state) ?? read.state;
|
|
291
|
+
await writeJsonAtomic(mapPath(home, MAP_STATE_FILE), next);
|
|
292
|
+
return next;
|
|
293
|
+
}, { what: `${MAP_DIR}/${STATE_LOCK}` });
|
|
294
|
+
}
|
|
295
|
+
export async function readServerRecord(home) {
|
|
296
|
+
try {
|
|
297
|
+
const record = JSON.parse(await readFile(mapPath(home, MAP_SERVER_FILE), 'utf8'));
|
|
298
|
+
if (typeof record?.port !== 'number' || typeof record?.instance !== 'string')
|
|
299
|
+
return undefined;
|
|
300
|
+
return record;
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
export async function fetchHealth(port, timeoutMs = 1_500) {
|
|
307
|
+
try {
|
|
308
|
+
const response = await fetch(`http://127.0.0.1:${port}/api/health`, {
|
|
309
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
310
|
+
});
|
|
311
|
+
if (!response.ok)
|
|
312
|
+
return undefined;
|
|
313
|
+
return (await response.json());
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
return undefined;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* The server recorded for `home`, if it is really up: same machine, process
|
|
321
|
+
* alive, and its health answer carries the recorded instance id and home. A
|
|
322
|
+
* record left by a crash, by another machine (a synced folder) or a reused pid
|
|
323
|
+
* reads as no server.
|
|
324
|
+
*/
|
|
325
|
+
export async function probeServer(home) {
|
|
326
|
+
const record = await readServerRecord(home);
|
|
327
|
+
if (!record || record.host !== os.hostname() || !pidAlive(record.pid))
|
|
328
|
+
return undefined;
|
|
329
|
+
const health = await fetchHealth(record.port);
|
|
330
|
+
if (!health || health.instance !== record.instance || health.home !== home)
|
|
331
|
+
return undefined;
|
|
332
|
+
return { ...record, clients: health.clients };
|
|
333
|
+
}
|
|
334
|
+
export const serverLockPath = (home) => mapPath(home, SERVER_LOCK);
|
|
335
|
+
/**
|
|
336
|
+
* Mark the server record stopped, but only if it still names `instance` —
|
|
337
|
+
* never a successor's. The port stays in it, so the next start binds the same
|
|
338
|
+
* address and a tab left open reconnects by itself; pid 0 and no token make it
|
|
339
|
+
* read as no server.
|
|
340
|
+
*/
|
|
341
|
+
export async function retireServerRecord(home, instance) {
|
|
342
|
+
const record = await readServerRecord(home);
|
|
343
|
+
if (record?.instance !== instance)
|
|
344
|
+
return;
|
|
345
|
+
await writeJsonAtomic(mapPath(home, MAP_SERVER_FILE), { ...record, pid: 0, token: '', stoppedAt: new Date().toISOString() }, 0o600);
|
|
346
|
+
}
|
|
347
|
+
//# sourceMappingURL=build-map-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-map-store.js","sourceRoot":"","sources":["../src/build-map-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `buildMap` in the local server's create_project and get_project_state answers
|
|
3
|
+
* (plan 2026-11-03-extension-build-map §6.7): whether the author's build map is
|
|
4
|
+
* open, its address, and how offering it went. Read from the same home and the
|
|
5
|
+
* same server record the CLI uses (build-map-store.ts, a copy of the CLI's), so
|
|
6
|
+
* every agent, from any git worktree of the project, sees the one map there is.
|
|
7
|
+
*
|
|
8
|
+
* Local only. The hosted connector has no access to the author's files and
|
|
9
|
+
* registers the tools without `readBuildMap`, so the field is absent there.
|
|
10
|
+
* Reading never starts a server and never writes the state file.
|
|
11
|
+
*/
|
|
12
|
+
export type BuildMapSummary = {
|
|
13
|
+
/** A map server for this project answers on this machine. */
|
|
14
|
+
running: boolean;
|
|
15
|
+
/** The page while it runs; `#node=<step>` opens one step. */
|
|
16
|
+
url?: string;
|
|
17
|
+
/** The author has been asked about the map at least once. */
|
|
18
|
+
offered: boolean;
|
|
19
|
+
/** The author's latest answer was no, and no map runs. */
|
|
20
|
+
declined: boolean;
|
|
21
|
+
/** How many times the author answered the offer, yes or no. */
|
|
22
|
+
offers: number;
|
|
23
|
+
/** The project directory whose `.extenshi/` holds the map: the main checkout when the given one is a linked git worktree. */
|
|
24
|
+
home: string;
|
|
25
|
+
/** Why the state file could not be read, when it exists but could not. */
|
|
26
|
+
problem?: string;
|
|
27
|
+
};
|
|
28
|
+
export declare function readBuildMapSummary(dir: string): Promise<BuildMapSummary>;
|
|
29
|
+
//# sourceMappingURL=build-map-summary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-map-summary.d.ts","sourceRoot":"","sources":["../src/build-map-summary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,MAAM,MAAM,eAAe,GAAG;IAC7B,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAA;IAChB,6DAA6D;IAC7D,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAA;IAChB,0DAA0D;IAC1D,QAAQ,EAAE,OAAO,CAAA;IACjB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAA;IACd,6HAA6H;IAC7H,IAAI,EAAE,MAAM,CAAA;IACZ,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAgB/E"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `buildMap` in the local server's create_project and get_project_state answers
|
|
3
|
+
* (plan 2026-11-03-extension-build-map §6.7): whether the author's build map is
|
|
4
|
+
* open, its address, and how offering it went. Read from the same home and the
|
|
5
|
+
* same server record the CLI uses (build-map-store.ts, a copy of the CLI's), so
|
|
6
|
+
* every agent, from any git worktree of the project, sees the one map there is.
|
|
7
|
+
*
|
|
8
|
+
* Local only. The hosted connector has no access to the author's files and
|
|
9
|
+
* registers the tools without `readBuildMap`, so the field is absent there.
|
|
10
|
+
* Reading never starts a server and never writes the state file.
|
|
11
|
+
*/
|
|
12
|
+
import { probeServer, readMapState, resolveMapHome } from './build-map-store.js';
|
|
13
|
+
export async function readBuildMapSummary(dir) {
|
|
14
|
+
const where = await resolveMapHome(dir);
|
|
15
|
+
const [read, server] = await Promise.all([readMapState(where.home), probeServer(where.home)]);
|
|
16
|
+
const { offer } = read.state;
|
|
17
|
+
const declinedAt = Date.parse(offer.declinedAt ?? '');
|
|
18
|
+
const acceptedAt = Date.parse(offer.acceptedAt ?? '');
|
|
19
|
+
return {
|
|
20
|
+
running: Boolean(server),
|
|
21
|
+
...(server ? { url: server.url } : {}),
|
|
22
|
+
offered: offer.count > 0 || Boolean(offer.offeredAt ?? offer.acceptedAt ?? offer.declinedAt),
|
|
23
|
+
// A map the author opened after saying no (acceptedAt later, or a server up now) is not declined.
|
|
24
|
+
declined: !server && Number.isFinite(declinedAt) && !(acceptedAt > declinedAt),
|
|
25
|
+
offers: offer.count,
|
|
26
|
+
home: where.home,
|
|
27
|
+
...(read.problem ? { problem: read.problem } : {}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=build-map-summary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-map-summary.js","sourceRoot":"","sources":["../src/build-map-summary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAmBhF,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,GAAW;IACpD,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,GAAG,CAAC,CAAA;IACvC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC7F,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,CAAA;IAC5B,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,IAAI,EAAE,CAAC,CAAA;IACrD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,IAAI,EAAE,CAAC,CAAA;IACrD,OAAO;QACN,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC;QACxB,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtC,OAAO,EAAE,KAAK,CAAC,KAAK,GAAG,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,UAAU,CAAC;QAC5F,kGAAkG;QAClG,QAAQ,EAAE,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,UAAU,GAAG,UAAU,CAAC;QAC9E,MAAM,EAAE,KAAK,CAAC,KAAK;QACnB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAClD,CAAA;AACF,CAAC"}
|
|
@@ -104,7 +104,7 @@ export declare const DEVELOPMENT_SERVICES: readonly [{
|
|
|
104
104
|
readonly urls: readonly [string, "https://dojo.extenshi.io/payments"];
|
|
105
105
|
}];
|
|
106
106
|
/** Top-level sections of the full guide, selectable via the `sections` parameter. */
|
|
107
|
-
export declare const GUIDE_SECTIONS: readonly ["tools", "localActions", "services", "repository", "workflow", "handoff", "storeMedia"];
|
|
107
|
+
export declare const GUIDE_SECTIONS: readonly ["tools", "localActions", "services", "repository", "workflow", "buildMap", "handoff", "storeMedia"];
|
|
108
108
|
export type GuideSection = (typeof GUIDE_SECTIONS)[number];
|
|
109
109
|
declare function buildSections(registeredTools: readonly GuideTool[]): {
|
|
110
110
|
tools: {
|
|
@@ -265,6 +265,23 @@ declare function buildSections(registeredTools: readonly GuideTool[]): {
|
|
|
265
265
|
readonly relatedTools: readonly ["get_reviews", "get_security", "get_project_state"];
|
|
266
266
|
readonly doneWhen: "A handoff records repository/commit, artifacts, URLs, checks, remaining blockers, support owner and follow-up work.";
|
|
267
267
|
}];
|
|
268
|
+
buildMap: {
|
|
269
|
+
readonly what: "A local page served by @extenshi/cli on 127.0.0.1: the build steps of the extension, from the idea to the live store listing, with the dependencies between them. Each step is done, in progress, waiting on the author, blocked by an unfinished step, or not needed for this extension. Steps waiting on the author (icon choice, approval of listing copy and screenshots, store accounts, submission) are counted in the header and open from there.";
|
|
270
|
+
readonly forTheAuthor: "One page shows what is done, what an agent is working on and where a decision is needed, instead of a long chat; visual choices such as the icon are compared there side by side. The page runs on the author’s machine, reads the project locally and works without an account.";
|
|
271
|
+
readonly commands: {
|
|
272
|
+
readonly open: "npx @extenshi/cli@latest map: starts the map server of the project when none runs and opens the page; with the page already open in a tab, no second tab opens.";
|
|
273
|
+
readonly status: "npx @extenshi/cli@latest map status --json: progress, steps waiting on the author, steps in progress and steps that can start now; starts nothing.";
|
|
274
|
+
readonly decline: "npx @extenshi/cli@latest map decline: records that the author declined the map.";
|
|
275
|
+
readonly stop: "npx @extenshi/cli@latest map stop: stops the server; the next start reuses its port, so open tabs and links keep working.";
|
|
276
|
+
readonly stepLink: "<map url>#node=<step id> opens one step, for example #node=iconConcepts.";
|
|
277
|
+
};
|
|
278
|
+
readonly oneMapPerProject: "A project has exactly one map and one map server, however many agents work on it. The map lives in the main checkout: from a linked git worktree the CLI resolves the same directory in the main working tree, so every agent and every worktree reads the same .extenshi/build-map.json and reaches the same server. Running `map` again, from any terminal, agent or worktree, returns the server already running; simultaneous starts end with one server.";
|
|
279
|
+
readonly progress: "Statuses are derived on every read, not stored: from the project files in the main checkout (manifest and the files it names, icons, builds, packages, the store folder, CI workflows); from the Dojo project the directory is bound to (owner decisions, and release evidence and readiness gates while the map server runs with an API key, refreshed every minute); and from .extenshi/build-map.json, which holds per-step progress, open reviews, the offer record and a log. Work on an agent branch appears once it is in the main checkout. The CLI writes the state file under .extenshi/build-map.lock; per-step progress reports from agents arrive with a later CLI version.";
|
|
280
|
+
readonly offer: "The map is an offer to the author, not a required step. It is offered once, after the extension idea is clear and before the first step that needs the author, in the author’s language and with a plain yes or no; the server starts only after a yes. After a no, the map comes up once more at the first visual choice (usually the icon), and after a second no it is not offered again. The answer is recorded in .extenshi/build-map.json (offer: count, acceptedAt, declinedAt): `map` records a yes and `map decline` a no, so a later agent session reads the answer instead of asking again.";
|
|
281
|
+
readonly withoutTheMap: "Everything also works in chat: previews are linked there and the author answers there. The state file is kept either way, so a map opened later shows the whole history.";
|
|
282
|
+
readonly localServerField: "On the local stdio server, create_project and get_project_state carry buildMap {running, url (while running), offered, declined, offers, home} for projectDir (default: the server’s working directory). The hosted connector has no access to the author’s files and returns no buildMap; there the map opens from a terminal on the author’s machine.";
|
|
283
|
+
readonly docs: `${string}#extenshi-map`;
|
|
284
|
+
};
|
|
268
285
|
storeMedia: {
|
|
269
286
|
readonly screenshots: {
|
|
270
287
|
readonly command: "npx @extenshi/cli@latest screenshots <unpacked-dir|zip> --scenes scenes.json [--locales en,de] [--store chrome,edge,firefox] [--small] [--out store-screenshots] [--json] [--evidence --listing <file> [--evidence-browser chrome,edge,firefox]]";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"development-guide.d.ts","sourceRoot":"","sources":["../src/development-guide.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE;QACZ,YAAY,CAAC,EAAE,OAAO,CAAA;QACtB,eAAe,CAAC,EAAE,OAAO,CAAA;KACzB,CAAA;CACD;AAKD,eAAO,MAAM,qBAAqB,QAA+B,CAAA;AAEjE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiQvB,CAAA;
|
|
1
|
+
{"version":3,"file":"development-guide.d.ts","sourceRoot":"","sources":["../src/development-guide.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE;QACZ,YAAY,CAAC,EAAE,OAAO,CAAA;QACtB,eAAe,CAAC,EAAE,OAAO,CAAA;KACzB,CAAA;CACD;AAKD,eAAO,MAAM,qBAAqB,QAA+B,CAAA;AAEjE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiQvB,CAAA;AA+JV,qFAAqF;AACrF,eAAO,MAAM,cAAc,+GASjB,CAAA;AACV,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AA6F1D,iBAAS,aAAa,CAAC,eAAe,EAAE,SAAS,SAAS,EAAE;;;cArhBrD,MAAM;qBACC,MAAM;qBACN;YACZ,YAAY,CAAC,EAAE,OAAO,CAAA;YACtB,eAAe,CAAC,EAAE,OAAO,CAAA;SACzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4kBD;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAA;AAE5D,wFAAwF;AACxF,wBAAgB,oBAAoB,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,YAAY,EAAE,CAIlF;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACpC,eAAe,EAAE,SAAS,SAAS,EAAE,EACrC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,GAC3B;IAAE,aAAa,EAAE,CAAC,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,aAAa,CAAC,GAC5F,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAuBvB"}
|