about-system 0.9.31 → 0.10.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.
@@ -0,0 +1 @@
1
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-500:oklch(63.7% .237 25.331);--color-amber-500:oklch(76.9% .188 70.08);--color-emerald-500:oklch(69.6% .17 162.48);--color-white:#fff;--spacing:.25rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height: 1.2 ;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*{border-color:var(--border);outline-color:var(--ring)}@supports (color:color-mix(in lab,red,red)){*{outline-color:color-mix(in oklab,var(--ring) 50%,transparent)}}body{background-color:var(--background);color:var(--foreground);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}}@layer components;@layer utilities{.absolute{position:absolute}.relative{position:relative}.top-1\/2{top:50%}.left-2\.5{left:calc(var(--spacing) * 2.5)}.col-span-full{grid-column:1/-1}.mx-auto{margin-inline:auto}.mt-10{margin-top:calc(var(--spacing) * 10)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.flex{display:flex}.grid{display:grid}.inline-flex{display:inline-flex}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-9{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9)}.h-2{height:calc(var(--spacing) * 2)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-32{height:calc(var(--spacing) * 32)}.h-64{height:calc(var(--spacing) * 64)}.h-full{height:100%}.min-h-screen{min-height:100vh}.w-fit{width:fit-content}.w-full{width:100%}.max-w-7xl{max-width:var(--container-7xl)}.max-w-full{max-width:100%}.min-w-0{min-width:0}.flex-1{flex:1}.shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-\[7rem_minmax\(0\,1fr\)\]{grid-template-columns:7rem minmax(0,1fr)}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-6{gap:calc(var(--spacing) * 6)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-xl{border-radius:calc(var(--radius) + 4px)}.border{border-style:var(--tw-border-style);border-width:1px}.border-destructive\/50{border-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.border-destructive\/50{border-color:color-mix(in oklab,var(--destructive) 50%,transparent)}}.border-input{border-color:var(--input)}.border-transparent{border-color:#0000}.bg-accent{background-color:var(--accent)}.bg-amber-500{background-color:var(--color-amber-500)}.bg-background{background-color:var(--background)}.bg-card{background-color:var(--card)}.bg-destructive{background-color:var(--destructive)}.bg-emerald-500{background-color:var(--color-emerald-500)}.bg-primary,.bg-primary\/20{background-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.bg-primary\/20{background-color:color-mix(in oklab,var(--primary) 20%,transparent)}}.bg-red-500{background-color:var(--color-red-500)}.bg-secondary{background-color:var(--secondary)}.bg-transparent{background-color:#0000}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-12{padding-block:calc(var(--spacing) * 12)}.pl-8{padding-left:calc(var(--spacing) * 8)}.text-center{text-align:center}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-none{--tw-leading:1;line-height:1}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.text-card-foreground{color:var(--card-foreground)}.text-destructive{color:var(--destructive)}.text-foreground{color:var(--foreground)}.text-muted-foreground{color:var(--muted-foreground)}.text-primary-foreground{color:var(--primary-foreground)}.text-secondary-foreground{color:var(--secondary-foreground)}.text-white{color:var(--color-white)}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.transition-\[color\,box-shadow\]{transition-property:color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.placeholder\:text-muted-foreground::placeholder{color:var(--muted-foreground)}.first\:pt-0:first-child{padding-top:0}.last\:pb-0:last-child{padding-bottom:0}@media(hover:hover){.hover\:bg-accent:hover{background-color:var(--accent)}.hover\:bg-primary\/90:hover{background-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-primary\/90:hover{background-color:color-mix(in oklab,var(--primary) 90%,transparent)}}.hover\:text-accent-foreground:hover{color:var(--accent-foreground)}}.focus-visible\:border-ring:focus-visible{border-color:var(--ring)}.focus-visible\:ring-\[3px\]:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab,red,red)){.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:color-mix(in oklab, var(--ring) 50%, transparent)}}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:opacity-50:disabled{opacity:.5}.has-\[\>svg\]\:px-2\.5:has(>svg){padding-inline:calc(var(--spacing) * 2.5)}.has-\[\>svg\]\:px-3:has(>svg){padding-inline:calc(var(--spacing) * 3)}@media(min-width:40rem){.sm\:col-span-2{grid-column:span 2/span 2}.sm\:w-56{width:calc(var(--spacing) * 56)}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-\[8rem_minmax\(0\,1fr\)\]{grid-template-columns:8rem minmax(0,1fr)}.sm\:flex-row{flex-direction:row}.sm\:items-end{align-items:flex-end}.sm\:justify-between{justify-content:space-between}.sm\:px-6{padding-inline:calc(var(--spacing) * 6)}}@media(min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}@media(min-width:64rem){.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}.dark\:border-input:is(.dark *){border-color:var(--input)}.dark\:bg-input\/30:is(.dark *){background-color:var(--input)}@supports (color:color-mix(in lab,red,red)){.dark\:bg-input\/30:is(.dark *){background-color:color-mix(in oklab,var(--input) 30%,transparent)}}@media(hover:hover){.dark\:hover\:bg-accent\/50:is(.dark *):hover{background-color:var(--accent)}@supports (color:color-mix(in lab,red,red)){.dark\:hover\:bg-accent\/50:is(.dark *):hover{background-color:color-mix(in oklab,var(--accent) 50%,transparent)}}.dark\:hover\:bg-input\/50:is(.dark *):hover{background-color:var(--input)}@supports (color:color-mix(in lab,red,red)){.dark\:hover\:bg-input\/50:is(.dark *):hover{background-color:color-mix(in oklab,var(--input) 50%,transparent)}}}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-4 svg:not([class*=size-]){width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.\[\&\>svg\]\:pointer-events-none>svg{pointer-events:none}.\[\&\>svg\]\:size-3>svg{width:calc(var(--spacing) * 3);height:calc(var(--spacing) * 3)}}:root{--radius:.625rem;--background:oklch(100% 0 0);--foreground:oklch(14.5% 0 0);--card:oklch(100% 0 0);--card-foreground:oklch(14.5% 0 0);--primary:oklch(20.5% 0 0);--primary-foreground:oklch(98.5% 0 0);--secondary:oklch(97% 0 0);--secondary-foreground:oklch(20.5% 0 0);--muted:oklch(97% 0 0);--muted-foreground:oklch(55.6% 0 0);--accent:oklch(97% 0 0);--accent-foreground:oklch(20.5% 0 0);--destructive:oklch(57.7% .245 27.325);--border:oklch(92.2% 0 0);--input:oklch(92.2% 0 0);--ring:oklch(70.8% 0 0)}.dark{--background:oklch(14.5% 0 0);--foreground:oklch(98.5% 0 0);--card:oklch(20.5% 0 0);--card-foreground:oklch(98.5% 0 0);--primary:oklch(92.2% 0 0);--primary-foreground:oklch(20.5% 0 0);--secondary:oklch(26.9% 0 0);--secondary-foreground:oklch(98.5% 0 0);--muted:oklch(26.9% 0 0);--muted-foreground:oklch(70.8% 0 0);--accent:oklch(26.9% 0 0);--accent-foreground:oklch(98.5% 0 0);--destructive:oklch(70.4% .191 22.216);--border:oklch(100% 0 0/.1);--input:oklch(100% 0 0/.15);--ring:oklch(55.6% 0 0)}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>About System</title>
7
+ <script type="module" crossorigin src="./assets/index--HOsj3jw.js"></script>
8
+ <link rel="stylesheet" crossorigin href="./assets/index-BU_blZyu.css">
9
+ </head>
10
+ <body>
11
+ <div id="root"></div>
12
+ </body>
13
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "about-system",
3
- "version": "0.9.31",
3
+ "version": "0.10.0",
4
4
  "description": "A Node.js script to display key system information with emojis. Cross-platform support for Windows, macOS, and Linux with customizable output and caching.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -26,11 +26,14 @@
26
26
  }
27
27
  },
28
28
  "scripts": {
29
- "build": "vite build",
29
+ "build": "vite build && vite build --config web/vite.config.ts",
30
+ "build:web": "vite build --config web/vite.config.ts",
30
31
  "build:tsc": "tsc",
31
32
  "dev": "vite build --watch",
33
+ "dev:web": "vite --config web/vite.config.ts",
32
34
  "ship": "npm run build && npx standard-version --release-as patch; rm CHANGELOG.md; npm publish",
33
35
  "start": "npm run build && node dist/about-system-cli.js",
36
+ "web": "npm run build && node dist/about-system-cli.js web",
34
37
  "install-greeting": "npm run build && node dist/about-system-cli.js --install",
35
38
  "show-settings": "npm run build && node dist/about-system-cli.js --settings-show",
36
39
  "reset-settings": "npm run build && node dist/about-system-cli.js --settings-reset",
@@ -39,6 +42,7 @@
39
42
  "app:dev": "cd native && npm run dev",
40
43
  "app:build": "cd native && npm run build:desktop",
41
44
  "test": "vitest run",
45
+ "typecheck:web": "tsc -p web/tsconfig.json",
42
46
  "test:ci": "vitest run --reporter=junit --outputFile=./junit.xml --coverage --coverage.reporter=lcov",
43
47
  "test:watch": "vitest",
44
48
  "coverage": "vitest run --coverage"
@@ -57,6 +61,7 @@
57
61
  "greeting",
58
62
  "emoji",
59
63
  "system-monitoring",
64
+ "dashboard",
60
65
  "desktop-app",
61
66
  "tauri"
62
67
  ],
@@ -89,8 +94,21 @@
89
94
  "ora": "^9.0.0"
90
95
  },
91
96
  "devDependencies": {
97
+ "@radix-ui/react-progress": "^1.1.16",
98
+ "@radix-ui/react-slot": "^1.3.3",
99
+ "@tailwindcss/vite": "^4.3.3",
92
100
  "@types/node": "^25.0.3",
101
+ "@types/react": "^19.3.0",
102
+ "@types/react-dom": "^19.3.0",
103
+ "@vitejs/plugin-react": "^5",
93
104
  "@vitest/coverage-v8": "^4.1.0",
105
+ "class-variance-authority": "^0.7.1",
106
+ "clsx": "^2.1.1",
107
+ "lucide-react": "^1.48.0",
108
+ "react": "^19.3.0",
109
+ "react-dom": "^19.3.0",
110
+ "tailwind-merge": "^3.7.0",
111
+ "tailwindcss": "^4.3.3",
94
112
  "typescript": "^5.9.3",
95
113
  "vite": "^7.3.1",
96
114
  "vite-plugin-dts": "^4.5.4",
@@ -5,6 +5,7 @@ import path from "path";
5
5
  import { fileURLToPath } from "url";
6
6
  import { getSystemInfo } from "./system-info-api";
7
7
  import type { SystemInfo, SystemInfoOptions } from "./systeminfo-types";
8
+ import { startWebServer, DEFAULT_WEB_PORT, DEFAULT_WEB_HOST } from "./web-server";
8
9
 
9
10
  const __filename = fileURLToPath(import.meta.url);
10
11
 
@@ -380,6 +381,33 @@ function installShellGreeting(): void {
380
381
  }
381
382
  }
382
383
 
384
+ /** Reads `--flag value` or `--flag=value` from args. */
385
+ function flagValue(args: string[], flag: string): string | undefined {
386
+ for (let i = 0; i < args.length; i++) {
387
+ if (args[i] === flag) return args[i + 1];
388
+ if (args[i].startsWith(flag + "=")) return args[i].slice(flag.length + 1);
389
+ }
390
+ return undefined;
391
+ }
392
+
393
+ async function runWebServer(args: string[]): Promise<void> {
394
+ const rawPort = flagValue(args, "--port") ?? process.env.PORT;
395
+ const port = rawPort === undefined ? DEFAULT_WEB_PORT : Number(rawPort);
396
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
397
+ throw new Error(`Invalid port: ${rawPort}`);
398
+ }
399
+ const host = flagValue(args, "--host") ?? DEFAULT_WEB_HOST;
400
+
401
+ const { url } = await startWebServer({ port, host });
402
+ console.log(`About System web UI running at ${url}`);
403
+ if (host === DEFAULT_WEB_HOST) {
404
+ console.log("Listening on localhost only. Use --host 0.0.0.0 to expose it on the network.");
405
+ } else {
406
+ console.log("Warning: system details (public IP, ports, processes) are visible to anyone who can reach this port.");
407
+ }
408
+ console.log("Press Ctrl+C to stop.");
409
+ }
410
+
383
411
  function parseCLIMode(args: string[]): string[] | null {
384
412
  for (const arg of args) {
385
413
  if (!arg.startsWith("--") && arg.includes(",")) {
@@ -406,6 +434,7 @@ System Info Script - TypeScript Version
406
434
  Usage:
407
435
  about-system [options]
408
436
  about-system <part1,part2,...> # CLI mode: show specific parts only
437
+ about-system web [--port N] [--host H] # Serve the web dashboard
409
438
 
410
439
  Options:
411
440
  --help, -h Show this help message
@@ -429,6 +458,8 @@ Examples:
429
458
  about-system --set emojis.cpu "🚀 "
430
459
  about-system --set labels.cpu "Processor"
431
460
  about-system --json
461
+ about-system web # http://127.0.0.1:${DEFAULT_WEB_PORT}
462
+ about-system web --port 8080 --host 0.0.0.0
432
463
 
433
464
  Settings file: ${SETTINGS_FILE}
434
465
  Cache file: ${CACHE_FILE}
@@ -470,6 +501,11 @@ async function main(): Promise<void> {
470
501
  return;
471
502
  }
472
503
 
504
+ if (args[0] === "web" || args.includes("--web")) {
505
+ await runWebServer(args);
506
+ return;
507
+ }
508
+
473
509
  if (args.includes("--json")) {
474
510
  const info = await getSystemInfo();
475
511
  console.log(JSON.stringify(info, null, 2));
@@ -0,0 +1,69 @@
1
+ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
2
+ import fs from "fs";
3
+ import os from "os";
4
+ import path from "path";
5
+ import type http from "http";
6
+
7
+ vi.mock("./system-info-api", () => ({
8
+ getSystemInfo: vi.fn(async () => ({ user: "tester", hostname: "box", ram_used: "8/16GB" })),
9
+ }));
10
+
11
+ import { resolveStaticFile, startWebServer } from "./web-server";
12
+
13
+ let webRoot: string;
14
+ let server: http.Server;
15
+ let baseUrl: string;
16
+
17
+ beforeAll(async () => {
18
+ webRoot = fs.mkdtempSync(path.join(os.tmpdir(), "about-system-web-"));
19
+ fs.mkdirSync(path.join(webRoot, "assets"));
20
+ fs.writeFileSync(path.join(webRoot, "index.html"), "<!doctype html><div id=root></div>");
21
+ fs.writeFileSync(path.join(webRoot, "assets", "app.js"), "console.log(1)");
22
+
23
+ ({ server, url: baseUrl } = await startWebServer({ port: 0, webRoot }));
24
+ });
25
+
26
+ afterAll(() => {
27
+ server?.close();
28
+ fs.rmSync(webRoot, { recursive: true, force: true });
29
+ });
30
+
31
+ describe("resolveStaticFile", () => {
32
+ it("serves files inside the web root", () => {
33
+ expect(resolveStaticFile(webRoot, "/assets/app.js")).toBe(path.join(webRoot, "assets", "app.js"));
34
+ });
35
+
36
+ it("falls back to index.html for unknown routes", () => {
37
+ expect(resolveStaticFile(webRoot, "/some/route")).toBe(path.join(webRoot, "index.html"));
38
+ });
39
+
40
+ it("never escapes the web root", () => {
41
+ for (const attempt of ["/../../etc/passwd", "/%2e%2e/%2e%2e/etc/passwd", "/..%2f..%2fetc/passwd"]) {
42
+ const file = resolveStaticFile(webRoot, attempt);
43
+ expect(file === null || file.startsWith(webRoot)).toBe(true);
44
+ }
45
+ });
46
+ });
47
+
48
+ describe("web server", () => {
49
+ it("returns system info as JSON from /api/info", async () => {
50
+ const res = await fetch(`${baseUrl}/api/info`);
51
+ expect(res.status).toBe(200);
52
+ expect(res.headers.get("content-type")).toContain("application/json");
53
+ expect(await res.json()).toEqual({ user: "tester", hostname: "box", ram_used: "8/16GB" });
54
+ });
55
+
56
+ it("serves the dashboard with the right content types", async () => {
57
+ const index = await fetch(`${baseUrl}/`);
58
+ expect(index.headers.get("content-type")).toContain("text/html");
59
+ expect(await index.text()).toContain('id=root');
60
+
61
+ const js = await fetch(`${baseUrl}/assets/app.js`);
62
+ expect(js.headers.get("content-type")).toContain("text/javascript");
63
+ });
64
+
65
+ it("rejects non-GET methods", async () => {
66
+ const res = await fetch(`${baseUrl}/api/info`, { method: "POST" });
67
+ expect(res.status).toBe(405);
68
+ });
69
+ });
@@ -0,0 +1,120 @@
1
+ /**
2
+ * @fileoverview `about-system web` — serves the React dashboard and a JSON API.
3
+ *
4
+ * The dashboard is prebuilt into dist/web by `vite build --config web/vite.config.ts`
5
+ * and served as static files; the only dynamic route is GET /api/info, which
6
+ * returns the same object as `about-system --json`.
7
+ */
8
+
9
+ import http from "http";
10
+ import fs from "fs";
11
+ import path from "path";
12
+ import { fileURLToPath } from "url";
13
+ import { getSystemInfo } from "./system-info-api";
14
+
15
+ export interface WebServerOptions {
16
+ port?: number;
17
+ host?: string;
18
+ /** Directory holding the built dashboard. Defaults to dist/web next to this file. */
19
+ webRoot?: string;
20
+ }
21
+
22
+ export const DEFAULT_WEB_PORT = 3777;
23
+ export const DEFAULT_WEB_HOST = "127.0.0.1";
24
+
25
+ const MIME_TYPES: Record<string, string> = {
26
+ ".html": "text/html; charset=utf-8",
27
+ ".js": "text/javascript; charset=utf-8",
28
+ ".css": "text/css; charset=utf-8",
29
+ ".json": "application/json; charset=utf-8",
30
+ ".svg": "image/svg+xml",
31
+ ".png": "image/png",
32
+ ".ico": "image/x-icon",
33
+ ".woff2": "font/woff2",
34
+ };
35
+
36
+ function defaultWebRoot(): string {
37
+ return path.join(path.dirname(fileURLToPath(import.meta.url)), "web");
38
+ }
39
+
40
+ /**
41
+ * Resolves a request path to a file inside webRoot, or null if it escapes the
42
+ * root or does not exist. Unknown paths fall back to index.html (SPA routing).
43
+ */
44
+ export function resolveStaticFile(webRoot: string, urlPath: string): string | null {
45
+ let decoded: string;
46
+ try {
47
+ decoded = decodeURIComponent(urlPath.split("?")[0]);
48
+ } catch {
49
+ return null;
50
+ }
51
+ const root = path.resolve(webRoot);
52
+ const candidate = path.resolve(root, "." + path.posix.normalize("/" + decoded));
53
+ if (candidate !== root && !candidate.startsWith(root + path.sep)) return null;
54
+
55
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate;
56
+ const index = path.join(root, "index.html");
57
+ return fs.existsSync(index) ? index : null;
58
+ }
59
+
60
+ function send(res: http.ServerResponse, status: number, type: string, body: string | Buffer): void {
61
+ res.writeHead(status, {
62
+ "Content-Type": type,
63
+ "Cache-Control": "no-store",
64
+ "X-Content-Type-Options": "nosniff",
65
+ });
66
+ res.end(body);
67
+ }
68
+
69
+ export function createWebServer(options: WebServerOptions = {}): http.Server {
70
+ const webRoot = options.webRoot ?? defaultWebRoot();
71
+
72
+ return http.createServer(async (req, res) => {
73
+ const url = req.url ?? "/";
74
+
75
+ if (req.method !== "GET" && req.method !== "HEAD") {
76
+ send(res, 405, "text/plain; charset=utf-8", "Method Not Allowed");
77
+ return;
78
+ }
79
+
80
+ if (url === "/api/info" || url.startsWith("/api/info?")) {
81
+ try {
82
+ const info = await getSystemInfo();
83
+ send(res, 200, MIME_TYPES[".json"], JSON.stringify(info));
84
+ } catch (error) {
85
+ send(res, 500, MIME_TYPES[".json"], JSON.stringify({ error: (error as Error).message }));
86
+ }
87
+ return;
88
+ }
89
+
90
+ const file = resolveStaticFile(webRoot, url);
91
+ if (!file) {
92
+ send(
93
+ res,
94
+ 404,
95
+ "text/plain; charset=utf-8",
96
+ "Dashboard not found. Build it with: npm run build:web"
97
+ );
98
+ return;
99
+ }
100
+ const type = MIME_TYPES[path.extname(file)] ?? "application/octet-stream";
101
+ send(res, 200, type, fs.readFileSync(file));
102
+ });
103
+ }
104
+
105
+ /** Starts the dashboard server and resolves with the URL it is listening on. */
106
+ export function startWebServer(options: WebServerOptions = {}): Promise<{ server: http.Server; url: string }> {
107
+ const port = options.port ?? DEFAULT_WEB_PORT;
108
+ const host = options.host ?? DEFAULT_WEB_HOST;
109
+ const server = createWebServer(options);
110
+
111
+ return new Promise((resolve, reject) => {
112
+ server.once("error", reject);
113
+ server.listen(port, host, () => {
114
+ const address = server.address();
115
+ const actualPort = typeof address === "object" && address ? address.port : port;
116
+ const shownHost = host === "0.0.0.0" || host === "::" ? "localhost" : host;
117
+ resolve({ server, url: `http://${shownHost}:${actualPort}` });
118
+ });
119
+ });
120
+ }