@codyswann/lisa 2.328.5 → 2.329.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 (85) hide show
  1. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  2. package/dist/core/upstream-evidence-manifest.js +39 -0
  3. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  4. package/package.json +1 -1
  5. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  6. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  7. package/plugins/lisa/.codex-plugin/skills/lisa-setup-workstation/SKILL.md +121 -0
  8. package/plugins/lisa/.codex-plugin/skills/lisa-setup-workstation/agents/openai.yaml +4 -0
  9. package/plugins/lisa/.codex-plugin/skills/lisa-setup-workstation/scripts/catalogue.mjs +262 -0
  10. package/plugins/lisa/.codex-plugin/skills/lisa-setup-workstation/scripts/cli.mjs +315 -0
  11. package/plugins/lisa/.codex-plugin/skills/lisa-setup-workstation/scripts/workstation.mjs +403 -0
  12. package/plugins/lisa/commands/setup/workstation.md +7 -0
  13. package/plugins/lisa/skills/lisa-setup-workstation/SKILL.md +121 -0
  14. package/plugins/lisa/skills/lisa-setup-workstation/agents/openai.yaml +4 -0
  15. package/plugins/lisa/skills/lisa-setup-workstation/scripts/catalogue.mjs +262 -0
  16. package/plugins/lisa/skills/lisa-setup-workstation/scripts/cli.mjs +315 -0
  17. package/plugins/lisa/skills/lisa-setup-workstation/scripts/workstation.mjs +403 -0
  18. package/plugins/lisa-agy/commands/lisa/setup/workstation.md +7 -0
  19. package/plugins/lisa-agy/plugin.json +1 -1
  20. package/plugins/lisa-agy/skills/lisa-setup-workstation/SKILL.md +121 -0
  21. package/plugins/lisa-agy/skills/lisa-setup-workstation/scripts/catalogue.mjs +262 -0
  22. package/plugins/lisa-agy/skills/lisa-setup-workstation/scripts/cli.mjs +315 -0
  23. package/plugins/lisa-agy/skills/lisa-setup-workstation/scripts/workstation.mjs +403 -0
  24. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  26. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  27. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-copilot/commands/lisa/setup/workstation.md +7 -0
  31. package/plugins/lisa-copilot/skills/lisa-setup-workstation/SKILL.md +121 -0
  32. package/plugins/lisa-copilot/skills/lisa-setup-workstation/scripts/catalogue.mjs +262 -0
  33. package/plugins/lisa-copilot/skills/lisa-setup-workstation/scripts/cli.mjs +315 -0
  34. package/plugins/lisa-copilot/skills/lisa-setup-workstation/scripts/workstation.mjs +403 -0
  35. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-cursor/commands/lisa/setup/workstation.md +7 -0
  37. package/plugins/lisa-cursor/skills/lisa-setup-workstation/SKILL.md +121 -0
  38. package/plugins/lisa-cursor/skills/lisa-setup-workstation/scripts/catalogue.mjs +262 -0
  39. package/plugins/lisa-cursor/skills/lisa-setup-workstation/scripts/cli.mjs +315 -0
  40. package/plugins/lisa-cursor/skills/lisa-setup-workstation/scripts/workstation.mjs +403 -0
  41. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  43. package/plugins/lisa-expo-agy/plugin.json +1 -1
  44. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  48. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  49. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  53. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  54. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  58. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  59. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  63. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  64. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  68. package/plugins/lisa-rails-agy/plugin.json +1 -1
  69. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  73. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  74. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  78. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  79. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  80. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  81. package/plugins/src/base/commands/setup/workstation.md +7 -0
  82. package/plugins/src/base/skills/lisa-setup-workstation/SKILL.md +121 -0
  83. package/plugins/src/base/skills/lisa-setup-workstation/scripts/catalogue.mjs +262 -0
  84. package/plugins/src/base/skills/lisa-setup-workstation/scripts/cli.mjs +315 -0
  85. package/plugins/src/base/skills/lisa-setup-workstation/scripts/workstation.mjs +403 -0
package/package.json CHANGED
@@ -115,7 +115,7 @@
115
115
  "brace-expansion": ">=5.0.9"
116
116
  },
117
117
  "name": "@codyswann/lisa",
118
- "version": "2.328.5",
118
+ "version": "2.329.0",
119
119
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
120
120
  "main": "dist/index.js",
121
121
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.328.5",
3
+ "version": "2.329.0",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.328.5",
3
+ "version": "2.329.0",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -0,0 +1,121 @@
1
+ ---
2
+ name: lisa-setup-workstation
3
+ description: "Prepare a machine — a fresh…"
4
+ allowed-tools: ["Bash", "Read", "Skill"]
5
+ ---
6
+
7
+ # Setup Workstation: $ARGUMENTS
8
+
9
+ Get a machine ready to run agents. No checkout required.
10
+
11
+ ## Why this exists separately
12
+
13
+ `lisa-setup-local-env` reads `remoteEnv.tools` — a **project's** declared toolchain — so it
14
+ needs a checkout to exist before it can answer anything. That leaves a gap at the start:
15
+ a bare laptop or an empty container has no repository yet, and therefore nothing to ask.
16
+
17
+ This skill fills that gap and stops there. It describes a **machine**, never a project:
18
+
19
+ ```text
20
+ workstation -> git clone -> lisa-detect-tooling -> lisa-setup-local-env
21
+ ```
22
+
23
+ Project-scoped tools stay in `remoteEnv.tools` where they belong. Nothing here reads a
24
+ repository, and nothing here duplicates that manifest.
25
+
26
+ ## Usage
27
+
28
+ ```sh
29
+ node scripts/cli.mjs # report what is present and missing
30
+ node scripts/cli.mjs --install # install what is missing
31
+ node scripts/cli.mjs --install --agents=claude,codex # only these agents
32
+ node scripts/cli.mjs --provider=bitwarden # credential manager (asked if a TTY)
33
+ node scripts/cli.mjs --json # machine-readable plan
34
+ node scripts/cli.mjs --print-dockerfile # an image that runs this same script
35
+ ```
36
+
37
+ Nothing is installed or written without `--install`. Exit status is non-zero when a required
38
+ tool is absent or an install fails, so this is usable as a gate.
39
+
40
+ ## Ephemerality is not a mode
41
+
42
+ There is no Docker branch anywhere in this skill. A container gets a throwaway `$HOME` and
43
+ dies with it; a laptop keeps its own. Same code path either way — so the rarely-run one
44
+ cannot rot, which is the failure mode a second install path eventually always has.
45
+
46
+ `--print-dockerfile` emits an image rather than shipping a committed one, so the image cannot
47
+ drift from the catalogue it provisions. It deliberately bakes in **no repository**: clone
48
+ inside the container, then run `lisa-detect-tooling` and `lisa-setup-local-env` for that
49
+ project's own tools.
50
+
51
+ ```sh
52
+ node scripts/cli.mjs --print-dockerfile --agents=claude,codex --provider=bitwarden > Dockerfile
53
+ docker build -t lisa-workstation . && docker run --rm -it lisa-workstation
54
+ ```
55
+
56
+ ## Already-installed wins, however it got there
57
+
58
+ A tool present by **any** means is reported and left alone. That dissolves the "Homebrew or
59
+ `~/.local/bin`" question entirely: we never contend for ownership of a tool someone else's
60
+ package manager installed, and never shadow it with an earlier PATH entry that would silently
61
+ win over their upgrades. The report names where each tool came from so the operator can see
62
+ why it was skipped.
63
+
64
+ Running twice installs nothing the second time, and says so rather than going quiet — silence
65
+ reads as "did nothing because it broke".
66
+
67
+ ## Install method is a per-tool property, not a global policy
68
+
69
+ The obvious design — put every binary in `~/.local/bin` so one code path serves a laptop and a
70
+ container alike — is wrong, and the agents show why:
71
+
72
+ ```text
73
+ claude -> ~/.local/share/claude/versions/2.1.221
74
+ cursor-agent -> ~/.local/share/cursor-agent/versions/2026.07.16-899851b
75
+ codex -> ~/.codex/packages/standalone/current/bin/codex
76
+ ```
77
+
78
+ Each vendor manages its own version directory, and `~/.local/bin` holds only a **symlink**.
79
+ Writing a raw binary there breaks the vendor's self-updater, which expects to swap a link
80
+ target and would instead find a real file in its way.
81
+
82
+ So each catalogue entry declares its own method, and the honest cost is recorded with it:
83
+
84
+ | kind | pinned | checksummed | notes |
85
+ | --- | --- | --- | --- |
86
+ | `release-zip` / `release-tar` | yes | yes | reuses the remote-env installer |
87
+ | `npm-global` | no | no | vendor registry |
88
+ | `vendor-script` | no | no | pipes a fetched script to a shell |
89
+ | `manual` | n/a | n/a | no headless installer published |
90
+ | `required` | n/a | n/a | expected from the OS or base image |
91
+
92
+ `vendor-script` is a real weakening of the guarantee `assertPinned` exists to provide, so the
93
+ report **names every unchecksummed install** rather than blending them in. Installing them
94
+ trusts the vendor's script at fetch time. The alternative — repackaging vendor binaries
95
+ ourselves — would break the self-updaters that own those directories.
96
+
97
+ ## The credential manager is asked, never assumed
98
+
99
+ `lisa-secrets-access` already treats the provider as an axis: Bitwarden, 1Password, Doppler,
100
+ Vault, AWS, or plain environment variables. A bootstrap that installed `bws` unconditionally
101
+ would contradict that — pushing every workstation onto one vendor and quietly making the
102
+ others second-class.
103
+
104
+ So the provider is a question, `none` is a first-class answer, and only the selected one is
105
+ installed. Installing all of them would leave four unused credential CLIs on the machine,
106
+ each an extra thing to keep patched for no benefit.
107
+
108
+ Headless is the primary mode, so with no TTY and no `--provider`, the answer is `none` — not
109
+ a prompt that would hang a container build forever. A misspelled provider stops the run
110
+ instead of falling back, because quietly provisioning the wrong credential manager is worse
111
+ than stopping.
112
+
113
+ ## What it will not do
114
+
115
+ - **Read a repository.** That is the next step, not this one.
116
+ - **Install a credential manager you did not pick.**
117
+ - **Reinstall over a tool that is already there**, whatever installed it.
118
+ - **Install anything without `--install`.**
119
+ - **Install `agy`.** Antigravity publishes no headless installer; it is reported as manual
120
+ with a pointer, because "not installed" and "cannot be installed by this tool" are
121
+ different answers to the operator.
@@ -0,0 +1,4 @@
1
+ display_name: "Setup Workstation"
2
+ short_description: "Prepare a machine — a fresh…"
3
+ default_prompt:
4
+ - "Use $lisa-setup-workstation: Prepare a machine — a fresh…."
@@ -0,0 +1,262 @@
1
+ /**
2
+ * What a workstation can hold, and how each piece is installed.
3
+ *
4
+ * This is the layer beneath `remoteEnv.tools`: it describes a MACHINE, not a
5
+ * project, so nothing here is read from a checkout and no repository needs to
6
+ * exist. The project-scoped manifest still owns project-scoped tools.
7
+ *
8
+ * ## Install method is a per-tool property, not a global policy
9
+ *
10
+ * The obvious design — put every binary in `~/.local/bin` so one code path
11
+ * serves a laptop and a container alike — is wrong, and the agents show why:
12
+ *
13
+ * claude -> ~/.local/share/claude/versions/2.1.221
14
+ * cursor-agent -> ~/.local/share/cursor-agent/versions/2026.07.16-899851b
15
+ * codex -> ~/.codex/packages/standalone/current/bin/codex
16
+ *
17
+ * Each vendor manages its own version directory and `~/.local/bin` holds only a
18
+ * SYMLINK. Writing a raw binary there breaks the vendor's self-updater, which
19
+ * expects to swap a link target and would instead find a real file in its way.
20
+ *
21
+ * So each entry declares its own method, and the honest cost is recorded with
22
+ * it: a `vendor-script` entry is NOT pinned and NOT checksummed. That is a real
23
+ * weakening of the guarantee `assertPinned` exists to provide, so the kind is
24
+ * visible in the catalogue and in the report rather than blended in with
25
+ * checksummed entries. A reader can see which tools are trusted to a vendor.
26
+ * @module catalogue
27
+ */
28
+
29
+ /** Lisa's supported coding agents. Kept in sync with the harness list. */
30
+ export const AGENTS = [
31
+ {
32
+ name: "claude",
33
+ label: "Claude Code",
34
+ kind: "vendor-script",
35
+ // The vendor script installs into ~/.local/share/claude/versions/<v> and
36
+ // links ~/.local/bin/claude at it, which is why we must not write there.
37
+ script: "https://claude.ai/install.sh",
38
+ selfUpdates: true,
39
+ },
40
+ {
41
+ name: "codex",
42
+ label: "Codex",
43
+ kind: "npm-global",
44
+ package: "@openai/codex",
45
+ selfUpdates: true,
46
+ },
47
+ {
48
+ name: "cursor-agent",
49
+ label: "Cursor",
50
+ kind: "vendor-script",
51
+ script: "https://cursor.com/install",
52
+ selfUpdates: true,
53
+ },
54
+ {
55
+ name: "opencode",
56
+ label: "OpenCode",
57
+ kind: "vendor-script",
58
+ script: "https://opencode.ai/install",
59
+ // Installs to its own directory and appends a PATH line to the user's
60
+ // shell rc — which a non-interactive container never sources. Declared
61
+ // here so the run puts it on PATH itself rather than reporting a binary
62
+ // that is on disk as missing.
63
+ binDir: "~/.opencode/bin",
64
+ selfUpdates: true,
65
+ },
66
+ {
67
+ name: "agy",
68
+ label: "Antigravity",
69
+ kind: "manual",
70
+ // No headless installer published. Reported as absent with a pointer
71
+ // rather than silently skipped, because "not installed" and "cannot be
72
+ // installed by this tool" are different answers to the operator.
73
+ note: "no headless installer published; install from the vendor",
74
+ },
75
+ {
76
+ name: "copilot",
77
+ label: "GitHub Copilot CLI",
78
+ kind: "npm-global",
79
+ package: "@github/copilot",
80
+ selfUpdates: false,
81
+ },
82
+ ];
83
+
84
+ /**
85
+ * Credential managers, one of which a workstation may use.
86
+ *
87
+ * A CHOICE, not a fixed tool. `lisa-secrets-access` already treats the provider
88
+ * as an axis — bitwarden, 1password, doppler, vault, aws, or plain `env` — and
89
+ * a bootstrap that installed `bws` unconditionally would contradict that,
90
+ * pushing every workstation onto one vendor and quietly making the others
91
+ * second-class.
92
+ *
93
+ * `none` is a first-class answer. A machine using plain environment variables
94
+ * is a supported configuration, not a degraded one, so it must be selectable
95
+ * rather than merely what happens when a question is skipped.
96
+ *
97
+ * Only the SELECTED provider is installed. Installing all of them would put
98
+ * four unused credential CLIs on a machine, each an extra thing to keep patched
99
+ * for no benefit.
100
+ */
101
+ export const PROVIDERS = [
102
+ {
103
+ name: "bitwarden",
104
+ binary: "bws",
105
+ label: "Bitwarden Secrets Manager",
106
+ // The one genuinely pinned credential CLI: Bitwarden publishes per-platform
107
+ // archives that can carry a checksum, and a credential bootstrap is exactly
108
+ // where an unverified download matters most.
109
+ kind: "release-zip",
110
+ version: "2.1.0",
111
+ platforms: {
112
+ "linux-x64": {
113
+ url: "https://github.com/bitwarden/sdk-sm/releases/download/bws-v2.1.0/bws-x86_64-unknown-linux-gnu-2.1.0.zip",
114
+ sha256:
115
+ "ba8233c3a4aee5d43e3c73bbd04d99e9bc5aba13bbbfd06d89b073abe732b860",
116
+ },
117
+ "linux-arm64": {
118
+ url: "https://github.com/bitwarden/sdk-sm/releases/download/bws-v2.1.0/bws-aarch64-unknown-linux-gnu-2.1.0.zip",
119
+ sha256:
120
+ "18253757286e119d450133a87eb463bf8c1ce418ce24c834f4f250d60cba6f9e",
121
+ },
122
+ },
123
+ },
124
+ {
125
+ name: "1password",
126
+ binary: "op",
127
+ label: "1Password CLI",
128
+ kind: "vendor-script",
129
+ note: "install from 1password.com/downloads/command-line",
130
+ },
131
+ {
132
+ name: "doppler",
133
+ binary: "doppler",
134
+ label: "Doppler CLI",
135
+ kind: "vendor-script",
136
+ script: "https://cli.doppler.com/install.sh",
137
+ },
138
+ {
139
+ name: "vault",
140
+ binary: "vault",
141
+ label: "HashiCorp Vault CLI",
142
+ kind: "vendor-script",
143
+ note: "install from developer.hashicorp.com/vault/install",
144
+ },
145
+ {
146
+ name: "aws",
147
+ binary: "aws",
148
+ label: "AWS Secrets Manager (via AWS CLI)",
149
+ kind: "vendor-script",
150
+ // Deliberately the same entry as the general AWS CLI below: a provider of
151
+ // `aws` needs exactly that binary and nothing extra.
152
+ note: "uses the AWS CLI, which is also a general tool",
153
+ },
154
+ {
155
+ name: "none",
156
+ binary: null,
157
+ label: "No credential manager (plain environment variables)",
158
+ kind: "none",
159
+ note: "supported configuration; nothing is installed",
160
+ },
161
+ ];
162
+
163
+ /**
164
+ * Tools every workstation needs regardless of project or provider.
165
+ */
166
+ export const TOOLS = [
167
+ {
168
+ name: "git",
169
+ label: "git",
170
+ kind: "required",
171
+ note: "expected from the OS or base image",
172
+ },
173
+ {
174
+ name: "node",
175
+ label: "Node.js",
176
+ kind: "required",
177
+ minVersion: "22",
178
+ note: "expected from the OS or base image; the installers run on it",
179
+ },
180
+ {
181
+ name: "gh",
182
+ label: "GitHub CLI",
183
+ kind: "release-tar",
184
+ version: "2.83.0",
185
+ platforms: {
186
+ "linux-x64": {
187
+ url: "https://github.com/cli/cli/releases/download/v2.83.0/gh_2.83.0_linux_amd64.tar.gz",
188
+ sha256:
189
+ "a5cf6cdb40fc67751adf561126b3314044779cea81ba4f254fbe8e9a69f1676f",
190
+ binary: "gh_2.83.0_linux_amd64/bin/gh",
191
+ },
192
+ "linux-arm64": {
193
+ url: "https://github.com/cli/cli/releases/download/v2.83.0/gh_2.83.0_linux_arm64.tar.gz",
194
+ sha256:
195
+ "12311e320d4cfdb54d7fa2d58cd1e3a2ccb4c12e1c3abb32b0a2e48bd0f991bf",
196
+ binary: "gh_2.83.0_linux_arm64/bin/gh",
197
+ },
198
+ "darwin-arm64": {
199
+ url: "https://github.com/cli/cli/releases/download/v2.83.0/gh_2.83.0_macOS_arm64.zip",
200
+ sha256:
201
+ "fecba907bc361d5e33620dbf1145f11432c39fb2b388a839463cfbb89a84820b",
202
+ binary: "gh_2.83.0_macOS_arm64/bin/gh",
203
+ },
204
+ },
205
+ },
206
+ {
207
+ name: "aws",
208
+ label: "AWS CLI v2",
209
+ kind: "aws-cli",
210
+ // Not expressible as release-zip: the artifact is a ~73 MB archive
211
+ // containing an installer, not a single binary on PATH. It gets its own
212
+ // kind rather than a vendor-script URL because the steps in between —
213
+ // unzip, then `./aws/install` with an explicit prefix — are the whole job.
214
+ note: "archive wraps an installer rather than a binary",
215
+ selfUpdates: false,
216
+ },
217
+ {
218
+ name: "sonar",
219
+ label: "SonarQube CLI",
220
+ kind: "vendor-script",
221
+ // The vendor's real installer. The plausible-looking `sonarsource.com/install`
222
+ // that was here first 404s, and because a 404 piped to a shell exits 0, it
223
+ // was reported as installed on a machine that had no `sonar` at all.
224
+ script:
225
+ "https://raw.githubusercontent.com/SonarSource/sonarqube-cli/refs/heads/master/user-scripts/install.sh",
226
+ // Same shell-rc assumption as OpenCode.
227
+ binDir: "~/.local/share/sonarqube-cli/bin",
228
+ selfUpdates: true,
229
+ },
230
+ ];
231
+
232
+ /** Install kinds this tool can actually perform, versus report only. */
233
+ export const INSTALLABLE = new Set([
234
+ "release-zip",
235
+ "release-tar",
236
+ "npm-global",
237
+ "vendor-script",
238
+ "aws-cli",
239
+ ]);
240
+
241
+ /** Kinds whose artifact is verified against a published checksum. */
242
+ export const CHECKSUMMED = new Set(["release-zip", "release-tar"]);
243
+
244
+ /**
245
+ * Every directory a workstation must have on PATH.
246
+ *
247
+ * Vendors that manage their own version directory append a PATH line to the
248
+ * user's shell rc, which a non-interactive run — a container build, a cron, an
249
+ * agent — never sources. Collecting them here means one place decides what PATH
250
+ * must contain, used identically by the runtime and by the emitted Dockerfile.
251
+ * @param {string} home The home directory to expand `~` against.
252
+ * @returns {string[]} Absolute directories, most specific last.
253
+ */
254
+ export function pathDirs(home) {
255
+ const declared = [...AGENTS, ...PROVIDERS, ...TOOLS]
256
+ .map(entry => entry.binDir)
257
+ .filter(Boolean)
258
+ .map(dir => dir.replace(/^~/, home));
259
+ // ~/.local/bin first: it is where the pinned, checksummed binaries land, and
260
+ // those must win over whatever an image happens to ship under the same name.
261
+ return [`${home}/.local/bin`, ...declared];
262
+ }