layoutbridge-spm 0.0.1 → 1.0.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +171 -0
  2. package/LICENSE +26 -0
  3. package/README.de.md +318 -0
  4. package/README.md +304 -4
  5. package/THIRD-PARTY-NOTICES.md +2878 -0
  6. package/dist/src/mcp/config.js +125 -0
  7. package/dist/src/mcp/dispatch.js +123 -0
  8. package/dist/src/mcp/license/free-tools.js +16 -0
  9. package/dist/src/mcp/license/gate.js +209 -0
  10. package/dist/src/mcp/license/index.js +96 -0
  11. package/dist/src/mcp/license/instance-name.js +56 -0
  12. package/dist/src/mcp/license/key-source.js +58 -0
  13. package/dist/src/mcp/license/lemonsqueezy-verifier.js +207 -0
  14. package/dist/src/mcp/license/placeholder-verifier.js +10 -0
  15. package/dist/src/mcp/license/polar-ids.js +63 -0
  16. package/dist/src/mcp/license/polar-verifier.js +265 -0
  17. package/dist/src/mcp/license/product-ids.js +72 -0
  18. package/dist/src/mcp/license/state-store.js +91 -0
  19. package/dist/src/mcp/license/types.js +11 -0
  20. package/dist/src/mcp/logging.js +270 -0
  21. package/dist/src/mcp/product-info.js +48 -0
  22. package/dist/src/mcp/server.js +250 -0
  23. package/dist/src/mcp/setup.js +608 -0
  24. package/dist/src/mcp/start.cjs +95 -0
  25. package/dist/src/mcp/tool-definition.js +2 -0
  26. package/dist/src/mcp/tools.js +2004 -0
  27. package/dist/src/mcp/typography-rules.js +70 -0
  28. package/dist/src/transport/com.js +250 -0
  29. package/dist/src/transport/osascript.js +171 -0
  30. package/dist/src/transport/queue.js +19 -0
  31. package/dist/src/transport/script-bundle.js +92 -0
  32. package/dist/src/transport/transport.js +30 -0
  33. package/indesign/commands/10-app.js +6 -0
  34. package/indesign/commands/20-documents.js +513 -0
  35. package/indesign/commands/22-document-setup.js +118 -0
  36. package/indesign/commands/23-save-package.js +255 -0
  37. package/indesign/commands/25-export.js +379 -0
  38. package/indesign/commands/26-export-image.js +197 -0
  39. package/indesign/commands/27-textdefaults.js +151 -0
  40. package/indesign/commands/28-selection.js +174 -0
  41. package/indesign/commands/30-pages.js +1032 -0
  42. package/indesign/commands/31-view.js +210 -0
  43. package/indesign/commands/32-sections.js +203 -0
  44. package/indesign/commands/35-parents.js +428 -0
  45. package/indesign/commands/37-layout.js +760 -0
  46. package/indesign/commands/40-frames.js +836 -0
  47. package/indesign/commands/41-shapes.js +505 -0
  48. package/indesign/commands/42-groups.js +117 -0
  49. package/indesign/commands/43-layers.js +291 -0
  50. package/indesign/commands/44-effects.js +274 -0
  51. package/indesign/commands/45-frame-options.js +247 -0
  52. package/indesign/commands/47-anchored.js +287 -0
  53. package/indesign/commands/50-modify.js +781 -0
  54. package/indesign/commands/52-align.js +139 -0
  55. package/indesign/commands/55-fit.js +106 -0
  56. package/indesign/commands/60-images.js +771 -0
  57. package/indesign/commands/62-embed.js +120 -0
  58. package/indesign/commands/69-placeholders.js +127 -0
  59. package/indesign/commands/70-text.js +567 -0
  60. package/indesign/commands/71-outlines.js +92 -0
  61. package/indesign/commands/72-place-text.js +366 -0
  62. package/indesign/commands/74-stories.js +116 -0
  63. package/indesign/commands/75-tables.js +676 -0
  64. package/indesign/commands/76-text-variables.js +260 -0
  65. package/indesign/commands/77-story-options.js +86 -0
  66. package/indesign/commands/78-frame-lines.js +304 -0
  67. package/indesign/commands/79-check-typography.js +303 -0
  68. package/indesign/commands/80-styles.js +1620 -0
  69. package/indesign/commands/81-composition.js +355 -0
  70. package/indesign/commands/82-object-styles.js +263 -0
  71. package/indesign/commands/83-indents-tabs.js +237 -0
  72. package/indesign/commands/84-import-styles.js +142 -0
  73. package/indesign/commands/85-swatches.js +112 -0
  74. package/indesign/commands/86-gradients.js +185 -0
  75. package/indesign/commands/87-replace-swatch.js +180 -0
  76. package/indesign/commands/88-replace-font.js +217 -0
  77. package/indesign/interpreter.js +758 -0
  78. package/package.json +55 -6
  79. package/rules/typography-rules.json +31 -0
@@ -0,0 +1,608 @@
1
+ /**
2
+ * Einrichtungsbefehl `layoutbridge-spm --setup` (Auftrag A-13 Nr. 1,
3
+ * Vermerk E-76): traegt den Server in die Konfiguration von Claude Desktop
4
+ * ein und legt den Lizenzschluessel am Vorgabeort ab.
5
+ *
6
+ * Nach dem Muster von `--deactivate` (E-62): kein Werkzeug im Katalog, keine
7
+ * Aenderung an der Schnittstelle (A-5). Fuenf feste Regeln aus dem Auftrag:
8
+ * 1. nur auf ausdrueckliche Eingabe (Aufruf mit --setup, nie automatisch);
9
+ * 2. anzeigen, fragen, dann schreiben; --dry-run schreibt nichts; ohne
10
+ * Terminal Abbruch;
11
+ * 3. Sicherungskopie, nur `mcpServers.layoutbridge-spm` wird geaendert,
12
+ * alles andere bleibt byte-genau (Eintrag wird textlich eingesetzt,
13
+ * nicht die Datei neu serialisiert); ungueltiges JSON = Abbruch mit
14
+ * Zeile und Spalte;
15
+ * 4. keine Rechte, kein Netz, kein Download; der Schluessel wird nie
16
+ * uebertragen, nie protokolliert, nie in die Konfiguration geschrieben,
17
+ * und die Lizenzpruefung wird nicht ausgeloest;
18
+ * 5. der Handweg bleibt dokumentiert (README).
19
+ * Deshalb importiert diese Datei nur Node-Bausteine fuer Dateien und
20
+ * Terminal und die Produktangaben – keinen Lizenzpruefer, kein `fetch`,
21
+ * kein Protokoll (Waechtertest tests/unit/setup.test.ts).
22
+ */
23
+ import { copyFileSync, chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
24
+ import { createInterface } from 'node:readline';
25
+ import { posix, win32 } from 'node:path';
26
+ import { isDeepStrictEqual } from 'node:util';
27
+ import { SUPPORT_EMAIL } from './product-info.js';
28
+ /** Name des Eintrags unter `mcpServers` (= Servername, = Werkzeugpraefix in Claude Desktop). */
29
+ export const SETUP_SERVER_KEY = 'layoutbridge-spm';
30
+ /** Der eingetragene Befehl: Bezugsweg npm, Schluessel in license.key, deshalb kein "env". */
31
+ export const SETUP_ENTRY = { command: 'npx', args: ['-y', 'layoutbridge-spm'] };
32
+ /** Vorerst einziger Client (A-13 Nr. 1); andere erst auf Auftrag. */
33
+ export const SETUP_CLIENTS = ['claude-desktop'];
34
+ const RESTART_SENTENCE = 'Now quit Claude Desktop completely and start it again.';
35
+ /** Liest die Optionen nach `--setup`. Unbekanntes = Fehlertext. */
36
+ export function parseSetupArgs(argv) {
37
+ const options = { dryRun: false, client: 'claude-desktop', licenseKeyFile: null };
38
+ for (let i = 0; i < argv.length; i += 1) {
39
+ const arg = argv[i];
40
+ if (arg === '--setup')
41
+ continue;
42
+ if (arg === '--dry-run') {
43
+ options.dryRun = true;
44
+ continue;
45
+ }
46
+ const valued = /^(--client|--license-key-file)(?:=(.*))?$/.exec(arg);
47
+ if (valued !== null) {
48
+ let value = valued[2];
49
+ if (value === undefined) {
50
+ value = argv[i + 1];
51
+ i += 1;
52
+ }
53
+ if (value === undefined || value.length === 0 || value.startsWith('--')) {
54
+ return { error: valued[1] + ' needs a value.' };
55
+ }
56
+ if (valued[1] === '--client')
57
+ options.client = value;
58
+ else
59
+ options.licenseKeyFile = value;
60
+ continue;
61
+ }
62
+ return { error: 'Unknown option ' + JSON.stringify(arg) + ' for --setup. Allowed: --dry-run, --client claude-desktop, --license-key-file <path>.' };
63
+ }
64
+ if (!SETUP_CLIENTS.includes(options.client)) {
65
+ return { error: 'Unknown client ' + JSON.stringify(options.client) + '. Supported: ' + SETUP_CLIENTS.join(', ') + '.' };
66
+ }
67
+ return { options };
68
+ }
69
+ // ---------------------------------------------------------------------------
70
+ // Pfade (E-71 fuer Claude Desktop, E-60 fuer den Schluessel)
71
+ /** Konfigurationsdatei von Claude Desktop; `null` auf Plattformen ohne Claude Desktop. */
72
+ export function claudeDesktopConfigPath(platform, home, env) {
73
+ if (platform === 'darwin')
74
+ return posix.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
75
+ if (platform === 'win32') {
76
+ const appData = env['APPDATA'] !== undefined && env['APPDATA'].length > 0 ? env['APPDATA'] : win32.join(home, 'AppData', 'Roaming');
77
+ return win32.join(appData, 'Claude', 'claude_desktop_config.json');
78
+ }
79
+ return null;
80
+ }
81
+ /** Vorgabeort des Schluessels, derselbe wie in license/key-source.ts. */
82
+ export function setupLicenseKeyPath(platform, home) {
83
+ if (platform === 'darwin')
84
+ return posix.join(home, 'Library', 'Application Support', 'layoutbridge-spm', 'license.key');
85
+ if (platform === 'win32')
86
+ return win32.join(home, '.layoutbridge-spm', 'license.key');
87
+ return posix.join(home, '.layoutbridge-spm', 'license.key');
88
+ }
89
+ function pathApi(platform) {
90
+ return platform === 'win32' ? win32 : posix;
91
+ }
92
+ /** `claude_desktop_config.json.bak-JJJJ-MM-TT_HHMM` (Ortszeit). */
93
+ export function backupPath(configPath, now) {
94
+ const pad = (n) => String(n).padStart(2, '0');
95
+ const stamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}`;
96
+ return configPath + '.bak-' + stamp;
97
+ }
98
+ export class JsonPositionError extends Error {
99
+ line;
100
+ column;
101
+ detail;
102
+ constructor(line, column, detail) {
103
+ super(`line ${line}, column ${column}: ${detail}`);
104
+ this.line = line;
105
+ this.column = column;
106
+ this.detail = detail;
107
+ }
108
+ }
109
+ function lineAndColumn(text, position) {
110
+ let line = 1;
111
+ let lineStart = 0;
112
+ for (let i = 0; i < position && i < text.length; i += 1) {
113
+ if (text[i] === '\n') {
114
+ line += 1;
115
+ lineStart = i + 1;
116
+ }
117
+ }
118
+ return { line, column: position - lineStart + 1 };
119
+ }
120
+ function parseWithPositions(text) {
121
+ let pos = 0;
122
+ const fail = (detail, at = pos) => {
123
+ const where = lineAndColumn(text, at);
124
+ throw new JsonPositionError(where.line, where.column, detail);
125
+ };
126
+ const describe = () => (pos >= text.length ? 'unexpected end of file' : 'unexpected character ' + JSON.stringify(text[pos]));
127
+ const skip = () => {
128
+ while (pos < text.length && ' \t\n\r'.includes(text[pos]))
129
+ pos += 1;
130
+ };
131
+ const parseString = () => {
132
+ if (text[pos] !== '"')
133
+ fail(describe());
134
+ pos += 1;
135
+ for (;;) {
136
+ if (pos >= text.length)
137
+ fail('unterminated string');
138
+ const ch = text[pos];
139
+ if (ch === '"') {
140
+ pos += 1;
141
+ return;
142
+ }
143
+ if (ch < ' ')
144
+ fail('control character in string');
145
+ if (ch === '\\') {
146
+ const next = text[pos + 1];
147
+ if (next === 'u') {
148
+ if (!/^[0-9a-fA-F]{4}$/.test(text.slice(pos + 2, pos + 6)))
149
+ fail('invalid \\u escape');
150
+ pos += 6;
151
+ }
152
+ else if (next !== undefined && '"\\/bfnrt'.includes(next)) {
153
+ pos += 2;
154
+ }
155
+ else {
156
+ fail('invalid escape in string');
157
+ }
158
+ continue;
159
+ }
160
+ pos += 1;
161
+ }
162
+ };
163
+ const parseValue = () => {
164
+ skip();
165
+ const start = pos;
166
+ const ch = text[pos];
167
+ if (ch === '{') {
168
+ pos += 1;
169
+ const members = [];
170
+ skip();
171
+ if (text[pos] === '}') {
172
+ pos += 1;
173
+ return { start, end: pos, members };
174
+ }
175
+ for (;;) {
176
+ skip();
177
+ if (text[pos] !== '"')
178
+ fail(pos >= text.length ? 'unexpected end of file' : 'expected a property name in double quotes');
179
+ const keyStart = pos;
180
+ parseString();
181
+ const keyEnd = pos;
182
+ skip();
183
+ if (text[pos] !== ':')
184
+ fail("expected ':' after property name");
185
+ pos += 1;
186
+ const value = parseValue();
187
+ members.push({ key: JSON.parse(text.slice(keyStart, keyEnd)), keyStart, keyEnd, value });
188
+ skip();
189
+ if (text[pos] === ',') {
190
+ pos += 1;
191
+ continue;
192
+ }
193
+ if (text[pos] === '}') {
194
+ pos += 1;
195
+ return { start, end: pos, members };
196
+ }
197
+ fail(pos >= text.length ? 'unexpected end of file' : "expected ',' or '}'");
198
+ }
199
+ }
200
+ if (ch === '[') {
201
+ pos += 1;
202
+ skip();
203
+ if (text[pos] === ']') {
204
+ pos += 1;
205
+ return { start, end: pos, members: null };
206
+ }
207
+ for (;;) {
208
+ parseValue();
209
+ skip();
210
+ if (text[pos] === ',') {
211
+ pos += 1;
212
+ continue;
213
+ }
214
+ if (text[pos] === ']') {
215
+ pos += 1;
216
+ return { start, end: pos, members: null };
217
+ }
218
+ fail(pos >= text.length ? 'unexpected end of file' : "expected ',' or ']'");
219
+ }
220
+ }
221
+ if (ch === '"') {
222
+ parseString();
223
+ return { start, end: pos, members: null };
224
+ }
225
+ const literal = /^(?:true|false|null|-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)/.exec(text.slice(pos, pos + 400));
226
+ if (literal !== null) {
227
+ pos += literal[0].length;
228
+ return { start, end: pos, members: null };
229
+ }
230
+ return fail(describe());
231
+ };
232
+ skip();
233
+ if (pos >= text.length)
234
+ fail('the file is empty');
235
+ const root = parseValue();
236
+ skip();
237
+ if (pos < text.length)
238
+ fail(describe());
239
+ return root;
240
+ }
241
+ // ---------------------------------------------------------------------------
242
+ // Eintrag textlich einsetzen
243
+ function lineIndent(text, position) {
244
+ const lineStart = text.lastIndexOf('\n', position - 1) + 1;
245
+ return /^[ \t]*/.exec(text.slice(lineStart))?.[0] ?? '';
246
+ }
247
+ function serializeAt(value, indent, unit, newline) {
248
+ return JSON.stringify(value, null, unit).split('\n').join(newline + indent);
249
+ }
250
+ function lastMember(object, key) {
251
+ const members = object.members ?? [];
252
+ for (let i = members.length - 1; i >= 0; i -= 1) {
253
+ if (members[i].key === key)
254
+ return members[i];
255
+ }
256
+ return undefined;
257
+ }
258
+ function detectIndentUnit(text, root) {
259
+ const first = root.members?.[0];
260
+ if (first !== undefined && text.slice(root.start, first.keyStart).includes('\n')) {
261
+ const own = lineIndent(text, first.keyStart);
262
+ const outer = lineIndent(text, root.start);
263
+ if (own.length > outer.length)
264
+ return own.slice(outer.length);
265
+ }
266
+ return ' ';
267
+ }
268
+ /** Setzt ein Mitglied in ein Objekt ein (hinter das letzte) oder fuellt ein leeres Objekt. */
269
+ function insertMember(text, object, key, value, unit, newline) {
270
+ const members = object.members;
271
+ const quotedKey = JSON.stringify(key);
272
+ if (members.length === 0) {
273
+ const outer = lineIndent(text, object.start);
274
+ const body = '{' + newline + outer + unit + quotedKey + ': ' + serializeAt(value, outer + unit, unit, newline) + newline + outer + '}';
275
+ return text.slice(0, object.start) + body + text.slice(object.end);
276
+ }
277
+ const last = members[members.length - 1];
278
+ const first = members[0];
279
+ const between = text.slice(last.keyEnd, last.value.start);
280
+ const colon = between.includes('\n') ? ': ' : between;
281
+ let insertion;
282
+ if (text.slice(object.start, first.keyStart).includes('\n')) {
283
+ const indent = lineIndent(text, last.keyStart);
284
+ insertion = ',' + newline + indent + quotedKey + colon + serializeAt(value, indent, unit, newline);
285
+ }
286
+ else {
287
+ insertion = ', ' + quotedKey + colon + JSON.stringify(value);
288
+ }
289
+ return text.slice(0, last.value.end) + insertion + text.slice(last.value.end);
290
+ }
291
+ export class SetupConfigError extends Error {
292
+ }
293
+ /**
294
+ * Berechnet den neuen Inhalt der Konfigurationsdatei. `current = null`: Datei
295
+ * fehlt. Alle anderen Eintraege und Schluessel bleiben byte-genau; das
296
+ * Ergebnis wird zur Sicherheit noch einmal geparst und verglichen.
297
+ */
298
+ export function computeConfigChange(current, key = SETUP_SERVER_KEY, entry = SETUP_ENTRY) {
299
+ if (current === null) {
300
+ return { text: JSON.stringify({ mcpServers: { [key]: entry } }, null, 2) + '\n', entryState: 'new', currentEntry: undefined };
301
+ }
302
+ const bom = current.startsWith('\uFEFF') ? '\uFEFF' : '';
303
+ const body = current.slice(bom.length);
304
+ const root = parseWithPositions(body);
305
+ if (root.members === null)
306
+ throw new SetupConfigError('The configuration file does not contain a JSON object at the top level.');
307
+ const parsed = JSON.parse(body);
308
+ const newline = body.includes('\r\n') ? '\r\n' : '\n';
309
+ const unit = detectIndentUnit(body, root);
310
+ let next;
311
+ let entryState = 'new';
312
+ let currentEntry;
313
+ const servers = lastMember(root, 'mcpServers');
314
+ if (servers === undefined) {
315
+ next = insertMember(body, root, 'mcpServers', { [key]: entry }, unit, newline);
316
+ }
317
+ else {
318
+ if (servers.value.members === null)
319
+ throw new SetupConfigError('"mcpServers" in the configuration file is not a JSON object.');
320
+ const existing = lastMember(servers.value, key);
321
+ if (existing === undefined) {
322
+ next = insertMember(body, servers.value, key, entry, unit, newline);
323
+ }
324
+ else {
325
+ currentEntry = parsed['mcpServers'][key];
326
+ if (isDeepStrictEqual(currentEntry, entry))
327
+ return { text: null, entryState: 'same', currentEntry };
328
+ entryState = 'different';
329
+ const indent = lineIndent(body, existing.keyStart);
330
+ const multiline = body.slice(servers.value.start, servers.value.end).includes('\n');
331
+ const replacement = multiline ? serializeAt(entry, indent, unit, newline) : JSON.stringify(entry);
332
+ next = body.slice(0, existing.value.start) + replacement + body.slice(existing.value.end);
333
+ }
334
+ }
335
+ const expected = { ...parsed, mcpServers: { ...(parsed['mcpServers'] ?? {}), [key]: entry } };
336
+ if (!isDeepStrictEqual(JSON.parse(next), expected)) {
337
+ throw new SetupConfigError('Internal check failed: the changed file would not contain exactly the old content plus the new entry.');
338
+ }
339
+ return { text: bom + next, entryState, currentEntry };
340
+ }
341
+ export class SetupCancelled extends Error {
342
+ }
343
+ const isYes = (answer) => /^(y|yes|j|ja)$/i.test(answer.trim());
344
+ function indentBlock(text, prefix) {
345
+ return text
346
+ .split('\n')
347
+ .map((line) => prefix + line)
348
+ .join('\n');
349
+ }
350
+ function entryText(value) {
351
+ return JSON.stringify(SETUP_SERVER_KEY) + ': ' + JSON.stringify(value, null, 2);
352
+ }
353
+ /** Schluessel aus einer Datei oder einer Eingabe bereinigen; leer = null. */
354
+ export function cleanLicenseKey(raw) {
355
+ const key = raw.replace(/\[20[01]~/g, '').trim();
356
+ if (key.length === 0)
357
+ return null;
358
+ if (/\s/.test(key))
359
+ return null;
360
+ return key;
361
+ }
362
+ /**
363
+ * Fuehrt `--setup` aus und gibt den Rueckgabewert des Prozesses zurueck:
364
+ * 0 nur, wenn alles geschrieben wurde (oder nichts zu aendern war, oder
365
+ * --dry-run ohne Befund).
366
+ */
367
+ export async function runSetup(argv, io, sys) {
368
+ const parsed = parseSetupArgs(argv);
369
+ if ('error' in parsed) {
370
+ io.err(parsed.error + ' Nothing was changed.\n');
371
+ return 1;
372
+ }
373
+ const { dryRun, licenseKeyFile } = parsed.options;
374
+ const configPath = claudeDesktopConfigPath(sys.platform, sys.home, sys.env);
375
+ if (configPath === null) {
376
+ io.err('Claude Desktop is available for macOS and Windows only; this computer runs ' + sys.platform + '. Nothing was changed.\n');
377
+ return 1;
378
+ }
379
+ if (!dryRun && !io.interactive) {
380
+ io.err('--setup asks before it writes and therefore needs an interactive terminal. Nothing was changed. Run it in Terminal (Mac) or PowerShell (Windows), or use --dry-run to only show the changes.\n');
381
+ return 1;
382
+ }
383
+ const paths = pathApi(sys.platform);
384
+ const keyPath = setupLicenseKeyPath(sys.platform, sys.home);
385
+ io.out('layoutbridge-spm setup for Claude Desktop\n');
386
+ io.out('Node.js: ' + sys.nodeVersion + '\n');
387
+ if (dryRun)
388
+ io.out('Dry run: nothing will be written.\n');
389
+ io.out('\n');
390
+ /* 1. Konfigurationsdatei lesen und die Aenderung berechnen (noch nichts schreiben). */
391
+ const configExists = existsSync(configPath);
392
+ let change;
393
+ try {
394
+ change = computeConfigChange(configExists ? readFileSync(configPath, 'utf8') : null);
395
+ }
396
+ catch (error) {
397
+ if (error instanceof JsonPositionError) {
398
+ io.err('The configuration file ' + configPath + ' is not valid JSON (' + error.message + '). Nothing was changed. Please correct the file (or restore a backup) and run --setup again. Support: ' + SUPPORT_EMAIL + '\n');
399
+ return 1;
400
+ }
401
+ if (error instanceof SetupConfigError) {
402
+ io.err(error.message + ' File: ' + configPath + '. Nothing was changed. Support: ' + SUPPORT_EMAIL + '\n');
403
+ return 1;
404
+ }
405
+ throw error;
406
+ }
407
+ const backup = configExists && change.text !== null ? backupPath(configPath, sys.now) : null;
408
+ io.out('Configuration file: ' + configPath + '\n');
409
+ if (!configExists) {
410
+ io.out(' The file does not exist yet and will be created.\n');
411
+ if (!existsSync(paths.dirname(configPath))) {
412
+ io.out(' Its folder does not exist either - is Claude Desktop installed? The folder will be created.\n');
413
+ }
414
+ }
415
+ else if (change.entryState === 'same') {
416
+ io.out(' The entry "' + SETUP_SERVER_KEY + '" is already set up exactly like this; the file stays unchanged.\n');
417
+ }
418
+ else {
419
+ io.out(' Only the entry "' + SETUP_SERVER_KEY + '" under "mcpServers" is ' + (change.entryState === 'different' ? 'replaced' : 'added') + '; everything else in the file stays exactly as it is.\n');
420
+ io.out(' Backup before writing: ' + backup + '\n');
421
+ }
422
+ if (change.entryState !== 'same') {
423
+ io.out('New entry:\n' + indentBlock(entryText(SETUP_ENTRY), ' ') + '\n');
424
+ }
425
+ io.out('\n');
426
+ /* 2. Schluessel: Datei, vorhandener Schluessel oder Eingabe. */
427
+ io.out('Licence key file: ' + keyPath + '\n');
428
+ let key = null;
429
+ let keyExisting = false;
430
+ try {
431
+ keyExisting = cleanLicenseKey(readFileSync(keyPath, 'utf8')) !== null;
432
+ }
433
+ catch {
434
+ keyExisting = false;
435
+ }
436
+ if (licenseKeyFile !== null) {
437
+ try {
438
+ key = cleanLicenseKey(readFileSync(licenseKeyFile, 'utf8'));
439
+ }
440
+ catch (error) {
441
+ io.err('The licence key file ' + licenseKeyFile + ' could not be read (' + (error instanceof Error ? error.message : String(error)) + '). Nothing was changed.\n');
442
+ return 1;
443
+ }
444
+ if (key === null) {
445
+ io.err('The licence key file ' + licenseKeyFile + ' does not contain a licence key (empty, or more than one word). Nothing was changed.\n');
446
+ return 1;
447
+ }
448
+ io.out(' The key is taken from ' + licenseKeyFile + ' and written to the licence key file.\n');
449
+ }
450
+ else if (keyExisting) {
451
+ io.out(' A licence key is already stored there.\n');
452
+ }
453
+ else {
454
+ io.out(' No licence key is stored there yet; you ' + (dryRun ? 'would' : 'will') + ' be asked for it (the input is not shown).\n');
455
+ }
456
+ io.out(' The key is only stored on this computer: not in the configuration file, not in the log, not sent anywhere. It is checked when a tool is first used.\n');
457
+ io.out('\n');
458
+ if (change.entryState === 'different') {
459
+ io.out('The entry "' + SETUP_SERVER_KEY + '" already exists:\n' + indentBlock(entryText(change.currentEntry), ' ') + '\n');
460
+ }
461
+ if (dryRun) {
462
+ if (change.entryState === 'different')
463
+ io.out('Without --dry-run you would be asked whether to replace it.\n');
464
+ if (licenseKeyFile === null && keyExisting)
465
+ io.out('Without --dry-run you would be asked whether to replace the stored licence key.\n');
466
+ io.out('Dry run: nothing was written.\n');
467
+ return 0;
468
+ }
469
+ /* 3. Rueckfragen. Nach einer Frage steht keine Leerzeile, deshalb davor eine setzen. */
470
+ let asked = false;
471
+ const ask = (question) => ((asked = true), io.ask(question));
472
+ const gap = () => (asked ? '\n' : '');
473
+ try {
474
+ const writeConfig = change.text !== null;
475
+ if (change.entryState === 'different') {
476
+ if (!isYes(await ask('Replace the existing entry with the new entry? [y/n] '))) {
477
+ io.out('The existing entry is kept. Nothing was changed.\n');
478
+ return 1;
479
+ }
480
+ }
481
+ let writeKey = key !== null;
482
+ if (key === null && keyExisting) {
483
+ writeKey = isYes(await ask('Replace the stored licence key? [y/n] '));
484
+ if (!writeKey)
485
+ io.out('The stored licence key is kept.\n');
486
+ }
487
+ if (key === null && (writeKey || !keyExisting)) {
488
+ asked = true;
489
+ key = cleanLicenseKey(await io.askHidden('Licence key (from your purchase receipt; input is hidden, finish with Enter): '));
490
+ if (key === null) {
491
+ io.err('No licence key entered (or it contains spaces). Nothing was changed.\n');
492
+ return 1;
493
+ }
494
+ writeKey = true;
495
+ }
496
+ if (!writeConfig && !writeKey) {
497
+ io.out(gap() + 'Nothing to change.\n');
498
+ summary(io, sys, configPath, 'unchanged', null, keyPath, 'kept');
499
+ return 0;
500
+ }
501
+ const plan = [];
502
+ if (writeConfig)
503
+ plan.push((configExists ? 'change ' : 'create ') + configPath + (backup !== null ? ' (backup first: ' + backup + ')' : ''));
504
+ if (writeKey)
505
+ plan.push('write the licence key to ' + keyPath);
506
+ io.out(gap() + 'About to:\n' + plan.map((line) => ' - ' + line).join('\n') + '\n');
507
+ if (!isYes(await ask('Write these changes now? [y/n] '))) {
508
+ io.out('Cancelled. Nothing was changed.\n');
509
+ return 1;
510
+ }
511
+ /* 4. Schreiben: Sicherung, Schluessel, Konfiguration. */
512
+ let backupWritten = null;
513
+ try {
514
+ if (writeConfig && backup !== null) {
515
+ copyFileSync(configPath, backup);
516
+ backupWritten = backup;
517
+ }
518
+ if (writeKey && key !== null) {
519
+ mkdirSync(paths.dirname(keyPath), { recursive: true, mode: 0o700 });
520
+ writeFileSync(keyPath, key + '\n', { encoding: 'utf8', mode: 0o600 });
521
+ chmodSync(keyPath, 0o600);
522
+ }
523
+ if (writeConfig && change.text !== null) {
524
+ mkdirSync(paths.dirname(configPath), { recursive: true });
525
+ writeFileSync(configPath, change.text, 'utf8');
526
+ }
527
+ }
528
+ catch (error) {
529
+ io.err('Writing failed: ' + (error instanceof Error ? error.message : String(error)) + (backupWritten !== null ? '. The previous configuration is saved as ' + backupWritten : '') + '. Support: ' + SUPPORT_EMAIL + '\n');
530
+ return 1;
531
+ }
532
+ io.out('\nDone.\n');
533
+ summary(io, sys, configPath, writeConfig ? (configExists ? 'changed' : 'created') : 'unchanged', backupWritten, keyPath, writeKey ? 'written' : 'kept');
534
+ return 0;
535
+ }
536
+ catch (error) {
537
+ if (error instanceof SetupCancelled) {
538
+ io.out('Cancelled. Nothing was changed.\n');
539
+ return 1;
540
+ }
541
+ throw error;
542
+ }
543
+ }
544
+ function summary(io, sys, configPath, configState, backup, keyPath, keyState) {
545
+ io.out(' Node.js: ' + sys.nodeVersion + '\n');
546
+ io.out(' Configuration file: ' + configPath + ' (' + configState + ')\n');
547
+ io.out(' Backup: ' + (backup ?? 'none') + '\n');
548
+ io.out(' Licence key file: ' + keyPath + ' (' + keyState + ')\n');
549
+ if (configState !== 'unchanged' || keyState === 'written') {
550
+ io.out(RESTART_SENTENCE + (sys.platform === 'darwin' ? ' (Cmd+Q)' : ' (also close it in the notification area of the taskbar)') + '\n');
551
+ }
552
+ }
553
+ // ---------------------------------------------------------------------------
554
+ // Terminal
555
+ /** Ein- und Ausgabe ueber das Terminal des Aufrufs. */
556
+ export function createTerminalIo() {
557
+ const stdin = process.stdin;
558
+ return {
559
+ interactive: stdin.isTTY === true && process.stdout.isTTY === true,
560
+ out: (text) => void process.stdout.write(text),
561
+ err: (text) => void process.stderr.write(text),
562
+ ask: (question) => new Promise((resolve) => {
563
+ const rl = createInterface({ input: stdin, output: process.stdout, terminal: true });
564
+ let answered = false;
565
+ rl.question(question, (answer) => {
566
+ answered = true;
567
+ rl.close();
568
+ resolve(answer);
569
+ });
570
+ rl.on('close', () => {
571
+ if (!answered) {
572
+ process.stdout.write('\n');
573
+ resolve('');
574
+ }
575
+ });
576
+ }),
577
+ askHidden: (question) => new Promise((resolve, reject) => {
578
+ /* Erst Echo aus, dann die Frage: sonst koennte schnell getippter oder
579
+ eingefuegter Text noch im Zeilenmodus mit Echo ankommen. */
580
+ stdin.setEncoding('utf8');
581
+ stdin.setRawMode(true);
582
+ process.stdout.write(question);
583
+ let value = '';
584
+ const finish = (done) => {
585
+ stdin.removeListener('data', onData);
586
+ stdin.setRawMode(false);
587
+ stdin.pause();
588
+ process.stdout.write('\n');
589
+ done();
590
+ };
591
+ const onData = (chunk) => {
592
+ for (const ch of chunk) {
593
+ if (ch === '\r' || ch === '\n' || ch === '\u0004')
594
+ return finish(() => resolve(value));
595
+ if (ch === '\u0003')
596
+ return finish(() => reject(new SetupCancelled()));
597
+ if (ch === '\u007f' || ch === '\b')
598
+ value = value.slice(0, -1);
599
+ else if (ch >= ' ')
600
+ value += ch;
601
+ }
602
+ };
603
+ stdin.resume();
604
+ stdin.on('data', onData);
605
+ }),
606
+ };
607
+ }
608
+ //# sourceMappingURL=setup.js.map