brew-tui 0.4.1 → 0.5.1

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 (38) hide show
  1. package/README.md +50 -16
  2. package/build/{brewbar-installer-6BR3MPVZ.js → brewbar-installer-ZEMXNDHP.js} +4 -3
  3. package/build/{brewbar-installer-6BR3MPVZ.js.map → brewbar-installer-ZEMXNDHP.js.map} +1 -1
  4. package/build/brewfile-manager-3SERRYNC.js +20 -0
  5. package/build/chunk-42URLVAJ.js +299 -0
  6. package/build/chunk-42URLVAJ.js.map +1 -0
  7. package/build/chunk-4I344KQX.js +221 -0
  8. package/build/chunk-4I344KQX.js.map +1 -0
  9. package/build/chunk-KDHEUNRI.js +62 -0
  10. package/build/chunk-KDHEUNRI.js.map +1 -0
  11. package/build/{chunk-E7ZREAJW.js → chunk-KDJNCZD7.js} +237 -17
  12. package/build/chunk-KDJNCZD7.js.map +1 -0
  13. package/build/chunk-KN4GCMIE.js +48 -0
  14. package/build/chunk-KN4GCMIE.js.map +1 -0
  15. package/build/{chunk-65YZJX2E.js → chunk-KVCVIRWI.js} +6 -20
  16. package/build/chunk-KVCVIRWI.js.map +1 -0
  17. package/build/chunk-LXF72RCD.js +467 -0
  18. package/build/chunk-LXF72RCD.js.map +1 -0
  19. package/build/chunk-U2DRWB7A.js +123 -0
  20. package/build/chunk-U2DRWB7A.js.map +1 -0
  21. package/build/chunk-UWS4A4F5.js +25 -0
  22. package/build/chunk-UWS4A4F5.js.map +1 -0
  23. package/build/compliance-checker-X7P623UF.js +12 -0
  24. package/build/compliance-checker-X7P623UF.js.map +1 -0
  25. package/build/{history-logger-2PGYSPFL.js → history-logger-PBDOLKNJ.js} +3 -2
  26. package/build/history-logger-PBDOLKNJ.js.map +1 -0
  27. package/build/index.js +2010 -457
  28. package/build/index.js.map +1 -1
  29. package/build/policy-io-EECGRKNA.js +11 -0
  30. package/build/policy-io-EECGRKNA.js.map +1 -0
  31. package/build/snapshot-RAPGMAJF.js +17 -0
  32. package/build/snapshot-RAPGMAJF.js.map +1 -0
  33. package/build/sync-engine-4ERSW4EQ.js +18 -0
  34. package/build/sync-engine-4ERSW4EQ.js.map +1 -0
  35. package/package.json +11 -9
  36. package/build/chunk-65YZJX2E.js.map +0 -1
  37. package/build/chunk-E7ZREAJW.js.map +0 -1
  38. /package/build/{history-logger-2PGYSPFL.js.map → brewfile-manager-3SERRYNC.js.map} +0 -0
@@ -0,0 +1,221 @@
1
+ import {
2
+ SNAPSHOTS_DIR,
3
+ ensureDataDirs
4
+ } from "./chunk-UWS4A4F5.js";
5
+ import {
6
+ logger
7
+ } from "./chunk-KDHEUNRI.js";
8
+
9
+ // src/lib/state-snapshot/snapshot.ts
10
+ import { readFile, writeFile, rename, readdir, unlink } from "fs/promises";
11
+ import { join } from "path";
12
+
13
+ // src/lib/brew-cli.ts
14
+ import { spawn } from "child_process";
15
+ var DEFAULT_TIMEOUT_MS = 3e4;
16
+ var STREAM_IDLE_TIMEOUT_MS = 5 * 60 * 1e3;
17
+ async function execBrew(args, timeoutMs = DEFAULT_TIMEOUT_MS) {
18
+ return new Promise((resolve, reject) => {
19
+ const proc = spawn("brew", args, { env: { ...process.env, HOMEBREW_NO_AUTO_UPDATE: "1" } });
20
+ let stdout = "";
21
+ let stderr = "";
22
+ let killed = false;
23
+ const timer = setTimeout(() => {
24
+ killed = true;
25
+ proc.kill();
26
+ reject(new Error(`brew ${args.join(" ")} timed out after ${timeoutMs}ms`));
27
+ }, timeoutMs);
28
+ proc.stdout.on("data", (d) => {
29
+ stdout += d.toString();
30
+ });
31
+ proc.stderr.on("data", (d) => {
32
+ stderr += d.toString();
33
+ });
34
+ proc.on("close", (code) => {
35
+ clearTimeout(timer);
36
+ if (killed) return;
37
+ if (code === 0) {
38
+ resolve(stdout);
39
+ } else {
40
+ reject(new Error(stderr.trim() || `brew ${args.join(" ")} exited with code ${code}`));
41
+ }
42
+ });
43
+ proc.on("error", (err) => {
44
+ clearTimeout(timer);
45
+ if (killed) return;
46
+ reject(new Error(`Failed to run brew: ${err.message}`));
47
+ });
48
+ });
49
+ }
50
+ async function* streamBrew(args) {
51
+ const proc = spawn("brew", args, {
52
+ env: { ...process.env, HOMEBREW_NO_AUTO_UPDATE: "1" },
53
+ stdio: ["ignore", "pipe", "pipe"]
54
+ });
55
+ let buffer = "";
56
+ const lines = [];
57
+ let done = false;
58
+ let exitError = null;
59
+ let lastOutputAt = Date.now();
60
+ const push = (chunk) => {
61
+ lastOutputAt = Date.now();
62
+ buffer += chunk.toString();
63
+ const parts = buffer.split("\n");
64
+ buffer = parts.pop() ?? "";
65
+ for (const line of parts) {
66
+ if (line.trim()) lines.push(line);
67
+ }
68
+ };
69
+ proc.stdout.on("data", push);
70
+ proc.stderr.on("data", push);
71
+ proc.on("close", (code) => {
72
+ if (buffer.trim()) lines.push(buffer.trim());
73
+ done = true;
74
+ if (code !== 0) {
75
+ exitError = `brew ${args.join(" ")} exited with code ${code}`;
76
+ }
77
+ });
78
+ proc.on("error", (err) => {
79
+ done = true;
80
+ exitError = err.message;
81
+ });
82
+ try {
83
+ while (!done || lines.length > 0) {
84
+ if (lines.length > 0) {
85
+ yield lines.shift();
86
+ } else if (!done) {
87
+ if (Date.now() - lastOutputAt > STREAM_IDLE_TIMEOUT_MS) {
88
+ proc.kill();
89
+ throw new Error(`brew ${args.join(" ")} timed out: no output for ${STREAM_IDLE_TIMEOUT_MS / 1e3}s`);
90
+ }
91
+ await new Promise((r) => setTimeout(r, 100));
92
+ }
93
+ }
94
+ } finally {
95
+ if (!done) {
96
+ proc.kill();
97
+ }
98
+ }
99
+ if (exitError) {
100
+ throw new Error(exitError);
101
+ }
102
+ }
103
+
104
+ // src/lib/state-snapshot/snapshot.ts
105
+ function isValidSnapshot(v) {
106
+ if (!v || typeof v !== "object") return false;
107
+ const s = v;
108
+ return typeof s["capturedAt"] === "string" && Array.isArray(s["formulae"]) && Array.isArray(s["casks"]) && Array.isArray(s["taps"]);
109
+ }
110
+ function parseVersionsList(output) {
111
+ return output.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
112
+ const parts = line.split(/\s+/);
113
+ const name = parts[0] ?? "";
114
+ const version = parts[parts.length - 1] ?? "";
115
+ return { name, version };
116
+ }).filter((e) => e.name !== "");
117
+ }
118
+ function parseTapsList(output) {
119
+ return output.split("\n").map((line) => line.trim()).filter(Boolean);
120
+ }
121
+ function parsePinnedList(output) {
122
+ return new Set(
123
+ output.split("\n").map((line) => line.trim()).filter(Boolean)
124
+ );
125
+ }
126
+ async function captureSnapshot() {
127
+ const [formulaeRaw, casksRaw, tapsRaw, pinnedRaw] = await Promise.all([
128
+ execBrew(["list", "--versions", "--formula"]),
129
+ execBrew(["list", "--cask", "--versions"]),
130
+ execBrew(["tap"]),
131
+ execBrew(["list", "--pinned"])
132
+ ]);
133
+ const pinned = parsePinnedList(pinnedRaw);
134
+ const formulae = parseVersionsList(formulaeRaw).map((f) => ({
135
+ ...f,
136
+ pinned: pinned.has(f.name)
137
+ }));
138
+ const casks = parseVersionsList(casksRaw);
139
+ const taps = parseTapsList(tapsRaw);
140
+ return {
141
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
142
+ formulae,
143
+ casks,
144
+ taps
145
+ };
146
+ }
147
+ function sanitizeLabel(label) {
148
+ const clean = label.replace(/[^A-Za-z0-9_-]/g, "_");
149
+ return clean.length > 0 ? clean : "auto";
150
+ }
151
+ function timestampToFilename(iso) {
152
+ return iso.replace(/[:.]/g, "-");
153
+ }
154
+ async function saveSnapshot(s, label) {
155
+ await ensureDataDirs();
156
+ const effectiveLabel = label ? sanitizeLabel(label) : "auto";
157
+ const filename = `${timestampToFilename(s.capturedAt)}-${effectiveLabel}.json`;
158
+ const filePath = join(SNAPSHOTS_DIR, filename);
159
+ const tmpPath = filePath + ".tmp";
160
+ const payload = label ? { ...s, label } : s;
161
+ await writeFile(tmpPath, JSON.stringify(payload, null, 2), { encoding: "utf-8", mode: 384 });
162
+ await rename(tmpPath, filePath);
163
+ }
164
+ async function loadSnapshots() {
165
+ let entries;
166
+ try {
167
+ entries = await readdir(SNAPSHOTS_DIR);
168
+ } catch {
169
+ return [];
170
+ }
171
+ const jsonFiles = entries.filter((f) => f.endsWith(".json"));
172
+ const snapshots = [];
173
+ for (const filename of jsonFiles) {
174
+ try {
175
+ const raw = await readFile(join(SNAPSHOTS_DIR, filename), "utf-8");
176
+ const parsed = JSON.parse(raw);
177
+ if (!isValidSnapshot(parsed)) {
178
+ logger.warn("Skipping corrupt snapshot file", { filename });
179
+ continue;
180
+ }
181
+ snapshots.push(parsed);
182
+ } catch {
183
+ logger.warn("Failed to read snapshot file", { filename });
184
+ }
185
+ }
186
+ return snapshots.sort((a, b) => b.capturedAt.localeCompare(a.capturedAt));
187
+ }
188
+ async function deleteSnapshot(capturedAt) {
189
+ let entries;
190
+ try {
191
+ entries = await readdir(SNAPSHOTS_DIR);
192
+ } catch {
193
+ return;
194
+ }
195
+ for (const filename of entries.filter((f) => f.endsWith(".json"))) {
196
+ try {
197
+ const raw = await readFile(join(SNAPSHOTS_DIR, filename), "utf-8");
198
+ const parsed = JSON.parse(raw);
199
+ if (isValidSnapshot(parsed) && parsed.capturedAt === capturedAt) {
200
+ await unlink(join(SNAPSHOTS_DIR, filename));
201
+ return;
202
+ }
203
+ } catch {
204
+ }
205
+ }
206
+ }
207
+ async function getLatestSnapshot() {
208
+ const all = await loadSnapshots();
209
+ return all[0] ?? null;
210
+ }
211
+
212
+ export {
213
+ execBrew,
214
+ streamBrew,
215
+ captureSnapshot,
216
+ saveSnapshot,
217
+ loadSnapshots,
218
+ deleteSnapshot,
219
+ getLatestSnapshot
220
+ };
221
+ //# sourceMappingURL=chunk-4I344KQX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/state-snapshot/snapshot.ts","../src/lib/brew-cli.ts"],"sourcesContent":["import { readFile, writeFile, rename, readdir, unlink } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { execBrew } from '../brew-cli.js';\nimport { SNAPSHOTS_DIR, ensureDataDirs } from '../data-dir.js';\nimport { logger } from '../../utils/logger.js';\n\nexport interface BrewSnapshot {\n capturedAt: string;\n label?: string;\n formulae: Array<{ name: string; version: string; pinned: boolean }>;\n casks: Array<{ name: string; version: string }>;\n taps: string[];\n}\n\nfunction isValidSnapshot(v: unknown): v is BrewSnapshot {\n if (!v || typeof v !== 'object') return false;\n const s = v as Record<string, unknown>;\n return (\n typeof s['capturedAt'] === 'string' &&\n Array.isArray(s['formulae']) &&\n Array.isArray(s['casks']) &&\n Array.isArray(s['taps'])\n );\n}\n\n/** Parse `brew list --versions --formula` or `brew list --cask --versions`.\n * Each line: `name version1 version2...` — last version is the current install. */\nfunction parseVersionsList(output: string): Array<{ name: string; version: string }> {\n return output\n .split('\\n')\n .map((line) => line.trim())\n .filter(Boolean)\n .map((line) => {\n const parts = line.split(/\\s+/);\n const name = parts[0] ?? '';\n const version = parts[parts.length - 1] ?? '';\n return { name, version };\n })\n .filter((e) => e.name !== '');\n}\n\n/** Parse `brew tap` — one tap per line. */\nfunction parseTapsList(output: string): string[] {\n return output\n .split('\\n')\n .map((line) => line.trim())\n .filter(Boolean);\n}\n\n/** Parse `brew list --pinned` — one formula per line. */\nfunction parsePinnedList(output: string): Set<string> {\n return new Set(\n output\n .split('\\n')\n .map((line) => line.trim())\n .filter(Boolean),\n );\n}\n\nexport async function captureSnapshot(): Promise<BrewSnapshot> {\n const [formulaeRaw, casksRaw, tapsRaw, pinnedRaw] = await Promise.all([\n execBrew(['list', '--versions', '--formula']),\n execBrew(['list', '--cask', '--versions']),\n execBrew(['tap']),\n execBrew(['list', '--pinned']),\n ]);\n\n const pinned = parsePinnedList(pinnedRaw);\n const formulae = parseVersionsList(formulaeRaw).map((f) => ({\n ...f,\n pinned: pinned.has(f.name),\n }));\n const casks = parseVersionsList(casksRaw);\n const taps = parseTapsList(tapsRaw);\n\n return {\n capturedAt: new Date().toISOString(),\n formulae,\n casks,\n taps,\n };\n}\n\n/** Sanitize a label for safe use in a filename. */\nfunction sanitizeLabel(label: string): string {\n const clean = label.replace(/[^A-Za-z0-9_-]/g, '_');\n return clean.length > 0 ? clean : 'auto';\n}\n\n/** Convert an ISO 8601 timestamp to a filename-safe string by replacing `:` and `.` with `-`. */\nfunction timestampToFilename(iso: string): string {\n return iso.replace(/[:.]/g, '-');\n}\n\nexport async function saveSnapshot(s: BrewSnapshot, label?: string): Promise<void> {\n await ensureDataDirs();\n\n const effectiveLabel = label ? sanitizeLabel(label) : 'auto';\n const filename = `${timestampToFilename(s.capturedAt)}-${effectiveLabel}.json`;\n const filePath = join(SNAPSHOTS_DIR, filename);\n const tmpPath = filePath + '.tmp';\n\n const payload: BrewSnapshot = label ? { ...s, label } : s;\n\n await writeFile(tmpPath, JSON.stringify(payload, null, 2), { encoding: 'utf-8', mode: 0o600 });\n await rename(tmpPath, filePath);\n}\n\nexport async function loadSnapshots(): Promise<BrewSnapshot[]> {\n let entries: string[];\n try {\n entries = await readdir(SNAPSHOTS_DIR);\n } catch {\n return [];\n }\n\n const jsonFiles = entries.filter((f) => f.endsWith('.json'));\n const snapshots: BrewSnapshot[] = [];\n\n for (const filename of jsonFiles) {\n try {\n const raw = await readFile(join(SNAPSHOTS_DIR, filename), 'utf-8');\n const parsed: unknown = JSON.parse(raw);\n if (!isValidSnapshot(parsed)) {\n logger.warn('Skipping corrupt snapshot file', { filename });\n continue;\n }\n snapshots.push(parsed);\n } catch {\n logger.warn('Failed to read snapshot file', { filename });\n }\n }\n\n return snapshots.sort((a, b) => b.capturedAt.localeCompare(a.capturedAt));\n}\n\nexport async function deleteSnapshot(capturedAt: string): Promise<void> {\n let entries: string[];\n try {\n entries = await readdir(SNAPSHOTS_DIR);\n } catch {\n return;\n }\n\n for (const filename of entries.filter((f) => f.endsWith('.json'))) {\n try {\n const raw = await readFile(join(SNAPSHOTS_DIR, filename), 'utf-8');\n const parsed: unknown = JSON.parse(raw);\n if (isValidSnapshot(parsed) && parsed.capturedAt === capturedAt) {\n await unlink(join(SNAPSHOTS_DIR, filename));\n return;\n }\n } catch {\n // Skip unreadable files\n }\n }\n}\n\nexport async function getLatestSnapshot(): Promise<BrewSnapshot | null> {\n const all = await loadSnapshots();\n return all[0] ?? null;\n}\n","import { spawn } from 'node:child_process';\n\nconst DEFAULT_TIMEOUT_MS = 30_000; // 30 seconds for instant commands\nconst STREAM_IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes idle timeout for streaming\n\nexport async function execBrew(args: string[], timeoutMs = DEFAULT_TIMEOUT_MS): Promise<string> {\n return new Promise((resolve, reject) => {\n const proc = spawn('brew', args, { env: { ...process.env, HOMEBREW_NO_AUTO_UPDATE: '1' } });\n let stdout = '';\n let stderr = '';\n let killed = false;\n\n // EP-012: Timeout with AbortController pattern\n const timer = setTimeout(() => {\n killed = true;\n proc.kill();\n reject(new Error(`brew ${args.join(' ')} timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n\n proc.stdout.on('data', (d: Buffer) => { stdout += d.toString(); });\n proc.stderr.on('data', (d: Buffer) => { stderr += d.toString(); });\n proc.on('close', (code) => {\n clearTimeout(timer);\n if (killed) return;\n if (code === 0) {\n resolve(stdout);\n } else {\n reject(new Error(stderr.trim() || `brew ${args.join(' ')} exited with code ${code}`));\n }\n });\n proc.on('error', (err) => {\n clearTimeout(timer);\n if (killed) return;\n reject(new Error(`Failed to run brew: ${err.message}`));\n });\n });\n}\n\nexport async function* streamBrew(args: string[]): AsyncGenerator<string> {\n const proc = spawn('brew', args, {\n env: { ...process.env, HOMEBREW_NO_AUTO_UPDATE: '1' },\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let buffer = '';\n const lines: string[] = [];\n let done = false;\n let exitError: string | null = null;\n let lastOutputAt = Date.now();\n\n const push = (chunk: Buffer) => {\n lastOutputAt = Date.now();\n buffer += chunk.toString();\n const parts = buffer.split('\\n');\n buffer = parts.pop() ?? '';\n for (const line of parts) {\n if (line.trim()) lines.push(line);\n }\n };\n\n proc.stdout.on('data', push);\n proc.stderr.on('data', push);\n\n proc.on('close', (code) => {\n if (buffer.trim()) lines.push(buffer.trim());\n done = true;\n if (code !== 0) {\n exitError = `brew ${args.join(' ')} exited with code ${code}`;\n }\n });\n\n proc.on('error', (err) => {\n done = true;\n exitError = err.message;\n });\n\n try {\n while (!done || lines.length > 0) {\n if (lines.length > 0) {\n yield lines.shift()!;\n } else if (!done) {\n // EP-012: Kill process if idle for too long\n if (Date.now() - lastOutputAt > STREAM_IDLE_TIMEOUT_MS) {\n proc.kill();\n throw new Error(`brew ${args.join(' ')} timed out: no output for ${STREAM_IDLE_TIMEOUT_MS / 1000}s`);\n }\n await new Promise((r) => setTimeout(r, 100));\n }\n }\n } finally {\n if (!done) {\n proc.kill();\n }\n }\n\n // Throw after all lines have been yielded so the consumer sees\n // brew's stderr output in the stream before the error surfaces.\n if (exitError) {\n throw new Error(exitError);\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,62 @@
1
+ // src/utils/logger.ts
2
+ import { appendFileSync, mkdirSync } from "fs";
3
+ import { join } from "path";
4
+ import { homedir } from "os";
5
+ var LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
6
+ var currentLevel = process.env.LOG_LEVEL || "warn";
7
+ function shouldLog(level) {
8
+ return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel];
9
+ }
10
+ var LOG_DIR = join(homedir(), ".brew-tui", "logs");
11
+ var LOG_FILE = join(LOG_DIR, "brew-tui.log");
12
+ var logDirReady = false;
13
+ function ensureLogDir() {
14
+ if (logDirReady) return true;
15
+ try {
16
+ mkdirSync(LOG_DIR, { recursive: true });
17
+ logDirReady = true;
18
+ return true;
19
+ } catch {
20
+ return false;
21
+ }
22
+ }
23
+ function isTuiActive() {
24
+ return process.env.BREW_TUI_TUI_MODE === "1";
25
+ }
26
+ function emit(level, msg, ctx) {
27
+ if (!shouldLog(level)) return;
28
+ const line = `[${level.toUpperCase()}] ${msg}${ctx ? " " + JSON.stringify(ctx) : ""}`;
29
+ if (isTuiActive() && ensureLogDir()) {
30
+ try {
31
+ appendFileSync(LOG_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
32
+ `);
33
+ return;
34
+ } catch {
35
+ }
36
+ }
37
+ switch (level) {
38
+ case "debug":
39
+ console.debug(line);
40
+ break;
41
+ case "info":
42
+ console.info(line);
43
+ break;
44
+ case "warn":
45
+ console.warn(line);
46
+ break;
47
+ case "error":
48
+ console.error(line);
49
+ break;
50
+ }
51
+ }
52
+ var logger = {
53
+ debug: (msg, ctx) => emit("debug", msg, ctx),
54
+ info: (msg, ctx) => emit("info", msg, ctx),
55
+ warn: (msg, ctx) => emit("warn", msg, ctx),
56
+ error: (msg, ctx) => emit("error", msg, ctx)
57
+ };
58
+
59
+ export {
60
+ logger
61
+ };
62
+ //# sourceMappingURL=chunk-KDHEUNRI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/logger.ts"],"sourcesContent":["import { appendFileSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\n\ntype LogLevel = 'debug' | 'info' | 'warn' | 'error';\nconst LOG_LEVELS: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3 };\nconst currentLevel: LogLevel = (process.env.LOG_LEVEL as LogLevel) || 'warn';\n\nfunction shouldLog(level: LogLevel): boolean {\n return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel];\n}\n\n// When the Ink TUI owns stdout (raw mode), writing to console corrupts the frame.\n// We detect this and redirect logs to a file under ~/.brew-tui/logs/ instead.\nconst LOG_DIR = join(homedir(), '.brew-tui', 'logs');\nconst LOG_FILE = join(LOG_DIR, 'brew-tui.log');\nlet logDirReady = false;\n\nfunction ensureLogDir(): boolean {\n if (logDirReady) return true;\n try {\n mkdirSync(LOG_DIR, { recursive: true });\n logDirReady = true;\n return true;\n } catch {\n return false;\n }\n}\n\nfunction isTuiActive(): boolean {\n // src/index.tsx sets BREW_TUI_TUI_MODE=1 before rendering Ink. Once set, any\n // console.* call would corrupt the rendered frame, so we route logs to a file.\n return process.env.BREW_TUI_TUI_MODE === '1';\n}\n\nfunction emit(level: LogLevel, msg: string, ctx?: Record<string, unknown>): void {\n if (!shouldLog(level)) return;\n\n const line = `[${level.toUpperCase()}] ${msg}${ctx ? ' ' + JSON.stringify(ctx) : ''}`;\n\n if (isTuiActive() && ensureLogDir()) {\n try {\n appendFileSync(LOG_FILE, `${new Date().toISOString()} ${line}\\n`);\n return;\n } catch {\n // Fall through to console as last resort.\n }\n }\n\n switch (level) {\n case 'debug': console.debug(line); break;\n case 'info': console.info(line); break;\n case 'warn': console.warn(line); break;\n case 'error': console.error(line); break;\n }\n}\n\nexport const logger = {\n debug: (msg: string, ctx?: Record<string, unknown>) => emit('debug', msg, ctx),\n info: (msg: string, ctx?: Record<string, unknown>) => emit('info', msg, ctx),\n warn: (msg: string, ctx?: Record<string, unknown>) => emit('warn', msg, ctx),\n error: (msg: string, ctx?: Record<string, unknown>) => emit('error', msg, ctx),\n};\n"],"mappings":";AAAA,SAAS,gBAAgB,iBAAiB;AAC1C,SAAS,YAAY;AACrB,SAAS,eAAe;AAGxB,IAAM,aAAuC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE;AACpF,IAAM,eAA0B,QAAQ,IAAI,aAA0B;AAEtE,SAAS,UAAU,OAA0B;AAC3C,SAAO,WAAW,KAAK,KAAK,WAAW,YAAY;AACrD;AAIA,IAAM,UAAU,KAAK,QAAQ,GAAG,aAAa,MAAM;AACnD,IAAM,WAAW,KAAK,SAAS,cAAc;AAC7C,IAAI,cAAc;AAElB,SAAS,eAAwB;AAC/B,MAAI,YAAa,QAAO;AACxB,MAAI;AACF,cAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AACtC,kBAAc;AACd,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,cAAuB;AAG9B,SAAO,QAAQ,IAAI,sBAAsB;AAC3C;AAEA,SAAS,KAAK,OAAiB,KAAa,KAAqC;AAC/E,MAAI,CAAC,UAAU,KAAK,EAAG;AAEvB,QAAM,OAAO,IAAI,MAAM,YAAY,CAAC,KAAK,GAAG,GAAG,MAAM,MAAM,KAAK,UAAU,GAAG,IAAI,EAAE;AAEnF,MAAI,YAAY,KAAK,aAAa,GAAG;AACnC,QAAI;AACF,qBAAe,UAAU,IAAG,oBAAI,KAAK,GAAE,YAAY,CAAC,IAAI,IAAI;AAAA,CAAI;AAChE;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,UAAQ,OAAO;AAAA,IACb,KAAK;AAAS,cAAQ,MAAM,IAAI;AAAG;AAAA,IACnC,KAAK;AAAQ,cAAQ,KAAK,IAAI;AAAG;AAAA,IACjC,KAAK;AAAQ,cAAQ,KAAK,IAAI;AAAG;AAAA,IACjC,KAAK;AAAS,cAAQ,MAAM,IAAI;AAAG;AAAA,EACrC;AACF;AAEO,IAAM,SAAS;AAAA,EACpB,OAAO,CAAC,KAAa,QAAkC,KAAK,SAAS,KAAK,GAAG;AAAA,EAC7E,MAAM,CAAC,KAAa,QAAkC,KAAK,QAAQ,KAAK,GAAG;AAAA,EAC3E,MAAM,CAAC,KAAa,QAAkC,KAAK,QAAQ,KAAK,GAAG;AAAA,EAC3E,OAAO,CAAC,KAAa,QAAkC,KAAK,SAAS,KAAK,GAAG;AAC/E;","names":[]}
@@ -18,6 +18,9 @@ var en = {
18
18
  view_profiles: "Profiles",
19
19
  view_smartCleanup: "Cleanup",
20
20
  view_history: "History",
21
+ view_rollback: "Rollback",
22
+ view_brewfile: "Brewfile",
23
+ view_sync: "Sync",
21
24
  view_securityAudit: "Security",
22
25
  view_account: "Account",
23
26
  // ── Keyboard hint actions ──
@@ -57,6 +60,15 @@ var en = {
57
60
  hint_replay: "replay",
58
61
  hint_edit: "edit",
59
62
  hint_pin: "pin/unpin",
63
+ hint_rollback_confirm: "rollback",
64
+ hint_add: "add",
65
+ hint_reconcile: "reconcile",
66
+ hint_export: "export",
67
+ hint_select: "select",
68
+ hint_sync: "sync now",
69
+ hint_conflict: "resolve",
70
+ hint_rollback: "rollback",
71
+ hint_check_compliance: "check compliance",
60
72
  // ── Loading / progress ──
61
73
  loading_default: "Loading...",
62
74
  loading_fetchingBrew: "Fetching Homebrew data...",
@@ -97,6 +109,17 @@ var en = {
97
109
  badge_fail: "fail",
98
110
  badge_error: "error",
99
111
  // ── Dashboard ──
112
+ dashboard_pro_status: "Pro Status",
113
+ dashboard_security: "Security",
114
+ dashboard_cves: "{{count}} CVEs",
115
+ dashboard_no_cves: "No CVEs",
116
+ dashboard_brewfile: "Brewfile",
117
+ dashboard_sync: "Sync",
118
+ dashboard_sync_never: "Never synced",
119
+ dashboard_sync_ago: "{{time}} ago",
120
+ dashboard_compliance: "Compliance",
121
+ dashboard_compliance_violations: "{{count}} violations",
122
+ dashboard_compliance_ok: "OK",
100
123
  dashboard_overview: "Overview",
101
124
  dashboard_formulae: "Formulae",
102
125
  dashboard_casks: "Casks",
@@ -215,6 +238,7 @@ var en = {
215
238
  history_confirmReplay: "Re-run: {{action}} {{name}}?",
216
239
  history_replayAll: "Re-run: upgrade all packages?",
217
240
  // ── Security Audit ──
241
+ security_rollback_hint: "R: open Rollback for version restoration",
218
242
  security_title: "Security Audit",
219
243
  security_scanned: "Scanned",
220
244
  security_vulnerable: "Vulnerable",
@@ -355,7 +379,100 @@ var en = {
355
379
  // ── SCR-010: Generic network error ──
356
380
  error_network: "Network error: unable to reach the server.",
357
381
  // ── ARQ-005: Security cache ──
358
- security_cachedResults: "Showing cached results ({{time}} ago). Press r to rescan."
382
+ security_cachedResults: "Showing cached results ({{time}} ago). Press r to rescan.",
383
+ // ── Impact Analysis ──
384
+ impact_brewfile_hint: "\u{1F4A1} Add to Brewfile to pin this version before upgrading",
385
+ impact_analyzing: "Analyzing upgrade impact...",
386
+ impact_high: "HIGH RISK",
387
+ impact_medium: "MEDIUM RISK",
388
+ impact_low: "LOW RISK",
389
+ impact_affects: "affects {{count}} installed packages",
390
+ impact_usedBy: "Used by: {{packages}}",
391
+ impact_hint: "Select package to see upgrade impact",
392
+ impact_reason_critical_package: "Critical system package",
393
+ impact_reason_major_bump: "Major version change",
394
+ impact_reason_many_deps: "{{count}} packages depend on this",
395
+ // ── Rollback ──
396
+ rollback_title: "Rollback \u2014 Restore Previous State",
397
+ rollback_no_snapshots: "No snapshots available. Snapshots are captured automatically after each operation.",
398
+ rollback_select_snapshot: "Select a snapshot to restore",
399
+ rollback_snapshot_label: "{{label}} \u2014 {{date}}",
400
+ rollback_snapshot_auto: "Auto",
401
+ rollback_diff_empty: "No changes detected between this snapshot and current state",
402
+ rollback_confirm: "Roll back {{count}} package(s) to this state?",
403
+ rollback_strategy_bottle: "from bottle cache",
404
+ rollback_strategy_versioned: "from versioned formula",
405
+ rollback_strategy_pin: "pin only (version not restorable)",
406
+ rollback_strategy_unavailable: "cannot restore",
407
+ rollback_executing: "Rolling back...",
408
+ rollback_success: "Rollback completed",
409
+ rollback_error: "Rollback failed: {{error}}",
410
+ rollback_item_downgrade: "{{name}}: {{from}} \u2192 {{to}}",
411
+ rollback_item_remove: "Remove: {{name}}",
412
+ rollback_item_install: "Install: {{name}} {{version}}",
413
+ rollback_warning_cask: "Casks will be pinned only (version restoration not supported)",
414
+ rollback_capturing: "Capturing current snapshot...",
415
+ // ── Sync ──
416
+ sync_title: "Cross-machine Sync",
417
+ sync_disabled: "Sync is not configured. Press s to set up iCloud sync.",
418
+ sync_status_ok: "In sync",
419
+ sync_status_drift: "{{count}} change(s) from other machines",
420
+ sync_status_conflict: "{{count}} conflict(s) need resolution",
421
+ sync_last_sync: "Last sync: {{date}}",
422
+ sync_machine: "This machine: {{name}}",
423
+ sync_other_machines: "Other machines: {{names}}",
424
+ sync_syncing: "Syncing...",
425
+ sync_success: "Sync complete",
426
+ sync_error: "Sync failed: {{error}}",
427
+ sync_conflict_title: "Conflict: {{package}}",
428
+ sync_conflict_local: "This machine: {{version}}",
429
+ sync_conflict_remote: "{{machine}}: {{version}}",
430
+ sync_conflict_use_local: "Keep local",
431
+ sync_conflict_use_remote: "Use remote",
432
+ sync_setup_icloud: "Setting up iCloud sync...",
433
+ sync_no_icloud: "iCloud Drive not found. Ensure iCloud Drive is enabled in System Settings.",
434
+ // ── Upgrade Prompt — Sync ──
435
+ upgrade_sync: "Cross-machine Sync",
436
+ upgrade_syncDesc: "Keep your Homebrew setup in sync across multiple Macs via iCloud Drive. Automatically merge installations and resolve conflicts.",
437
+ // ── Brewfile ──
438
+ brewfile_title: "Declarative Brewfile",
439
+ brewfile_compliant: "compliant",
440
+ brewfile_no_brewfile: "No Brewfile found. Press n to create one from your current installation.",
441
+ brewfile_create_name: "Brewfile name (Enter to confirm):",
442
+ brewfile_created: "Brewfile created: {{name}}",
443
+ brewfile_drift_missing: "{{count}} packages missing",
444
+ brewfile_drift_extra: "{{count}} extra packages",
445
+ brewfile_drift_wrong: "{{count}} wrong versions",
446
+ brewfile_reconciling: "Reconciling...",
447
+ brewfile_reconcile_success: "Reconciliation complete",
448
+ brewfile_reconcile_error: "Reconciliation failed: {{error}}",
449
+ brewfile_exported: "Exported to {{path}}",
450
+ brewfile_formulae_count: "{{count}} formulae",
451
+ brewfile_casks_count: "{{count}} casks",
452
+ brewfile_strict_mode: "Strict mode",
453
+ // ── Compliance ──
454
+ view_compliance: "Compliance",
455
+ compliance_title: "Team Compliance",
456
+ compliance_no_policy: "No policy loaded. Press i to import a policy file.",
457
+ compliance_score: "{{score}}% compliant",
458
+ compliance_violations: "{{count}} violation(s)",
459
+ compliance_ok: "Fully compliant",
460
+ compliance_import_prompt: "Policy file path (Enter to confirm):",
461
+ compliance_import_error: "Failed to load policy: {{error}}",
462
+ compliance_violation_missing: "Missing: {{name}} (required)",
463
+ compliance_violation_forbidden: "Forbidden: {{name}} \u2014 {{reason}}",
464
+ compliance_violation_version: "Wrong version: {{name}} (required {{required}}, installed {{installed}})",
465
+ compliance_violation_extra: "Extra package: {{name}}",
466
+ compliance_remediating: "Remediating...",
467
+ compliance_remediate_success: "Remediation complete",
468
+ compliance_remediate_error: "Remediation failed: {{error}}",
469
+ compliance_export_done: "Report exported to {{path}}",
470
+ compliance_machine: "Machine: {{name}}",
471
+ compliance_policy_name: "Policy: {{name}}",
472
+ compliance_policy_by: "Maintained by: {{maintainer}}",
473
+ // ── Upgrade Prompt — Compliance ──
474
+ upgrade_compliance: "Team Compliance",
475
+ upgrade_complianceDesc: "Enforce package policies across your team. Define required, forbidden, and version-pinned packages and automatically remediate deviations."
359
476
  };
360
477
  var en_default = en;
361
478
 
@@ -376,6 +493,9 @@ var es = {
376
493
  view_profiles: "Perfiles",
377
494
  view_smartCleanup: "Limpieza",
378
495
  view_history: "Historial",
496
+ view_rollback: "Rollback",
497
+ view_brewfile: "Brewfile",
498
+ view_sync: "Sync",
379
499
  view_securityAudit: "Seguridad",
380
500
  view_account: "Cuenta",
381
501
  // ── Keyboard hint actions ──
@@ -415,6 +535,15 @@ var es = {
415
535
  hint_replay: "repetir",
416
536
  hint_edit: "editar",
417
537
  hint_pin: "fijar/desfijar",
538
+ hint_rollback_confirm: "revertir",
539
+ hint_add: "a\xF1adir",
540
+ hint_reconcile: "reconciliar",
541
+ hint_export: "exportar",
542
+ hint_select: "seleccionar",
543
+ hint_sync: "sincronizar",
544
+ hint_conflict: "resolver",
545
+ hint_rollback: "revertir",
546
+ hint_check_compliance: "verificar conformidad",
418
547
  // ── Loading / progress ──
419
548
  loading_default: "Cargando...",
420
549
  loading_fetchingBrew: "Obteniendo datos de Homebrew...",
@@ -455,6 +584,17 @@ var es = {
455
584
  badge_fail: "fallo",
456
585
  badge_error: "error",
457
586
  // ── Dashboard ──
587
+ dashboard_pro_status: "Estado Pro",
588
+ dashboard_security: "Seguridad",
589
+ dashboard_cves: "{{count}} CVEs",
590
+ dashboard_no_cves: "Sin CVEs",
591
+ dashboard_brewfile: "Brewfile",
592
+ dashboard_sync: "Sync",
593
+ dashboard_sync_never: "Nunca sincronizado",
594
+ dashboard_sync_ago: "hace {{time}}",
595
+ dashboard_compliance: "Conformidad",
596
+ dashboard_compliance_violations: "{{count}} violaciones",
597
+ dashboard_compliance_ok: "OK",
458
598
  dashboard_overview: "Resumen",
459
599
  dashboard_formulae: "Formulae",
460
600
  dashboard_casks: "Casks",
@@ -713,7 +853,101 @@ var es = {
713
853
  // ── SCR-010: Generic network error ──
714
854
  error_network: "Error de red: no se puede conectar con el servidor.",
715
855
  // ── ARQ-005: Security cache ──
716
- security_cachedResults: "Mostrando resultados en cach\xE9 (hace {{time}}). Presiona r para re-escanear."
856
+ security_rollback_hint: "R: abrir Rollback para restaurar versiones",
857
+ security_cachedResults: "Mostrando resultados en cach\xE9 (hace {{time}}). Presiona r para re-escanear.",
858
+ // ── Impact Analysis ──
859
+ impact_brewfile_hint: "\u{1F4A1} A\xF1ade al Brewfile para fijar esta versi\xF3n antes de actualizar",
860
+ impact_analyzing: "Analizando impacto de actualizaci\xF3n...",
861
+ impact_high: "RIESGO ALTO",
862
+ impact_medium: "RIESGO MEDIO",
863
+ impact_low: "RIESGO BAJO",
864
+ impact_affects: "afecta {{count}} paquetes instalados",
865
+ impact_usedBy: "Usado por: {{packages}}",
866
+ impact_hint: "Selecciona un paquete para ver el impacto",
867
+ impact_reason_critical_package: "Paquete cr\xEDtico del sistema",
868
+ impact_reason_major_bump: "Cambio de versi\xF3n mayor",
869
+ impact_reason_many_deps: "{{count}} paquetes dependen de este",
870
+ // ── Rollback ──
871
+ rollback_title: "Rollback \u2014 Restaurar Estado Anterior",
872
+ rollback_no_snapshots: "No hay snapshots disponibles. Los snapshots se capturan autom\xE1ticamente tras cada operaci\xF3n.",
873
+ rollback_select_snapshot: "Selecciona un snapshot para restaurar",
874
+ rollback_snapshot_label: "{{label}} \u2014 {{date}}",
875
+ rollback_snapshot_auto: "Auto",
876
+ rollback_diff_empty: "No se detectaron cambios entre este snapshot y el estado actual",
877
+ rollback_confirm: "\xBFRevertir {{count}} paquete(s) a este estado?",
878
+ rollback_strategy_bottle: "desde cach\xE9 de bottle",
879
+ rollback_strategy_versioned: "desde formula versionada",
880
+ rollback_strategy_pin: "solo fijar (versi\xF3n no restaurable)",
881
+ rollback_strategy_unavailable: "no se puede restaurar",
882
+ rollback_executing: "Revirtiendo...",
883
+ rollback_success: "Rollback completado",
884
+ rollback_error: "Rollback fallido: {{error}}",
885
+ rollback_item_downgrade: "{{name}}: {{from}} \u2192 {{to}}",
886
+ rollback_item_remove: "Eliminar: {{name}}",
887
+ rollback_item_install: "Instalar: {{name}} {{version}}",
888
+ rollback_warning_cask: "Los Casks solo se fijar\xE1n (restauraci\xF3n de versi\xF3n no disponible)",
889
+ rollback_capturing: "Capturando snapshot actual...",
890
+ // ── Sync ──
891
+ sync_title: "Sincronizaci\xF3n entre m\xE1quinas",
892
+ sync_disabled: "Sync no est\xE1 configurado. Pulsa s para configurar iCloud sync.",
893
+ sync_status_ok: "Sincronizado",
894
+ sync_status_drift: "{{count}} cambio(s) desde otras m\xE1quinas",
895
+ sync_status_conflict: "{{count}} conflicto(s) requieren resoluci\xF3n",
896
+ sync_last_sync: "\xDAltima sync: {{date}}",
897
+ sync_machine: "Esta m\xE1quina: {{name}}",
898
+ sync_other_machines: "Otras m\xE1quinas: {{names}}",
899
+ sync_syncing: "Sincronizando...",
900
+ sync_success: "Sincronizaci\xF3n completa",
901
+ sync_error: "Sync fallida: {{error}}",
902
+ sync_conflict_title: "Conflicto: {{package}}",
903
+ sync_conflict_local: "Esta m\xE1quina: {{version}}",
904
+ sync_conflict_remote: "{{machine}}: {{version}}",
905
+ sync_conflict_use_local: "Mantener local",
906
+ sync_conflict_use_remote: "Usar remoto",
907
+ sync_setup_icloud: "Configurando iCloud sync...",
908
+ sync_no_icloud: "iCloud Drive no encontrado. Aseg\xFArate de que iCloud Drive est\xE1 activado en Ajustes del Sistema.",
909
+ // ── Upgrade Prompt — Sync ──
910
+ upgrade_sync: "Sincronizaci\xF3n entre m\xE1quinas",
911
+ upgrade_syncDesc: "Mant\xE9n tu configuraci\xF3n de Homebrew sincronizada entre varios Macs mediante iCloud Drive. Fusiona instalaciones autom\xE1ticamente y resuelve conflictos.",
912
+ // ── Brewfile ──
913
+ brewfile_title: "Brewfile Declarativo",
914
+ brewfile_compliant: "en conformidad",
915
+ brewfile_no_brewfile: "No se encontr\xF3 Brewfile. Pulsa n para crear uno desde tu instalaci\xF3n actual.",
916
+ brewfile_create_name: "Nombre del Brewfile (Enter para confirmar):",
917
+ brewfile_created: "Brewfile creado: {{name}}",
918
+ brewfile_drift_missing: "{{count}} paquetes faltantes",
919
+ brewfile_drift_extra: "{{count}} paquetes extra",
920
+ brewfile_drift_wrong: "{{count}} versiones incorrectas",
921
+ brewfile_reconciling: "Reconciliando...",
922
+ brewfile_reconcile_success: "Reconciliaci\xF3n completa",
923
+ brewfile_reconcile_error: "Reconciliaci\xF3n fallida: {{error}}",
924
+ brewfile_exported: "Exportado a {{path}}",
925
+ brewfile_formulae_count: "{{count}} formulae",
926
+ brewfile_casks_count: "{{count}} casks",
927
+ brewfile_strict_mode: "Modo estricto",
928
+ // ── Compliance ──
929
+ view_compliance: "Compliance",
930
+ compliance_title: "Conformidad de Equipo",
931
+ compliance_no_policy: "No hay pol\xEDtica cargada. Pulsa i para importar un archivo de pol\xEDtica.",
932
+ compliance_score: "{{score}}% en conformidad",
933
+ compliance_violations: "{{count}} violaci\xF3n(es)",
934
+ compliance_ok: "Totalmente en conformidad",
935
+ compliance_import_prompt: "Ruta del archivo de pol\xEDtica (Enter para confirmar):",
936
+ compliance_import_error: "Error al cargar la pol\xEDtica: {{error}}",
937
+ compliance_violation_missing: "Faltante: {{name}} (requerido)",
938
+ compliance_violation_forbidden: "Prohibido: {{name}} \u2014 {{reason}}",
939
+ compliance_violation_version: "Versi\xF3n incorrecta: {{name}} (requerida {{required}}, instalada {{installed}})",
940
+ compliance_violation_extra: "Paquete extra: {{name}}",
941
+ compliance_remediating: "Remediando...",
942
+ compliance_remediate_success: "Remediaci\xF3n completa",
943
+ compliance_remediate_error: "Remediaci\xF3n fallida: {{error}}",
944
+ compliance_export_done: "Informe exportado a {{path}}",
945
+ compliance_machine: "M\xE1quina: {{name}}",
946
+ compliance_policy_name: "Pol\xEDtica: {{name}}",
947
+ compliance_policy_by: "Mantenida por: {{maintainer}}",
948
+ // ── Upgrade Prompt — Compliance ──
949
+ upgrade_compliance: "Conformidad de Equipo",
950
+ upgrade_complianceDesc: "Aplica pol\xEDticas de paquetes en todo tu equipo. Define paquetes requeridos, prohibidos y con versi\xF3n fijada, y remedia desviaciones autom\xE1ticamente."
717
951
  };
718
952
  var es_default = es;
719
953
 
@@ -755,19 +989,6 @@ function tp(baseKey, count, values) {
755
989
  return t(`${baseKey}${suffix}`, { count, ...values });
756
990
  }
757
991
 
758
- // src/utils/logger.ts
759
- var LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
760
- var currentLevel = process.env.LOG_LEVEL || "warn";
761
- function shouldLog(level) {
762
- return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel];
763
- }
764
- var logger = {
765
- debug: (msg, ctx) => shouldLog("debug") && console.debug(`[DEBUG] ${msg}`, ctx || ""),
766
- info: (msg, ctx) => shouldLog("info") && console.info(`[INFO] ${msg}`, ctx || ""),
767
- warn: (msg, ctx) => shouldLog("warn") && console.warn(`[WARN] ${msg}`, ctx || ""),
768
- error: (msg, ctx) => shouldLog("error") && console.error(`[ERROR] ${msg}`, ctx || "")
769
- };
770
-
771
992
  // src/lib/fetch-timeout.ts
772
993
  function fetchWithTimeout(url, options = {}, timeoutMs = 15e3) {
773
994
  return fetch(url, { ...options, signal: AbortSignal.timeout(timeoutMs) });
@@ -778,7 +999,6 @@ export {
778
999
  getLocale,
779
1000
  t,
780
1001
  tp,
781
- logger,
782
1002
  fetchWithTimeout
783
1003
  };
784
- //# sourceMappingURL=chunk-E7ZREAJW.js.map
1004
+ //# sourceMappingURL=chunk-KDJNCZD7.js.map