@tienne/gestalt 0.107.0 → 0.109.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/CLAUDE.md +2 -0
- package/README.ko.md +1 -0
- package/README.md +1 -0
- package/dist/package.json +1 -1
- package/dist/plugin/role-agents/lens-context/AGENT.md +94 -0
- package/dist/plugin/role-agents/lens-link/AGENT.md +83 -0
- package/dist/plugin/role-agents/lens-reader/AGENT.md +230 -0
- package/dist/plugin/skills/_shared/proactive-routing.md +1 -0
- package/dist/plugin/skills/lens/SKILL.md +375 -0
- package/dist/schemas/gestalt.schema.json +13 -0
- package/dist/src/cli/commands/digest.d.ts +2 -0
- package/dist/src/cli/commands/digest.d.ts.map +1 -1
- package/dist/src/cli/commands/digest.js +6 -0
- package/dist/src/cli/commands/digest.js.map +1 -1
- package/dist/src/cli/commands/lens.d.ts +27 -0
- package/dist/src/cli/commands/lens.d.ts.map +1 -0
- package/dist/src/cli/commands/lens.js +200 -0
- package/dist/src/cli/commands/lens.js.map +1 -0
- package/dist/src/cli/commands/peer.d.ts +19 -0
- package/dist/src/cli/commands/peer.d.ts.map +1 -0
- package/dist/src/cli/commands/peer.js +147 -0
- package/dist/src/cli/commands/peer.js.map +1 -0
- package/dist/src/cli/index.d.ts.map +1 -1
- package/dist/src/cli/index.js +72 -2
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/digest/format.d.ts.map +1 -1
- package/dist/src/digest/format.js +1 -0
- package/dist/src/digest/format.js.map +1 -1
- package/dist/src/digest/frames.d.ts.map +1 -1
- package/dist/src/digest/frames.js +1 -1
- package/dist/src/digest/frames.js.map +1 -1
- package/dist/src/digest/ingest.d.ts +8 -1
- package/dist/src/digest/ingest.d.ts.map +1 -1
- package/dist/src/digest/ingest.js +2 -1
- package/dist/src/digest/ingest.js.map +1 -1
- package/dist/src/digest/lens-schema.d.ts +542 -0
- package/dist/src/digest/lens-schema.d.ts.map +1 -0
- package/dist/src/digest/lens-schema.js +108 -0
- package/dist/src/digest/lens-schema.js.map +1 -0
- package/dist/src/digest/lens-source.d.ts +260 -0
- package/dist/src/digest/lens-source.d.ts.map +1 -0
- package/dist/src/digest/lens-source.js +92 -0
- package/dist/src/digest/lens-source.js.map +1 -0
- package/dist/src/digest/lens-widget-schema.d.ts +1225 -0
- package/dist/src/digest/lens-widget-schema.d.ts.map +1 -0
- package/dist/src/digest/lens-widget-schema.js +80 -0
- package/dist/src/digest/lens-widget-schema.js.map +1 -0
- package/dist/src/digest/lens-widgets.d.ts +25 -0
- package/dist/src/digest/lens-widgets.d.ts.map +1 -0
- package/dist/src/digest/lens-widgets.js +225 -0
- package/dist/src/digest/lens-widgets.js.map +1 -0
- package/dist/src/digest/lint-lens-context.d.ts +102 -0
- package/dist/src/digest/lint-lens-context.d.ts.map +1 -0
- package/dist/src/digest/lint-lens-context.js +192 -0
- package/dist/src/digest/lint-lens-context.js.map +1 -0
- package/dist/src/digest/lint-lens.d.ts +17 -0
- package/dist/src/digest/lint-lens.d.ts.map +1 -0
- package/dist/src/digest/lint-lens.js +237 -0
- package/dist/src/digest/lint-lens.js.map +1 -0
- package/dist/src/digest/lint.d.ts +14 -0
- package/dist/src/digest/lint.d.ts.map +1 -1
- package/dist/src/digest/lint.js +156 -2
- package/dist/src/digest/lint.js.map +1 -1
- package/dist/src/digest/merge.d.ts.map +1 -1
- package/dist/src/digest/merge.js +11 -2
- package/dist/src/digest/merge.js.map +1 -1
- package/dist/src/digest/prose.d.ts.map +1 -1
- package/dist/src/digest/prose.js +8 -0
- package/dist/src/digest/prose.js.map +1 -1
- package/dist/src/digest/render-html.d.ts +5 -0
- package/dist/src/digest/render-html.d.ts.map +1 -1
- package/dist/src/digest/render-html.js +93 -18
- package/dist/src/digest/render-html.js.map +1 -1
- package/dist/src/digest/render-lens.d.ts +61 -0
- package/dist/src/digest/render-lens.d.ts.map +1 -0
- package/dist/src/digest/render-lens.js +273 -0
- package/dist/src/digest/render-lens.js.map +1 -0
- package/dist/src/digest/render-md.d.ts +5 -0
- package/dist/src/digest/render-md.d.ts.map +1 -1
- package/dist/src/digest/render-md.js +116 -7
- package/dist/src/digest/render-md.js.map +1 -1
- package/dist/src/digest/render-widgets.d.ts +13 -0
- package/dist/src/digest/render-widgets.d.ts.map +1 -0
- package/dist/src/digest/render-widgets.js +197 -0
- package/dist/src/digest/render-widgets.js.map +1 -0
- package/dist/src/digest/render.d.ts +2 -0
- package/dist/src/digest/render.d.ts.map +1 -1
- package/dist/src/digest/render.js +17 -3
- package/dist/src/digest/render.js.map +1 -1
- package/dist/src/digest/schema.d.ts +14683 -2022
- package/dist/src/digest/schema.d.ts.map +1 -1
- package/dist/src/digest/schema.js +67 -6
- package/dist/src/digest/schema.js.map +1 -1
- package/dist/src/digest/server.d.ts +2 -1
- package/dist/src/digest/server.d.ts.map +1 -1
- package/dist/src/digest/server.js +26 -11
- package/dist/src/digest/server.js.map +1 -1
- package/dist/src/digest/viewer-js.d.ts.map +1 -1
- package/dist/src/digest/viewer-js.js +120 -4
- package/dist/src/digest/viewer-js.js.map +1 -1
- package/dist/src/lens/attach.d.ts +436 -0
- package/dist/src/lens/attach.d.ts.map +1 -0
- package/dist/src/lens/attach.js +46 -0
- package/dist/src/lens/attach.js.map +1 -0
- package/dist/src/lens/ingest.d.ts +75 -0
- package/dist/src/lens/ingest.d.ts.map +1 -0
- package/dist/src/lens/ingest.js +558 -0
- package/dist/src/lens/ingest.js.map +1 -0
- package/dist/src/lens/kind.d.ts +11 -0
- package/dist/src/lens/kind.d.ts.map +1 -0
- package/dist/src/lens/kind.js +59 -0
- package/dist/src/lens/kind.js.map +1 -0
- package/dist/src/lens/knowledge.d.ts +53 -0
- package/dist/src/lens/knowledge.d.ts.map +1 -0
- package/dist/src/lens/knowledge.js +202 -0
- package/dist/src/lens/knowledge.js.map +1 -0
- package/dist/src/lens/notes.d.ts +73 -0
- package/dist/src/lens/notes.d.ts.map +1 -0
- package/dist/src/lens/notes.js +276 -0
- package/dist/src/lens/notes.js.map +1 -0
- package/dist/src/lens/panels.d.ts +10 -0
- package/dist/src/lens/panels.d.ts.map +1 -0
- package/dist/src/lens/panels.js +25 -0
- package/dist/src/lens/panels.js.map +1 -0
- package/dist/src/lens/pdf-extract-py.d.ts +15 -0
- package/dist/src/lens/pdf-extract-py.d.ts.map +1 -0
- package/dist/src/lens/pdf-extract-py.js +414 -0
- package/dist/src/lens/pdf-extract-py.js.map +1 -0
- package/dist/src/lens/podcast.d.ts +36 -0
- package/dist/src/lens/podcast.d.ts.map +1 -0
- package/dist/src/lens/podcast.js +104 -0
- package/dist/src/lens/podcast.js.map +1 -0
- package/dist/src/lens/python.d.ts +33 -0
- package/dist/src/lens/python.d.ts.map +1 -0
- package/dist/src/lens/python.js +61 -0
- package/dist/src/lens/python.js.map +1 -0
- package/dist/src/lens/web-extract-py.d.ts +9 -0
- package/dist/src/lens/web-extract-py.d.ts.map +1 -0
- package/dist/src/lens/web-extract-py.js +114 -0
- package/dist/src/lens/web-extract-py.js.map +1 -0
- package/dist/src/mcp/schemas.d.ts +3 -0
- package/dist/src/mcp/schemas.d.ts.map +1 -1
- package/dist/src/mcp/schemas.js +4 -0
- package/dist/src/mcp/schemas.js.map +1 -1
- package/dist/src/mcp/server.js +1 -1
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/mcp/tools/digest-passthrough.d.ts.map +1 -1
- package/dist/src/mcp/tools/digest-passthrough.js +5 -0
- package/dist/src/mcp/tools/digest-passthrough.js.map +1 -1
- package/dist/src/peer/client.d.ts +9 -0
- package/dist/src/peer/client.d.ts.map +1 -0
- package/dist/src/peer/client.js +51 -0
- package/dist/src/peer/client.js.map +1 -0
- package/dist/src/peer/daemon.d.ts +71 -0
- package/dist/src/peer/daemon.d.ts.map +1 -0
- package/dist/src/peer/daemon.js +407 -0
- package/dist/src/peer/daemon.js.map +1 -0
- package/dist/src/peer/errors.d.ts +2 -0
- package/dist/src/peer/errors.d.ts.map +1 -0
- package/dist/src/peer/errors.js +4 -0
- package/dist/src/peer/errors.js.map +1 -0
- package/dist/src/peer/index.d.ts +14 -0
- package/dist/src/peer/index.d.ts.map +1 -0
- package/dist/src/peer/index.js +9 -0
- package/dist/src/peer/index.js.map +1 -0
- package/dist/src/peer/locate.d.ts +44 -0
- package/dist/src/peer/locate.d.ts.map +1 -0
- package/dist/src/peer/locate.js +116 -0
- package/dist/src/peer/locate.js.map +1 -0
- package/dist/src/peer/paths.d.ts +23 -0
- package/dist/src/peer/paths.d.ts.map +1 -0
- package/dist/src/peer/paths.js +50 -0
- package/dist/src/peer/paths.js.map +1 -0
- package/dist/src/peer/protocol.d.ts +61 -0
- package/dist/src/peer/protocol.d.ts.map +1 -0
- package/dist/src/peer/protocol.js +7 -0
- package/dist/src/peer/protocol.js.map +1 -0
- package/dist/src/peer/redact.d.ts +30 -0
- package/dist/src/peer/redact.d.ts.map +1 -0
- package/dist/src/peer/redact.js +214 -0
- package/dist/src/peer/redact.js.map +1 -0
- package/dist/src/peer/share.d.ts +123 -0
- package/dist/src/peer/share.d.ts.map +1 -0
- package/dist/src/peer/share.js +204 -0
- package/dist/src/peer/share.js.map +1 -0
- package/dist/src/peer/state.d.ts +37 -0
- package/dist/src/peer/state.d.ts.map +1 -0
- package/dist/src/peer/state.js +99 -0
- package/dist/src/peer/state.js.map +1 -0
- package/dist/src/peer/tail.d.ts +55 -0
- package/dist/src/peer/tail.d.ts.map +1 -0
- package/dist/src/peer/tail.js +148 -0
- package/dist/src/peer/tail.js.map +1 -0
- package/dist/src/peer/transform.d.ts +79 -0
- package/dist/src/peer/transform.d.ts.map +1 -0
- package/dist/src/peer/transform.js +237 -0
- package/dist/src/peer/transform.js.map +1 -0
- package/dist/src/peer/viewer-html.d.ts +10 -0
- package/dist/src/peer/viewer-html.d.ts.map +1 -0
- package/dist/src/peer/viewer-html.js +157 -0
- package/dist/src/peer/viewer-html.js.map +1 -0
- package/dist/src/peer/viewer.d.ts +58 -0
- package/dist/src/peer/viewer.d.ts.map +1 -0
- package/dist/src/peer/viewer.js +249 -0
- package/dist/src/peer/viewer.js.map +1 -0
- package/dist/src/research/source-create.d.ts +6 -1
- package/dist/src/research/source-create.d.ts.map +1 -1
- package/dist/src/research/source-create.js +41 -10
- package/dist/src/research/source-create.js.map +1 -1
- package/package.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.mcp.json +1 -1
- package/plugin/mcp.json +1 -1
- package/plugin/role-agents/lens-context/AGENT.md +94 -0
- package/plugin/role-agents/lens-link/AGENT.md +83 -0
- package/plugin/role-agents/lens-reader/AGENT.md +230 -0
- package/plugin/skills/_shared/proactive-routing.md +1 -0
- package/plugin/skills/lens/SKILL.md +375 -0
- package/schemas/gestalt.schema.json +13 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code 대화 기록 위치 찾기.
|
|
3
|
+
*
|
|
4
|
+
* `sessions/<pid>.json`과 `projects/<슬러그>/<sessionId>.jsonl` 둘 다 문서화되지 않은
|
|
5
|
+
* 형식이다. 그래서 형식이 예상과 다르면 던지지 않고 이유를 담아 돌려준다 — 부르는 쪽이
|
|
6
|
+
* "어디서 무엇을 찾다 못 찾았는지"를 사람에게 그대로 보여줄 수 있어야 한다.
|
|
7
|
+
*/
|
|
8
|
+
export type LocateResult = {
|
|
9
|
+
ok: true;
|
|
10
|
+
path: string;
|
|
11
|
+
sessionId: string;
|
|
12
|
+
configDir: string;
|
|
13
|
+
cwd?: string;
|
|
14
|
+
} | {
|
|
15
|
+
ok: false;
|
|
16
|
+
reason: string;
|
|
17
|
+
configDir: string;
|
|
18
|
+
sessionId?: string;
|
|
19
|
+
};
|
|
20
|
+
export interface SessionRecord {
|
|
21
|
+
sessionId: string;
|
|
22
|
+
cwd: string;
|
|
23
|
+
pid?: number;
|
|
24
|
+
}
|
|
25
|
+
/** 설정 디렉토리. Orca 프로필처럼 `CLAUDE_CONFIG_DIR`로 옮겨 둔 경우가 있다 */
|
|
26
|
+
export declare function claudeConfigDir(env?: NodeJS.ProcessEnv): string;
|
|
27
|
+
/**
|
|
28
|
+
* cwd를 `projects/` 아래 디렉토리 이름으로 바꾼다. 실제 이름을 보면 영숫자와 `-`
|
|
29
|
+
* 말고는 전부 `-`가 된다(`/Users/a/.x` → `-Users-a--x`, 한글도 `-`).
|
|
30
|
+
*/
|
|
31
|
+
export declare function cwdSlug(cwd: string): string;
|
|
32
|
+
export declare function isSessionId(value: string): boolean;
|
|
33
|
+
/** `sessions/<pid>.json` 하나를 읽는다. 형식이 다르면 null */
|
|
34
|
+
export declare function readSessionFile(path: string): SessionRecord | null;
|
|
35
|
+
/** 부모 pid로 세션을 찾는다. MCP 서버처럼 세션의 자식이 쓴다 */
|
|
36
|
+
export declare function findSessionByPid(pid: number, configDir?: string): SessionRecord | null;
|
|
37
|
+
/**
|
|
38
|
+
* sessionId로 대화 기록 경로를 찾는다.
|
|
39
|
+
*
|
|
40
|
+
* 1. `sessions/*.json`에서 그 세션의 cwd를 찾아 슬러그 경로를 본다
|
|
41
|
+
* 2. 없으면(세션이 끝났거나 슬러그 규칙이 다르면) `projects/*` 아래를 훑는다
|
|
42
|
+
*/
|
|
43
|
+
export declare function locateTranscript(sessionId: string, configDir?: string): LocateResult;
|
|
44
|
+
//# sourceMappingURL=locate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locate.d.ts","sourceRoot":"","sources":["../../../src/peer/locate.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AAEH,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9E;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzE,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,2DAA2D;AAC3D,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAI5E;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3C;AAKD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAElD;AAED,kDAAkD;AAClD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAclE;AAED,2CAA2C;AAC3C,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,SAAoB,GAAG,aAAa,GAAG,IAAI,CAGjG;AA0BD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,SAAoB,GAAG,YAAY,CAoC/F"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { isAbsolute, join } from 'node:path';
|
|
4
|
+
/** 설정 디렉토리. Orca 프로필처럼 `CLAUDE_CONFIG_DIR`로 옮겨 둔 경우가 있다 */
|
|
5
|
+
export function claudeConfigDir(env = process.env) {
|
|
6
|
+
const raw = env['CLAUDE_CONFIG_DIR'];
|
|
7
|
+
if (raw && isAbsolute(raw))
|
|
8
|
+
return raw;
|
|
9
|
+
return join(homedir(), '.claude');
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* cwd를 `projects/` 아래 디렉토리 이름으로 바꾼다. 실제 이름을 보면 영숫자와 `-`
|
|
13
|
+
* 말고는 전부 `-`가 된다(`/Users/a/.x` → `-Users-a--x`, 한글도 `-`).
|
|
14
|
+
*/
|
|
15
|
+
export function cwdSlug(cwd) {
|
|
16
|
+
return cwd.replace(/[^A-Za-z0-9-]/g, '-');
|
|
17
|
+
}
|
|
18
|
+
/** sessionId가 경로 조각으로 쓰이므로 `..`이나 `/`가 들어오면 안 된다 */
|
|
19
|
+
const SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
20
|
+
export function isSessionId(value) {
|
|
21
|
+
return SESSION_ID.test(value);
|
|
22
|
+
}
|
|
23
|
+
/** `sessions/<pid>.json` 하나를 읽는다. 형식이 다르면 null */
|
|
24
|
+
export function readSessionFile(path) {
|
|
25
|
+
let parsed;
|
|
26
|
+
try {
|
|
27
|
+
parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
33
|
+
return null;
|
|
34
|
+
const rec = parsed;
|
|
35
|
+
const sessionId = rec['sessionId'];
|
|
36
|
+
const cwd = rec['cwd'];
|
|
37
|
+
if (typeof sessionId !== 'string' || typeof cwd !== 'string')
|
|
38
|
+
return null;
|
|
39
|
+
const pid = typeof rec['pid'] === 'number' ? rec['pid'] : undefined;
|
|
40
|
+
return { sessionId, cwd, pid };
|
|
41
|
+
}
|
|
42
|
+
/** 부모 pid로 세션을 찾는다. MCP 서버처럼 세션의 자식이 쓴다 */
|
|
43
|
+
export function findSessionByPid(pid, configDir = claudeConfigDir()) {
|
|
44
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
45
|
+
return null;
|
|
46
|
+
return readSessionFile(join(configDir, 'sessions', `${pid}.json`));
|
|
47
|
+
}
|
|
48
|
+
function findSessionById(sessionId, configDir) {
|
|
49
|
+
const dir = join(configDir, 'sessions');
|
|
50
|
+
let names;
|
|
51
|
+
try {
|
|
52
|
+
names = readdirSync(dir);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
for (const name of names) {
|
|
58
|
+
if (!/^\d+\.json$/.test(name))
|
|
59
|
+
continue;
|
|
60
|
+
const rec = readSessionFile(join(dir, name));
|
|
61
|
+
if (rec?.sessionId === sessionId)
|
|
62
|
+
return rec;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
function isFile(path) {
|
|
67
|
+
try {
|
|
68
|
+
return statSync(path).isFile();
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* sessionId로 대화 기록 경로를 찾는다.
|
|
76
|
+
*
|
|
77
|
+
* 1. `sessions/*.json`에서 그 세션의 cwd를 찾아 슬러그 경로를 본다
|
|
78
|
+
* 2. 없으면(세션이 끝났거나 슬러그 규칙이 다르면) `projects/*` 아래를 훑는다
|
|
79
|
+
*/
|
|
80
|
+
export function locateTranscript(sessionId, configDir = claudeConfigDir()) {
|
|
81
|
+
if (!isSessionId(sessionId)) {
|
|
82
|
+
return { ok: false, reason: 'sessionId 형식이 아니에요', configDir };
|
|
83
|
+
}
|
|
84
|
+
if (!existsSync(configDir)) {
|
|
85
|
+
return { ok: false, reason: '설정 디렉토리가 없어요', configDir, sessionId };
|
|
86
|
+
}
|
|
87
|
+
const session = findSessionById(sessionId, configDir);
|
|
88
|
+
if (session) {
|
|
89
|
+
const bySlug = join(configDir, 'projects', cwdSlug(session.cwd), `${sessionId}.jsonl`);
|
|
90
|
+
if (isFile(bySlug))
|
|
91
|
+
return { ok: true, path: bySlug, sessionId, configDir, cwd: session.cwd };
|
|
92
|
+
}
|
|
93
|
+
const projects = join(configDir, 'projects');
|
|
94
|
+
let dirs;
|
|
95
|
+
try {
|
|
96
|
+
dirs = readdirSync(projects);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return { ok: false, reason: 'projects 디렉토리를 못 읽었어요', configDir, sessionId };
|
|
100
|
+
}
|
|
101
|
+
for (const dir of dirs) {
|
|
102
|
+
const candidate = join(projects, dir, `${sessionId}.jsonl`);
|
|
103
|
+
if (isFile(candidate)) {
|
|
104
|
+
return { ok: true, path: candidate, sessionId, configDir, cwd: session?.cwd };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
ok: false,
|
|
109
|
+
reason: session
|
|
110
|
+
? '세션은 있는데 대화 기록 파일을 못 찾았어요'
|
|
111
|
+
: '그 sessionId의 세션도 대화 기록도 못 찾았어요',
|
|
112
|
+
configDir,
|
|
113
|
+
sessionId,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=locate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locate.js","sourceRoot":"","sources":["../../../src/peer/locate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 데몬이 소켓과 상태 DB를 두는 자리. 기본은 `~/.gestalt/peer`이고 테스트는
|
|
3
|
+
* `GESTALT_PEER_HOME`으로 옮긴다.
|
|
4
|
+
*
|
|
5
|
+
* 상대경로는 받지 않고 던진다. `GESTALT_HOME`처럼 진짜 홈으로 돌아가면 테스트가
|
|
6
|
+
* 실수로 사용자의 데몬 소켓에 붙거나 사용자 DB에 쓴다.
|
|
7
|
+
*/
|
|
8
|
+
export declare function peerRoot(env?: NodeJS.ProcessEnv): string;
|
|
9
|
+
export declare function socketPath(root: string): string;
|
|
10
|
+
export declare function statePath(root: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* 같은 OS 사용자만 들어오게 디렉토리를 0700으로 만든다. 이미 있으면 권한을 다시
|
|
13
|
+
* 좁힌다 — 소켓은 listen과 chmod 사이에 잠깐 기본 권한으로 열려 있어서 그 틈을
|
|
14
|
+
* 막는 건 디렉토리다.
|
|
15
|
+
*
|
|
16
|
+
* 루트가 심링크거나 남의 것이면 거부한다. mkdir과 chmod는 심링크를 따라가서 남이 걸어
|
|
17
|
+
* 둔 링크 끝의 디렉토리에 소켓과 DB를 만들고 권한까지 바꾼다. realpath로 비교하지 않는
|
|
18
|
+
* 건 macOS 임시 디렉토리가 `/var` → `/private/var` 링크 아래라 정상 경로도 걸리기 때문이다.
|
|
19
|
+
*/
|
|
20
|
+
export declare function ensurePrivateDir(dir: string): void;
|
|
21
|
+
/** macOS 유닉스 소켓 경로 한도(sun_path 104바이트) */
|
|
22
|
+
export declare const MAX_SOCKET_PATH_BYTES = 103;
|
|
23
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../../src/peer/paths.ts"],"names":[],"mappings":"AAMA;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAKrE;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAUlD;AAED,0CAA0C;AAC1C,eAAO,MAAM,qBAAqB,MAAM,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { chmodSync, lstatSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { isAbsolute, join, normalize } from 'node:path';
|
|
3
|
+
import { gestaltHome } from '../core/home.js';
|
|
4
|
+
const PEER_HOME_ENV = 'GESTALT_PEER_HOME';
|
|
5
|
+
/**
|
|
6
|
+
* 데몬이 소켓과 상태 DB를 두는 자리. 기본은 `~/.gestalt/peer`이고 테스트는
|
|
7
|
+
* `GESTALT_PEER_HOME`으로 옮긴다.
|
|
8
|
+
*
|
|
9
|
+
* 상대경로는 받지 않고 던진다. `GESTALT_HOME`처럼 진짜 홈으로 돌아가면 테스트가
|
|
10
|
+
* 실수로 사용자의 데몬 소켓에 붙거나 사용자 DB에 쓴다.
|
|
11
|
+
*/
|
|
12
|
+
export function peerRoot(env = process.env) {
|
|
13
|
+
const raw = env[PEER_HOME_ENV];
|
|
14
|
+
if (raw === undefined || raw === '')
|
|
15
|
+
return join(gestaltHome(), 'peer');
|
|
16
|
+
if (!isAbsolute(raw))
|
|
17
|
+
throw new Error(`${PEER_HOME_ENV}는 절대경로여야 해요: ${raw}`);
|
|
18
|
+
return normalize(raw);
|
|
19
|
+
}
|
|
20
|
+
export function socketPath(root) {
|
|
21
|
+
return join(root, 'peer.sock');
|
|
22
|
+
}
|
|
23
|
+
export function statePath(root) {
|
|
24
|
+
return join(root, 'state.db');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 같은 OS 사용자만 들어오게 디렉토리를 0700으로 만든다. 이미 있으면 권한을 다시
|
|
28
|
+
* 좁힌다 — 소켓은 listen과 chmod 사이에 잠깐 기본 권한으로 열려 있어서 그 틈을
|
|
29
|
+
* 막는 건 디렉토리다.
|
|
30
|
+
*
|
|
31
|
+
* 루트가 심링크거나 남의 것이면 거부한다. mkdir과 chmod는 심링크를 따라가서 남이 걸어
|
|
32
|
+
* 둔 링크 끝의 디렉토리에 소켓과 DB를 만들고 권한까지 바꾼다. realpath로 비교하지 않는
|
|
33
|
+
* 건 macOS 임시 디렉토리가 `/var` → `/private/var` 링크 아래라 정상 경로도 걸리기 때문이다.
|
|
34
|
+
*/
|
|
35
|
+
export function ensurePrivateDir(dir) {
|
|
36
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
37
|
+
const st = lstatSync(dir);
|
|
38
|
+
if (st.isSymbolicLink())
|
|
39
|
+
throw new Error(`peer 디렉토리가 심링크예요: ${dir}`);
|
|
40
|
+
if (!st.isDirectory())
|
|
41
|
+
throw new Error(`peer 디렉토리 자리에 다른 파일이 있어요: ${dir}`);
|
|
42
|
+
const uid = process.getuid?.();
|
|
43
|
+
if (uid !== undefined && st.uid !== uid) {
|
|
44
|
+
throw new Error(`peer 디렉토리의 주인이 내가 아니에요: ${dir}`);
|
|
45
|
+
}
|
|
46
|
+
chmodSync(dir, 0o700);
|
|
47
|
+
}
|
|
48
|
+
/** macOS 유닉스 소켓 경로 한도(sun_path 104바이트) */
|
|
49
|
+
export const MAX_SOCKET_PATH_BYTES = 103;
|
|
50
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../src/peer/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,MAAM,aAAa,GAAG,mBAAmB,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC3D,MAAM,GAAG,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC;IAC/B,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC,WAAW,EAAE,EAAE,MAAM,CAAC,CAAC;IACxE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,aAAa,gBAAgB,GAAG,EAAE,CAAC,CAAC;IAC7E,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,OAAO,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;AAChC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACjD,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC1B,IAAI,EAAE,CAAC,cAAc,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,EAAE,CAAC,CAAC;IACrE,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,GAAG,EAAE,CAAC,CAAC;IAC3E,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;IAC/B,IAAI,GAAG,KAAK,SAAS,IAAI,EAAE,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,2BAA2B,GAAG,EAAE,CAAC,CAAC;IACpD,CAAC;IACD,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;AACxB,CAAC;AAED,0CAA0C;AAC1C,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { ToolsLevel } from './redact.js';
|
|
2
|
+
import type { ShareStatus } from './share.js';
|
|
3
|
+
/** IPC 한 줄 요청. 줄 단위 JSON이다 */
|
|
4
|
+
export type PeerRequest = {
|
|
5
|
+
op: 'ping';
|
|
6
|
+
} | {
|
|
7
|
+
op: 'status';
|
|
8
|
+
} | {
|
|
9
|
+
op: 'shares';
|
|
10
|
+
} | {
|
|
11
|
+
op: 'share';
|
|
12
|
+
transcriptPath: string;
|
|
13
|
+
sessionId?: string;
|
|
14
|
+
tools?: ToolsLevel;
|
|
15
|
+
expiresMinutes?: number;
|
|
16
|
+
} | {
|
|
17
|
+
op: 'unshare';
|
|
18
|
+
id: string;
|
|
19
|
+
};
|
|
20
|
+
export type PeerResponse<T = unknown> = {
|
|
21
|
+
ok: true;
|
|
22
|
+
result: T;
|
|
23
|
+
} | {
|
|
24
|
+
ok: false;
|
|
25
|
+
error: string;
|
|
26
|
+
};
|
|
27
|
+
export interface ShareInfo {
|
|
28
|
+
id: string;
|
|
29
|
+
sessionId: string | null;
|
|
30
|
+
transcriptPath: string;
|
|
31
|
+
tools: ToolsLevel;
|
|
32
|
+
createdAt: number;
|
|
33
|
+
expiresAt: number;
|
|
34
|
+
status: ShareStatus;
|
|
35
|
+
viewers: number;
|
|
36
|
+
events: number;
|
|
37
|
+
unknownLines: number;
|
|
38
|
+
/** 바꾸다 예외가 나서 버린 줄 */
|
|
39
|
+
failedLines: number;
|
|
40
|
+
/** 한 줄 상한을 넘어 버린 줄 */
|
|
41
|
+
oversizedLines: number;
|
|
42
|
+
/** 메모리 상한에 밀려 새 뷰어에게 다시 못 보내는 앞부분 이벤트 */
|
|
43
|
+
droppedEvents: number;
|
|
44
|
+
title?: string;
|
|
45
|
+
url: string;
|
|
46
|
+
}
|
|
47
|
+
export interface DaemonStatus {
|
|
48
|
+
pid: number;
|
|
49
|
+
root: string;
|
|
50
|
+
viewerPort: number;
|
|
51
|
+
startedAt: number;
|
|
52
|
+
shares: number;
|
|
53
|
+
viewers: number;
|
|
54
|
+
}
|
|
55
|
+
/** 한 요청 줄의 한도. 소켓은 0600이지만 같은 사용자의 프로세스가 메모리를 못 불리게 둔다 */
|
|
56
|
+
export declare const MAX_REQUEST_BYTES: number;
|
|
57
|
+
/** 기본 만료. 설계 원문의 8시간 */
|
|
58
|
+
export declare const DEFAULT_EXPIRES_MINUTES: number;
|
|
59
|
+
/** 만료 상한. 공유를 사실상 영구로 열어 두지 못하게 한다 */
|
|
60
|
+
export declare const MAX_EXPIRES_MINUTES: number;
|
|
61
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../../src/peer/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,8BAA8B;AAC9B,MAAM,MAAM,WAAW,GACnB;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,GACd;IAAE,EAAE,EAAE,QAAQ,CAAA;CAAE,GAChB;IAAE,EAAE,EAAE,QAAQ,CAAA;CAAE,GAChB;IACE,EAAE,EAAE,OAAO,CAAC;IACZ,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,GACD;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC,MAAM,MAAM,YAAY,CAAC,CAAC,GAAG,OAAO,IAAI;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/F,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,sBAAsB;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,sBAAsB;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,yCAAyC;IACzC,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,0DAA0D;AAC1D,eAAO,MAAM,iBAAiB,QAAY,CAAC;AAE3C,wBAAwB;AACxB,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C,sCAAsC;AACtC,eAAO,MAAM,mBAAmB,QAAc,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** 한 요청 줄의 한도. 소켓은 0600이지만 같은 사용자의 프로세스가 메모리를 못 불리게 둔다 */
|
|
2
|
+
export const MAX_REQUEST_BYTES = 64 * 1024;
|
|
3
|
+
/** 기본 만료. 설계 원문의 8시간 */
|
|
4
|
+
export const DEFAULT_EXPIRES_MINUTES = 8 * 60;
|
|
5
|
+
/** 만료 상한. 공유를 사실상 영구로 열어 두지 못하게 한다 */
|
|
6
|
+
export const MAX_EXPIRES_MINUTES = 7 * 24 * 60;
|
|
7
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../../src/peer/protocol.ts"],"names":[],"mappings":"AAiDA,0DAA0D;AAC1D,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,GAAG,IAAI,CAAC;AAE3C,wBAAwB;AACxB,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,GAAG,EAAE,CAAC;AAE9C,sCAAsC;AACtC,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export declare const REDACTED = "[\uAC00\uB9BC]";
|
|
2
|
+
/** `--tools summary`일 때 도구 결과를 자르는 길이 */
|
|
3
|
+
export declare const TOOL_RESULT_SUMMARY_BYTES: number;
|
|
4
|
+
export type ToolsLevel = 'full' | 'summary' | 'none';
|
|
5
|
+
export declare const TOOLS_LEVELS: readonly ToolsLevel[];
|
|
6
|
+
export declare function isToolsLevel(value: unknown): value is ToolsLevel;
|
|
7
|
+
export interface RedactOptions {
|
|
8
|
+
/** `~`로 바꿀 홈 디렉토리. 테스트가 가짜 홈을 넣는다 */
|
|
9
|
+
home?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function redactText(text: string, options?: RedactOptions): string;
|
|
12
|
+
/** 키를 `_`, `-`, `.`, camelCase 경계로 나눠 민감한 낱말이 있는지 본다 */
|
|
13
|
+
export declare function isSensitiveKey(key: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* 도구 입력처럼 모양을 모르는 값을 가린다. 문자열은 패턴으로 가리고 민감한 키 아래
|
|
16
|
+
* 값은 패턴과 상관없이 가린다. `{ password: "hunter2" }`의 값은 어떤 패턴에도 안 걸린다.
|
|
17
|
+
*
|
|
18
|
+
* 민감한 키 아래가 평범한 객체면 안으로 들어가고 원시값이나 배열이면 통째로 가린다.
|
|
19
|
+
* 배열까지 들어가면 `cookies: ["sid=abc"]`의 원소가 그대로 나간다.
|
|
20
|
+
*/
|
|
21
|
+
export declare function redactValue(value: unknown, options?: RedactOptions): unknown;
|
|
22
|
+
/**
|
|
23
|
+
* UTF-8 바이트 기준으로 앞부분만 남긴다. 반드시 가린 **뒤에** 자른다 — 먼저 자르면
|
|
24
|
+
* PEM 끝 표시가 잘려 나가거나 토큰 앞부분만 남아 패턴에 안 걸린다.
|
|
25
|
+
*/
|
|
26
|
+
export declare function truncateBytes(text: string, maxBytes: number): {
|
|
27
|
+
text: string;
|
|
28
|
+
truncated: boolean;
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=redact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../../src/peer/redact.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,QAAQ,mBAAS,CAAC;AAE/B,yCAAyC;AACzC,eAAO,MAAM,yBAAyB,QAAW,CAAC;AAElD,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;AAErD,eAAO,MAAM,YAAY,EAAE,SAAS,UAAU,EAAgC,CAAC;AAE/E,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAEhE;AAED,MAAM,WAAW,aAAa;IAC5B,qCAAqC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAmID,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAkB5E;AAyCD,wDAAwD;AACxD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CASnD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAYhF;AAMD;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAOtC"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
export const REDACTED = '[가림]';
|
|
3
|
+
/** `--tools summary`일 때 도구 결과를 자르는 길이 */
|
|
4
|
+
export const TOOL_RESULT_SUMMARY_BYTES = 4 * 1024;
|
|
5
|
+
export const TOOLS_LEVELS = ['full', 'summary', 'none'];
|
|
6
|
+
export function isToolsLevel(value) {
|
|
7
|
+
return typeof value === 'string' && TOOLS_LEVELS.includes(value);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* 비밀값 패턴. 순서가 의미 있다 — PEM 블록을 먼저 통째로 지워야 그 안의 줄이
|
|
11
|
+
* 다른 패턴에 반쯤 걸려 조각이 남지 않는다.
|
|
12
|
+
*
|
|
13
|
+
* 다 잡지는 못한다. 그래서 공유를 열 때와 뷰어 상단에 같은 경고를 띄운다.
|
|
14
|
+
*/
|
|
15
|
+
const PATTERNS = [
|
|
16
|
+
// 끝 표시가 없으면(잘린 출력) 끝까지 지운다. 본문 일부가 남는 것보다 낫다
|
|
17
|
+
/-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z0-9 ]*PRIVATE KEY-----|$)/g,
|
|
18
|
+
/\b(?:AKIA|ASIA|ABIA|ACCA)[0-9A-Z]{16}\b/g,
|
|
19
|
+
/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}\b/g,
|
|
20
|
+
/\bgithub_pat_[A-Za-z0-9_]{20,}\b/g,
|
|
21
|
+
/\bglpat-[A-Za-z0-9_-]{20,}/g,
|
|
22
|
+
/\bnpm_[A-Za-z0-9]{30,}/g,
|
|
23
|
+
/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g,
|
|
24
|
+
/\bsk-ant-[A-Za-z0-9_-]{16,}/g,
|
|
25
|
+
/\bsk-[A-Za-z0-9_-]{20,}/g,
|
|
26
|
+
/\b[rs]k_(?:live|test)_[A-Za-z0-9]{16,}/g,
|
|
27
|
+
/\bAIza[A-Za-z0-9_-]{35}/g,
|
|
28
|
+
/\bxox[abposr]-[A-Za-z0-9-]{10,}/g,
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* `https://user:pass@host`. 누구로 붙는지는 남기고 비밀번호만 지운다. Redis 접속 문자열에
|
|
32
|
+
* 흔한 `redis://:pass@host`처럼 이름 없이 비밀번호만 둔 꼴도 잡으려고 이름 자리를 비워도 받는다.
|
|
33
|
+
*/
|
|
34
|
+
const URL_PASSWORD = /\b([a-z][a-z0-9+.-]{0,20}:\/\/[^\s:/@]{0,256}:)[^\s/@]{1,256}@/gi;
|
|
35
|
+
/** `Authorization: Bearer xxx` 같은 헤더. 이름은 남기고 값만 지운다 */
|
|
36
|
+
const BEARER = /\b(Bearer)\s+[A-Za-z0-9._~+/=-]{8,}/gi;
|
|
37
|
+
/**
|
|
38
|
+
* `Basic` 뒤 base64 값. 대소문자를 가리고 값에 숫자나 `+`, `/`, `=`가 있거나 둘째 글자부터
|
|
39
|
+
* 대문자가 있어야 건다. `Basic authentication`이나 `Basic Configuration` 같은 영어 문장은
|
|
40
|
+
* 그대로 둔다. 헤더 꼴이 아닌 `{ authorization: 'Basic …' }` 같은 객체 출력은 이 패턴만 잡는다.
|
|
41
|
+
* 전부 대문자인 `BASIC`은 헤더 줄과 따옴표 안 헤더 패턴에만 맡긴다. 여기서 받으면 `BASIC SETTINGS` 같은 대문자
|
|
42
|
+
* 제목이 가려진다.
|
|
43
|
+
*
|
|
44
|
+
* 값은 base64 글자로만 되고 끝의 `=` 채움까지 쳐서 길이가 4의 배수인 8자 이상이어야 한다.
|
|
45
|
+
* 그래야 `Basic README.md`나 `Basic 2024-10-28` 같은 낱말이 값으로 안 읽힌다. 값 뒤에
|
|
46
|
+
* 마침표가 와도 그 뒤에 글자가 오면 값으로 안 본다. 문장 끝 마침표 앞의 값은 가리고
|
|
47
|
+
* `Ubuntu22.04` 같은 낱말은 남긴다.
|
|
48
|
+
*/
|
|
49
|
+
const BASIC = /\b(Basic)[ \t]+(?=[A-Za-z0-9+/]*?[0-9+/=]|[A-Za-z0-9+/][a-z0-9+/]*?[A-Z])(?:(?:[A-Za-z0-9+/]{4})+(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)|(?:[A-Za-z0-9+/]{4}){2,})(?![A-Za-z0-9+/=_~-]|\.[A-Za-z0-9])/g;
|
|
50
|
+
/**
|
|
51
|
+
* 헤더 이름. token과 secret은 앞에 하이픈으로 이어진 조각이 있을 때만 받는다. 그래야
|
|
52
|
+
* `X-Auth-Token`과 `PRIVATE-TOKEN`은 걸리고 코드의 `token: string;`이나 `max_tokens: 100`은
|
|
53
|
+
* 줄 끝까지 가려지지 않는다.
|
|
54
|
+
*/
|
|
55
|
+
const HEADER_NAME = '(?:set-cookie|cookie|(?:[a-z0-9]+-)*api-key|(?:[a-z0-9]+-)+(?:token|secret)|(?:proxy-)?authorization)';
|
|
56
|
+
/**
|
|
57
|
+
* `curl -v`가 찍는 `> Cookie: …`나 `< X-Auth-Token: …` 줄. 값에 정해진 꼴이 없어서 콜론 뒤를
|
|
58
|
+
* 줄 끝까지 가린다. `Bearer`나 `Basic`은 머리에 넣어 BEARER와 BASIC이 남긴 `Bearer [가림]`과
|
|
59
|
+
* `Basic [가림]`을 그대로 둔다.
|
|
60
|
+
*/
|
|
61
|
+
const HEADER_LINE = new RegExp(String.raw `^([ \t]*(?:[<>][ \t]*)?${HEADER_NAME}[ \t]*:[ \t]*(?:(?:Bearer|Basic)[ \t]+)?)[^\n]+`, 'gim');
|
|
62
|
+
/**
|
|
63
|
+
* `curl -H "X-Api-Key: …" https://…`처럼 따옴표 안에 든 헤더. 줄 끝까지 가리면 뒤 URL과
|
|
64
|
+
* 옵션까지 사라지므로 닫는 따옴표 앞에서 멈춘다.
|
|
65
|
+
*/
|
|
66
|
+
const HEADER_QUOTED = new RegExp(String.raw `(["'])([ \t]*${HEADER_NAME}[ \t]*:[ \t]*(?:(?:Bearer|Basic)[ \t]+)?)[^"'\n]+`, 'gi');
|
|
67
|
+
/**
|
|
68
|
+
* `API_KEY=…`, `export GH_TOKEN=…`처럼 이름에 KEY, TOKEN, SECRET, PASSWORD, PASSWD, PASS,
|
|
69
|
+
* PWD, COOKIE, CREDENTIAL(S), AUTH가 든 대입. 이름은 남겨야 무엇이 가려졌는지 읽힌다. `=` 앞
|
|
70
|
+
* 공백은 안 받는다. 코드의 `key = value`까지 걸면 도구 출력이 거의 다 가려진다.
|
|
71
|
+
*
|
|
72
|
+
* PASS와 AUTH는 앞뒤가 글자가 아닐 때만 받는다. 안 그러면 `BYPASS=1`, `--bypass=true`,
|
|
73
|
+
* `OAUTH_CLIENT_ID=…`, `AUTHOR=…`가 걸린다. PWD는 `_PWD`만 받는다. 셸이 찍는 `PWD=`와
|
|
74
|
+
* `OLDPWD=`는 비밀이 아니라 작업 경로다.
|
|
75
|
+
*
|
|
76
|
+
* 매치는 이름 속 KEY 같은 낱말에서 시작한다. 그 앞의 이름 조각은 매치 밖이라 그대로
|
|
77
|
+
* 남는다. 이름 앞부분까지 정규식에 넣으면 `a.a.a.…`나 `KEYKEY…` 같은 입력에서 시작점마다
|
|
78
|
+
* 앞뒤 조합을 다 훑느라 역추적이 터진다. 낱말 뒤 조각은 64자까지 미리보기로 한 번에
|
|
79
|
+
* 잡고 되돌아가지 않는다(`(?=(…))\2`).
|
|
80
|
+
*
|
|
81
|
+
* 값은 닫힌 따옴표, 따옴표 안 줄바꿈(닫는 따옴표를 4096자 안에서 찾는다), JSON 문자열
|
|
82
|
+
* 안에 이스케이프된 따옴표, 못 닫힌 따옴표(줄 끝까지), 맨 값 순서로 본다.
|
|
83
|
+
*
|
|
84
|
+
* 이스케이프된 따옴표 값은 백슬래시 개수로 갈라 읽는다. 한 번 더 감싸인 글자에서 값 안의
|
|
85
|
+
* 따옴표는 백슬래시 셋과 따옴표가 되고 값 끝 백슬래시는 닫는 따옴표 앞에 백슬래시 넷을
|
|
86
|
+
* 더 붙인다. 이 둘을 닫는 따옴표로 잘못 읽으면 뒷부분이 그대로 나가거나 다음 따옴표까지
|
|
87
|
+
* 넘어가 이웃 값이 새므로 넷, 셋과 따옴표, 둘, 하나를 서로 겹치지 않게 나눈다.
|
|
88
|
+
*/
|
|
89
|
+
const ASSIGNMENT = /((?:KEY|TOKEN|SECRET|PASSWORD|PASSWD|(?<![a-z])PASS(?![a-z])|_PWD(?![a-z])|COOKIE|CREDENTIALS?|(?<![a-z])AUTH(?![a-z]))(?=([A-Za-z0-9_.-]{0,64}))\2)=(?:"(?:[^"\\]|\\[\s\S]){0,4096}"|'[^']{0,4096}'|\\"(?:[^"\\\n]|\\[^"\\\n]|\\\\\\\\|\\\\\\"|\\\\[^"\\\n]){0,4096}\\"|\\?["'][^\n]*|[^\s"'\\]+)/gi;
|
|
90
|
+
/**
|
|
91
|
+
* JSON이나 YAML 꼴 `"api_key": "…"`. 큰따옴표 친 키만 본다. 대화 기록 안에서 JSON이
|
|
92
|
+
* 문자열로 한 번 더 감싸이면 `\"api_key\": \"…\"`가 되므로 이스케이프된 따옴표도 받는다.
|
|
93
|
+
* 감싼 값은 ASSIGNMENT와 같이 백슬래시 개수로 갈라 읽는다.
|
|
94
|
+
*
|
|
95
|
+
* 값 안에 `\n`으로 이스케이프하지 않은 줄바꿈이 나오면 닫는 따옴표를 찾지 않고 그 줄 끝까지만
|
|
96
|
+
* 가린다. JSON 문자열에는 이스케이프하지 않은 줄바꿈이 못 들어가므로 이건 따옴표가 안 닫힌
|
|
97
|
+
* 값이다. 다음 줄까지 넘어가 닫는 따옴표를 찾으면 그 줄에 있던 다른 민감한 키를 건너뛰어 그
|
|
98
|
+
* 값이 샌다.
|
|
99
|
+
*
|
|
100
|
+
* 낱말 목록은 isSensitiveKey와 따로 간다. 여기는 키 이름의 부분 문자열로 맞추므로
|
|
101
|
+
* `auth`는 뒤가, `pass`는 앞뒤가 글자가 아닐 때만 받는다. 안 그러면 `"author"`와
|
|
102
|
+
* `"passed"`, `"bypass"`가 걸린다.
|
|
103
|
+
*/
|
|
104
|
+
const SENSITIVE_FIELD_WORD = '(?:key|token|secret|password|passwd|passphrase|(?<![a-z])pass(?![a-z])|pwd|cookie|credential|authorization|auth(?![a-z]))';
|
|
105
|
+
const QUOTED_FIELD = new RegExp(String.raw `(\\?"[A-Za-z0-9_.-]{0,64}${SENSITIVE_FIELD_WORD}[A-Za-z0-9_.-]{0,64}\\?"\s*:\s*)(?:"(?:[^"\\\n]|\\.){0,4096}"|\\"(?:[^"\\\n]|\\[^"\\\n]|\\\\\\\\|\\\\\\"|\\\\[^"\\\n]){0,4096}\\"|\\?"[^\n]*)`, 'gi');
|
|
106
|
+
/**
|
|
107
|
+
* Python이 찍는 `{'Cookie': 'sid=…'}`처럼 작은따옴표 친 키. QUOTED_FIELD와 같은 낱말을 쓴다.
|
|
108
|
+
* 값은 같은 줄에서 4096자 안에 닫는 작은따옴표가 있어야 가린다.
|
|
109
|
+
*/
|
|
110
|
+
const SINGLE_QUOTED_FIELD = new RegExp(String.raw `('[A-Za-z0-9_.-]{0,64}${SENSITIVE_FIELD_WORD}[A-Za-z0-9_.-]{0,64}'\s*:\s*)'(?:[^'\\\n]|\\.){0,4096}'`, 'gi');
|
|
111
|
+
export function redactText(text, options = {}) {
|
|
112
|
+
let out = text;
|
|
113
|
+
for (const pattern of PATTERNS)
|
|
114
|
+
out = out.replace(pattern, REDACTED);
|
|
115
|
+
out = out.replace(URL_PASSWORD, (_m, head) => `${head}${REDACTED}@`);
|
|
116
|
+
out = out.replace(BEARER, (_m, scheme) => `${scheme} ${REDACTED}`);
|
|
117
|
+
out = out.replace(BASIC, (_m, scheme) => `${scheme} ${REDACTED}`);
|
|
118
|
+
out = out.replace(HEADER_LINE, (_m, head) => `${head}${REDACTED}`);
|
|
119
|
+
out = out.replace(HEADER_QUOTED, (_m, quote, head) => `${quote}${head}${REDACTED}`);
|
|
120
|
+
out = out.replace(ASSIGNMENT, (_m, name) => `${name}=${REDACTED}`);
|
|
121
|
+
out = out.replace(QUOTED_FIELD, (_m, head) => {
|
|
122
|
+
const q = head.startsWith('\\') ? '\\"' : '"';
|
|
123
|
+
return `${head}${q}${REDACTED}${q}`;
|
|
124
|
+
});
|
|
125
|
+
out = out.replace(SINGLE_QUOTED_FIELD, (_m, head) => `${head}'${REDACTED}'`);
|
|
126
|
+
return replaceHome(out, options.home ?? homedir());
|
|
127
|
+
}
|
|
128
|
+
function replaceHome(text, home) {
|
|
129
|
+
const trimmed = home.replace(/\/+$/, '');
|
|
130
|
+
// `/`나 빈 홈이면 모든 경로가 `~`로 바뀌어 읽을 수 없게 된다
|
|
131
|
+
if (trimmed.length < 2)
|
|
132
|
+
return text;
|
|
133
|
+
const escaped = trimmed.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
134
|
+
// `/Users/alice`가 `/Users/alice2`까지 먹지 않게 뒤를 경로 경계로 묶는다
|
|
135
|
+
return text.replace(new RegExp(`${escaped}(?=[/\\s"'\`:;,)\\]}]|$)`, 'g'), '~');
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* 이름만 봐도 값이 비밀인 키. 낱말 단위로 맞춘다. `keyword`나 `author`처럼 낱말 일부만
|
|
139
|
+
* 겹치는 키는 안 건드린다.
|
|
140
|
+
*
|
|
141
|
+
* 소문자로 붙여 쓴 `accesstoken`이나 `privatekey` 같은 낱말은 SENSITIVE_KEY_SUFFIX로 낱말
|
|
142
|
+
* 끝을 본다. `key`와 `pass`는 낱말 끝만 보면 `monkey`나 `bypass`가 걸린다. 그래서 `pass`는
|
|
143
|
+
* 낱말이 똑같을 때만 걸고 `key`는 access, secret, private, signing, encryption, master, api가
|
|
144
|
+
* 바로 앞에 붙은 합성어만 받는다. token은 복수형을 안 받는다. `max_tokens`나
|
|
145
|
+
* `input_tokens`가 흔한 도구 입력 키다.
|
|
146
|
+
*/
|
|
147
|
+
const SENSITIVE_KEY_WORDS = new Set([
|
|
148
|
+
'key',
|
|
149
|
+
'apikey',
|
|
150
|
+
'token',
|
|
151
|
+
'secret',
|
|
152
|
+
'password',
|
|
153
|
+
'passwd',
|
|
154
|
+
'pass',
|
|
155
|
+
'pwd',
|
|
156
|
+
'auth',
|
|
157
|
+
'authorization',
|
|
158
|
+
'credential',
|
|
159
|
+
'credentials',
|
|
160
|
+
'cookie',
|
|
161
|
+
'cookies',
|
|
162
|
+
]);
|
|
163
|
+
const SENSITIVE_KEY_SUFFIX = /(?:token|(?:secret|password|passwd|passphrase)s?|(?:access|secret|private|signing|encryption|master|api)keys?)$/;
|
|
164
|
+
/** 키를 `_`, `-`, `.`, camelCase 경계로 나눠 민감한 낱말이 있는지 본다 */
|
|
165
|
+
export function isSensitiveKey(key) {
|
|
166
|
+
const words = key
|
|
167
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
168
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
169
|
+
.split(/[\s_.-]+/);
|
|
170
|
+
return words.some((w) => {
|
|
171
|
+
const lower = w.toLowerCase();
|
|
172
|
+
return SENSITIVE_KEY_WORDS.has(lower) || SENSITIVE_KEY_SUFFIX.test(lower);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* 도구 입력처럼 모양을 모르는 값을 가린다. 문자열은 패턴으로 가리고 민감한 키 아래
|
|
177
|
+
* 값은 패턴과 상관없이 가린다. `{ password: "hunter2" }`의 값은 어떤 패턴에도 안 걸린다.
|
|
178
|
+
*
|
|
179
|
+
* 민감한 키 아래가 평범한 객체면 안으로 들어가고 원시값이나 배열이면 통째로 가린다.
|
|
180
|
+
* 배열까지 들어가면 `cookies: ["sid=abc"]`의 원소가 그대로 나간다.
|
|
181
|
+
*/
|
|
182
|
+
export function redactValue(value, options = {}) {
|
|
183
|
+
if (typeof value === 'string')
|
|
184
|
+
return redactText(value, options);
|
|
185
|
+
if (Array.isArray(value))
|
|
186
|
+
return value.map((v) => redactValue(v, options));
|
|
187
|
+
if (value && typeof value === 'object') {
|
|
188
|
+
const out = {};
|
|
189
|
+
for (const [k, v] of Object.entries(value)) {
|
|
190
|
+
const hide = isSensitiveKey(k) && !isPlainObject(v);
|
|
191
|
+
out[redactText(k, options)] = hide ? REDACTED : redactValue(v, options);
|
|
192
|
+
}
|
|
193
|
+
return out;
|
|
194
|
+
}
|
|
195
|
+
return value;
|
|
196
|
+
}
|
|
197
|
+
function isPlainObject(value) {
|
|
198
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* UTF-8 바이트 기준으로 앞부분만 남긴다. 반드시 가린 **뒤에** 자른다 — 먼저 자르면
|
|
202
|
+
* PEM 끝 표시가 잘려 나가거나 토큰 앞부분만 남아 패턴에 안 걸린다.
|
|
203
|
+
*/
|
|
204
|
+
export function truncateBytes(text, maxBytes) {
|
|
205
|
+
const buf = Buffer.from(text, 'utf8');
|
|
206
|
+
if (buf.length <= maxBytes)
|
|
207
|
+
return { text, truncated: false };
|
|
208
|
+
// 멀티바이트 글자 중간에서 끊기면 깨진 글자가 남는다. 이어지는 바이트(10xxxxxx)를 건너뛴다
|
|
209
|
+
let end = maxBytes;
|
|
210
|
+
while (end > 0 && ((buf[end] ?? 0) & 0xc0) === 0x80)
|
|
211
|
+
end--;
|
|
212
|
+
return { text: buf.subarray(0, end).toString('utf8'), truncated: true };
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../../../src/peer/redact.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC;AAE/B,yCAAyC;AACzC,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,GAAG,IAAI,CAAC;AAIlD,MAAM,CAAC,MAAM,YAAY,GAA0B,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;AAE/E,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,YAAkC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC1F,CAAC;AAOD;;;;;GAKG;AACH,MAAM,QAAQ,GAAsB;IAClC,6CAA6C;IAC7C,yFAAyF;IACzF,0CAA0C;IAC1C,+CAA+C;IAC/C,mCAAmC;IACnC,6BAA6B;IAC7B,yBAAyB;IACzB,+DAA+D;IAC/D,8BAA8B;IAC9B,0BAA0B;IAC1B,yCAAyC;IACzC,0BAA0B;IAC1B,kCAAkC;CACnC,CAAC;AAEF;;;GAGG;AACH,MAAM,YAAY,GAAG,kEAAkE,CAAC;AAExF,wDAAwD;AACxD,MAAM,MAAM,GAAG,uCAAuC,CAAC;AAEvD;;;;;;;;;;;GAWG;AACH,MAAM,KAAK,GACT,yMAAyM,CAAC;AAE5M;;;;GAIG;AACH,MAAM,WAAW,GACf,uGAAuG,CAAC;AAE1G;;;;GAIG;AACH,MAAM,WAAW,GAAG,IAAI,MAAM,CAC5B,MAAM,CAAC,GAAG,CAAA,0BAA0B,WAAW,iDAAiD,EAChG,KAAK,CACN,CAAC;AAEF;;;GAGG;AACH,MAAM,aAAa,GAAG,IAAI,MAAM,CAC9B,MAAM,CAAC,GAAG,CAAA,gBAAgB,WAAW,mDAAmD,EACxF,IAAI,CACL,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,GACd,sSAAsS,CAAC;AAEzS;;;;;;;;;;;;;GAaG;AACH,MAAM,oBAAoB,GACxB,2HAA2H,CAAC;AAE9H,MAAM,YAAY,GAAG,IAAI,MAAM,CAC7B,MAAM,CAAC,GAAG,CAAA,4BAA4B,oBAAoB,+IAA+I,EACzM,IAAI,CACL,CAAC;AAEF;;;GAGG;AACH,MAAM,mBAAmB,GAAG,IAAI,MAAM,CACpC,MAAM,CAAC,GAAG,CAAA,yBAAyB,oBAAoB,yDAAyD,EAChH,IAAI,CACL,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,UAAyB,EAAE;IAClE,IAAI,GAAG,GAAG,IAAI,CAAC;IACf,KAAK,MAAM,OAAO,IAAI,QAAQ;QAAE,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACrE,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,EAAE,EAAE,IAAY,EAAE,EAAE,CAAC,GAAG,IAAI,GAAG,QAAQ,GAAG,CAAC,CAAC;IAC7E,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,MAAc,EAAE,EAAE,CAAC,GAAG,MAAM,IAAI,QAAQ,EAAE,CAAC,CAAC;IAC3E,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,MAAc,EAAE,EAAE,CAAC,GAAG,MAAM,IAAI,QAAQ,EAAE,CAAC,CAAC;IAC1E,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,EAAE,EAAE,IAAY,EAAE,EAAE,CAAC,GAAG,IAAI,GAAG,QAAQ,EAAE,CAAC,CAAC;IAC3E,GAAG,GAAG,GAAG,CAAC,OAAO,CACf,aAAa,EACb,CAAC,EAAE,EAAE,KAAa,EAAE,IAAY,EAAE,EAAE,CAAC,GAAG,KAAK,GAAG,IAAI,GAAG,QAAQ,EAAE,CAClE,CAAC;IACF,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,IAAY,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,QAAQ,EAAE,CAAC,CAAC;IAC3E,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,EAAE,EAAE,IAAY,EAAE,EAAE;QACnD,MAAM,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;QAC9C,OAAO,GAAG,IAAI,GAAG,CAAC,GAAG,QAAQ,GAAG,CAAC,EAAE,CAAC;IACtC,CAAC,CAAC,CAAC;IACH,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,mBAAmB,EAAE,CAAC,EAAE,EAAE,IAAY,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,QAAQ,GAAG,CAAC,CAAC;IACrF,OAAO,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,IAAI,OAAO,EAAE,CAAC,CAAC;AACrD,CAAC;AAED,SAAS,WAAW,CAAC,IAAY,EAAE,IAAY;IAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACzC,wCAAwC;IACxC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;IAC/D,wDAAwD;IACxD,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,GAAG,OAAO,0BAA0B,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;AAClF,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC;IAClC,KAAK;IACL,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,UAAU;IACV,QAAQ;IACR,MAAM;IACN,KAAK;IACL,MAAM;IACN,eAAe;IACf,YAAY;IACZ,aAAa;IACb,QAAQ;IACR,SAAS;CACV,CAAC,CAAC;AAEH,MAAM,oBAAoB,GACxB,iHAAiH,CAAC;AAEpH,wDAAwD;AACxD,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,MAAM,KAAK,GAAG,GAAG;SACd,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC;SACtC,OAAO,CAAC,uBAAuB,EAAE,OAAO,CAAC;SACzC,KAAK,CAAC,UAAU,CAAC,CAAC;IACrB,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;QACtB,MAAM,KAAK,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;QAC9B,OAAO,mBAAmB,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,KAAc,EAAE,UAAyB,EAAE;IACrE,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACjE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3E,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvC,MAAM,GAAG,GAA4B,EAAE,CAAC;QACxC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3C,MAAM,IAAI,GAAG,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;YACpD,GAAG,CAAC,UAAU,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;QAC1E,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAC3B,IAAY,EACZ,QAAgB;IAEhB,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACtC,IAAI,GAAG,CAAC,MAAM,IAAI,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IAC9D,yDAAyD;IACzD,IAAI,GAAG,GAAG,QAAQ,CAAC;IACnB,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,IAAI;QAAE,GAAG,EAAE,CAAC;IAC3D,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AAC1E,CAAC"}
|