@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.
Files changed (163) hide show
  1. package/README.md +15 -2
  2. package/bin/all-configs.js +44 -0
  3. package/package.json +20 -4
  4. package/runtime/allconfigs/__init__.py +1 -0
  5. package/runtime/allconfigs/__main__.py +42 -0
  6. package/runtime/allconfigs/__pycache__/__init__.cpython-312.pyc +0 -0
  7. package/runtime/allconfigs/__pycache__/__main__.cpython-312.pyc +0 -0
  8. package/runtime/allconfigs/__pycache__/modules.cpython-312.pyc +0 -0
  9. package/runtime/allconfigs/__pycache__/probe.cpython-312.pyc +0 -0
  10. package/runtime/allconfigs/__pycache__/runner.cpython-312.pyc +0 -0
  11. package/runtime/allconfigs/modules.py +221 -0
  12. package/runtime/allconfigs/probe.py +60 -0
  13. package/runtime/install.sh +74 -0
  14. package/runtime/lib/common.sh +147 -0
  15. package/runtime/lib/uninstall.sh +35 -0
  16. package/runtime/modules/aldente/install.sh +23 -0
  17. package/runtime/modules/aldente/module.toml +11 -0
  18. package/runtime/modules/aliases/aliases.sh +31 -0
  19. package/runtime/modules/aliases/install.sh +36 -0
  20. package/runtime/modules/aliases/module.toml +7 -0
  21. package/runtime/modules/aqua-voice/install.sh +23 -0
  22. package/runtime/modules/aqua-voice/module.toml +11 -0
  23. package/runtime/modules/betterdisplay/install.sh +23 -0
  24. package/runtime/modules/betterdisplay/module.toml +11 -0
  25. package/runtime/modules/bitwarden/install.sh +19 -0
  26. package/runtime/modules/bitwarden/module.toml +11 -0
  27. package/runtime/modules/browsers/install.sh +33 -0
  28. package/runtime/modules/browsers/module.toml +11 -0
  29. package/runtime/modules/bun/install.sh +72 -0
  30. package/runtime/modules/bun/module.toml +7 -0
  31. package/runtime/modules/caveman/install.sh +79 -0
  32. package/runtime/modules/caveman/module.toml +7 -0
  33. package/runtime/modules/claude/install.sh +22 -0
  34. package/runtime/modules/claude/module.toml +7 -0
  35. package/runtime/modules/codex/install.sh +20 -0
  36. package/runtime/modules/codex/module.toml +7 -0
  37. package/runtime/modules/docker/install.sh +19 -0
  38. package/runtime/modules/docker/module.toml +11 -0
  39. package/runtime/modules/external-skills/install.sh +30 -0
  40. package/runtime/modules/external-skills/module.toml +4 -0
  41. package/runtime/modules/ffmpeg/install.sh +13 -0
  42. package/runtime/modules/ffmpeg/module.toml +11 -0
  43. package/runtime/modules/fonts/install.sh +62 -0
  44. package/runtime/modules/fonts/module.toml +8 -0
  45. package/runtime/modules/ghostty/config +44 -0
  46. package/runtime/modules/ghostty/install.sh +64 -0
  47. package/runtime/modules/ghostty/module.toml +9 -0
  48. package/runtime/modules/ghostty/themes/ayu-dark +22 -0
  49. package/runtime/modules/ghostty/themes/catppuccin-frappe +23 -0
  50. package/runtime/modules/ghostty/themes/catppuccin-latte +23 -0
  51. package/runtime/modules/ghostty/themes/catppuccin-latte.conf +23 -0
  52. package/runtime/modules/ghostty/themes/catppuccin-macchiato +23 -0
  53. package/runtime/modules/ghostty/themes/catppuccin-mocha +23 -0
  54. package/runtime/modules/ghostty/themes/visual-studio-light +27 -0
  55. package/runtime/modules/ghostty/themes/vscode-light +24 -0
  56. package/runtime/modules/ghostty/themes/vscode-light-soft +24 -0
  57. package/runtime/modules/ghostty/xterm-ghostty.terminfo +81 -0
  58. package/runtime/modules/github/install.sh +101 -0
  59. package/runtime/modules/github/module.toml +8 -0
  60. package/runtime/modules/hf/install.sh +98 -0
  61. package/runtime/modules/hf/module.toml +7 -0
  62. package/runtime/modules/karabiner/README.md +146 -0
  63. package/runtime/modules/karabiner/agents.md +39 -0
  64. package/runtime/modules/karabiner/app_launchers.json +236 -0
  65. package/runtime/modules/karabiner/custom_delete_mapping.json +13 -0
  66. package/runtime/modules/karabiner/fn_to_control.json +9 -0
  67. package/runtime/modules/karabiner/install.sh +60 -0
  68. package/runtime/modules/karabiner/karabiner.json +866 -0
  69. package/runtime/modules/karabiner/keychron-modifier-swap.sh +80 -0
  70. package/runtime/modules/karabiner/module.toml +8 -0
  71. package/runtime/modules/karabiner/remap_caps_lock_shift_super.json +23 -0
  72. package/runtime/modules/karabiner/remap_escape_to_control.json +49 -0
  73. package/runtime/modules/karabiner/rename_caps_lock_to_super.json +23 -0
  74. package/runtime/modules/karabiner/right_shift_mouse_click.json +44 -0
  75. package/runtime/modules/karabiner/swap_esc_delete.json +10 -0
  76. package/runtime/modules/karabiner/troubleshooting.md +242 -0
  77. package/runtime/modules/karabiner/vim_arrow_keys.json +477 -0
  78. package/runtime/modules/keepingyouawake/install.sh +23 -0
  79. package/runtime/modules/keepingyouawake/module.toml +11 -0
  80. package/runtime/modules/keycastr/install.sh +23 -0
  81. package/runtime/modules/keycastr/module.toml +11 -0
  82. package/runtime/modules/multi-github/install.sh +179 -0
  83. package/runtime/modules/multi-github/module.toml +4 -0
  84. package/runtime/modules/ncdu/install.sh +13 -0
  85. package/runtime/modules/ncdu/module.toml +11 -0
  86. package/runtime/modules/node/install.sh +73 -0
  87. package/runtime/modules/node/module.toml +11 -0
  88. package/runtime/modules/nvim/.neoconf.json +15 -0
  89. package/runtime/modules/nvim/init.lua +2 -0
  90. package/runtime/modules/nvim/install.sh +142 -0
  91. package/runtime/modules/nvim/lazy-lock.json +35 -0
  92. package/runtime/modules/nvim/lazyvim.json +10 -0
  93. package/runtime/modules/nvim/lua/config/autocmds.lua +18 -0
  94. package/runtime/modules/nvim/lua/config/keymaps.lua +10 -0
  95. package/runtime/modules/nvim/lua/config/lazy.lua +53 -0
  96. package/runtime/modules/nvim/lua/config/options.lua +3 -0
  97. package/runtime/modules/nvim/lua/plugins/image.lua +9 -0
  98. package/runtime/modules/nvim/lua/plugins/markdown.lua +56 -0
  99. package/runtime/modules/nvim/lua/plugins/noice.lua +17 -0
  100. package/runtime/modules/nvim/lua/plugins/rose-pine.lua +16 -0
  101. package/runtime/modules/nvim/lua/plugins/snacks.lua +20 -0
  102. package/runtime/modules/nvim/module.toml +8 -0
  103. package/runtime/modules/nvim/stylua.toml +3 -0
  104. package/runtime/modules/p10k/install.sh +46 -0
  105. package/runtime/modules/p10k/module.toml +8 -0
  106. package/runtime/modules/p10k/p10k.zsh +1711 -0
  107. package/runtime/modules/pi/install.sh +77 -0
  108. package/runtime/modules/pi/module.toml +8 -0
  109. package/runtime/modules/pi/packages +5 -0
  110. package/runtime/modules/pi/settings.json +21 -0
  111. package/runtime/modules/presentify/install.sh +25 -0
  112. package/runtime/modules/presentify/module.toml +7 -0
  113. package/runtime/modules/rectangle/RectangleConfig.json +353 -0
  114. package/runtime/modules/rectangle/install.sh +64 -0
  115. package/runtime/modules/rectangle/module.toml +11 -0
  116. package/runtime/modules/shortcat/install.sh +19 -0
  117. package/runtime/modules/shortcat/module.toml +11 -0
  118. package/runtime/modules/skills/install.sh +25 -0
  119. package/runtime/modules/skills/module.toml +4 -0
  120. package/runtime/modules/skills/skills-lock.json +35 -0
  121. package/runtime/modules/slack/install.sh +23 -0
  122. package/runtime/modules/slack/module.toml +11 -0
  123. package/runtime/modules/spotify/install.sh +23 -0
  124. package/runtime/modules/spotify/module.toml +11 -0
  125. package/runtime/modules/ssh-keygen/install.sh +130 -0
  126. package/runtime/modules/ssh-keygen/module.toml +4 -0
  127. package/runtime/modules/statusline/install.sh +69 -0
  128. package/runtime/modules/statusline/module.toml +7 -0
  129. package/runtime/modules/statusline/statusline.sh +168 -0
  130. package/runtime/modules/statusline/turn-counter.sh +11 -0
  131. package/runtime/modules/superfile/cheatsheet.md +127 -0
  132. package/runtime/modules/superfile/config.toml +181 -0
  133. package/runtime/modules/superfile/hotkeys.toml +96 -0
  134. package/runtime/modules/superfile/install.sh +73 -0
  135. package/runtime/modules/superfile/module.toml +7 -0
  136. package/runtime/modules/tailscale/install.sh +29 -0
  137. package/runtime/modules/tailscale/module.toml +11 -0
  138. package/runtime/modules/tinycast/install.sh +68 -0
  139. package/runtime/modules/tinycast/module.toml +7 -0
  140. package/runtime/modules/tinycast/settings.json +226 -0
  141. package/runtime/modules/tmux/.tmux.conf.local +168 -0
  142. package/runtime/modules/tmux/README.md +50 -0
  143. package/runtime/modules/tmux/install.sh +57 -0
  144. package/runtime/modules/tmux/installer.mjs +191 -0
  145. package/runtime/modules/tmux/module.toml +8 -0
  146. package/runtime/modules/tmux/shell-helpers.sh +20 -0
  147. package/runtime/modules/tmux/uninstall.sh +134 -0
  148. package/runtime/modules/uv/install.sh +72 -0
  149. package/runtime/modules/uv/module.toml +7 -0
  150. package/runtime/modules/vlc/install.sh +23 -0
  151. package/runtime/modules/vlc/module.toml +11 -0
  152. package/runtime/modules/vscode/install.sh +33 -0
  153. package/runtime/modules/vscode/keybindings.json +74 -0
  154. package/runtime/modules/vscode/module.toml +8 -0
  155. package/runtime/modules/vscode/settings.json +426 -0
  156. package/runtime/modules/vscode/tasks.json +1 -0
  157. package/runtime/modules/yt-dlp/install.sh +13 -0
  158. package/runtime/modules/yt-dlp/module.toml +11 -0
  159. package/runtime/modules/zed/install.sh +43 -0
  160. package/runtime/modules/zed/module.toml +8 -0
  161. package/runtime/modules/zed/settings.json +208 -0
  162. package/runtime/modules/zoxide/install.sh +41 -0
  163. package/runtime/modules/zoxide/module.toml +11 -0
package/README.md CHANGED
@@ -1,3 +1,16 @@
1
- # Temporary Holding Version
1
+ # @quickcall/all-configs
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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())
@@ -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
+ }