@uluops/setup 0.7.0 → 0.8.1
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 +91 -9
- package/assets/codex/skills/uluops-operator/SKILL.md +159 -0
- package/dist/cli/select-harnesses.d.ts +91 -0
- package/dist/cli/select-harnesses.js +108 -0
- package/dist/cli.js +78 -37
- package/dist/commands/errors.d.ts +24 -0
- package/dist/commands/errors.js +28 -0
- package/dist/commands/helpers.d.ts +7 -0
- package/dist/commands/helpers.js +80 -3
- package/dist/commands/per-harness.d.ts +64 -0
- package/dist/commands/per-harness.js +37 -0
- package/dist/commands/setup.d.ts +5 -3
- package/dist/commands/setup.js +174 -48
- package/dist/commands/uninstall-filter.d.ts +36 -0
- package/dist/commands/uninstall-filter.js +69 -0
- package/dist/commands/uninstall.d.ts +12 -2
- package/dist/commands/uninstall.js +121 -45
- package/dist/harnesses/codex.d.ts +5 -10
- package/dist/harnesses/codex.js +89 -21
- package/dist/harnesses/index.js +6 -1
- package/dist/harnesses/opencode.d.ts +8 -0
- package/dist/harnesses/opencode.js +24 -1
- package/dist/harnesses/types.d.ts +2 -0
- package/dist/harnesses/types.js +5 -1
- package/dist/lib/atomic-write.js +10 -2
- package/dist/lib/config-merger.d.ts +6 -3
- package/dist/lib/config-merger.js +50 -7
- package/dist/lib/display.d.ts +21 -5
- package/dist/lib/display.js +118 -13
- package/dist/lib/file-ops.d.ts +13 -5
- package/dist/lib/file-ops.js +34 -34
- package/dist/lib/install-lock.js +11 -1
- package/dist/lib/json-guards.d.ts +22 -0
- package/dist/lib/json-guards.js +33 -0
- package/dist/lib/manifest.d.ts +20 -0
- package/dist/lib/manifest.js +61 -12
- package/dist/lib/paths.d.ts +0 -17
- package/dist/lib/paths.js +0 -19
- package/dist/lib/settings-merger.js +3 -1
- package/dist/steps/agent-metrics-cli.d.ts +9 -1
- package/dist/steps/agent-metrics-cli.js +66 -20
- package/dist/steps/agents.d.ts +11 -0
- package/dist/steps/agents.js +30 -25
- package/dist/steps/auth.d.ts +13 -0
- package/dist/steps/auth.js +61 -5
- package/dist/steps/cli.d.ts +6 -0
- package/dist/steps/cli.js +29 -10
- package/dist/steps/commands.d.ts +10 -0
- package/dist/steps/commands.js +31 -30
- package/dist/steps/detect.js +15 -1
- package/dist/steps/mcp.js +1 -8
- package/dist/steps/metrics.js +10 -3
- package/dist/steps/shell.js +3 -13
- package/dist/steps/signup.js +14 -1
- package/dist/steps/skills.d.ts +14 -0
- package/dist/steps/skills.js +95 -0
- package/dist/steps/verify.js +195 -91
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -19,19 +19,68 @@ npx @uluops/setup
|
|
|
19
19
|
| Claude Code | Fully supported (default) | `claude` | `~/.claude.json` |
|
|
20
20
|
| OpenCode | Fully supported | `oc` | `~/.config/opencode/opencode.json` |
|
|
21
21
|
| Gemini CLI | Fully supported | `gemini` | `~/.gemini/settings.json` |
|
|
22
|
-
| Codex |
|
|
22
|
+
| Codex | Experimental (opt-in via `--harness codex`) | — | `~/.codex/config.toml` |
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
25
|
# Install for Claude Code (default)
|
|
26
26
|
npx @uluops/setup
|
|
27
27
|
|
|
28
|
-
# Install for
|
|
28
|
+
# Install for one specific harness
|
|
29
29
|
npx @uluops/setup --harness opencode
|
|
30
|
-
|
|
31
|
-
# Install for Gemini CLI
|
|
32
30
|
npx @uluops/setup --harness gemini-cli
|
|
31
|
+
|
|
32
|
+
# Install into every detected stable harness in a single run
|
|
33
|
+
npx @uluops/setup --all-detected
|
|
34
|
+
|
|
35
|
+
# Install into a specific subset (comma-separated)
|
|
36
|
+
npx @uluops/setup --harness claude-code,gemini-cli
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Harness auto-detection
|
|
40
|
+
|
|
41
|
+
If you don't pass `--harness` or `--all-detected`, setup probes your home directory for known harness install markers and picks a target:
|
|
42
|
+
|
|
43
|
+
- **One harness detected** — that harness is used as the target. A dimmed `Detected <Name>` line confirms the choice (suppressed when the detected harness is the default `claude-code`).
|
|
44
|
+
- **Multiple harnesses detected (interactive)** — you get a multi-select checkbox listing every detected harness, with **every option checked by default** so the "install everywhere" case is a single Enter press. Use space to toggle entries off.
|
|
45
|
+
- **Multiple harnesses detected (non-interactive — `--yes`, `--api-key`, piped stdin)** — to keep CI scripts predictable, this preserves earlier behavior: the first detected harness installs and a dimmed notice lists the others. CI users who want multi-install opt in explicitly with `--all-detected`.
|
|
46
|
+
- **No harnesses detected** — falls back to the default (`claude-code`) so `npx @uluops/setup` always does something useful on a fresh machine.
|
|
47
|
+
|
|
48
|
+
Passing `--harness <name>` always wins — auto-detection is skipped entirely. Experimental harnesses are excluded from auto-detection (an explicit `--harness` is the only way to opt in).
|
|
49
|
+
|
|
50
|
+
### Multi-harness install
|
|
51
|
+
|
|
52
|
+
`@uluops/setup` is a zero-friction installer for any agentic stack you have. When you've installed multiple harnesses on the same machine, one invocation can wire UluOps into all of them — once-per-run prompts (API key, signup, CLI install) run a single time across the whole batch.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Install into every detected stable harness
|
|
56
|
+
npx @uluops/setup --all-detected
|
|
57
|
+
|
|
58
|
+
# Same thing, alternative form (--all-detected is the canonical name)
|
|
59
|
+
npx @uluops/setup --harness all
|
|
60
|
+
|
|
61
|
+
# Subset
|
|
62
|
+
npx @uluops/setup --harness claude-code,opencode
|
|
63
|
+
|
|
64
|
+
# Aliases work in the comma-separated list
|
|
65
|
+
npx @uluops/setup --harness claude,oc
|
|
33
66
|
```
|
|
34
67
|
|
|
68
|
+
Each harness gets its own per-section block in the summary:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
Setup complete: 3 installed of 3 harnesses
|
|
72
|
+
|
|
73
|
+
✓ [Claude Code] installed (23 agents · 28 commands · metrics)
|
|
74
|
+
✓ [OpenCode] installed (23 agents)
|
|
75
|
+
✓ [Gemini CLI] installed (23 agents · 28 commands · metrics)
|
|
76
|
+
|
|
77
|
+
Restart each of Claude Code, OpenCode, Gemini CLI to load agents.
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Failure isolation:** one harness failing does not abort the others. The failing harness lands as `✗ [<Name>] failed — <reason>` in the summary with a `Re-run: npx @uluops/setup --harness <name>` hint; siblings install cleanly. The process exits 1 when any harness failed operationally, 0 when every harness either succeeded or was declined at the conflict prompt (user choice is not failure).
|
|
81
|
+
|
|
82
|
+
**Partial state:** if a per-harness step throws after MCP succeeded (e.g., a `mkdir` permission error during the agents step), the manifest records what landed plus `partial: "<step>"` naming the failed step. `--verify` surfaces this with a `partial install — failed at "<step>"` warning so you know a re-run is needed.
|
|
83
|
+
|
|
35
84
|
## What it does
|
|
36
85
|
|
|
37
86
|
| Artifact | Count | Destination (Claude Code) |
|
|
@@ -74,8 +123,16 @@ Setup will first ask whether you're creating a new UluOps account. Pick **Y** to
|
|
|
74
123
|
npx @uluops/setup [options]
|
|
75
124
|
|
|
76
125
|
--api-key <key> API key (skip prompt)
|
|
77
|
-
--harness <
|
|
126
|
+
--harness <value> Target harness(es). Single name, comma-separated subset,
|
|
127
|
+
or "all" sentinel:
|
|
128
|
+
--harness claude-code
|
|
129
|
+
--harness claude-code,codex
|
|
130
|
+
--harness all
|
|
131
|
+
Names: claude-code, opencode, gemini-cli, codex
|
|
78
132
|
Aliases: claude, oc, gemini (default: claude-code)
|
|
133
|
+
--all-detected Install into every detected stable harness. Canonical
|
|
134
|
+
synonym for --harness all. Cannot be combined with
|
|
135
|
+
--harness <single-name> (fail-fast conflict error).
|
|
79
136
|
--signup Create account from terminal (email + password)
|
|
80
137
|
--scope <mode> MCP config scope: "global" or "local" (default: global)
|
|
81
138
|
--local-defs Save definitions to ./uluops/ for review
|
|
@@ -85,7 +142,11 @@ npx @uluops/setup [options]
|
|
|
85
142
|
--skip-validation Accept API key without server verification
|
|
86
143
|
--list Show available agents and workflows without installing
|
|
87
144
|
--verify Check installation health: manifest, files, MCP config, API connectivity (no changes)
|
|
88
|
-
--uninstall Remove
|
|
145
|
+
--uninstall Remove UluOps-managed artifacts. Combine with --harness
|
|
146
|
+
<name>[,<name>] to uninstall from a subset only —
|
|
147
|
+
remaining harnesses (and global infrastructure like
|
|
148
|
+
@uluops/cli) are preserved. Plain --uninstall removes
|
|
149
|
+
everything (today's behavior).
|
|
89
150
|
--dry-run Show what would happen without making changes
|
|
90
151
|
-y, --yes Skip confirmations
|
|
91
152
|
```
|
|
@@ -96,7 +157,7 @@ npx @uluops/setup [options]
|
|
|
96
157
|
Displays all agents and workflows included in the current version of the setup tool.
|
|
97
158
|
|
|
98
159
|
```text
|
|
99
|
-
⟨u⟩ ulu·ops v0.
|
|
160
|
+
⟨u⟩ ulu·ops v0.8.1 — available agents and workflows
|
|
100
161
|
|
|
101
162
|
WORKFLOWS
|
|
102
163
|
/workflows:post-implementation Iterative validation after coding
|
|
@@ -115,7 +176,7 @@ Displays all agents and workflows included in the current version of the setup t
|
|
|
115
176
|
Validates your current installation against the local manifest and checks API connectivity.
|
|
116
177
|
|
|
117
178
|
```text
|
|
118
|
-
⟨u⟩ ulu·ops Installation Check v0.
|
|
179
|
+
⟨u⟩ ulu·ops Installation Check v0.8.1
|
|
119
180
|
|
|
120
181
|
✓ Manifest found (~/.uluops/manifest.json)
|
|
121
182
|
✓ All 23 agents present in ~/.claude/agents/
|
|
@@ -139,9 +200,18 @@ npx @uluops/setup --signup
|
|
|
139
200
|
# Install for OpenCode
|
|
140
201
|
npx @uluops/setup --harness opencode
|
|
141
202
|
|
|
203
|
+
# Install into every detected stable harness in one run
|
|
204
|
+
npx @uluops/setup --all-detected
|
|
205
|
+
|
|
206
|
+
# Install into a specific subset
|
|
207
|
+
npx @uluops/setup --harness claude-code,gemini-cli
|
|
208
|
+
|
|
142
209
|
# Non-interactive (CI/automation)
|
|
143
210
|
npx @uluops/setup --api-key ulr_abc123 -y
|
|
144
211
|
|
|
212
|
+
# CI multi-harness: explicit opt-in to all-detected
|
|
213
|
+
npx @uluops/setup --api-key ulr_abc123 --all-detected -y
|
|
214
|
+
|
|
145
215
|
# Local MCP config (project-scoped)
|
|
146
216
|
npx @uluops/setup --scope local
|
|
147
217
|
|
|
@@ -180,10 +250,22 @@ Setup manages four surfaces: agent files, command files, MCP config entries, and
|
|
|
180
250
|
## Uninstall
|
|
181
251
|
|
|
182
252
|
```
|
|
253
|
+
# Uninstall everything (every harness in the manifest + globals + shell export)
|
|
183
254
|
npx @uluops/setup --uninstall
|
|
255
|
+
|
|
256
|
+
# Uninstall from a specific harness only — other harnesses + globals preserved
|
|
257
|
+
npx @uluops/setup --uninstall --harness opencode
|
|
258
|
+
|
|
259
|
+
# Uninstall from a comma-separated subset
|
|
260
|
+
npx @uluops/setup --uninstall --harness opencode,gemini-cli
|
|
261
|
+
|
|
262
|
+
# Same as no filter — uninstall everything
|
|
263
|
+
npx @uluops/setup --uninstall --all-detected
|
|
184
264
|
```
|
|
185
265
|
|
|
186
|
-
Removes only UluOps-managed files: agents, commands, MCP config entries, shell profile export (if `--shell` was used), and the global `@uluops/cli` package (only if this setup installed it — a CLI you installed yourself is left alone). Your custom agents and other MCP servers are preserved.
|
|
266
|
+
Removes only UluOps-managed files: agents, commands, MCP config entries, shell profile export (if `--shell` was used), and the global `@uluops/cli` package (only if this setup installed it — a CLI you installed yourself is left alone). Your custom agents and other MCP servers are preserved.
|
|
267
|
+
|
|
268
|
+
**Subset uninstall** (`--uninstall --harness <name>`) removes only the named harness(es) from the manifest and disk. Shared infrastructure (the global `@uluops/cli`, `@uluops/agent-metrics`, and the shell-profile export) is left in place because remaining harnesses still need it. The manifest is updated rather than deleted. A subset uninstall that names a harness not in the manifest fails fast with an error listing what IS in the manifest — no silent no-op.
|
|
187
269
|
|
|
188
270
|
## Requirements
|
|
189
271
|
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: uluops-operator
|
|
3
|
+
description: Use when operating inside UluOps from Codex: using UluOps MCP tools or the ulu CLI; rendering or running registry agents, commands, workflows, or pipelines; saving Codex subagent runs to tracker projects; querying, triaging, or closing tracker issues; inspecting analysis records; or deciding how UluOps definitions should map onto Codex skills, agents, and local assets.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# UluOps Operator
|
|
7
|
+
|
|
8
|
+
Use this skill as the operating map for UluOps in Codex. UluOps has three main surfaces:
|
|
9
|
+
|
|
10
|
+
- **Registry**: source of truth for agents, commands, workflows, pipelines, versions, dependencies, and harness-specific rendering.
|
|
11
|
+
- **Tracker**: source of truth for projects, runs, issues, analysis summaries, analysis records, reliability, and lifecycle.
|
|
12
|
+
- **RAH**: statistical and higher-order analysis over registry/tracker state.
|
|
13
|
+
|
|
14
|
+
Prefer MCP tools for tracker/registry state changes already exposed in the session. Prefer the `ulu` CLI for rendering definitions, execution surfaces not exposed by MCP, and machine-readable scripting.
|
|
15
|
+
|
|
16
|
+
## Tool Choice
|
|
17
|
+
|
|
18
|
+
| Task | Prefer | Notes |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| Query tracker projects, runs, issues, summaries | `mcp__uluops_tracker` | Use `query_issues`, `search_issues`, `get_project_summary`, run/analysis tools when available. |
|
|
21
|
+
| Update tracker issue status or add notes | `mcp__uluops_tracker` | Use structured status updates with concise resolution reasons. |
|
|
22
|
+
| Save Codex agent/pipeline output to tracker | `mcp__uluops_tracker` | Shape recommendations/issues/analysis records explicitly; preserve custom record types when supported. |
|
|
23
|
+
| Query registry definitions, versions, dependencies | `mcp__uluops_registry` or `ulu` | Use MCP for lightweight lookups; CLI when rendered output or JSON scripting is needed. |
|
|
24
|
+
| Render definitions for Codex | `ulu def get ... --rendered --target codex` | Registry remains source of truth; rendered files are working copies. |
|
|
25
|
+
| Execute UluOps definitions directly | `ulu exec ...` | Use when the user explicitly wants CLI execution rather than Codex subagents. |
|
|
26
|
+
| Analyze cross-run/project patterns | `mcp__rah` | Use for statistical or trend questions when exposed. |
|
|
27
|
+
|
|
28
|
+
If a localhost registry/tracker CLI command fails inside Codex with a connection or sandbox-like error, retry the same command with sandbox escalation before changing environment variables or config.
|
|
29
|
+
|
|
30
|
+
## Codex Surface Rules
|
|
31
|
+
|
|
32
|
+
Codex does not have Claude-style slash commands as the primary reusable surface.
|
|
33
|
+
|
|
34
|
+
- Use **skills** for stable UluOps workflows that should trigger naturally in Codex.
|
|
35
|
+
- Use rendered **agent TOML** as source material for spawning Codex subagents.
|
|
36
|
+
- Treat rendered commands, workflows, and pipelines as **orchestration instructions**. For Codex, they may need to become skills or be executed stepwise by Codex rather than copied as slash commands.
|
|
37
|
+
- Do not bulk-install every registry definition as a skill. Keep registry as source of truth and render on demand unless a workflow is stable and high-value.
|
|
38
|
+
|
|
39
|
+
Default Codex global paths:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
~/.codex/config.toml
|
|
43
|
+
~/.codex/agents/
|
|
44
|
+
~/.codex/skills/
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Common Commands
|
|
48
|
+
|
|
49
|
+
Render definitions for Codex:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
ulu def get agent code-validator --rendered --target codex
|
|
53
|
+
ulu def get workflow ship --rendered --target codex
|
|
54
|
+
ulu def get pipeline foundations --rendered --target codex
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Render with a model envelope:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
ulu def get agent assumption-excavator --rendered --target codex -m gpt
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
List and inspect definitions:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
ulu def list --type agent --json
|
|
67
|
+
ulu def get agent code-validator --json
|
|
68
|
+
ulu deps pipeline foundations --json
|
|
69
|
+
ulu versions list agent code-validator --json
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Tracker reads via CLI when MCP does not expose the needed surface:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
ulu projects list --json
|
|
76
|
+
ulu issues list uluops-plans --status open --json
|
|
77
|
+
ulu runs list uluops-plans --json
|
|
78
|
+
ulu analytics burndown --project uluops-plans --days 30 --json
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Canonical Loop
|
|
82
|
+
|
|
83
|
+
When asked to close the loop on an artifact:
|
|
84
|
+
|
|
85
|
+
1. Identify the tracker project and artifact path.
|
|
86
|
+
2. Pull current open issues or run the requested agent/pipeline.
|
|
87
|
+
3. Inspect the artifact and nearby code/docs before editing.
|
|
88
|
+
4. Patch the artifact or code with narrow, repo-consistent changes.
|
|
89
|
+
5. Verify with tests, typecheck, or text audits appropriate to the change.
|
|
90
|
+
6. Save the run or analysis to tracker when new agent output was produced.
|
|
91
|
+
7. Mark resolved issues completed with a concrete reason naming the artifact or code path changed.
|
|
92
|
+
8. Report what changed, what was verified, and any remaining risk.
|
|
93
|
+
|
|
94
|
+
Do not mark an issue completed merely because it was inspected. Complete it only when the artifact/code now addresses the finding. Use `deferred`, `wontfix`, `false-positive`, or `observation` when that status is more accurate.
|
|
95
|
+
|
|
96
|
+
## Running Registry Agents As Codex Subagents
|
|
97
|
+
|
|
98
|
+
When the user asks to use a UluOps agent through Codex:
|
|
99
|
+
|
|
100
|
+
1. Render or locate the agent definition for Codex.
|
|
101
|
+
2. Read only enough of the TOML to understand the role, inputs, expected output, and scoring vocabulary.
|
|
102
|
+
3. Spawn a Codex subagent using that definition's instructions and the target artifact.
|
|
103
|
+
4. Ask the subagent for structured output: decision, score, findings/recommendations, analysis records, and evidence.
|
|
104
|
+
5. Normalize the output before saving to tracker.
|
|
105
|
+
|
|
106
|
+
For large artifacts, tell the subagent whether it read the full artifact or performed a targeted pass. Preserve that note in the tracker run summary.
|
|
107
|
+
|
|
108
|
+
## Saving Runs To Tracker
|
|
109
|
+
|
|
110
|
+
When saving a Codex subagent result:
|
|
111
|
+
|
|
112
|
+
- Project: use the user-provided tracker project, or infer from local context only when obvious.
|
|
113
|
+
- Workflow type: use the registry definition name or agent name.
|
|
114
|
+
- Agent name: use the UluOps agent identifier, not the subagent nickname.
|
|
115
|
+
- Recommendations/issues: preserve title, severity, category/failure mode, evidence, and actionable remediation.
|
|
116
|
+
- Analysis records: preserve custom `record_type` values; do not force them into a closed enum unless the API requires it.
|
|
117
|
+
- Summary: include artifact path, decision, score, method, caveats, and counts.
|
|
118
|
+
|
|
119
|
+
If the save tool rejects a custom analysis shape, simplify only the rejected field and preserve the semantic content in `content`, `metadata`, or the run summary.
|
|
120
|
+
|
|
121
|
+
## Issue Triage
|
|
122
|
+
|
|
123
|
+
For top-issue workflows:
|
|
124
|
+
|
|
125
|
+
1. Query open issues for the project with `priority: "all"` or the requested severity.
|
|
126
|
+
2. Sort by tracker order unless the user requests a different ordering.
|
|
127
|
+
3. Investigate each issue against the artifact or code.
|
|
128
|
+
4. Patch the source of truth, not only the generated tracker text.
|
|
129
|
+
5. Bulk update statuses only after the fixes are in place.
|
|
130
|
+
|
|
131
|
+
Good resolution reasons are short and concrete:
|
|
132
|
+
|
|
133
|
+
```text
|
|
134
|
+
Resolved in plans/example.md by adding skipMissing runtime semantics and validation coverage.
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Registry And Definition Hygiene
|
|
138
|
+
|
|
139
|
+
- Registry definitions are source of truth; rendered Codex assets can drift.
|
|
140
|
+
- Use exact versions when reproducibility matters.
|
|
141
|
+
- Use dependencies to understand pipelines before running them.
|
|
142
|
+
- For Codex, a pipeline may be better represented as a skill recipe than a slash command or a static TOML file.
|
|
143
|
+
- Keep stable, high-value Codex workflows as skills. Keep broad, changing catalogs in the registry.
|
|
144
|
+
|
|
145
|
+
## Naming Discipline
|
|
146
|
+
|
|
147
|
+
Use canonical project/repo/service names from tracker or registry when available. Do not invent aliases in specs or issue closures. If names disagree across artifacts, fix the source artifact or call out the mismatch explicitly.
|
|
148
|
+
|
|
149
|
+
Current known canonical examples:
|
|
150
|
+
|
|
151
|
+
- Tracker project: `uluops-plans`
|
|
152
|
+
- Tracker API service/repo in specs: `ops-uluops-api`
|
|
153
|
+
- Registry frontend: `ops-uluops-registry`
|
|
154
|
+
|
|
155
|
+
## Safety
|
|
156
|
+
|
|
157
|
+
- Be conservative with mutating registry operations such as publish, archive, deprecate, fork, or delete.
|
|
158
|
+
- Be conservative with tracker bulk updates; use them when fixes are already made and the status is clear.
|
|
159
|
+
- Never overwrite user edits while installing/rendering local Codex assets. Prefer re-rendering on demand and scoped patches.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-target harness selection (spec §5 behavior matrix).
|
|
3
|
+
*
|
|
4
|
+
* Pure-ish: every external dependency (prompt, info-emitter) is injected
|
|
5
|
+
* so this module is testable without spawning the CLI or mocking inquirer.
|
|
6
|
+
* The cli.ts entry point wires it to real @inquirer/prompts + chalk-styled
|
|
7
|
+
* info() calls; tests pass plain callbacks and assert on the returned
|
|
8
|
+
* harnessNames + the prompt-input received by the callback.
|
|
9
|
+
*
|
|
10
|
+
* Flag conflict detection happens here, not in cli.ts, so the error
|
|
11
|
+
* messaging is uniform and a single test surface covers every CLI
|
|
12
|
+
* combinatoric.
|
|
13
|
+
*/
|
|
14
|
+
import type { HarnessProfile } from "../harnesses/index.js";
|
|
15
|
+
export interface HarnessSelectionInput {
|
|
16
|
+
/**
|
|
17
|
+
* Value of the --harness flag as received from commander. May be a single
|
|
18
|
+
* canonical name ('claude-code'), an alias ('claude'), a comma-separated
|
|
19
|
+
* list ('claude-code,codex'), the 'all' sentinel, or the default
|
|
20
|
+
* (e.g. 'claude-code' when no --harness was passed).
|
|
21
|
+
*/
|
|
22
|
+
harnessArg: string;
|
|
23
|
+
/**
|
|
24
|
+
* True when commander.getOptionValueSource('harness') === 'cli', i.e.
|
|
25
|
+
* the user actually passed --harness. False when only the default fired.
|
|
26
|
+
*/
|
|
27
|
+
harnessFromCli: boolean;
|
|
28
|
+
/** Whether --all-detected was passed. */
|
|
29
|
+
allDetected: boolean;
|
|
30
|
+
/** Result of detectHarnesses() — stable profiles whose home dirs exist. */
|
|
31
|
+
detected: HarnessProfile[];
|
|
32
|
+
/**
|
|
33
|
+
* Fallback harness when zero are detected and the user did not pass an
|
|
34
|
+
* explicit single name. Today: 'claude-code' (the landing-page promise).
|
|
35
|
+
*/
|
|
36
|
+
defaultHarness: string;
|
|
37
|
+
/**
|
|
38
|
+
* Whether the run is interactive (TTY + no --yes / --api-key /
|
|
39
|
+
* ULUOPS_API_KEY). The selection branch only prompts when this is true
|
|
40
|
+
* AND multiple harnesses are detected.
|
|
41
|
+
*/
|
|
42
|
+
isInteractive: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Async callback that presents the multi-select checkbox to the user.
|
|
45
|
+
* Receives the detected profiles; returns the chosen subset of harness
|
|
46
|
+
* names. Only called when isInteractive && detected.length > 1 && no
|
|
47
|
+
* explicit --harness / --all-detected was provided. May return an empty
|
|
48
|
+
* array (user unchecked everything) — runSetup handles that case.
|
|
49
|
+
*/
|
|
50
|
+
promptCheckbox: (profiles: HarnessProfile[]) => Promise<string[]>;
|
|
51
|
+
/**
|
|
52
|
+
* Optional info-line emitter for the "dimmed notice" branches. No-op when
|
|
53
|
+
* undefined (tests can pass a recorder; cli.ts passes the styled info()).
|
|
54
|
+
*/
|
|
55
|
+
emitInfo?: (msg: string) => void;
|
|
56
|
+
}
|
|
57
|
+
export declare class HarnessSelectionError extends Error {
|
|
58
|
+
constructor(message: string);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Parse a --harness value into a list of names (or the 'all' sentinel).
|
|
62
|
+
* Comma-separated parsing splits on `,` and trims whitespace; empty
|
|
63
|
+
* tokens are dropped. 'all' is reserved — a literal harness named 'all'
|
|
64
|
+
* would conflict (no such harness exists today).
|
|
65
|
+
*/
|
|
66
|
+
export declare function parseHarnessArg(arg: string): string[] | "all";
|
|
67
|
+
/**
|
|
68
|
+
* Resolve the set of harness names to install into.
|
|
69
|
+
*
|
|
70
|
+
* Behavior matrix mirrors spec §5:
|
|
71
|
+
*
|
|
72
|
+
* | --harness | --all-detected | TTY | detected | result |
|
|
73
|
+
* |---------------|----------------|-----|----------|---------------------------------|
|
|
74
|
+
* | omitted | no | * | 0 | [defaultHarness] |
|
|
75
|
+
* | omitted | no | * | 1 | [detected[0]] |
|
|
76
|
+
* | omitted | no | yes | >1 | promptCheckbox(detected) |
|
|
77
|
+
* | omitted | no | no | >1 | [detected[0]] + dimmed notice |
|
|
78
|
+
* | <name> | no | * | * | [<name>] |
|
|
79
|
+
* | <a>,<b> | no | * | * | [<a>, <b>] |
|
|
80
|
+
* | all | no | * | >0 | detected.map(p => p.name) |
|
|
81
|
+
* | all | no | * | 0 | [defaultHarness] |
|
|
82
|
+
* | omitted | yes | * | >0 | detected.map(p => p.name) |
|
|
83
|
+
* | omitted | yes | * | 0 | [defaultHarness] |
|
|
84
|
+
* | all | yes | * | * | (same as either alone) |
|
|
85
|
+
* | <name> | yes | * | * | ERROR: conflicting flags |
|
|
86
|
+
*
|
|
87
|
+
* Caller is responsible for validating each returned name via getProfile()
|
|
88
|
+
* (typo detection) and for handling the empty-array case (user unchecked
|
|
89
|
+
* everything — runSetup exits cleanly).
|
|
90
|
+
*/
|
|
91
|
+
export declare function selectHarnesses(input: HarnessSelectionInput): Promise<string[]>;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-target harness selection (spec §5 behavior matrix).
|
|
3
|
+
*
|
|
4
|
+
* Pure-ish: every external dependency (prompt, info-emitter) is injected
|
|
5
|
+
* so this module is testable without spawning the CLI or mocking inquirer.
|
|
6
|
+
* The cli.ts entry point wires it to real @inquirer/prompts + chalk-styled
|
|
7
|
+
* info() calls; tests pass plain callbacks and assert on the returned
|
|
8
|
+
* harnessNames + the prompt-input received by the callback.
|
|
9
|
+
*
|
|
10
|
+
* Flag conflict detection happens here, not in cli.ts, so the error
|
|
11
|
+
* messaging is uniform and a single test surface covers every CLI
|
|
12
|
+
* combinatoric.
|
|
13
|
+
*/
|
|
14
|
+
export class HarnessSelectionError extends Error {
|
|
15
|
+
constructor(message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "HarnessSelectionError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Parse a --harness value into a list of names (or the 'all' sentinel).
|
|
22
|
+
* Comma-separated parsing splits on `,` and trims whitespace; empty
|
|
23
|
+
* tokens are dropped. 'all' is reserved — a literal harness named 'all'
|
|
24
|
+
* would conflict (no such harness exists today).
|
|
25
|
+
*/
|
|
26
|
+
export function parseHarnessArg(arg) {
|
|
27
|
+
const trimmed = arg.trim();
|
|
28
|
+
if (trimmed === "all")
|
|
29
|
+
return "all";
|
|
30
|
+
return trimmed
|
|
31
|
+
.split(",")
|
|
32
|
+
.map((s) => s.trim())
|
|
33
|
+
.filter((s) => s.length > 0);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the set of harness names to install into.
|
|
37
|
+
*
|
|
38
|
+
* Behavior matrix mirrors spec §5:
|
|
39
|
+
*
|
|
40
|
+
* | --harness | --all-detected | TTY | detected | result |
|
|
41
|
+
* |---------------|----------------|-----|----------|---------------------------------|
|
|
42
|
+
* | omitted | no | * | 0 | [defaultHarness] |
|
|
43
|
+
* | omitted | no | * | 1 | [detected[0]] |
|
|
44
|
+
* | omitted | no | yes | >1 | promptCheckbox(detected) |
|
|
45
|
+
* | omitted | no | no | >1 | [detected[0]] + dimmed notice |
|
|
46
|
+
* | <name> | no | * | * | [<name>] |
|
|
47
|
+
* | <a>,<b> | no | * | * | [<a>, <b>] |
|
|
48
|
+
* | all | no | * | >0 | detected.map(p => p.name) |
|
|
49
|
+
* | all | no | * | 0 | [defaultHarness] |
|
|
50
|
+
* | omitted | yes | * | >0 | detected.map(p => p.name) |
|
|
51
|
+
* | omitted | yes | * | 0 | [defaultHarness] |
|
|
52
|
+
* | all | yes | * | * | (same as either alone) |
|
|
53
|
+
* | <name> | yes | * | * | ERROR: conflicting flags |
|
|
54
|
+
*
|
|
55
|
+
* Caller is responsible for validating each returned name via getProfile()
|
|
56
|
+
* (typo detection) and for handling the empty-array case (user unchecked
|
|
57
|
+
* everything — runSetup exits cleanly).
|
|
58
|
+
*/
|
|
59
|
+
export async function selectHarnesses(input) {
|
|
60
|
+
// Flag conflict — explicit --harness <name> + --all-detected is
|
|
61
|
+
// ambiguous and should fail fast before any state is touched. The 'all'
|
|
62
|
+
// sentinel is the one case where the two flags ARE compatible (they mean
|
|
63
|
+
// the same thing) so we accept that combination silently.
|
|
64
|
+
if (input.allDetected &&
|
|
65
|
+
input.harnessFromCli &&
|
|
66
|
+
input.harnessArg.trim() !== "all") {
|
|
67
|
+
throw new HarnessSelectionError(`--harness ${input.harnessArg} conflicts with --all-detected; pick one`);
|
|
68
|
+
}
|
|
69
|
+
// Explicit --harness wins over --all-detected, with one harmonization:
|
|
70
|
+
// --harness all behaves identically to --all-detected.
|
|
71
|
+
if (input.harnessFromCli) {
|
|
72
|
+
const parsed = parseHarnessArg(input.harnessArg);
|
|
73
|
+
if (parsed === "all") {
|
|
74
|
+
return input.detected.length > 0
|
|
75
|
+
? input.detected.map((p) => p.name)
|
|
76
|
+
: [input.defaultHarness];
|
|
77
|
+
}
|
|
78
|
+
return parsed;
|
|
79
|
+
}
|
|
80
|
+
// --all-detected without --harness
|
|
81
|
+
if (input.allDetected) {
|
|
82
|
+
return input.detected.length > 0
|
|
83
|
+
? input.detected.map((p) => p.name)
|
|
84
|
+
: [input.defaultHarness];
|
|
85
|
+
}
|
|
86
|
+
// Auto-detection (no explicit flags)
|
|
87
|
+
if (input.detected.length === 0) {
|
|
88
|
+
return [input.defaultHarness];
|
|
89
|
+
}
|
|
90
|
+
if (input.detected.length === 1) {
|
|
91
|
+
const only = input.detected[0];
|
|
92
|
+
if (only.name !== input.defaultHarness && input.emitInfo) {
|
|
93
|
+
input.emitInfo(`Detected ${only.displayName} — using as target (pass --harness to override)`);
|
|
94
|
+
}
|
|
95
|
+
return [only.name];
|
|
96
|
+
}
|
|
97
|
+
// detected.length > 1
|
|
98
|
+
if (input.isInteractive) {
|
|
99
|
+
return await input.promptCheckbox(input.detected);
|
|
100
|
+
}
|
|
101
|
+
// Non-interactive with multiple detected: today's behavior preserved
|
|
102
|
+
// (first detected + dimmed notice). Multi-harness CI users opt in with
|
|
103
|
+
// --all-detected. Spec §10.1.
|
|
104
|
+
if (input.emitInfo) {
|
|
105
|
+
input.emitInfo(`Multiple harnesses detected (${input.detected.map((p) => p.displayName).join(", ")}); defaulting to ${input.detected[0].displayName} — pass --all-detected to install into all`);
|
|
106
|
+
}
|
|
107
|
+
return [input.detected[0].name];
|
|
108
|
+
}
|