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 +21 -0
- package/README.md +42 -0
- package/bin/usight.js +60 -0
- package/lib/config.js +31 -0
- package/lib/github.js +58 -0
- package/lib/ubersicht.js +25 -0
- package/lib/widgets.js +133 -0
- package/package.json +43 -0
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
|
+
}
|
package/lib/ubersicht.js
ADDED
|
@@ -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
|
+
}
|