@quickcall/all-configs 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/bin/all-configs.js +44 -0
- package/package.json +20 -4
- package/runtime/allconfigs/__init__.py +1 -0
- package/runtime/allconfigs/__main__.py +42 -0
- package/runtime/allconfigs/__pycache__/__init__.cpython-312.pyc +0 -0
- package/runtime/allconfigs/__pycache__/__main__.cpython-312.pyc +0 -0
- package/runtime/allconfigs/__pycache__/modules.cpython-312.pyc +0 -0
- package/runtime/allconfigs/__pycache__/probe.cpython-312.pyc +0 -0
- package/runtime/allconfigs/__pycache__/runner.cpython-312.pyc +0 -0
- package/runtime/allconfigs/modules.py +221 -0
- package/runtime/allconfigs/probe.py +60 -0
- package/runtime/install.sh +74 -0
- package/runtime/lib/common.sh +147 -0
- package/runtime/lib/uninstall.sh +35 -0
- package/runtime/modules/aldente/install.sh +23 -0
- package/runtime/modules/aldente/module.toml +11 -0
- package/runtime/modules/aliases/aliases.sh +31 -0
- package/runtime/modules/aliases/install.sh +36 -0
- package/runtime/modules/aliases/module.toml +7 -0
- package/runtime/modules/aqua-voice/install.sh +23 -0
- package/runtime/modules/aqua-voice/module.toml +11 -0
- package/runtime/modules/betterdisplay/install.sh +23 -0
- package/runtime/modules/betterdisplay/module.toml +11 -0
- package/runtime/modules/bitwarden/install.sh +19 -0
- package/runtime/modules/bitwarden/module.toml +11 -0
- package/runtime/modules/browsers/install.sh +33 -0
- package/runtime/modules/browsers/module.toml +11 -0
- package/runtime/modules/bun/install.sh +72 -0
- package/runtime/modules/bun/module.toml +7 -0
- package/runtime/modules/caveman/install.sh +79 -0
- package/runtime/modules/caveman/module.toml +7 -0
- package/runtime/modules/claude/install.sh +22 -0
- package/runtime/modules/claude/module.toml +7 -0
- package/runtime/modules/codex/install.sh +20 -0
- package/runtime/modules/codex/module.toml +7 -0
- package/runtime/modules/docker/install.sh +19 -0
- package/runtime/modules/docker/module.toml +11 -0
- package/runtime/modules/external-skills/install.sh +30 -0
- package/runtime/modules/external-skills/module.toml +4 -0
- package/runtime/modules/ffmpeg/install.sh +13 -0
- package/runtime/modules/ffmpeg/module.toml +11 -0
- package/runtime/modules/fonts/install.sh +62 -0
- package/runtime/modules/fonts/module.toml +8 -0
- package/runtime/modules/ghostty/config +44 -0
- package/runtime/modules/ghostty/install.sh +64 -0
- package/runtime/modules/ghostty/module.toml +9 -0
- package/runtime/modules/ghostty/themes/ayu-dark +22 -0
- package/runtime/modules/ghostty/themes/catppuccin-frappe +23 -0
- package/runtime/modules/ghostty/themes/catppuccin-latte +23 -0
- package/runtime/modules/ghostty/themes/catppuccin-latte.conf +23 -0
- package/runtime/modules/ghostty/themes/catppuccin-macchiato +23 -0
- package/runtime/modules/ghostty/themes/catppuccin-mocha +23 -0
- package/runtime/modules/ghostty/themes/visual-studio-light +27 -0
- package/runtime/modules/ghostty/themes/vscode-light +24 -0
- package/runtime/modules/ghostty/themes/vscode-light-soft +24 -0
- package/runtime/modules/ghostty/xterm-ghostty.terminfo +81 -0
- package/runtime/modules/github/install.sh +101 -0
- package/runtime/modules/github/module.toml +8 -0
- package/runtime/modules/hf/install.sh +98 -0
- package/runtime/modules/hf/module.toml +7 -0
- package/runtime/modules/karabiner/README.md +146 -0
- package/runtime/modules/karabiner/agents.md +39 -0
- package/runtime/modules/karabiner/app_launchers.json +236 -0
- package/runtime/modules/karabiner/custom_delete_mapping.json +13 -0
- package/runtime/modules/karabiner/fn_to_control.json +9 -0
- package/runtime/modules/karabiner/install.sh +60 -0
- package/runtime/modules/karabiner/karabiner.json +866 -0
- package/runtime/modules/karabiner/keychron-modifier-swap.sh +80 -0
- package/runtime/modules/karabiner/module.toml +8 -0
- package/runtime/modules/karabiner/remap_caps_lock_shift_super.json +23 -0
- package/runtime/modules/karabiner/remap_escape_to_control.json +49 -0
- package/runtime/modules/karabiner/rename_caps_lock_to_super.json +23 -0
- package/runtime/modules/karabiner/right_shift_mouse_click.json +44 -0
- package/runtime/modules/karabiner/swap_esc_delete.json +10 -0
- package/runtime/modules/karabiner/troubleshooting.md +242 -0
- package/runtime/modules/karabiner/vim_arrow_keys.json +477 -0
- package/runtime/modules/keepingyouawake/install.sh +23 -0
- package/runtime/modules/keepingyouawake/module.toml +11 -0
- package/runtime/modules/keycastr/install.sh +23 -0
- package/runtime/modules/keycastr/module.toml +11 -0
- package/runtime/modules/multi-github/install.sh +179 -0
- package/runtime/modules/multi-github/module.toml +4 -0
- package/runtime/modules/ncdu/install.sh +13 -0
- package/runtime/modules/ncdu/module.toml +11 -0
- package/runtime/modules/node/install.sh +73 -0
- package/runtime/modules/node/module.toml +11 -0
- package/runtime/modules/nvim/.neoconf.json +15 -0
- package/runtime/modules/nvim/init.lua +2 -0
- package/runtime/modules/nvim/install.sh +142 -0
- package/runtime/modules/nvim/lazy-lock.json +35 -0
- package/runtime/modules/nvim/lazyvim.json +10 -0
- package/runtime/modules/nvim/lua/config/autocmds.lua +18 -0
- package/runtime/modules/nvim/lua/config/keymaps.lua +10 -0
- package/runtime/modules/nvim/lua/config/lazy.lua +53 -0
- package/runtime/modules/nvim/lua/config/options.lua +3 -0
- package/runtime/modules/nvim/lua/plugins/image.lua +9 -0
- package/runtime/modules/nvim/lua/plugins/markdown.lua +56 -0
- package/runtime/modules/nvim/lua/plugins/noice.lua +17 -0
- package/runtime/modules/nvim/lua/plugins/rose-pine.lua +16 -0
- package/runtime/modules/nvim/lua/plugins/snacks.lua +20 -0
- package/runtime/modules/nvim/module.toml +8 -0
- package/runtime/modules/nvim/stylua.toml +3 -0
- package/runtime/modules/p10k/install.sh +46 -0
- package/runtime/modules/p10k/module.toml +8 -0
- package/runtime/modules/p10k/p10k.zsh +1711 -0
- package/runtime/modules/pi/install.sh +77 -0
- package/runtime/modules/pi/module.toml +8 -0
- package/runtime/modules/pi/packages +5 -0
- package/runtime/modules/pi/settings.json +21 -0
- package/runtime/modules/presentify/install.sh +25 -0
- package/runtime/modules/presentify/module.toml +7 -0
- package/runtime/modules/rectangle/RectangleConfig.json +353 -0
- package/runtime/modules/rectangle/install.sh +64 -0
- package/runtime/modules/rectangle/module.toml +11 -0
- package/runtime/modules/shortcat/install.sh +19 -0
- package/runtime/modules/shortcat/module.toml +11 -0
- package/runtime/modules/skills/install.sh +25 -0
- package/runtime/modules/skills/module.toml +4 -0
- package/runtime/modules/skills/skills-lock.json +35 -0
- package/runtime/modules/slack/install.sh +23 -0
- package/runtime/modules/slack/module.toml +11 -0
- package/runtime/modules/spotify/install.sh +23 -0
- package/runtime/modules/spotify/module.toml +11 -0
- package/runtime/modules/ssh-keygen/install.sh +130 -0
- package/runtime/modules/ssh-keygen/module.toml +4 -0
- package/runtime/modules/statusline/install.sh +69 -0
- package/runtime/modules/statusline/module.toml +7 -0
- package/runtime/modules/statusline/statusline.sh +168 -0
- package/runtime/modules/statusline/turn-counter.sh +11 -0
- package/runtime/modules/superfile/cheatsheet.md +127 -0
- package/runtime/modules/superfile/config.toml +181 -0
- package/runtime/modules/superfile/hotkeys.toml +96 -0
- package/runtime/modules/superfile/install.sh +73 -0
- package/runtime/modules/superfile/module.toml +7 -0
- package/runtime/modules/tailscale/install.sh +29 -0
- package/runtime/modules/tailscale/module.toml +11 -0
- package/runtime/modules/tinycast/install.sh +68 -0
- package/runtime/modules/tinycast/module.toml +7 -0
- package/runtime/modules/tinycast/settings.json +226 -0
- package/runtime/modules/tmux/.tmux.conf.local +168 -0
- package/runtime/modules/tmux/README.md +50 -0
- package/runtime/modules/tmux/install.sh +57 -0
- package/runtime/modules/tmux/installer.mjs +191 -0
- package/runtime/modules/tmux/module.toml +8 -0
- package/runtime/modules/tmux/shell-helpers.sh +20 -0
- package/runtime/modules/tmux/uninstall.sh +134 -0
- package/runtime/modules/uv/install.sh +72 -0
- package/runtime/modules/uv/module.toml +7 -0
- package/runtime/modules/vlc/install.sh +23 -0
- package/runtime/modules/vlc/module.toml +11 -0
- package/runtime/modules/vscode/install.sh +33 -0
- package/runtime/modules/vscode/keybindings.json +74 -0
- package/runtime/modules/vscode/module.toml +8 -0
- package/runtime/modules/vscode/settings.json +426 -0
- package/runtime/modules/vscode/tasks.json +1 -0
- package/runtime/modules/yt-dlp/install.sh +13 -0
- package/runtime/modules/yt-dlp/module.toml +11 -0
- package/runtime/modules/zed/install.sh +43 -0
- package/runtime/modules/zed/module.toml +8 -0
- package/runtime/modules/zed/settings.json +208 -0
- package/runtime/modules/zoxide/install.sh +41 -0
- package/runtime/modules/zoxide/module.toml +11 -0
package/README.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @quickcall/all-configs
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Run the all-configs terminal UI without installing Bun:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx @quickcall/all-configs
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The package selects a precompiled Bun/OpenTUI executable for the current platform. The executable contains the runtime; Bun is not required on the user's machine.
|
|
10
|
+
|
|
11
|
+
Supported platforms:
|
|
12
|
+
|
|
13
|
+
- macOS arm64 and x64
|
|
14
|
+
- Linux arm64 and x64
|
|
15
|
+
|
|
16
|
+
The package also includes the module definitions and installer scripts used by the TUI.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require("node:child_process")
|
|
4
|
+
const { dirname, join, resolve } = require("node:path")
|
|
5
|
+
|
|
6
|
+
const platform = process.platform
|
|
7
|
+
const arch = process.arch
|
|
8
|
+
const key = `${platform}-${arch}`
|
|
9
|
+
const packages = {
|
|
10
|
+
"darwin-arm64": "@quickcall/all-configs-darwin-arm64",
|
|
11
|
+
"darwin-x64": "@quickcall/all-configs-darwin-x64",
|
|
12
|
+
"linux-arm64": "@quickcall/all-configs-linux-arm64",
|
|
13
|
+
"linux-x64": "@quickcall/all-configs-linux-x64",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const platformPackage = packages[key]
|
|
17
|
+
if (!platformPackage) {
|
|
18
|
+
console.error(`all-configs does not support ${platform}/${arch}`)
|
|
19
|
+
process.exit(1)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let packageJson
|
|
23
|
+
try {
|
|
24
|
+
packageJson = require.resolve(`${platformPackage}/package.json`)
|
|
25
|
+
} catch {
|
|
26
|
+
console.error(`The all-configs binary for ${platform}/${arch} is unavailable.`)
|
|
27
|
+
console.error(`Try reinstalling without omitting optional dependencies:`)
|
|
28
|
+
console.error(` npm install -g @quickcall/all-configs`)
|
|
29
|
+
process.exit(1)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const binary = join(dirname(packageJson), "bin", "all-configs")
|
|
33
|
+
const runtimeRoot = resolve(__dirname, "..", "runtime")
|
|
34
|
+
const result = spawnSync(binary, process.argv.slice(2), {
|
|
35
|
+
stdio: "inherit",
|
|
36
|
+
env: { ...process.env, ALL_CONFIGS_ROOT: runtimeRoot },
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
if (result.error) {
|
|
40
|
+
console.error(`Failed to start all-configs: ${result.error.message}`)
|
|
41
|
+
process.exit(1)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
process.exit(result.status ?? 1)
|
package/package.json
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quickcall/all-configs",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Modular developer environment installer with an interactive OpenTUI interface",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/quickcall-dev/all-configs.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/quickcall-dev/all-configs",
|
|
11
|
+
"bugs": "https://github.com/quickcall-dev/all-configs/issues",
|
|
12
|
+
"bin": {
|
|
13
|
+
"all-configs": "bin/all-configs.js"
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
},
|
|
18
|
+
"files": ["bin", "runtime", "README.md"],
|
|
19
|
+
"optionalDependencies": {
|
|
20
|
+
"@quickcall/all-configs-darwin-arm64": "1.0.0"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""all-configs: module catalogue, detection, pty runner and the Textual TUI."""
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Entry point for all-configs.
|
|
2
|
+
|
|
3
|
+
The interactive TUI is the OpenTUI rewrite (``experimental/opentui``); the
|
|
4
|
+
original Textual TUI is archived under ``archive/textual-tui/``. This module
|
|
5
|
+
locates Bun and hands off to the TypeScript app so both
|
|
6
|
+
``python3 -m allconfigs`` and ``./install.sh`` keep working.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import shutil
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main() -> int:
|
|
18
|
+
if not (sys.stdin.isatty() and sys.stdout.isatty()):
|
|
19
|
+
print("all-configs TUI needs an interactive terminal; use ./install.sh <module>", file=sys.stderr)
|
|
20
|
+
return 1
|
|
21
|
+
|
|
22
|
+
root = Path(__file__).resolve().parents[1]
|
|
23
|
+
bun = shutil.which("bun")
|
|
24
|
+
if bun:
|
|
25
|
+
os.chdir(root)
|
|
26
|
+
os.execvp(bun, [bun, "run", "experimental/opentui/src/index.tsx"])
|
|
27
|
+
|
|
28
|
+
# No Bun: fall back to guidance; the platform check needs 3.11+ (tomllib).
|
|
29
|
+
from allconfigs.modules import PLATFORM
|
|
30
|
+
|
|
31
|
+
if PLATFORM == "unsupported":
|
|
32
|
+
print(f"all-configs supports macOS and Linux only (got {sys.platform})", file=sys.stderr)
|
|
33
|
+
print(
|
|
34
|
+
"The all-configs TUI needs Bun: https://bun.sh\n"
|
|
35
|
+
"Or run a module directly: ./install.sh <module>",
|
|
36
|
+
file=sys.stderr,
|
|
37
|
+
)
|
|
38
|
+
return 1
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
if __name__ == "__main__":
|
|
42
|
+
sys.exit(main())
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""Module discovery and install-state detection.
|
|
2
|
+
|
|
3
|
+
A module is a directory with an ``install.sh`` and a ``module.toml``::
|
|
4
|
+
|
|
5
|
+
name = "tmux"
|
|
6
|
+
description = "tmux config, TPM, vim nav, clipboard"
|
|
7
|
+
platforms = ["mac", "linux"]
|
|
8
|
+
kind = "config" # app | cli | config
|
|
9
|
+
|
|
10
|
+
[detect] # every key optional
|
|
11
|
+
commands = ["tmux"] # any on PATH -> tool present
|
|
12
|
+
brew = ["tmux"] # any brew formula/cask (macOS)
|
|
13
|
+
apps = ["Ghostty"] # any /Applications/<name>.app (macOS)
|
|
14
|
+
paths = ["~/.tmux.conf"] # all must exist -> config applied
|
|
15
|
+
paths_mac = [...] # extra paths, macOS only
|
|
16
|
+
paths_linux = [...] # extra paths, Linux only
|
|
17
|
+
|
|
18
|
+
[uninstall] # omit = uninstall disabled
|
|
19
|
+
brew = ["tmux"] # macOS: Homebrew formula/cask names
|
|
20
|
+
packages = ["tmux"] # Linux: apt/dnf/pacman package names
|
|
21
|
+
|
|
22
|
+
Status combines the two halves: tool present and config applied.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import os
|
|
28
|
+
import shutil
|
|
29
|
+
import subprocess
|
|
30
|
+
import sys
|
|
31
|
+
import tomllib
|
|
32
|
+
from dataclasses import dataclass, field
|
|
33
|
+
from enum import Enum
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
|
|
36
|
+
if sys.platform == "darwin":
|
|
37
|
+
PLATFORM = "mac"
|
|
38
|
+
elif sys.platform.startswith("linux"):
|
|
39
|
+
PLATFORM = "linux"
|
|
40
|
+
else:
|
|
41
|
+
PLATFORM = "unsupported"
|
|
42
|
+
|
|
43
|
+
KINDS = ("app", "cli", "config")
|
|
44
|
+
|
|
45
|
+
# Installers drop binaries here; the parent shell may not have them on PATH yet.
|
|
46
|
+
EXTRA_BIN_DIRS = (
|
|
47
|
+
"~/.local/bin",
|
|
48
|
+
"~/.bun/bin",
|
|
49
|
+
"~/.cargo/bin",
|
|
50
|
+
"/opt/homebrew/bin",
|
|
51
|
+
"/usr/local/bin",
|
|
52
|
+
"/home/linuxbrew/.linuxbrew/bin",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
APP_DIRS = (Path("/Applications"), Path("~/Applications").expanduser())
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Status(str, Enum):
|
|
59
|
+
CHECKING = "checking"
|
|
60
|
+
INSTALLED = "installed"
|
|
61
|
+
PARTIAL = "partial"
|
|
62
|
+
MISSING = "missing"
|
|
63
|
+
UNKNOWN = "unknown"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ModuleError(ValueError):
|
|
67
|
+
"""A module.toml that cannot be used."""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class Module:
|
|
72
|
+
name: str
|
|
73
|
+
description: str
|
|
74
|
+
platforms: tuple[str, ...]
|
|
75
|
+
kind: str
|
|
76
|
+
path: Path
|
|
77
|
+
commands: tuple[str, ...] = ()
|
|
78
|
+
brew: tuple[str, ...] = ()
|
|
79
|
+
apps: tuple[str, ...] = ()
|
|
80
|
+
paths: tuple[str, ...] = ()
|
|
81
|
+
uninstall_packages: tuple[str, ...] = ()
|
|
82
|
+
status: Status = Status.CHECKING
|
|
83
|
+
last_run_failed: bool = field(default=False, compare=False)
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def install_script(self) -> Path:
|
|
87
|
+
return self.path / "install.sh"
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def can_uninstall(self) -> bool:
|
|
91
|
+
# Config modules only configure a tool; removing them must never
|
|
92
|
+
# remove the tool itself, so uninstall is disabled for them.
|
|
93
|
+
return self.kind != "config" and bool(self.uninstall_packages)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _str_list(table: dict, key: str, where: str) -> tuple[str, ...]:
|
|
97
|
+
value = table.get(key, [])
|
|
98
|
+
if not isinstance(value, list) or not all(isinstance(v, str) for v in value):
|
|
99
|
+
raise ModuleError(f"{where}: '{key}' must be a list of strings")
|
|
100
|
+
return tuple(value)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def parse_module(toml_path: Path, platform: str = PLATFORM) -> Module | None:
|
|
104
|
+
"""Parse one module.toml. Returns None when the module targets another platform."""
|
|
105
|
+
where = str(toml_path)
|
|
106
|
+
try:
|
|
107
|
+
data = tomllib.loads(toml_path.read_text())
|
|
108
|
+
except (OSError, tomllib.TOMLDecodeError) as exc:
|
|
109
|
+
raise ModuleError(f"{where}: {exc}") from exc
|
|
110
|
+
|
|
111
|
+
directory = toml_path.parent
|
|
112
|
+
name = data.get("name", directory.name)
|
|
113
|
+
if name != directory.name:
|
|
114
|
+
raise ModuleError(f"{where}: name '{name}' must match directory '{directory.name}'")
|
|
115
|
+
platforms = _str_list(data, "platforms", where) or ("mac", "linux")
|
|
116
|
+
if platform not in platforms:
|
|
117
|
+
return None
|
|
118
|
+
kind = data.get("kind", "config")
|
|
119
|
+
if kind not in KINDS:
|
|
120
|
+
raise ModuleError(f"{where}: kind must be one of {', '.join(KINDS)}")
|
|
121
|
+
if not (directory / "install.sh").is_file():
|
|
122
|
+
raise ModuleError(f"{where}: missing install.sh")
|
|
123
|
+
|
|
124
|
+
detect = data.get("detect", {})
|
|
125
|
+
uninstall = data.get("uninstall", {})
|
|
126
|
+
paths = _str_list(detect, "paths", where) + _str_list(detect, f"paths_{platform}", where)
|
|
127
|
+
packages = _str_list(uninstall, "brew" if platform == "mac" else "packages", where)
|
|
128
|
+
|
|
129
|
+
return Module(
|
|
130
|
+
name=name,
|
|
131
|
+
description=str(data.get("description", "")),
|
|
132
|
+
platforms=platforms,
|
|
133
|
+
kind=kind,
|
|
134
|
+
path=directory,
|
|
135
|
+
commands=_str_list(detect, "commands", where),
|
|
136
|
+
brew=_str_list(detect, "brew", where),
|
|
137
|
+
apps=_str_list(detect, "apps", where),
|
|
138
|
+
paths=paths,
|
|
139
|
+
uninstall_packages=packages,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def load_modules(root: Path, platform: str = PLATFORM) -> tuple[list[Module], list[str]]:
|
|
144
|
+
"""Discover modules under ``root/modules``. Bad module.toml files are reported, not fatal."""
|
|
145
|
+
modules: list[Module] = []
|
|
146
|
+
errors: list[str] = []
|
|
147
|
+
for toml_path in sorted((root / "modules").glob("*/module.toml")):
|
|
148
|
+
try:
|
|
149
|
+
module = parse_module(toml_path, platform)
|
|
150
|
+
except ModuleError as exc:
|
|
151
|
+
errors.append(str(exc))
|
|
152
|
+
continue
|
|
153
|
+
if module is not None:
|
|
154
|
+
modules.append(module)
|
|
155
|
+
return modules, errors
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@dataclass(frozen=True)
|
|
159
|
+
class Environment:
|
|
160
|
+
"""Facts about the machine shared by every module's detection."""
|
|
161
|
+
|
|
162
|
+
path: str
|
|
163
|
+
brew_packages: frozenset[str] = frozenset()
|
|
164
|
+
platform: str = PLATFORM
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def search_path() -> str:
|
|
168
|
+
dirs = os.environ.get("PATH", "").split(os.pathsep)
|
|
169
|
+
dirs += [os.path.expanduser(d) for d in EXTRA_BIN_DIRS]
|
|
170
|
+
return os.pathsep.join(dict.fromkeys(d for d in dirs if d))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def probe_environment() -> Environment:
|
|
174
|
+
"""Collect PATH and Homebrew package lists (two `brew list` calls, run in parallel)."""
|
|
175
|
+
path = search_path()
|
|
176
|
+
brew = shutil.which("brew", path=path)
|
|
177
|
+
if PLATFORM != "mac" or not brew:
|
|
178
|
+
return Environment(path=path)
|
|
179
|
+
procs = [
|
|
180
|
+
subprocess.Popen(
|
|
181
|
+
[brew, "list", flag, "-1"],
|
|
182
|
+
stdout=subprocess.PIPE,
|
|
183
|
+
stderr=subprocess.DEVNULL,
|
|
184
|
+
text=True,
|
|
185
|
+
)
|
|
186
|
+
for flag in ("--formula", "--cask")
|
|
187
|
+
]
|
|
188
|
+
names: set[str] = set()
|
|
189
|
+
for proc in procs:
|
|
190
|
+
try:
|
|
191
|
+
out, _ = proc.communicate(timeout=60)
|
|
192
|
+
except subprocess.TimeoutExpired:
|
|
193
|
+
proc.kill()
|
|
194
|
+
continue
|
|
195
|
+
if proc.returncode == 0:
|
|
196
|
+
names.update(line.strip() for line in out.splitlines() if line.strip())
|
|
197
|
+
return Environment(path=path, brew_packages=frozenset(names))
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def detect(module: Module, env: Environment) -> Status:
|
|
201
|
+
tool_checks: list[bool] = []
|
|
202
|
+
if module.commands:
|
|
203
|
+
tool_checks.append(any(shutil.which(c, path=env.path) is not None for c in module.commands))
|
|
204
|
+
if module.brew and env.platform == "mac" and env.brew_packages:
|
|
205
|
+
tool_checks.append(any(b in env.brew_packages for b in module.brew))
|
|
206
|
+
if module.apps and env.platform == "mac":
|
|
207
|
+
tool_checks.append(any((d / f"{a}.app").is_dir() for d in APP_DIRS for a in module.apps))
|
|
208
|
+
|
|
209
|
+
results: list[bool] = []
|
|
210
|
+
if tool_checks:
|
|
211
|
+
results.append(any(tool_checks))
|
|
212
|
+
if module.paths:
|
|
213
|
+
results.append(all(Path(p).expanduser().exists() for p in module.paths))
|
|
214
|
+
|
|
215
|
+
if not results:
|
|
216
|
+
return Status.UNKNOWN
|
|
217
|
+
if all(results):
|
|
218
|
+
return Status.INSTALLED
|
|
219
|
+
if any(results):
|
|
220
|
+
return Status.PARTIAL
|
|
221
|
+
return Status.MISSING
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""NDJSON probe for external TUIs (e.g. the OpenTUI experiment).
|
|
2
|
+
|
|
3
|
+
Reuses the same discovery and detection logic as the Textual TUI and prints
|
|
4
|
+
one JSON object per line so a parent process can stream results:
|
|
5
|
+
|
|
6
|
+
{"type": "env", "platform": "mac", "root": "/path/to/repo"}
|
|
7
|
+
{"type": "error", "message": "modules/foo/module.toml: ..."}
|
|
8
|
+
{"type": "module", "module": {"name": "tmux", "status": "installed", ...}}
|
|
9
|
+
|
|
10
|
+
Usage::
|
|
11
|
+
|
|
12
|
+
python3 -m allconfigs.probe [repo-root] # run from the repo root
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from allconfigs.modules import detect, load_modules, probe_environment
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def main() -> int:
|
|
25
|
+
root = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else Path(__file__).resolve().parents[1]
|
|
26
|
+
|
|
27
|
+
def emit(payload: dict) -> None:
|
|
28
|
+
try:
|
|
29
|
+
print(json.dumps(payload), flush=True)
|
|
30
|
+
except BrokenPipeError:
|
|
31
|
+
# The consumer went away; nothing useful left to do.
|
|
32
|
+
raise SystemExit(0)
|
|
33
|
+
|
|
34
|
+
env = probe_environment()
|
|
35
|
+
emit({"type": "env", "platform": env.platform, "root": str(root)})
|
|
36
|
+
|
|
37
|
+
modules, errors = load_modules(root, env.platform)
|
|
38
|
+
for error in errors:
|
|
39
|
+
emit({"type": "error", "message": error})
|
|
40
|
+
for module in modules:
|
|
41
|
+
module.status = detect(module, env)
|
|
42
|
+
emit(
|
|
43
|
+
{
|
|
44
|
+
"type": "module",
|
|
45
|
+
"module": {
|
|
46
|
+
"name": module.name,
|
|
47
|
+
"description": module.description,
|
|
48
|
+
"kind": module.kind,
|
|
49
|
+
"platforms": list(module.platforms),
|
|
50
|
+
"status": module.status.value,
|
|
51
|
+
"can_uninstall": module.can_uninstall,
|
|
52
|
+
"uninstall_packages": list(module.uninstall_packages),
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
return 0
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
sys.exit(main())
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# all-configs entry point (macOS and Linux only).
|
|
3
|
+
#
|
|
4
|
+
# ./install.sh interactive TUI
|
|
5
|
+
# ./install.sh tmux nvim install the named modules directly, no TUI
|
|
6
|
+
# ./install.sh --list list modules available on this platform
|
|
7
|
+
set -e
|
|
8
|
+
|
|
9
|
+
trap 'echo ""; fail "Interrupted"; exit 130' INT
|
|
10
|
+
|
|
11
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
MODULES_DIR="$ROOT_DIR/modules"
|
|
13
|
+
# common.sh exits on anything other than Darwin/Linux
|
|
14
|
+
source "$ROOT_DIR/lib/common.sh" || { echo "Error: lib/common.sh not found" >&2; exit 1; }
|
|
15
|
+
|
|
16
|
+
PYTHON_VERSION="3.12"
|
|
17
|
+
|
|
18
|
+
ensure_uv() {
|
|
19
|
+
# uv may already be installed but not on PATH in this shell
|
|
20
|
+
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
|
|
21
|
+
command -v uv &>/dev/null && return 0
|
|
22
|
+
|
|
23
|
+
[[ -f "$MODULES_DIR/uv/install.sh" ]] || { fail "uv not found and modules/uv/install.sh missing"; exit 1; }
|
|
24
|
+
step "uv not found — installing"
|
|
25
|
+
bash "$MODULES_DIR/uv/install.sh"
|
|
26
|
+
command -v uv &>/dev/null || { fail "uv install finished but uv is not on PATH"; exit 1; }
|
|
27
|
+
ok "uv installed ${D}$(command -v uv)${R}"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
list_modules() {
|
|
31
|
+
local toml name platforms
|
|
32
|
+
for toml in "$MODULES_DIR"/*/module.toml; do
|
|
33
|
+
name="$(basename "$(dirname "$toml")")"
|
|
34
|
+
platforms="$(grep -E '^platforms' "$toml" || true)"
|
|
35
|
+
if [[ "$platforms" == *"\"$PLATFORM\""* ]]; then echo "$name"; fi
|
|
36
|
+
done
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
case "${1:-}" in
|
|
40
|
+
-h|--help)
|
|
41
|
+
sed -n '2,6p' "$0" | sed 's/^# \{0,1\}//'
|
|
42
|
+
exit 0
|
|
43
|
+
;;
|
|
44
|
+
-l|--list)
|
|
45
|
+
list_modules
|
|
46
|
+
exit 0
|
|
47
|
+
;;
|
|
48
|
+
esac
|
|
49
|
+
|
|
50
|
+
# Direct install mode: ./install.sh tmux nvim
|
|
51
|
+
if [[ $# -gt 0 ]]; then
|
|
52
|
+
for mod in "$@"; do
|
|
53
|
+
# module names are plain directory names; reject paths like ../x
|
|
54
|
+
if [[ ! "$mod" =~ ^[a-z0-9][a-z0-9_-]*$ ]] || [[ ! -f "$MODULES_DIR/$mod/install.sh" ]]; then
|
|
55
|
+
fail "Unknown module: $mod (see ./install.sh --list)"
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
done
|
|
59
|
+
for mod in "$@"; do
|
|
60
|
+
bash "$MODULES_DIR/$mod/install.sh"
|
|
61
|
+
done
|
|
62
|
+
exit 0
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# Interactive TUI mode needs a real terminal
|
|
66
|
+
if [[ ! -t 0 || ! -t 1 ]]; then
|
|
67
|
+
fail "The TUI needs an interactive terminal. Use ./install.sh <module>... instead."
|
|
68
|
+
exit 1
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
ensure_uv
|
|
72
|
+
cd "$ROOT_DIR"
|
|
73
|
+
# The OpenTUI TUI runs on Bun; __main__ locates it and hands off.
|
|
74
|
+
exec uv run --quiet --no-project --python "$PYTHON_VERSION" python3 -m allconfigs
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Shared helpers for all installers
|
|
3
|
+
|
|
4
|
+
R='\033[0m' B='\033[1m' D='\033[2m'
|
|
5
|
+
GRN='\033[32m' YLW='\033[33m' RED='\033[31m'
|
|
6
|
+
CYN='\033[36m' BLU='\033[34m' MAG='\033[35m'
|
|
7
|
+
|
|
8
|
+
ok() { printf " ${GRN}${B}✓${R} %b\n" "$1"; }
|
|
9
|
+
warn() { printf " ${YLW}${B}!${R} %b\n" "$1"; }
|
|
10
|
+
fail() { printf " ${RED}${B}✗${R} %b\n" "$1"; }
|
|
11
|
+
step() { printf "\n ${CYN}${B}→${R} %b\n" "$1"; }
|
|
12
|
+
|
|
13
|
+
OS="$(uname -s)"
|
|
14
|
+
case "$OS" in
|
|
15
|
+
Darwin) PLATFORM="mac" ;;
|
|
16
|
+
Linux) PLATFORM="linux" ;;
|
|
17
|
+
*) fail "Unsupported OS: $OS"; exit 1 ;;
|
|
18
|
+
esac
|
|
19
|
+
|
|
20
|
+
pkg_install() {
|
|
21
|
+
local pkg="$1"
|
|
22
|
+
if [[ "$PLATFORM" == "mac" ]]; then
|
|
23
|
+
command -v brew &>/dev/null || { fail "Homebrew not found: https://brew.sh"; return 1; }
|
|
24
|
+
yes | NONINTERACTIVE=1 CI=1 brew install "$pkg"
|
|
25
|
+
else
|
|
26
|
+
if command -v apt-get &>/dev/null; then
|
|
27
|
+
sudo apt-get update -qq && sudo apt-get install -y -qq "$pkg"
|
|
28
|
+
elif command -v dnf &>/dev/null; then
|
|
29
|
+
sudo dnf install -y "$pkg"
|
|
30
|
+
elif command -v pacman &>/dev/null; then
|
|
31
|
+
sudo pacman -S --noconfirm "$pkg"
|
|
32
|
+
else
|
|
33
|
+
fail "No supported package manager found"; return 1
|
|
34
|
+
fi
|
|
35
|
+
fi
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
pkg_check() {
|
|
39
|
+
local pkg="$1"
|
|
40
|
+
if [[ "$PLATFORM" == "mac" ]]; then
|
|
41
|
+
if command -v brew &>/dev/null; then
|
|
42
|
+
brew list --cask "$pkg" &>/dev/null && return 0
|
|
43
|
+
brew list "$pkg" &>/dev/null && return 0
|
|
44
|
+
fi
|
|
45
|
+
fi
|
|
46
|
+
command -v "$pkg" &>/dev/null && return 0
|
|
47
|
+
if command -v dpkg &>/dev/null && dpkg -l "$pkg" &>/dev/null 2>&1; then
|
|
48
|
+
return 0
|
|
49
|
+
fi
|
|
50
|
+
if command -v rpm &>/dev/null && rpm -q "$pkg" &>/dev/null 2>&1; then
|
|
51
|
+
return 0
|
|
52
|
+
fi
|
|
53
|
+
if command -v pacman &>/dev/null && pacman -Q "$pkg" &>/dev/null 2>&1; then
|
|
54
|
+
return 0
|
|
55
|
+
fi
|
|
56
|
+
return 1
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
pkg_uninstall() {
|
|
60
|
+
local pkg="$1"
|
|
61
|
+
if [[ "$PLATFORM" == "mac" ]]; then
|
|
62
|
+
# macOS: only Homebrew-managed packages can be removed here; never
|
|
63
|
+
# fall through to Linux package managers.
|
|
64
|
+
command -v brew &>/dev/null || { fail "Homebrew not found"; return 1; }
|
|
65
|
+
if brew_cask_installed "$pkg"; then
|
|
66
|
+
NONINTERACTIVE=1 CI=1 brew uninstall --cask "$pkg"
|
|
67
|
+
elif brew_formula_installed "$pkg"; then
|
|
68
|
+
NONINTERACTIVE=1 CI=1 brew uninstall "$pkg"
|
|
69
|
+
else
|
|
70
|
+
warn "$pkg is not managed by Homebrew — remove it manually"
|
|
71
|
+
return 1
|
|
72
|
+
fi
|
|
73
|
+
return
|
|
74
|
+
fi
|
|
75
|
+
if command -v apt-get &>/dev/null; then
|
|
76
|
+
dpkg -s "$pkg" &>/dev/null || { warn "$pkg is not installed (dpkg)"; return 1; }
|
|
77
|
+
sudo apt-get remove -y -qq "$pkg"
|
|
78
|
+
elif command -v dnf &>/dev/null; then
|
|
79
|
+
rpm -q "$pkg" &>/dev/null || { warn "$pkg is not installed (rpm)"; return 1; }
|
|
80
|
+
sudo dnf remove -y "$pkg"
|
|
81
|
+
elif command -v pacman &>/dev/null; then
|
|
82
|
+
pacman -Q "$pkg" &>/dev/null || { warn "$pkg is not installed (pacman)"; return 1; }
|
|
83
|
+
sudo pacman -R --noconfirm "$pkg"
|
|
84
|
+
else
|
|
85
|
+
fail "No supported package manager for uninstall"; return 1
|
|
86
|
+
fi
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
ensure_cmd() {
|
|
90
|
+
local cmd="$1" pkg="${2:-$1}"
|
|
91
|
+
if command -v "$cmd" &>/dev/null; then
|
|
92
|
+
ok "$cmd ${D}$(command -v "$cmd")${R}"
|
|
93
|
+
return 0
|
|
94
|
+
else
|
|
95
|
+
warn "$cmd not found — installing"
|
|
96
|
+
pkg_install "$pkg"
|
|
97
|
+
if command -v "$cmd" &>/dev/null; then
|
|
98
|
+
ok "$cmd installed"
|
|
99
|
+
else
|
|
100
|
+
fail "$cmd installation failed"
|
|
101
|
+
return 1
|
|
102
|
+
fi
|
|
103
|
+
fi
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
brew_cask_installed() {
|
|
107
|
+
local name="$1"
|
|
108
|
+
command -v brew &>/dev/null && brew list --cask "$name" &>/dev/null 2>&1
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
brew_formula_installed() {
|
|
112
|
+
local name="$1"
|
|
113
|
+
command -v brew &>/dev/null && brew list "$name" &>/dev/null 2>&1
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
brew_install_cask() {
|
|
117
|
+
local name="$1"
|
|
118
|
+
if brew_cask_installed "$name"; then
|
|
119
|
+
ok "$name already installed"
|
|
120
|
+
return 0
|
|
121
|
+
fi
|
|
122
|
+
step "Installing $name"
|
|
123
|
+
# no `yes |` pipe: under `set -o pipefail` yes exits 141 (SIGPIPE) and
|
|
124
|
+
# fails the whole pipeline even when brew succeeds
|
|
125
|
+
NONINTERACTIVE=1 CI=1 brew install --cask "$name"
|
|
126
|
+
ok "$name installed"
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
brew_install_formula() {
|
|
130
|
+
local name="$1"
|
|
131
|
+
if brew_formula_installed "$name"; then
|
|
132
|
+
ok "$name already installed"
|
|
133
|
+
return 0
|
|
134
|
+
fi
|
|
135
|
+
step "Installing $name"
|
|
136
|
+
NONINTERACTIVE=1 CI=1 brew install "$name"
|
|
137
|
+
ok "$name installed"
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
backup_file() {
|
|
141
|
+
local file="$1"
|
|
142
|
+
if [[ -f "$file" ]] && [[ ! -L "$file" ]]; then
|
|
143
|
+
local bak="${file}.backup.$(date +%Y%m%d_%H%M%S)"
|
|
144
|
+
cp "$file" "$bak"
|
|
145
|
+
ok "backed up ${D}→ $bak${R}"
|
|
146
|
+
fi
|
|
147
|
+
}
|