@m0x373/pimpt 0.0.0-stage → 0.1.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/dist/index.js ADDED
@@ -0,0 +1,1628 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { readFileSync as readFileSync2 } from "fs";
5
+ import pc2 from "picocolors";
6
+
7
+ // src/installer.ts
8
+ import {
9
+ cancel,
10
+ confirm,
11
+ intro,
12
+ isCancel,
13
+ log,
14
+ note,
15
+ outro,
16
+ select,
17
+ spinner,
18
+ text
19
+ } from "@clack/prompts";
20
+ import { spawn } from "child_process";
21
+ import { statSync } from "fs";
22
+ import { parse as parse2 } from "jsonc-parser";
23
+ import { win32 as pathWin32 } from "path";
24
+ import pc from "picocolors";
25
+ import { setTimeout as delay } from "timers/promises";
26
+
27
+ // src/settings.ts
28
+ var BACKGROUND_CUSTOM = "custom";
29
+ var transparencyOptions = [
30
+ { value: "off", label: "Off", hint: "Solid background" },
31
+ { value: "90", label: "Subtle", hint: "90% opacity" },
32
+ { value: "80", label: "Balanced", hint: "80% opacity" },
33
+ { value: "70", label: "Frosted", hint: "70% opacity" },
34
+ { value: "50", label: "Heavy", hint: "50% opacity" },
35
+ { value: "0", label: "Fully transparent", hint: "Content 0%; tab row 50% acrylic" }
36
+ ];
37
+ var backgroundOptions = [
38
+ { value: "keep", label: "Keep current", hint: "Leave the background unchanged" },
39
+ { value: "#0B1021", label: "Midnight", hint: "#0B1021" },
40
+ { value: "#2E3440", label: "Nord", hint: "#2E3440" },
41
+ { value: "#282A36", label: "Dracula", hint: "#282A36" },
42
+ { value: "#002B36", label: "Solarized Dark", hint: "#002B36" },
43
+ {
44
+ value: BACKGROUND_CUSTOM,
45
+ label: "Custom hex",
46
+ hint: "Enter your own color"
47
+ }
48
+ ];
49
+ var fontOptions = [
50
+ { value: "keep", label: "Keep current", hint: "Use the configured font" },
51
+ { value: "Cascadia Mono", label: "Cascadia Mono", hint: "Windows Terminal default" },
52
+ { value: "Cascadia Code", label: "Cascadia Code", hint: "Ligatures included" },
53
+ { value: "Consolas", label: "Consolas", hint: "Classic Windows font" },
54
+ { value: "JetBrains Mono", label: "JetBrains Mono", hint: "Popular developer font" },
55
+ { value: "Fira Code", label: "Fira Code", hint: "Monospace with ligatures" },
56
+ { value: "Hack", label: "Hack", hint: "Designed for source code" },
57
+ { value: "Source Code Pro", label: "Source Code Pro", hint: "Adobe's monospace font" },
58
+ { value: "IBM Plex Mono", label: "IBM Plex Mono", hint: "Technical, readable monospace" }
59
+ ];
60
+ var fontSizeOptions = ["10", "11", "12", "14", "16", "18"].map(
61
+ (value) => ({ value, label: `${value} pt`, hint: "Points" })
62
+ );
63
+ var cursorOptions = [
64
+ { value: "keep", label: "Keep current", hint: "Use the configured cursor" },
65
+ { value: "bar", label: "Bar", hint: "Thin vertical line" },
66
+ { value: "filledBox", label: "Block", hint: "Filled rectangle" },
67
+ { value: "underscore", label: "Underline", hint: "Line below the text" },
68
+ { value: "vintage", label: "Vintage", hint: "Retro terminal blink" }
69
+ ];
70
+ var schemeOptions = [
71
+ { value: "keep", label: "Keep current", hint: "Use the configured scheme" },
72
+ { value: "Campbell", label: "Campbell", hint: "Windows Terminal default" },
73
+ { value: "One Half Dark", label: "One Half Dark", hint: "Cool, muted palette" },
74
+ { value: "Solarized Dark", label: "Solarized Dark", hint: "Low-contrast classic" },
75
+ { value: "Tango Dark", label: "Tango Dark", hint: "Colorful and warm" },
76
+ { value: "Vintage", label: "Vintage", hint: "Retro phosphor look" }
77
+ ];
78
+ var launchModeOptions = [
79
+ { value: "default", label: "Windowed", hint: "Normal window" },
80
+ { value: "maximized", label: "Maximized", hint: "Fill the screen" },
81
+ { value: "fullscreen", label: "Fullscreen", hint: "Hide the window frame" },
82
+ { value: "focus", label: "Focus mode", hint: "Minimal distractions" },
83
+ {
84
+ value: "maximizedFocus",
85
+ label: "Maximized focus",
86
+ hint: "Maximized and distraction-free"
87
+ }
88
+ ];
89
+ var appThemeOptions = [
90
+ { value: "system", label: "System", hint: "Follow Windows" },
91
+ { value: "dark", label: "Dark", hint: "Always dark" },
92
+ { value: "light", label: "Light", hint: "Always light" }
93
+ ];
94
+ var tabLayoutOptions = [
95
+ { value: "titlebar", label: "Tabs in titlebar", hint: "Compact, modern layout" },
96
+ { value: "separate", label: "Separate tab row", hint: "Tabs below the titlebar" }
97
+ ];
98
+ var tabWidthOptions = [
99
+ { value: "equal", label: "Equal", hint: "All tabs share the width" },
100
+ { value: "titleLength", label: "Title length", hint: "Width fits the title" },
101
+ { value: "compact", label: "Compact", hint: "Shrink to fit" }
102
+ ];
103
+ var iconOptions = [
104
+ { value: "classic", label: "Classic terminal", hint: "Monochrome prompt glyph" },
105
+ { value: "rocket", label: "Rocket", hint: "Launch-ready" },
106
+ { value: "bolt", label: "Bolt", hint: "Fast and loud" },
107
+ { value: "code", label: "Code", hint: "Developer vibes" }
108
+ ];
109
+ function labelOf(options, value) {
110
+ return options.find((option) => option.value === value)?.label ?? value;
111
+ }
112
+ function onOff(value) {
113
+ return value === true ? "Enabled" : "Disabled";
114
+ }
115
+ var settings = [
116
+ {
117
+ id: "transparency",
118
+ group: "Appearance",
119
+ label: "Transparency",
120
+ hint: "Window opacity",
121
+ prompt: {
122
+ kind: "select",
123
+ message: "How transparent should the window be?",
124
+ options: transparencyOptions,
125
+ initialValue: "80"
126
+ },
127
+ summarize: (value) => {
128
+ if (value === "off") return "Off";
129
+ if (value === "0") return "Fully transparent content";
130
+ return `Acrylic \xB7 ${String(value)}% opacity`;
131
+ },
132
+ jsonPath: ["profiles", "defaults", "opacity"]
133
+ },
134
+ {
135
+ id: "background",
136
+ group: "Appearance",
137
+ label: "Background color",
138
+ hint: "Preset or custom color",
139
+ prompt: {
140
+ kind: "select",
141
+ message: "Pick a background color",
142
+ options: backgroundOptions,
143
+ initialValue: "keep"
144
+ },
145
+ summarize: (value) => {
146
+ if (value === "keep") return "Keep current";
147
+ const preset = backgroundOptions.find(
148
+ (option) => option.value === value && option.value !== "keep" && option.value !== BACKGROUND_CUSTOM
149
+ );
150
+ return preset ? `${preset.label} (${preset.value})` : `Custom (${String(value)})`;
151
+ },
152
+ jsonPath: ["profiles", "defaults", "background"],
153
+ skipIfKeep: true
154
+ },
155
+ {
156
+ id: "backgroundImage",
157
+ group: "Appearance",
158
+ label: "Background image",
159
+ hint: "Path to a local image",
160
+ prompt: {
161
+ kind: "text",
162
+ message: "Path to a background image (leave empty to skip)",
163
+ placeholder: "C:\\Users\\you\\wallpaper.png",
164
+ initialValue: ""
165
+ },
166
+ summarize: (value) => String(value).trim() ? String(value) : "Not set"
167
+ },
168
+ {
169
+ id: "fontFace",
170
+ group: "Appearance",
171
+ label: "Font",
172
+ hint: "Terminal font",
173
+ prompt: {
174
+ kind: "select",
175
+ message: "Choose a font",
176
+ options: fontOptions,
177
+ initialValue: "keep"
178
+ },
179
+ summarize: (value) => labelOf(fontOptions, String(value)),
180
+ jsonPath: ["profiles", "defaults", "font", "face"],
181
+ skipIfKeep: true
182
+ },
183
+ {
184
+ id: "fontSize",
185
+ group: "Appearance",
186
+ label: "Font size",
187
+ hint: "Text size in points",
188
+ prompt: {
189
+ kind: "select",
190
+ message: "Choose a font size",
191
+ options: fontSizeOptions,
192
+ initialValue: "12"
193
+ },
194
+ summarize: (value) => `${String(value)} pt`
195
+ },
196
+ {
197
+ id: "cursorShape",
198
+ group: "Appearance",
199
+ label: "Cursor",
200
+ hint: "Shape of the text cursor",
201
+ prompt: {
202
+ kind: "select",
203
+ message: "Choose a cursor shape",
204
+ options: cursorOptions,
205
+ initialValue: "keep"
206
+ },
207
+ summarize: (value) => labelOf(cursorOptions, String(value))
208
+ },
209
+ {
210
+ id: "colorScheme",
211
+ group: "Colors",
212
+ label: "Color scheme",
213
+ hint: "ANSI palette for text and accents",
214
+ prompt: {
215
+ kind: "select",
216
+ message: "Choose a color scheme",
217
+ options: schemeOptions,
218
+ initialValue: "keep"
219
+ },
220
+ summarize: (value) => labelOf(schemeOptions, String(value))
221
+ },
222
+ {
223
+ id: "launchMode",
224
+ group: "Window & tabs",
225
+ label: "Launch mode",
226
+ hint: "How the window opens",
227
+ prompt: {
228
+ kind: "select",
229
+ message: "How should Windows Terminal open?",
230
+ options: launchModeOptions,
231
+ initialValue: "default"
232
+ },
233
+ summarize: (value) => labelOf(launchModeOptions, String(value))
234
+ },
235
+ {
236
+ id: "appTheme",
237
+ group: "Window & tabs",
238
+ label: "Application theme",
239
+ hint: "Light, dark, or follow Windows",
240
+ prompt: {
241
+ kind: "select",
242
+ message: "Choose the application theme",
243
+ options: appThemeOptions,
244
+ initialValue: "system"
245
+ },
246
+ summarize: (value) => labelOf(appThemeOptions, String(value))
247
+ },
248
+ {
249
+ id: "tabLayout",
250
+ group: "Window & tabs",
251
+ label: "Tab bar",
252
+ hint: "Where the tab row is displayed",
253
+ prompt: {
254
+ kind: "select",
255
+ message: "Where should the tab bar live?",
256
+ options: tabLayoutOptions,
257
+ initialValue: "titlebar"
258
+ },
259
+ summarize: (value) => labelOf(tabLayoutOptions, String(value))
260
+ },
261
+ {
262
+ id: "tabWidthMode",
263
+ group: "Window & tabs",
264
+ label: "Tab width",
265
+ hint: "How tab widths are calculated",
266
+ prompt: {
267
+ kind: "select",
268
+ message: "How should tabs be sized?",
269
+ options: tabWidthOptions,
270
+ initialValue: "equal"
271
+ },
272
+ summarize: (value) => labelOf(tabWidthOptions, String(value))
273
+ },
274
+ {
275
+ id: "icon",
276
+ group: "Profile",
277
+ label: "Profile icon",
278
+ hint: "Icon style for your profile",
279
+ prompt: {
280
+ kind: "select",
281
+ message: "Pick a profile icon style",
282
+ options: iconOptions,
283
+ initialValue: "classic"
284
+ },
285
+ summarize: (value) => labelOf(iconOptions, String(value))
286
+ },
287
+ {
288
+ id: "tabTitle",
289
+ group: "Profile",
290
+ label: "Tab title",
291
+ hint: "Custom title shown on the tab",
292
+ prompt: {
293
+ kind: "text",
294
+ message: "Custom tab title (leave empty for automatic)",
295
+ placeholder: "pimpt",
296
+ initialValue: ""
297
+ },
298
+ summarize: (value) => String(value).trim() ? String(value) : "Automatic"
299
+ },
300
+ {
301
+ id: "startingDirectory",
302
+ group: "Features",
303
+ label: "Starting directory",
304
+ hint: "Default shell folder",
305
+ prompt: {
306
+ kind: "text",
307
+ message: "Enter or paste a path; relative paths start in your user folder",
308
+ placeholder: "C:\\Users\\you\\Projects",
309
+ initialValue: ""
310
+ },
311
+ summarize: (value) => String(value).trim() ? String(value) : "Default",
312
+ jsonPath: ["profiles", "defaults", "startingDirectory"]
313
+ },
314
+ {
315
+ id: "copyOnSelect",
316
+ group: "Behavior",
317
+ label: "Copy on select",
318
+ hint: "Copy text as soon as it is selected",
319
+ prompt: {
320
+ kind: "confirm",
321
+ message: "Copy text automatically when selected?",
322
+ initialValue: false
323
+ },
324
+ summarize: onOff
325
+ },
326
+ {
327
+ id: "confirmClose",
328
+ group: "Behavior",
329
+ label: "Confirm close",
330
+ hint: "Ask before closing multiple tabs",
331
+ prompt: {
332
+ kind: "confirm",
333
+ message: "Confirm before closing a window with many tabs?",
334
+ initialValue: true
335
+ },
336
+ summarize: onOff
337
+ },
338
+ {
339
+ id: "terminalIcons",
340
+ group: "Appearance",
341
+ label: "PowerShell file icons",
342
+ hint: "File icons in PowerShell",
343
+ prompt: {
344
+ kind: "confirm",
345
+ message: "Enable PowerShell file icons? This installs Terminal-Icons and Hack Nerd Font Mono if needed, adds it to your PowerShell profile, and switches Windows Terminal to that font.",
346
+ initialValue: true
347
+ },
348
+ summarize: onOff
349
+ },
350
+ {
351
+ id: "historyPreview",
352
+ group: "Features",
353
+ label: "Command history preview",
354
+ hint: "Browse recent commands",
355
+ prompt: {
356
+ kind: "confirm",
357
+ message: "Enable a scrollable PowerShell history list? This installs PSReadLine if needed and lets the Up and Down arrows browse the visible history suggestions.",
358
+ initialValue: true
359
+ },
360
+ summarize: onOff
361
+ },
362
+ {
363
+ id: "treeListing",
364
+ group: "Features",
365
+ label: "Tree-style ls",
366
+ hint: "Show files in a colored directory tree",
367
+ prompt: {
368
+ kind: "confirm",
369
+ message: "Show directory contents as a colored tree when you run ls? Output is capped at 50 entries by default. Use -Limit or -All for more. Get-ChildItem remains available as gci.",
370
+ initialValue: true
371
+ },
372
+ summarize: onOff
373
+ },
374
+ {
375
+ id: "hidePowerShellBanner",
376
+ group: "Appearance",
377
+ label: "Hide PowerShell startup banner",
378
+ hint: "Hide the welcome text",
379
+ prompt: {
380
+ kind: "confirm",
381
+ message: "Hide the Windows PowerShell startup banner?",
382
+ initialValue: true
383
+ },
384
+ summarize: onOff
385
+ },
386
+ {
387
+ id: "focusFollowMouse",
388
+ group: "Behavior",
389
+ label: "Focus follows mouse",
390
+ hint: "Focus panes on hover",
391
+ prompt: {
392
+ kind: "confirm",
393
+ message: "Focus panes when hovering with the mouse?",
394
+ initialValue: false
395
+ },
396
+ summarize: onOff
397
+ },
398
+ {
399
+ id: "trimPaste",
400
+ group: "Behavior",
401
+ label: "Trim paste",
402
+ hint: "Remove trailing whitespace when pasting",
403
+ prompt: {
404
+ kind: "confirm",
405
+ message: "Trim trailing whitespace when pasting?",
406
+ initialValue: true
407
+ },
408
+ summarize: onOff
409
+ }
410
+ ];
411
+
412
+ // src/fonts.ts
413
+ import { createHash } from "crypto";
414
+ import { execFileSync, spawnSync } from "child_process";
415
+ import { mkdir, mkdtemp, rm, writeFile } from "fs/promises";
416
+ import { tmpdir } from "os";
417
+ import { join, resolve } from "path";
418
+ var googleFontsRevision = "29b4ec6df2522a8aee279414b78a07910b065874";
419
+ var hackRevision = "a737c121cabb337fdfe655d8c7304729f351e30f";
420
+ var fontRegistryKeys = [
421
+ "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts",
422
+ "HKCU\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"
423
+ ];
424
+ var userFontsRegistryKey = "HKCU\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts";
425
+ var NERD_FONT_FAMILY = "Hack Nerd Font Mono";
426
+ var nerdFontArchive = {
427
+ fileName: "Hack.tar.xz",
428
+ url: "https://github.com/ryanoasis/nerd-fonts/releases/download/v3.5.1/Hack.tar.xz",
429
+ sha256: "cdd389472e10e2261520140ff1b382b4f8a226af5fd0b2735b975d31151d9c3c",
430
+ fontEntry: "HackNerdFontMono-Regular.ttf",
431
+ licenseEntry: "LICENSE.md"
432
+ };
433
+ var fontAssets = {
434
+ "Cascadia Mono": {
435
+ fileName: `pimpt-cascadia-mono-${googleFontsRevision.slice(0, 8)}.ttf`,
436
+ fontUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/cascadiamono/CascadiaMono%5Bwght%5D.ttf`,
437
+ licenseFileName: "pimpt-cascadia-mono-OFL.txt",
438
+ licenseUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/cascadiamono/OFL.txt`
439
+ },
440
+ "Cascadia Code": {
441
+ fileName: `pimpt-cascadia-code-${googleFontsRevision.slice(0, 8)}.ttf`,
442
+ fontUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/cascadiacode/CascadiaCode%5Bwght%5D.ttf`,
443
+ licenseFileName: "pimpt-cascadia-code-OFL.txt",
444
+ licenseUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/cascadiacode/OFL.txt`
445
+ },
446
+ "JetBrains Mono": {
447
+ fileName: `pimpt-jetbrains-mono-${googleFontsRevision.slice(0, 8)}.ttf`,
448
+ fontUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/jetbrainsmono/JetBrainsMono%5Bwght%5D.ttf`,
449
+ licenseFileName: "pimpt-jetbrains-mono-OFL.txt",
450
+ licenseUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/jetbrainsmono/OFL.txt`
451
+ },
452
+ "Fira Code": {
453
+ fileName: `pimpt-fira-code-${googleFontsRevision.slice(0, 8)}.ttf`,
454
+ fontUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/firacode/FiraCode%5Bwght%5D.ttf`,
455
+ licenseFileName: "pimpt-fira-code-OFL.txt",
456
+ licenseUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/firacode/OFL.txt`
457
+ },
458
+ Hack: {
459
+ fileName: `pimpt-hack-${hackRevision.slice(0, 8)}.ttf`,
460
+ fontUrl: `https://raw.githubusercontent.com/source-foundry/Hack/${hackRevision}/build/ttf/Hack-Regular.ttf`,
461
+ licenseFileName: "pimpt-hack-LICENSE.md",
462
+ licenseUrl: `https://raw.githubusercontent.com/source-foundry/Hack/${hackRevision}/LICENSE.md`
463
+ },
464
+ "Source Code Pro": {
465
+ fileName: `pimpt-source-code-pro-${googleFontsRevision.slice(0, 8)}.ttf`,
466
+ fontUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/sourcecodepro/SourceCodePro%5Bwght%5D.ttf`,
467
+ licenseFileName: "pimpt-source-code-pro-OFL.txt",
468
+ licenseUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/sourcecodepro/OFL.txt`
469
+ },
470
+ "IBM Plex Mono": {
471
+ fileName: `pimpt-ibm-plex-mono-${googleFontsRevision.slice(0, 8)}.ttf`,
472
+ fontUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/ibmplexmono/IBMPlexMono-Regular.ttf`,
473
+ licenseFileName: "pimpt-ibm-plex-mono-OFL.txt",
474
+ licenseUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/ibmplexmono/OFL.txt`
475
+ }
476
+ };
477
+ function assertWindows() {
478
+ if (process.platform !== "win32") {
479
+ throw new Error("Automatic font installation is only available on Windows.");
480
+ }
481
+ }
482
+ function queryFontRegistry(key) {
483
+ const result = spawnSync("reg.exe", ["query", key], {
484
+ encoding: "utf8",
485
+ windowsHide: true
486
+ });
487
+ if (result.error) {
488
+ throw new Error(`Could not query the Windows font registry: ${result.error.message}`, {
489
+ cause: result.error
490
+ });
491
+ }
492
+ if (result.status === 1) return "";
493
+ if (result.status !== 0) {
494
+ throw new Error(
495
+ `Could not query the Windows font registry: ${result.stderr.trim() || `reg.exe exited with code ${String(result.status)}`}`
496
+ );
497
+ }
498
+ return result.stdout;
499
+ }
500
+ function isFontInstalled(fontFamily) {
501
+ assertWindows();
502
+ const family = fontFamily.toLocaleLowerCase("en-US");
503
+ return fontRegistryKeys.map(queryFontRegistry).join("\n").split(/\r?\n/).some((line) => line.trimStart().toLocaleLowerCase("en-US").startsWith(family));
504
+ }
505
+ function canInstallFont(fontFamily) {
506
+ return fontAssets[fontFamily] !== void 0;
507
+ }
508
+ async function download(url, description) {
509
+ let response;
510
+ try {
511
+ response = await fetch(url, { signal: AbortSignal.timeout(3e4) });
512
+ } catch (error) {
513
+ throw new Error(`Could not download ${description}: ${String(error)}`, {
514
+ cause: error
515
+ });
516
+ }
517
+ if (!response.ok) {
518
+ throw new Error(`Could not download ${description}: HTTP ${response.status}.`);
519
+ }
520
+ const data = Buffer.from(await response.arrayBuffer());
521
+ if (data.length === 0 || data.length > 3e7) {
522
+ throw new Error(`Downloaded ${description} has an invalid file size.`);
523
+ }
524
+ return data;
525
+ }
526
+ function isFontFile(data) {
527
+ return data[0] === 0 && data[1] === 1 && data[2] === 0 && data[3] === 0 || data.toString("ascii", 0, 4) === "OTTO";
528
+ }
529
+ async function saveAndRegisterFont(localAppData, fontFamily, fileName, licenseFileName, fontData, licenseData) {
530
+ if (!isFontFile(fontData)) {
531
+ throw new Error(
532
+ `The downloaded ${fontFamily} file is not a valid TrueType/OpenType font.`
533
+ );
534
+ }
535
+ const licenseText = licenseData.toString("utf8").toLowerCase();
536
+ if (!licenseText.includes("license")) {
537
+ throw new Error(`The downloaded ${fontFamily} license file is invalid.`);
538
+ }
539
+ const fontDirectory = join(localAppData, "Microsoft", "Windows", "Fonts");
540
+ await mkdir(fontDirectory, { recursive: true });
541
+ const fontPath = join(fontDirectory, fileName);
542
+ const licensePath = join(fontDirectory, licenseFileName);
543
+ await writeFile(fontPath, fontData);
544
+ await writeFile(licensePath, licenseData);
545
+ execFileSync(
546
+ "reg.exe",
547
+ [
548
+ "add",
549
+ userFontsRegistryKey,
550
+ "/v",
551
+ `${fontFamily} (TrueType)`,
552
+ "/t",
553
+ "REG_SZ",
554
+ "/d",
555
+ fontPath,
556
+ "/f"
557
+ ],
558
+ { encoding: "utf8", windowsHide: true, stdio: "pipe" }
559
+ );
560
+ }
561
+ async function installFont(fontFamily) {
562
+ assertWindows();
563
+ const asset = fontAssets[fontFamily];
564
+ if (!asset) {
565
+ throw new Error(`Automatic installation is not supported for ${fontFamily}.`);
566
+ }
567
+ const localAppData = process.env.LOCALAPPDATA;
568
+ if (!localAppData) {
569
+ throw new Error("LOCALAPPDATA is not set; the font cannot be installed.");
570
+ }
571
+ const [fontData, licenseData] = await Promise.all([
572
+ download(asset.fontUrl, `${fontFamily} font`),
573
+ download(asset.licenseUrl, `${fontFamily} license`)
574
+ ]);
575
+ await saveAndRegisterFont(
576
+ localAppData,
577
+ fontFamily,
578
+ asset.fileName,
579
+ asset.licenseFileName,
580
+ fontData,
581
+ licenseData
582
+ );
583
+ }
584
+ async function installNerdFont() {
585
+ assertWindows();
586
+ if (isFontInstalled(NERD_FONT_FAMILY)) return;
587
+ const localAppData = process.env.LOCALAPPDATA;
588
+ if (!localAppData) {
589
+ throw new Error("LOCALAPPDATA is not set; the font cannot be installed.");
590
+ }
591
+ const archiveData = await download(nerdFontArchive.url, "Hack Nerd Font archive");
592
+ const actualHash = createHash("sha256").update(archiveData).digest("hex");
593
+ if (actualHash !== nerdFontArchive.sha256) {
594
+ throw new Error("The downloaded Hack Nerd Font archive failed its SHA-256 check.");
595
+ }
596
+ const temporaryDirectory = await mkdtemp(join(tmpdir(), "pimpt-nerd-font-"));
597
+ const archivePath = resolve(temporaryDirectory, nerdFontArchive.fileName);
598
+ try {
599
+ await writeFile(archivePath, archiveData);
600
+ let fontData;
601
+ let licenseData;
602
+ try {
603
+ fontData = execFileSync(
604
+ "tar.exe",
605
+ ["-xOf", archivePath, nerdFontArchive.fontEntry],
606
+ { maxBuffer: 1e7, windowsHide: true }
607
+ );
608
+ licenseData = execFileSync(
609
+ "tar.exe",
610
+ ["-xOf", archivePath, nerdFontArchive.licenseEntry],
611
+ { maxBuffer: 1e6, windowsHide: true }
612
+ );
613
+ } catch (error) {
614
+ throw new Error("Could not extract Hack Nerd Font from its archive.", {
615
+ cause: error
616
+ });
617
+ }
618
+ await saveAndRegisterFont(
619
+ localAppData,
620
+ NERD_FONT_FAMILY,
621
+ "pimpt-hack-nerd-font-v3.5.1.ttf",
622
+ "pimpt-hack-nerd-font-LICENSE.md",
623
+ fontData,
624
+ licenseData
625
+ );
626
+ } finally {
627
+ await rm(temporaryDirectory, { recursive: true, force: true });
628
+ }
629
+ }
630
+
631
+ // src/powershell.ts
632
+ import { execFileSync as execFileSync2 } from "child_process";
633
+ var terminalIconsVersion = "0.11.0";
634
+ var psReadLineVersion = "2.4.5";
635
+ function runPowerShell(script, operation) {
636
+ if (process.platform !== "win32") {
637
+ throw new Error(`${operation} is only supported on Windows.`);
638
+ }
639
+ try {
640
+ execFileSync2(
641
+ "powershell.exe",
642
+ [
643
+ "-NoLogo",
644
+ "-NoProfile",
645
+ "-NonInteractive",
646
+ "-Command",
647
+ script
648
+ ],
649
+ { encoding: "utf8", windowsHide: true, stdio: "pipe" }
650
+ );
651
+ } catch (error) {
652
+ throw new Error(`${operation} failed: ${String(error)}`, { cause: error });
653
+ }
654
+ }
655
+ function installPowerShellGalleryModule(moduleName, version, allowClobber) {
656
+ const clobberOption = allowClobber ? "-AllowClobber " : "";
657
+ const script = String.raw`$ErrorActionPreference = 'Stop'
658
+ $protocols = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
659
+ [Net.ServicePointManager]::SecurityProtocol = $protocols
660
+ $nugetProvider = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue
661
+ if (-not $nugetProvider) {
662
+ Install-PackageProvider -Name NuGet -MinimumVersion '2.8.5.201' -Scope CurrentUser -Force -Confirm:$false -ErrorAction Stop | Out-Null
663
+ }
664
+
665
+ $requiredVersion = [version]'${version}'
666
+ $module = Get-Module -ListAvailable -Name '${moduleName}' | Where-Object { $_.Version -eq $requiredVersion }
667
+ if (-not $module) {
668
+ Install-Module -Name '${moduleName}' -RequiredVersion '${version}' -Scope CurrentUser -Repository PSGallery ${clobberOption}-Force -Confirm:$false -ErrorAction Stop
669
+ }
670
+
671
+ $module = Get-Module -ListAvailable -Name '${moduleName}' | Where-Object { $_.Version -eq $requiredVersion }
672
+ if (-not $module) {
673
+ throw '${moduleName} ${version} is not available after installation.'
674
+ }`;
675
+ runPowerShell(script, `Installing the ${moduleName} PowerShell module`);
676
+ }
677
+ function addPowerShellProfileBlock(feature, commands, operation) {
678
+ const beginMarker = `# >>> pimpt ${feature} >>>`;
679
+ const endMarker = `# <<< pimpt ${feature} <<<`;
680
+ const block = [beginMarker, ...commands, endMarker].join("\r\n");
681
+ const encodedMarker = Buffer.from(beginMarker, "utf16le").toString("base64");
682
+ const encodedBlock = Buffer.from(block, "utf16le").toString("base64");
683
+ const script = String.raw`$ErrorActionPreference = 'Stop'
684
+ $profilePath = $PROFILE.CurrentUserCurrentHost
685
+ $profileDirectory = Split-Path -Parent $profilePath
686
+ if (-not (Test-Path -LiteralPath $profileDirectory)) {
687
+ New-Item -ItemType Directory -Path $profileDirectory -Force | Out-Null
688
+ }
689
+
690
+ $profileContent = ''
691
+ if (Test-Path -LiteralPath $profilePath) {
692
+ $profileContent = Get-Content -LiteralPath $profilePath -Raw
693
+ }
694
+
695
+ $beginMarker = [System.Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('${encodedMarker}'))
696
+ if ($profileContent -notmatch [regex]::Escape($beginMarker)) {
697
+ if (-not (Test-Path -LiteralPath $profilePath)) {
698
+ New-Item -ItemType File -Path $profilePath -Force | Out-Null
699
+ }
700
+
701
+ $encoding = 'Default'
702
+ $bytes = [System.IO.File]::ReadAllBytes($profilePath)
703
+ if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) {
704
+ $encoding = 'Unicode'
705
+ } elseif ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF) {
706
+ $encoding = 'BigEndianUnicode'
707
+ } elseif ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
708
+ $encoding = 'UTF8'
709
+ }
710
+
711
+ $profileBlock = [System.Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('${encodedBlock}'))
712
+ $newline = [Environment]::NewLine
713
+ $separator = if ($profileContent.Length -gt 0 -and -not $profileContent.EndsWith([string][char]10)) { $newline } else { '' }
714
+ Add-Content -LiteralPath $profilePath -Encoding $encoding -Value "$separator$profileBlock"
715
+ }`;
716
+ runPowerShell(script, operation);
717
+ }
718
+ function installTerminalIconsModule() {
719
+ installPowerShellGalleryModule(
720
+ "Terminal-Icons",
721
+ terminalIconsVersion,
722
+ false
723
+ );
724
+ }
725
+ function installPSReadLineModule() {
726
+ installPowerShellGalleryModule("PSReadLine", psReadLineVersion, true);
727
+ }
728
+ function addTerminalIconsToPowerShellProfile() {
729
+ addPowerShellProfileBlock(
730
+ "terminal icons",
731
+ ["Import-Module Terminal-Icons -ErrorAction Stop"],
732
+ "Adding Terminal-Icons to the Windows PowerShell profile"
733
+ );
734
+ }
735
+ function addHistoryPreviewToPowerShellProfile() {
736
+ addPowerShellProfileBlock(
737
+ "command history preview",
738
+ [
739
+ `Import-Module PSReadLine -RequiredVersion '${psReadLineVersion}' -Force -ErrorAction Stop`,
740
+ "Set-PSReadLineOption -PredictionSource History -PredictionViewStyle ListView"
741
+ ],
742
+ "Adding command history preview to the Windows PowerShell profile"
743
+ );
744
+ }
745
+ function readPowerShellProfileFeatures() {
746
+ const features = {
747
+ terminalIcons: false,
748
+ historyPreview: false,
749
+ treeListing: false
750
+ };
751
+ if (process.platform !== "win32") return features;
752
+ let encodedProfile;
753
+ try {
754
+ encodedProfile = execFileSync2(
755
+ "powershell.exe",
756
+ [
757
+ "-NoLogo",
758
+ "-NoProfile",
759
+ "-NonInteractive",
760
+ "-Command",
761
+ String.raw`$profilePath = $PROFILE.CurrentUserCurrentHost
762
+ if (Test-Path -LiteralPath $profilePath) {
763
+ $content = [System.IO.File]::ReadAllText($profilePath)
764
+ [Console]::Out.Write([Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($content)))
765
+ }`
766
+ ],
767
+ { encoding: "utf8", windowsHide: true, stdio: "pipe" }
768
+ ).trim();
769
+ } catch (error) {
770
+ throw new Error("Could not inspect the Windows PowerShell profile.", {
771
+ cause: error
772
+ });
773
+ }
774
+ const profile = Buffer.from(encodedProfile, "base64").toString("utf16le");
775
+ const hasBlock = (name) => profile.includes(`# >>> pimpt ${name} >>>`) && profile.includes(`# <<< pimpt ${name} <<<`);
776
+ features.terminalIcons = hasBlock("terminal icons");
777
+ features.historyPreview = hasBlock("command history preview");
778
+ features.treeListing = hasBlock("tree-style ls v2") || hasBlock("tree-style ls");
779
+ return features;
780
+ }
781
+ function addTreeListingToPowerShellProfile() {
782
+ addPowerShellProfileBlock(
783
+ "tree-style ls alias",
784
+ ["Remove-Item Alias:ls -Force -ErrorAction SilentlyContinue"],
785
+ "Enabling the tree-style ls command in the Windows PowerShell profile"
786
+ );
787
+ addPowerShellProfileBlock(
788
+ "tree-style ls v2",
789
+ [
790
+ "function ls {",
791
+ " [CmdletBinding()]",
792
+ " param(",
793
+ " [Parameter(Position = 0)] [string]$Path = '.',",
794
+ " [ValidateRange(1, 32)] [int]$Depth = 4,",
795
+ " [ValidateRange(1, 10000)] [int]$Limit = 50,",
796
+ " [switch]$All,",
797
+ " [switch]$Force",
798
+ " )",
799
+ " try {",
800
+ " $root = Get-Item -LiteralPath $Path -ErrorAction Stop",
801
+ " } catch {",
802
+ ` Write-Host ("ls: cannot access '{0}': {1}" -f $Path, $_.Exception.Message) -ForegroundColor Red`,
803
+ " return",
804
+ " }",
805
+ " if (-not $root.PSIsContainer) {",
806
+ " Write-Host $root.Name -ForegroundColor Gray",
807
+ " return",
808
+ " }",
809
+ " Write-Host $root.FullName -ForegroundColor Cyan",
810
+ " $state = [pscustomobject]@{ Shown = 0; Limit = $Limit; All = [bool]$All; Truncated = $false }",
811
+ " $walk = {",
812
+ " param($Directory, [string]$Prefix, [int]$CurrentDepth, $State)",
813
+ " if ($CurrentDepth -ge $Depth) { return }",
814
+ " try {",
815
+ " $children = @(Get-ChildItem -LiteralPath $Directory.FullName -Force:$Force -ErrorAction Stop | Sort-Object @{ Expression = { if ($_.PSIsContainer) { 0 } else { 1 } } }, Name)",
816
+ " } catch {",
817
+ ' Write-Host ("{0}\u2514\u2500\u2500 [cannot read: {1}]" -f $Prefix, $_.Exception.Message) -ForegroundColor Red',
818
+ " return",
819
+ " }",
820
+ " for ($index = 0; $index -lt $children.Count; $index++) {",
821
+ " if (-not $State.All -and $State.Shown -ge $State.Limit) {",
822
+ " $State.Truncated = $true",
823
+ " break",
824
+ " }",
825
+ " $entry = $children[$index]",
826
+ " $isLast = $index -eq ($children.Count - 1)",
827
+ " $connector = if ($isLast) { '\u2514\u2500\u2500 ' } else { '\u251C\u2500\u2500 ' }",
828
+ " $color = if ($entry.PSIsContainer) { 'Cyan' } else { 'Gray' }",
829
+ " Write-Host ($Prefix + $connector + $entry.Name) -ForegroundColor $color",
830
+ " $State.Shown++",
831
+ " if ($entry.PSIsContainer -and ($CurrentDepth + 1 -lt $Depth)) {",
832
+ " $childPrefix = $Prefix + $(if ($isLast) { ' ' } else { '\u2502 ' })",
833
+ " & $walk $entry $childPrefix ($CurrentDepth + 1) $State",
834
+ " if ($State.Truncated) { break }",
835
+ " }",
836
+ " }",
837
+ " }",
838
+ " & $walk $root '' 0 $state",
839
+ " if ($state.Truncated) {",
840
+ ' Write-Host ("\u2026 output capped at {0} entries (use -Limit <number> or -All)" -f $state.Limit) -ForegroundColor DarkGray',
841
+ " }",
842
+ "}"
843
+ ],
844
+ "Adding the tree-style ls command to the Windows PowerShell profile"
845
+ );
846
+ }
847
+
848
+ // src/summary.ts
849
+ function normalizeHex(value) {
850
+ return value.trim().replace(/^#?/, "#").toUpperCase();
851
+ }
852
+ function buildSummarySections(answers) {
853
+ const sections = [];
854
+ let current = null;
855
+ for (const setting of settings) {
856
+ const value = answers[setting.id];
857
+ if (value === void 0) continue;
858
+ if (!current || current.group !== setting.group) {
859
+ current = { group: setting.group, rows: [] };
860
+ sections.push(current);
861
+ }
862
+ current.rows.push([setting.label, setting.summarize(value)]);
863
+ }
864
+ return sections;
865
+ }
866
+
867
+ // src/wtsettings.ts
868
+ import {
869
+ copyFileSync,
870
+ existsSync,
871
+ lstatSync,
872
+ readFileSync,
873
+ readdirSync,
874
+ writeFileSync
875
+ } from "fs";
876
+ import { basename, dirname, join as join2 } from "path";
877
+ import { applyEdits, modify, parse } from "jsonc-parser";
878
+ var InvalidSettingsFileError = class extends Error {
879
+ constructor() {
880
+ super(
881
+ "Windows Terminal settings.json is invalid JSONC. No changes were written. Restore the file from a backup before trying again."
882
+ );
883
+ this.name = "InvalidSettingsFileError";
884
+ }
885
+ };
886
+ function findSettingsPath() {
887
+ const localAppData = process.env.LOCALAPPDATA;
888
+ if (!localAppData) return void 0;
889
+ const candidates = [
890
+ join2(localAppData, "Packages", "Microsoft.WindowsTerminal_8wekyb3d8bbwe", "LocalState", "settings.json"),
891
+ join2(localAppData, "Packages", "Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe", "LocalState", "settings.json"),
892
+ join2(localAppData, "Packages", "Microsoft.WindowsTerminal.Canary_8wekyb3d8bbwe", "LocalState", "settings.json")
893
+ ];
894
+ for (const path of candidates) {
895
+ if (existsSync(path)) return path;
896
+ }
897
+ return void 0;
898
+ }
899
+ function readSettingsFile(settingsPath) {
900
+ return readFileSync(settingsPath, "utf8");
901
+ }
902
+ function writeSettingsFile(settingsPath, content) {
903
+ writeFileSync(settingsPath, content, "utf8");
904
+ }
905
+ function createSettingsBackup(settingsPath) {
906
+ const timestamp = Date.now();
907
+ const backupPrefix = `${basename(settingsPath)}.backup-${timestamp}-`;
908
+ let sequence = 0;
909
+ let backupPath;
910
+ do {
911
+ backupPath = join2(dirname(settingsPath), `${backupPrefix}${sequence}`);
912
+ sequence += 1;
913
+ } while (existsSync(backupPath));
914
+ copyFileSync(settingsPath, backupPath);
915
+ return backupPath;
916
+ }
917
+ function listSettingsBackups(settingsPath) {
918
+ const backupPrefix = `${basename(settingsPath)}.backup-`;
919
+ return readdirSync(dirname(settingsPath)).filter((name) => name.startsWith(backupPrefix)).flatMap((name) => {
920
+ const match = /^(\d+)-(\d+)$/.exec(name.slice(backupPrefix.length));
921
+ if (!match) return [];
922
+ const path = join2(dirname(settingsPath), name);
923
+ if (!lstatSync(path).isFile()) return [];
924
+ return [{ path, createdAt: new Date(Number(match[1])) }];
925
+ }).sort((left, right) => right.createdAt.getTime() - left.createdAt.getTime());
926
+ }
927
+ function restoreSettingsBackup(settingsPath, backupPath) {
928
+ const backup = listSettingsBackups(settingsPath).find(
929
+ (candidate) => candidate.path === backupPath
930
+ );
931
+ if (!backup) {
932
+ throw new Error("The selected settings backup is not available.");
933
+ }
934
+ const content = readFileSync(backup.path, "utf8");
935
+ const parseErrors = [];
936
+ const parsedContent = parse(content, parseErrors, { allowTrailingComma: true });
937
+ if (parseErrors.length > 0 || parsedContent === null || typeof parsedContent !== "object" || Array.isArray(parsedContent)) {
938
+ throw new Error("The selected backup is invalid JSONC. Nothing was changed.");
939
+ }
940
+ const safetyBackupPath = createSettingsBackup(settingsPath);
941
+ writeSettingsFile(settingsPath, content);
942
+ return safetyBackupPath;
943
+ }
944
+ function setSetting(content, jsonPath, value) {
945
+ const parseErrors = [];
946
+ const parsedContent = parse(content, parseErrors, { allowTrailingComma: true });
947
+ if (parseErrors.length > 0 || parsedContent === null || typeof parsedContent !== "object" || Array.isArray(parsedContent)) {
948
+ throw new InvalidSettingsFileError();
949
+ }
950
+ const edits = modify(content, jsonPath, value, {
951
+ formattingOptions: {
952
+ tabSize: 4,
953
+ insertSpaces: true
954
+ }
955
+ });
956
+ return applyEdits(content, edits);
957
+ }
958
+
959
+ // src/installer.ts
960
+ var BANNER = `
961
+ \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
962
+ \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D
963
+ \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551
964
+ \u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2551
965
+ \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551
966
+ \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D
967
+ `;
968
+ function bail(value) {
969
+ if (isCancel(value)) {
970
+ cancel("Setup cancelled. Nothing was changed.");
971
+ process.exit(0);
972
+ }
973
+ return value;
974
+ }
975
+ var StartingDirectoryInputError = class extends Error {
976
+ };
977
+ function normalizeStartingDirectory(value) {
978
+ let directory = value.trim();
979
+ if (!directory) return "";
980
+ if (directory.startsWith('"') && directory.endsWith('"') || directory.startsWith("'") && directory.endsWith("'")) {
981
+ directory = directory.slice(1, -1).trim();
982
+ }
983
+ if (!directory) return "";
984
+ const userProfile = process.env.USERPROFILE ?? process.env.HOME;
985
+ if (/^~(?=$|[\\/])/.test(directory)) {
986
+ if (!userProfile) {
987
+ throw new StartingDirectoryInputError(
988
+ "Cannot resolve '~' because the user folder is unknown."
989
+ );
990
+ }
991
+ directory = directory.replace(/^~/, userProfile);
992
+ }
993
+ directory = directory.replace(/%([^%]+)%/g, (variable, name) => {
994
+ const key = Object.keys(process.env).find(
995
+ (candidate) => candidate.toLowerCase() === name.toLowerCase()
996
+ );
997
+ const environmentValue = key ? process.env[key] : void 0;
998
+ if (environmentValue === void 0) {
999
+ throw new StartingDirectoryInputError(
1000
+ `Unknown environment variable in starting path: ${variable}`
1001
+ );
1002
+ }
1003
+ return environmentValue;
1004
+ });
1005
+ if (/^[a-zA-Z]:(?![\\/])/.test(directory)) {
1006
+ throw new StartingDirectoryInputError(
1007
+ "Use an absolute drive path, for example C:\\Users\\you\\Downloads."
1008
+ );
1009
+ }
1010
+ if (/^[\\/](?![\\/])/.test(directory)) {
1011
+ if (!userProfile) {
1012
+ throw new StartingDirectoryInputError(
1013
+ "Cannot resolve a rooted path because the user folder is unknown."
1014
+ );
1015
+ }
1016
+ directory = pathWin32.resolve(pathWin32.parse(userProfile).root, directory);
1017
+ }
1018
+ if (pathWin32.isAbsolute(directory)) return pathWin32.normalize(directory);
1019
+ if (!userProfile) {
1020
+ throw new StartingDirectoryInputError(
1021
+ "Cannot resolve a relative starting path because the user folder is unknown."
1022
+ );
1023
+ }
1024
+ return pathWin32.resolve(userProfile, directory);
1025
+ }
1026
+ function validateStartingDirectory(value) {
1027
+ let directory;
1028
+ try {
1029
+ directory = normalizeStartingDirectory(value ?? "");
1030
+ } catch (error) {
1031
+ if (error instanceof StartingDirectoryInputError) return error.message;
1032
+ throw error;
1033
+ }
1034
+ if (!directory || process.platform !== "win32") return void 0;
1035
+ try {
1036
+ if (!statSync(directory).isDirectory()) {
1037
+ return "The starting path must be an existing folder.";
1038
+ }
1039
+ } catch (error) {
1040
+ if (error !== null && typeof error === "object" && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
1041
+ return `Folder not found: ${directory}`;
1042
+ }
1043
+ if (error !== null && typeof error === "object" && "code" in error && (error.code === "EACCES" || error.code === "EPERM")) {
1044
+ return `Cannot access folder: ${directory}`;
1045
+ }
1046
+ throw error;
1047
+ }
1048
+ return void 0;
1049
+ }
1050
+ async function askSetting(setting, currentValue) {
1051
+ const prompt = setting.prompt;
1052
+ if (prompt.kind === "select") {
1053
+ const selectValue = async () => bail(
1054
+ await select({
1055
+ message: prompt.message,
1056
+ options: prompt.options,
1057
+ initialValue: prompt.initialValue
1058
+ })
1059
+ );
1060
+ if (setting.id === "fontFace") {
1061
+ while (true) {
1062
+ const fontFamily = await selectValue();
1063
+ if (fontFamily === "keep" || isFontInstalled(fontFamily)) {
1064
+ return fontFamily;
1065
+ }
1066
+ if (!canInstallFont(fontFamily)) {
1067
+ log.warn(
1068
+ pc.yellow(
1069
+ `${fontFamily} is not installed and cannot be installed automatically. Choose another font.`
1070
+ )
1071
+ );
1072
+ continue;
1073
+ }
1074
+ const shouldInstall = bail(
1075
+ await confirm({
1076
+ message: `${fontFamily} is not installed. Install it now? Select No to choose another font.`,
1077
+ initialValue: true
1078
+ })
1079
+ );
1080
+ if (!shouldInstall) continue;
1081
+ const fontSpinner = spinner();
1082
+ fontSpinner.start(`Installing ${fontFamily}\u2026`);
1083
+ try {
1084
+ await installFont(fontFamily);
1085
+ } catch (error) {
1086
+ fontSpinner.error(`Could not install ${fontFamily}.`);
1087
+ throw error;
1088
+ }
1089
+ fontSpinner.stop(
1090
+ `${fontFamily} installed. Restart Windows Terminal to use it.`
1091
+ );
1092
+ return fontFamily;
1093
+ }
1094
+ }
1095
+ const value = await selectValue();
1096
+ if (setting.id === "background" && value === BACKGROUND_CUSTOM) {
1097
+ const custom = bail(
1098
+ await text({
1099
+ message: "Enter a background color",
1100
+ placeholder: "#1E1E2E",
1101
+ initialValue: "",
1102
+ validate: (input) => {
1103
+ const raw = input ?? "";
1104
+ return /^#?[0-9a-fA-F]{6}$/.test(raw.trim()) ? void 0 : "Use a 6-digit hex color, e.g. #1E1E2E";
1105
+ }
1106
+ })
1107
+ );
1108
+ return normalizeHex(custom);
1109
+ }
1110
+ return value;
1111
+ }
1112
+ if (prompt.kind === "text") {
1113
+ const value = bail(
1114
+ await text({
1115
+ message: prompt.message,
1116
+ placeholder: prompt.placeholder,
1117
+ initialValue: currentValue === void 0 ? prompt.initialValue : String(currentValue),
1118
+ ...setting.id === "startingDirectory" ? { validate: validateStartingDirectory } : prompt.validate ? { validate: prompt.validate } : {}
1119
+ })
1120
+ );
1121
+ return setting.id === "startingDirectory" ? normalizeStartingDirectory(value) : value;
1122
+ }
1123
+ return bail(
1124
+ await confirm({
1125
+ message: prompt.message,
1126
+ initialValue: prompt.initialValue
1127
+ })
1128
+ );
1129
+ }
1130
+ function renderSummary(answers) {
1131
+ const sections = buildSummarySections(answers);
1132
+ if (sections.length === 0) {
1133
+ note("No settings selected.", "Configuration summary");
1134
+ return;
1135
+ }
1136
+ const labelWidth = Math.max(
1137
+ ...sections.flatMap(
1138
+ (section) => section.rows.map(([label]) => label.length)
1139
+ )
1140
+ );
1141
+ const lines = [];
1142
+ sections.forEach((section, index) => {
1143
+ if (index > 0) lines.push("");
1144
+ lines.push(pc.bold(pc.cyan(section.group)));
1145
+ for (const [label, value] of section.rows) {
1146
+ lines.push(` ${pc.dim(label.padEnd(labelWidth))} ${value}`);
1147
+ }
1148
+ });
1149
+ note(lines.join("\n"), "Your configuration");
1150
+ }
1151
+ function openNewTerminalTab() {
1152
+ if (!process.env.WT_SESSION) return false;
1153
+ try {
1154
+ const terminal = spawn("wt.exe", ["-w", "0", "new-tab"], {
1155
+ detached: true,
1156
+ stdio: "ignore",
1157
+ windowsHide: true
1158
+ });
1159
+ terminal.on("error", (error) => {
1160
+ log.warn(
1161
+ pc.yellow(`Could not open a new Windows Terminal tab: ${error.message}`)
1162
+ );
1163
+ });
1164
+ terminal.unref();
1165
+ return true;
1166
+ } catch (error) {
1167
+ log.warn(
1168
+ pc.yellow(`Could not open a new Windows Terminal tab: ${String(error)}`)
1169
+ );
1170
+ return false;
1171
+ }
1172
+ }
1173
+ function getDefaultProfileIndex(content) {
1174
+ const parsed = parse2(content);
1175
+ if (typeof parsed.defaultProfile !== "string" || !parsed.profiles || typeof parsed.profiles !== "object" || !Array.isArray(parsed.profiles.list)) {
1176
+ return void 0;
1177
+ }
1178
+ const index = parsed.profiles.list.findIndex(
1179
+ (profile) => profile !== null && typeof profile === "object" && "guid" in profile && profile.guid === parsed.defaultProfile
1180
+ );
1181
+ return index < 0 ? void 0 : index;
1182
+ }
1183
+ function getWindowsPowerShellProfiles(content) {
1184
+ const parseErrors = [];
1185
+ const parsed = parse2(content, parseErrors, {
1186
+ allowTrailingComma: true
1187
+ });
1188
+ if (parseErrors.length > 0 || parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
1189
+ throw new InvalidSettingsFileError();
1190
+ }
1191
+ const profiles = "profiles" in parsed ? parsed.profiles : void 0;
1192
+ if (profiles === null || typeof profiles !== "object" || Array.isArray(profiles) || !("list" in profiles) || !Array.isArray(profiles.list)) {
1193
+ return [];
1194
+ }
1195
+ return profiles.list.flatMap((profile, index) => {
1196
+ if (profile === null || typeof profile !== "object" || Array.isArray(profile)) {
1197
+ return [];
1198
+ }
1199
+ const name = "name" in profile && typeof profile.name === "string" ? profile.name : "";
1200
+ const commandline = "commandline" in profile && typeof profile.commandline === "string" ? profile.commandline : void 0;
1201
+ return name.toLowerCase() === "windows powershell" || /(?:^|[\\/"])powershell\.exe(?:["\s]|$)/i.test(commandline ?? "") ? [
1202
+ {
1203
+ index,
1204
+ ...commandline !== void 0 ? { commandline } : {}
1205
+ }
1206
+ ] : [];
1207
+ });
1208
+ }
1209
+ function readConfiguredAnswers(settingsPath) {
1210
+ const answers = {};
1211
+ try {
1212
+ const profileFeatures = readPowerShellProfileFeatures();
1213
+ if (profileFeatures.terminalIcons) answers.terminalIcons = true;
1214
+ if (profileFeatures.historyPreview) answers.historyPreview = true;
1215
+ if (profileFeatures.treeListing) answers.treeListing = true;
1216
+ } catch (error) {
1217
+ log.warn(
1218
+ pc.yellow(
1219
+ `Could not detect configured PowerShell features: ${error instanceof Error ? error.message : String(error)}`
1220
+ )
1221
+ );
1222
+ }
1223
+ if (!settingsPath) return answers;
1224
+ const content = readSettingsFile(settingsPath);
1225
+ const parseErrors = [];
1226
+ const parsed = parse2(content, parseErrors, {
1227
+ allowTrailingComma: true
1228
+ });
1229
+ if (parseErrors.length > 0 || parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
1230
+ log.warn(
1231
+ pc.yellow(
1232
+ "Could not read existing settings.json values because the file contains invalid JSONC."
1233
+ )
1234
+ );
1235
+ return answers;
1236
+ }
1237
+ const profiles = "profiles" in parsed && parsed.profiles !== null ? parsed.profiles : void 0;
1238
+ const profileList = profiles !== null && typeof profiles === "object" && !Array.isArray(profiles) && "list" in profiles && Array.isArray(profiles.list) ? profiles.list : [];
1239
+ const defaultProfileGuid = "defaultProfile" in parsed ? parsed.defaultProfile : void 0;
1240
+ const defaultProfile = typeof defaultProfileGuid === "string" ? profileList.find(
1241
+ (profile) => profile !== null && typeof profile === "object" && !Array.isArray(profile) && "guid" in profile && profile.guid === defaultProfileGuid
1242
+ ) : void 0;
1243
+ const defaults = profiles !== null && typeof profiles === "object" && !Array.isArray(profiles) && "defaults" in profiles ? profiles.defaults : void 0;
1244
+ if (defaults !== null && typeof defaults === "object" && !Array.isArray(defaults)) {
1245
+ if ("opacity" in defaults && typeof defaults.opacity === "number" && Number.isInteger(defaults.opacity) && defaults.opacity >= 0 && defaults.opacity <= 100) {
1246
+ answers.transparency = defaults.opacity === 100 && "useAcrylic" in defaults && defaults.useAcrylic === false ? "off" : String(defaults.opacity);
1247
+ }
1248
+ if ("background" in defaults && typeof defaults.background === "string") {
1249
+ answers.background = defaults.background;
1250
+ }
1251
+ const defaultStartingDirectory = "startingDirectory" in defaults && typeof defaults.startingDirectory === "string" ? defaults.startingDirectory : void 0;
1252
+ const font = "font" in defaults && defaults.font !== null ? defaults.font : void 0;
1253
+ if (font !== null && typeof font === "object" && !Array.isArray(font) && "face" in font && typeof font.face === "string") {
1254
+ answers.fontFace = font.face;
1255
+ }
1256
+ const profileStartingDirectory = defaultProfile !== null && typeof defaultProfile === "object" && !Array.isArray(defaultProfile) && "startingDirectory" in defaultProfile && typeof defaultProfile.startingDirectory === "string" ? defaultProfile.startingDirectory : void 0;
1257
+ const startingDirectory = profileStartingDirectory ?? defaultStartingDirectory;
1258
+ if (startingDirectory !== void 0) {
1259
+ const error = validateStartingDirectory(startingDirectory);
1260
+ if (error) {
1261
+ log.warn(pc.yellow(`Ignoring the saved starting directory: ${error}`));
1262
+ } else {
1263
+ answers.startingDirectory = normalizeStartingDirectory(startingDirectory);
1264
+ }
1265
+ }
1266
+ }
1267
+ const powerShellProfiles = getWindowsPowerShellProfiles(content);
1268
+ if (powerShellProfiles.length > 0 && powerShellProfiles.every(
1269
+ (profile) => /(?:^|\s)-NoLogo(?:\s|$)/i.test(profile.commandline ?? "")
1270
+ )) {
1271
+ answers.hidePowerShellBanner = true;
1272
+ }
1273
+ return answers;
1274
+ }
1275
+ async function applySettings(answers, settingsPath = findSettingsPath()) {
1276
+ const terminalIconsEnabled = answers.terminalIcons === true;
1277
+ const hidePowerShellBanner = answers.hidePowerShellBanner === true;
1278
+ if (!settingsPath) {
1279
+ if (terminalIconsEnabled) {
1280
+ throw new Error(
1281
+ "Cannot enable PowerShell file icons because Windows Terminal settings.json could not be found."
1282
+ );
1283
+ }
1284
+ if (hidePowerShellBanner) {
1285
+ throw new Error(
1286
+ "Cannot hide the PowerShell startup banner because Windows Terminal settings.json could not be found."
1287
+ );
1288
+ }
1289
+ log.warn(
1290
+ pc.dim("Could not find Windows Terminal settings.json. Skipping write.")
1291
+ );
1292
+ return;
1293
+ }
1294
+ log.info(pc.dim(`Writing to ${settingsPath}\u2026`));
1295
+ const originalContent = readSettingsFile(settingsPath);
1296
+ let content = originalContent;
1297
+ for (const setting of settings) {
1298
+ const value = answers[setting.id];
1299
+ if (value === void 0) continue;
1300
+ if (!setting.jsonPath) continue;
1301
+ if (setting.skipIfKeep && value === "keep") continue;
1302
+ if (terminalIconsEnabled && setting.id === "fontFace") continue;
1303
+ if (setting.id === "transparency") {
1304
+ const opacity = value === "off" ? 100 : Number(value);
1305
+ if (!Number.isInteger(opacity) || opacity < 0 || opacity > 100) {
1306
+ throw new Error(`Invalid transparency value: ${String(value)}`);
1307
+ }
1308
+ content = setSetting(content, setting.jsonPath, opacity);
1309
+ content = setSetting(
1310
+ content,
1311
+ ["profiles", "defaults", "useAcrylic"],
1312
+ value !== "off"
1313
+ );
1314
+ content = setSetting(
1315
+ content,
1316
+ ["useAcrylicInTabRow"],
1317
+ value !== "off"
1318
+ );
1319
+ } else if (setting.id === "background") {
1320
+ content = setSetting(content, setting.jsonPath, value);
1321
+ content = setSetting(
1322
+ content,
1323
+ ["profiles", "defaults", "tabColor"],
1324
+ value
1325
+ );
1326
+ } else if (setting.id === "fontFace") {
1327
+ const defaultProfileIndex = getDefaultProfileIndex(content);
1328
+ content = setSetting(content, setting.jsonPath, value);
1329
+ if (defaultProfileIndex !== void 0) {
1330
+ content = setSetting(
1331
+ content,
1332
+ ["profiles", "list", defaultProfileIndex, "font", "face"],
1333
+ value
1334
+ );
1335
+ }
1336
+ } else if (setting.id === "startingDirectory") {
1337
+ const defaultProfileIndex = getDefaultProfileIndex(content);
1338
+ const startingDirectory = String(value).trim() || void 0;
1339
+ content = setSetting(content, setting.jsonPath, startingDirectory);
1340
+ if (defaultProfileIndex !== void 0) {
1341
+ content = setSetting(
1342
+ content,
1343
+ ["profiles", "list", defaultProfileIndex, "startingDirectory"],
1344
+ startingDirectory
1345
+ );
1346
+ }
1347
+ } else {
1348
+ content = setSetting(content, setting.jsonPath, value);
1349
+ }
1350
+ }
1351
+ if (hidePowerShellBanner) {
1352
+ const powerShellProfiles = getWindowsPowerShellProfiles(content);
1353
+ if (powerShellProfiles.length === 0) {
1354
+ throw new Error(
1355
+ "Cannot hide the PowerShell startup banner because no Windows PowerShell profile was found."
1356
+ );
1357
+ }
1358
+ for (const profile of powerShellProfiles) {
1359
+ const commandline = profile.commandline?.trim() ? profile.commandline : "powershell.exe";
1360
+ if (/(?:^|\s)-NoLogo(?:\s|$)/i.test(commandline)) continue;
1361
+ content = setSetting(
1362
+ content,
1363
+ ["profiles", "list", profile.index, "commandline"],
1364
+ `${commandline} -NoLogo`
1365
+ );
1366
+ }
1367
+ }
1368
+ if (terminalIconsEnabled) {
1369
+ const defaultProfileIndex = getDefaultProfileIndex(content);
1370
+ content = setSetting(
1371
+ content,
1372
+ ["profiles", "defaults", "font", "face"],
1373
+ NERD_FONT_FAMILY
1374
+ );
1375
+ if (defaultProfileIndex !== void 0) {
1376
+ content = setSetting(
1377
+ content,
1378
+ ["profiles", "list", defaultProfileIndex, "font", "face"],
1379
+ NERD_FONT_FAMILY
1380
+ );
1381
+ }
1382
+ }
1383
+ if (terminalIconsEnabled) {
1384
+ await installNerdFont();
1385
+ installTerminalIconsModule();
1386
+ }
1387
+ const dirty = content !== originalContent;
1388
+ if (dirty) {
1389
+ const backupPath = createSettingsBackup(settingsPath);
1390
+ log.info(pc.dim(`Backup created at ${backupPath}`));
1391
+ writeSettingsFile(settingsPath, content);
1392
+ }
1393
+ if (terminalIconsEnabled) {
1394
+ addTerminalIconsToPowerShellProfile();
1395
+ }
1396
+ }
1397
+ async function runInstaller() {
1398
+ console.log(pc.cyan(BANNER));
1399
+ intro(pc.bold("Pimp up your Terminal."));
1400
+ const selectableSettings = settings.filter(
1401
+ (setting) => setting.group === "Appearance" && (setting.jsonPath !== void 0 || setting.id === "terminalIcons" || setting.id === "hidePowerShellBanner") || setting.group === "Features" && (setting.jsonPath !== void 0 || setting.id === "historyPreview" || setting.id === "treeListing")
1402
+ );
1403
+ const settingsPath = findSettingsPath();
1404
+ const backups = settingsPath ? listSettingsBackups(settingsPath) : [];
1405
+ const answers = readConfiguredAnswers(settingsPath);
1406
+ let initialPickerValue = selectableSettings[0]?.id;
1407
+ let initialArea = "Appearance";
1408
+ while (true) {
1409
+ const areaOptions = [
1410
+ { value: "Appearance", label: "Appearance", hint: "Colors and profile settings" },
1411
+ { value: "Features", label: "Features", hint: "Optional extras" },
1412
+ { value: "Other", label: "Other", hint: "Backups and recovery" }
1413
+ ];
1414
+ if (Object.keys(answers).length > 0) {
1415
+ areaOptions.push({
1416
+ value: "done",
1417
+ label: "Done",
1418
+ hint: "Review and apply your settings"
1419
+ });
1420
+ }
1421
+ const selectedArea = bail(
1422
+ await select({
1423
+ message: "Choose an area",
1424
+ options: areaOptions,
1425
+ initialValue: initialArea
1426
+ })
1427
+ );
1428
+ if (selectedArea === "done") break;
1429
+ initialArea = selectedArea;
1430
+ if (selectedArea === "Other") {
1431
+ if (backups.length === 0) {
1432
+ note(
1433
+ "No backups yet. pimpt creates one before changing settings.json.",
1434
+ "Other"
1435
+ );
1436
+ continue;
1437
+ }
1438
+ const selectedAction = bail(
1439
+ await select({
1440
+ message: "Other",
1441
+ options: [
1442
+ {
1443
+ value: "restoreBackup",
1444
+ label: "Restore settings backup",
1445
+ hint: "Choose a saved version"
1446
+ },
1447
+ { value: "back", label: "Back", hint: "Choose another area" }
1448
+ ],
1449
+ initialValue: "restoreBackup"
1450
+ })
1451
+ );
1452
+ if (selectedAction === "back") continue;
1453
+ if (!settingsPath) {
1454
+ throw new Error("Windows Terminal settings.json could not be found.");
1455
+ }
1456
+ const selectedBackup = bail(
1457
+ await select({
1458
+ message: "Which backup should be restored?",
1459
+ options: backups.map((backup) => ({
1460
+ value: backup.path,
1461
+ label: backup.createdAt.toLocaleString(),
1462
+ hint: backup.path
1463
+ })),
1464
+ ...backups[0] ? { initialValue: backups[0].path } : {}
1465
+ })
1466
+ );
1467
+ const shouldRestore = bail(
1468
+ await confirm({
1469
+ message: "This replaces settings.json. The current file will be backed up first. Continue?",
1470
+ initialValue: false
1471
+ })
1472
+ );
1473
+ if (!shouldRestore) continue;
1474
+ const restoreSpinner = spinner();
1475
+ restoreSpinner.start("Restoring settings.json backup\u2026");
1476
+ try {
1477
+ const safetyBackupPath = restoreSettingsBackup(
1478
+ settingsPath,
1479
+ selectedBackup
1480
+ );
1481
+ restoreSpinner.stop("Settings restored successfully.");
1482
+ log.info(
1483
+ pc.dim(`The previous settings were backed up to ${safetyBackupPath}`)
1484
+ );
1485
+ outro(pc.green("\u2713 Windows Terminal settings.json was restored."));
1486
+ } catch (error) {
1487
+ restoreSpinner.error("Could not restore the settings backup.");
1488
+ throw error;
1489
+ }
1490
+ return;
1491
+ }
1492
+ while (true) {
1493
+ const visibleSettings = selectableSettings.filter(
1494
+ (setting2) => setting2.group === selectedArea && !(answers.terminalIcons === true && setting2.id === "fontFace")
1495
+ );
1496
+ const options = visibleSettings.map((setting2) => ({
1497
+ value: setting2.id,
1498
+ label: answers[setting2.id] === void 0 ? setting2.label : `${pc.green("\u25CF")} ${setting2.label}`,
1499
+ hint: setting2.hint
1500
+ }));
1501
+ options.push({
1502
+ value: "back",
1503
+ label: "Back",
1504
+ hint: "Choose another area"
1505
+ });
1506
+ const firstOption = options[0];
1507
+ if (!firstOption) {
1508
+ throw new Error(`No settings are available in ${selectedArea}.`);
1509
+ }
1510
+ const selectedInitialValue = initialPickerValue && options.some((option) => option.value === initialPickerValue) ? initialPickerValue : firstOption.value;
1511
+ const selectedId = bail(
1512
+ await select({
1513
+ message: selectedArea,
1514
+ options,
1515
+ initialValue: selectedInitialValue
1516
+ })
1517
+ );
1518
+ if (selectedId === "back") break;
1519
+ const setting = selectableSettings.find(
1520
+ (candidate) => candidate.id === selectedId && candidate.group === selectedArea
1521
+ );
1522
+ if (!setting) {
1523
+ throw new Error(
1524
+ `Unknown setting selected in ${selectedArea}: ${selectedId}`
1525
+ );
1526
+ }
1527
+ initialPickerValue = selectedId;
1528
+ log.step(pc.bold(pc.cyan(setting.group)));
1529
+ answers[setting.id] = await askSetting(setting, answers[setting.id]);
1530
+ if (setting.id === "terminalIcons" && answers.terminalIcons === true) {
1531
+ answers.fontFace = NERD_FONT_FAMILY;
1532
+ initialPickerValue = "terminalIcons";
1533
+ }
1534
+ }
1535
+ }
1536
+ renderSummary(answers);
1537
+ const proceed = bail(
1538
+ await confirm({
1539
+ message: "Install these settings? (writes to settings.json)",
1540
+ initialValue: true
1541
+ })
1542
+ );
1543
+ if (!proceed) {
1544
+ cancel("Setup cancelled. Nothing was changed.");
1545
+ return;
1546
+ }
1547
+ const s = spinner();
1548
+ s.start("Building your configuration\u2026");
1549
+ await delay(300);
1550
+ s.message("Checking compatibility\u2026");
1551
+ await delay(300);
1552
+ s.message(
1553
+ answers.terminalIcons === true || answers.historyPreview === true || answers.treeListing === true || answers.hidePowerShellBanner === true ? "Applying PowerShell options\u2026" : "Writing settings.json\u2026"
1554
+ );
1555
+ await delay(400);
1556
+ try {
1557
+ await applySettings(answers, settingsPath);
1558
+ if (answers.historyPreview === true) {
1559
+ installPSReadLineModule();
1560
+ addHistoryPreviewToPowerShellProfile();
1561
+ }
1562
+ if (answers.treeListing === true) {
1563
+ addTreeListingToPowerShellProfile();
1564
+ }
1565
+ } catch (error) {
1566
+ if (!(error instanceof InvalidSettingsFileError)) throw error;
1567
+ s.error(error.message);
1568
+ process.exitCode = 1;
1569
+ return;
1570
+ }
1571
+ s.stop("Configuration applied successfully.");
1572
+ const fontSelected = answers.fontFace !== void 0 && answers.fontFace !== "keep";
1573
+ const powerShellFeatureSelected = answers.terminalIcons === true || answers.historyPreview === true || answers.treeListing === true || answers.hidePowerShellBanner === true;
1574
+ if (fontSelected || powerShellFeatureSelected) openNewTerminalTab();
1575
+ outro(
1576
+ [
1577
+ `${pc.green("\u2713")} Your configuration is ready.`,
1578
+ pc.dim("Your selected settings and features are ready.")
1579
+ ].join("\n")
1580
+ );
1581
+ }
1582
+
1583
+ // src/index.ts
1584
+ function readVersion() {
1585
+ const pkg = JSON.parse(
1586
+ readFileSync2(new URL("../package.json", import.meta.url), "utf8")
1587
+ );
1588
+ return pkg.version;
1589
+ }
1590
+ function printHelp() {
1591
+ console.log(`
1592
+ ${pc2.bold(pc2.cyan("pimpt"))} ${pc2.dim("\u2014 interactive Windows Terminal setup assistant")}
1593
+
1594
+ ${pc2.bold("Usage")}
1595
+ pimpt Start the interactive installer
1596
+ pimpt --help Show this help
1597
+ pimpt --version Show the installed version
1598
+
1599
+ ${pc2.bold("Status")}
1600
+ Preview: preferences are collected, but settings.json is not modified yet.
1601
+ `);
1602
+ }
1603
+ async function main() {
1604
+ const args = process.argv.slice(2);
1605
+ if (args.includes("--help") || args.includes("-h")) {
1606
+ printHelp();
1607
+ return;
1608
+ }
1609
+ if (args.includes("--version") || args.includes("-v")) {
1610
+ console.log(`pimpt ${readVersion()}`);
1611
+ return;
1612
+ }
1613
+ if (args.length > 0) {
1614
+ console.error(`Unknown option: ${args[0]}`);
1615
+ printHelp();
1616
+ process.exitCode = 1;
1617
+ return;
1618
+ }
1619
+ await runInstaller();
1620
+ }
1621
+ try {
1622
+ await main();
1623
+ } catch (error) {
1624
+ console.error(pc2.red("pimpt failed:"));
1625
+ console.error(error instanceof Error ? error.stack ?? error.message : String(error));
1626
+ process.exitCode = 1;
1627
+ }
1628
+ //# sourceMappingURL=index.js.map