usight 0.1.4

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jonatan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # usight
2
+
3
+ Install and manage [Übersicht](https://tracesof.net/uebersicht/) widgets directly from GitHub — no git required.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g usight
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ # Install a widget from GitHub
15
+ usight install hw2007/ubersicht-neofetch
16
+ usight install https://github.com/hw2007/ubersicht-neofetch
17
+
18
+ # Remove a widget
19
+ usight uninstall ubersicht-neofetch
20
+
21
+ # List installed widgets
22
+ usight list
23
+
24
+ # Change where widgets are cached
25
+ usight set --path ~/my-widgets-cache
26
+
27
+ # Show current config
28
+ usight config
29
+ ```
30
+
31
+ ## How it works
32
+
33
+ Downloads a GitHub repository as a tarball, extracts it into a local cache (`~/.cache/usight` by default), and creates a symlink in your Übersicht widgets directory. No git, no background processes, no telemetry.
34
+
35
+ ## Requirements
36
+
37
+ - macOS
38
+ - Node.js ≥ 18
39
+
40
+ ## License
41
+
42
+ MIT
package/bin/usight.js ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { install, uninstall, list, setPath } from '../lib/widgets.js';
4
+ import { getConfig } from '../lib/config.js';
5
+ import { getWidgetsDir } from '../lib/ubersicht.js';
6
+
7
+ const [,, cmd, ...args] = process.argv;
8
+
9
+ const commands = {
10
+ install: () => install(args[0]),
11
+ add: () => install(args[0]),
12
+ uninstall: () => uninstall(args[0]),
13
+ remove: () => uninstall(args[0]),
14
+ list: () => list(),
15
+ set: () => {
16
+ const i = args.indexOf('--path');
17
+ if (i === -1 || !args[i + 1]) {
18
+ console.error('Usage: usight set --path <path>');
19
+ process.exit(1);
20
+ }
21
+ setPath(args[i + 1]);
22
+ },
23
+ config: () => {
24
+ const cfg = getConfig();
25
+ console.log(`Cache path: ${cfg.cachePath}`);
26
+ console.log(`Widgets dir: ${getWidgetsDir()}`);
27
+ console.log(`Installed: ${Object.keys(cfg.widgets).length} widget(s)`);
28
+ },
29
+ help: () => help(),
30
+ };
31
+
32
+ if (!cmd || !commands[cmd]) {
33
+ help();
34
+ process.exit(cmd ? 1 : 0);
35
+ }
36
+
37
+ Promise.resolve(commands[cmd]()).catch((err) => {
38
+ console.error(`Error: ${err.message}`);
39
+ process.exit(1);
40
+ });
41
+
42
+ function help() {
43
+ console.log(`
44
+ usight — Übersicht widget manager
45
+
46
+ usight install <owner/repo> Install a widget from GitHub
47
+ usight add <owner/repo> Alias for install
48
+ usight uninstall <name> Remove a widget
49
+ usight remove <name> Alias for uninstall
50
+ usight list List installed widgets
51
+ usight set --path <path> Change widget cache directory
52
+ usight config Show current configuration
53
+ usight help Show this help
54
+
55
+ Examples:
56
+ usight install hw2007/ubersicht-neofetch
57
+ usight install https://github.com/hw2007/ubersicht-neofetch
58
+ usight uninstall ubersicht-neofetch
59
+ `.trim());
60
+ }
package/lib/config.js ADDED
@@ -0,0 +1,31 @@
1
+ // Config lives at a fixed path so it survives cachePath changes.
2
+ // Widget metadata is embedded in config — one file, no sync issues.
3
+
4
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
5
+ import { join } from 'path';
6
+ import { homedir } from 'os';
7
+
8
+ const CONFIG_DIR = join(homedir(), '.config', 'usight');
9
+ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
10
+ const DEFAULT_CACHE = join(homedir(), '.cache', 'usight');
11
+
12
+ export function getConfig() {
13
+ if (!existsSync(CONFIG_FILE)) {
14
+ return { cachePath: DEFAULT_CACHE, widgets: {} };
15
+ }
16
+ try {
17
+ return JSON.parse(readFileSync(CONFIG_FILE, 'utf8'));
18
+ } catch {
19
+ return { cachePath: DEFAULT_CACHE, widgets: {} };
20
+ }
21
+ }
22
+
23
+ export function saveConfig(cfg) {
24
+ mkdirSync(CONFIG_DIR, { recursive: true });
25
+ mkdirSync(cfg.cachePath, { recursive: true });
26
+ writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2) + '\n');
27
+ }
28
+
29
+ export function resolvePath(p) {
30
+ return p.startsWith('~') ? join(homedir(), p.slice(1)) : p;
31
+ }
package/lib/github.js ADDED
@@ -0,0 +1,58 @@
1
+ import https from 'https';
2
+ import { mkdirSync } from 'fs';
3
+ import { spawn } from 'child_process';
4
+
5
+ // Parse "owner/repo" or full GitHub URL into { owner, repo }
6
+ export function parseRepo(input) {
7
+ if (!input) throw new Error('No repository specified');
8
+
9
+ // strip trailing .git and leading https://github.com/
10
+ const clean = input
11
+ .replace(/^https?:\/\/github\.com\//, '')
12
+ .replace(/\.git$/, '')
13
+ .trim();
14
+
15
+ const parts = clean.split('/');
16
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
17
+ throw new Error(`Invalid repo format: "${input}" — use owner/repo or https://github.com/owner/repo`);
18
+ }
19
+
20
+ return { owner: parts[0], repo: parts[1] };
21
+ }
22
+
23
+ // Download GitHub tarball and extract into destDir (streaming, no temp file).
24
+ export function downloadAndExtract(owner, repo, destDir) {
25
+ mkdirSync(destDir, { recursive: true });
26
+
27
+ const url = `https://api.github.com/repos/${owner}/${repo}/tarball/HEAD`;
28
+
29
+ return new Promise((resolve, reject) => {
30
+ const get = (url) => {
31
+ https.get(url, { headers: { 'User-Agent': 'usight-cli' } }, (res) => {
32
+ if (res.statusCode === 301 || res.statusCode === 302 || res.statusCode === 307) {
33
+ get(res.headers.location);
34
+ return;
35
+ }
36
+ if (res.statusCode !== 200) {
37
+ reject(new Error(`GitHub returned HTTP ${res.statusCode} for ${owner}/${repo}`));
38
+ return;
39
+ }
40
+
41
+ // pipe into tar — strips the top-level GitHub-generated directory
42
+ const tar = spawn('tar', ['-xzf', '-', '-C', destDir, '--strip-components=1'], {
43
+ stdio: ['pipe', 'inherit', 'inherit'],
44
+ });
45
+
46
+ res.pipe(tar.stdin);
47
+ res.on('error', reject);
48
+ tar.on('error', reject);
49
+ tar.on('close', (code) => {
50
+ if (code === 0) resolve();
51
+ else reject(new Error(`tar exited with code ${code}`));
52
+ });
53
+ }).on('error', reject);
54
+ };
55
+
56
+ get(url);
57
+ });
58
+ }
@@ -0,0 +1,25 @@
1
+ import { execSync } from 'child_process';
2
+ import { join } from 'path';
3
+ import { homedir } from 'os';
4
+
5
+ const DEFAULT_WIDGETS_DIR = join(
6
+ homedir(),
7
+ 'Library',
8
+ 'Application Support',
9
+ 'Übersicht',
10
+ 'widgets'
11
+ );
12
+
13
+ // Try reading the actual configured path from Übersicht preferences.
14
+ // Falls back to the default if Übersicht isn't installed or plist is unreadable.
15
+ export function getWidgetsDir() {
16
+ try {
17
+ const plist = join(homedir(), 'Library/Preferences/com.felixhageloh.uebersicht.plist');
18
+ const json = execSync(`plutil -convert json -o - "${plist}"`, { stdio: ['pipe', 'pipe', 'pipe'] }).toString();
19
+ const prefs = JSON.parse(json);
20
+ if (prefs.widgetPath) return prefs.widgetPath;
21
+ } catch {
22
+ // Übersicht not installed or plist unreadable — use default
23
+ }
24
+ return DEFAULT_WIDGETS_DIR;
25
+ }
package/lib/widgets.js ADDED
@@ -0,0 +1,133 @@
1
+ import { existsSync, lstatSync, mkdirSync, rmSync, symlinkSync, unlinkSync, renameSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { getConfig, saveConfig, resolvePath } from './config.js';
4
+ import { parseRepo, downloadAndExtract } from './github.js';
5
+ import { getWidgetsDir } from './ubersicht.js';
6
+
7
+ export async function install(repoArg) {
8
+ const { owner, repo } = parseRepo(repoArg);
9
+ const name = repo;
10
+
11
+ const cfg = getConfig();
12
+ if (cfg.widgets[name]) {
13
+ console.error(`"${name}" is already installed. Run: usight uninstall ${name}`);
14
+ process.exit(1);
15
+ }
16
+
17
+ const cacheDir = join(cfg.cachePath, name);
18
+ const widgetsDir = getWidgetsDir();
19
+ const symlinkPath = join(widgetsDir, name);
20
+
21
+ mkdirSync(widgetsDir, { recursive: true });
22
+
23
+ console.log(`Downloading ${owner}/${repo}...`);
24
+ await downloadAndExtract(owner, repo, cacheDir);
25
+
26
+ // Remove stale symlink if present
27
+ if (existsSync(symlinkPath) || isSymlink(symlinkPath)) {
28
+ unlinkSync(symlinkPath);
29
+ }
30
+ symlinkSync(cacheDir, symlinkPath);
31
+
32
+ cfg.widgets[name] = { repo: `${owner}/${repo}`, installedAt: new Date().toISOString() };
33
+ saveConfig(cfg);
34
+
35
+ console.log(`✓ Installed "${name}" → ${symlinkPath}`);
36
+ }
37
+
38
+ export function uninstall(name) {
39
+ if (!name) {
40
+ console.error('Usage: usight uninstall <name>');
41
+ process.exit(1);
42
+ }
43
+
44
+ const cfg = getConfig();
45
+ if (!cfg.widgets[name]) {
46
+ console.error(`"${name}" is not managed by usight`);
47
+ process.exit(1);
48
+ }
49
+
50
+ const cacheDir = join(cfg.cachePath, name);
51
+ const widgetsDir = getWidgetsDir();
52
+ const symlinkPath = join(widgetsDir, name);
53
+
54
+ if (existsSync(symlinkPath) || isSymlink(symlinkPath)) {
55
+ unlinkSync(symlinkPath);
56
+ }
57
+ if (existsSync(cacheDir)) {
58
+ rmSync(cacheDir, { recursive: true, force: true });
59
+ }
60
+
61
+ delete cfg.widgets[name];
62
+ saveConfig(cfg);
63
+
64
+ console.log(`✓ Removed "${name}"`);
65
+ }
66
+
67
+ export function list() {
68
+ const cfg = getConfig();
69
+ const entries = Object.entries(cfg.widgets);
70
+
71
+ if (entries.length === 0) {
72
+ console.log('No widgets installed. Try: usight install <owner/repo>');
73
+ return;
74
+ }
75
+
76
+ for (const [name, info] of entries) {
77
+ const date = info.installedAt ? new Date(info.installedAt).toLocaleDateString() : '?';
78
+ console.log(` ${name} (${info.repo}) installed ${date}`);
79
+ }
80
+ }
81
+
82
+ export function setPath(rawPath) {
83
+ if (!rawPath) {
84
+ console.error('Usage: usight set --path <path>');
85
+ process.exit(1);
86
+ }
87
+
88
+ const newPath = resolvePath(rawPath);
89
+ const cfg = getConfig();
90
+ const oldPath = cfg.cachePath;
91
+
92
+ if (newPath === oldPath) {
93
+ console.log(`Cache path is already ${oldPath}`);
94
+ return;
95
+ }
96
+
97
+ mkdirSync(newPath, { recursive: true });
98
+
99
+ const widgetsDir = getWidgetsDir();
100
+
101
+ // Move each managed widget dir and update its symlink
102
+ for (const name of Object.keys(cfg.widgets)) {
103
+ const src = join(oldPath, name);
104
+ const dst = join(newPath, name);
105
+ const symlinkPath = join(widgetsDir, name);
106
+
107
+ if (existsSync(src)) {
108
+ renameSync(src, dst);
109
+ }
110
+
111
+ // Re-point symlink to new location
112
+ if (existsSync(symlinkPath) || isSymlink(symlinkPath)) {
113
+ unlinkSync(symlinkPath);
114
+ }
115
+ if (existsSync(dst)) {
116
+ symlinkSync(dst, symlinkPath);
117
+ }
118
+ }
119
+
120
+ cfg.cachePath = newPath;
121
+ saveConfig(cfg);
122
+
123
+ console.log(`✓ Cache path set to ${newPath}`);
124
+ }
125
+
126
+ // lstatSync doesn't throw on broken symlinks, existsSync does — need both
127
+ function isSymlink(p) {
128
+ try {
129
+ return lstatSync(p).isSymbolicLink();
130
+ } catch {
131
+ return false;
132
+ }
133
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "usight",
3
+ "version": "0.1.4",
4
+ "description": "Manage installs for Übersicht widgets from GitHub — no git required",
5
+ "type": "module",
6
+ "bin": {
7
+ "usight": "bin/usight.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "lib",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "test": "node test/test.js",
17
+ "release": "bash publish-npm.sh"
18
+ },
19
+ "engines": {
20
+ "node": ">=18"
21
+ },
22
+ "keywords": [
23
+ "ubersicht",
24
+ "übersicht",
25
+ "widgets",
26
+ "macos",
27
+ "cli",
28
+ "widget-manager"
29
+ ],
30
+ "author": {
31
+ "name": "Jay F0x",
32
+ "email": "jjay56883@gmail.com"
33
+ },
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/jayf0x/usight.git"
38
+ },
39
+ "homepage": "https://github.com/jayf0x/usight#readme",
40
+ "bugs": {
41
+ "url": "https://github.com/jayf0x/usight/issues"
42
+ }
43
+ }