@uluops/setup 0.8.1 → 0.9.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 +5 -3
- package/dist/harnesses/codex.js +130 -8
- package/dist/harnesses/types.js +6 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,9 @@ 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 | Fully supported | — | `~/.codex/config.toml` |
|
|
23
|
+
|
|
24
|
+
> **Codex note:** the TOML writer seeds `approval_mode = "approve"` for every read-side MCP tool exported by `@uluops/ops-mcp` and `@uluops/registry-mcp` so interactive sessions don't surface an approval prompt on every `list_*`/`get_*`/`query_*` call. Write-side tools (`save_run`, `bulk_update_status`, `publish_definition`, etc.) are intentionally NOT pre-approved — Codex still asks before any state-changing operation. A re-install over a hand-tuned config (any `[mcp_servers.<server>.tools.*]` block present) preserves your customizations verbatim and skips the seed step.
|
|
23
25
|
|
|
24
26
|
```bash
|
|
25
27
|
# Install for Claude Code (default)
|
|
@@ -45,7 +47,7 @@ If you don't pass `--harness` or `--all-detected`, setup probes your home direct
|
|
|
45
47
|
- **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
48
|
- **No harnesses detected** — falls back to the default (`claude-code`) so `npx @uluops/setup` always does something useful on a fresh machine.
|
|
47
49
|
|
|
48
|
-
Passing `--harness <name>` always wins — auto-detection is skipped entirely.
|
|
50
|
+
Passing `--harness <name>` always wins — auto-detection is skipped entirely. Auto-detection only returns harnesses marked stable; when a future harness ships as experimental, an explicit `--harness <name>` will remain the only way to opt in.
|
|
49
51
|
|
|
50
52
|
### Multi-harness install
|
|
51
53
|
|
|
@@ -92,7 +94,7 @@ Each harness gets its own per-section block in the summary:
|
|
|
92
94
|
| Pipeline commands | 2 | `~/.claude/commands/pipelines/` |
|
|
93
95
|
| Agent metrics hook | 1 | `~/.claude/tools/agent-metrics/` |
|
|
94
96
|
|
|
95
|
-
> Paths shown are for Claude Code (default). Gemini CLI installs agents as `.md` and commands as `.toml` to `~/.gemini/`. OpenCode installs agents to `~/.config/opencode/agents/`. Agent definitions and commands are transformed to the target harness format at install time from a single source.
|
|
97
|
+
> Paths shown are for Claude Code (default). Gemini CLI installs agents as `.md` and commands as `.toml` to `~/.gemini/`. OpenCode installs agents to `~/.config/opencode/agents/`. Codex installs agents as `.toml` to `~/.codex/agents/` and ships a single `uluops-operator` skill under `~/.codex/skills/` instead of slash commands. Agent definitions and commands are transformed to the target harness format at install time from a single source.
|
|
96
98
|
|
|
97
99
|
The installer runs these steps in sequence:
|
|
98
100
|
|
package/dist/harnesses/codex.js
CHANGED
|
@@ -12,27 +12,134 @@ import { join } from "node:path";
|
|
|
12
12
|
import { ULUOPS_SERVERS, } from "./types.js";
|
|
13
13
|
import { atomicWrite } from "../lib/atomic-write.js";
|
|
14
14
|
const RAW_TOML = "__rawToml";
|
|
15
|
+
/**
|
|
16
|
+
* Tools to seed with `approval_mode = "approve"` (Codex's auto-allow opt-in).
|
|
17
|
+
*
|
|
18
|
+
* Seeded ONLY when the user has no prior `[mcp_servers.NAME.tools.*]` blocks
|
|
19
|
+
* for the server — a re-install over a hand-tuned config preserves the user's
|
|
20
|
+
* choices verbatim (the merge bails on its own seeds the moment it sees user
|
|
21
|
+
* intent).
|
|
22
|
+
*
|
|
23
|
+
* Only read-side tools are seeded. Writes still prompt — the user retains a
|
|
24
|
+
* choice point on every state-changing operation. Lists mirror the
|
|
25
|
+
* `sideEffects: 'read'` declarations in the respective server's tool-registry
|
|
26
|
+
* (sources of truth: ops-uluops-mcp/src/config/tool-registry.ts and
|
|
27
|
+
* uluops-registry-mcp/src/config/tool-registry.ts). If a new read tool ships
|
|
28
|
+
* there, add it here in the same PR — Codex users will silently get a prompt
|
|
29
|
+
* on first use otherwise.
|
|
30
|
+
*/
|
|
31
|
+
const TRACKER_READ_TOOLS = [
|
|
32
|
+
"diff_runs",
|
|
33
|
+
"get_agent_lifecycle",
|
|
34
|
+
"get_agent_matrix",
|
|
35
|
+
"get_agent_reliability",
|
|
36
|
+
"get_agent_runs_analysis",
|
|
37
|
+
"get_analytics",
|
|
38
|
+
"get_burndown",
|
|
39
|
+
"get_discovery",
|
|
40
|
+
"get_full_taxonomy_analytics",
|
|
41
|
+
"get_issue_by_fingerprint",
|
|
42
|
+
"get_issue_details",
|
|
43
|
+
"get_issue_history",
|
|
44
|
+
"get_latest_run",
|
|
45
|
+
"get_project",
|
|
46
|
+
"get_project_analysis",
|
|
47
|
+
"get_project_summary",
|
|
48
|
+
"get_project_trends",
|
|
49
|
+
"get_run",
|
|
50
|
+
"get_run_analysis",
|
|
51
|
+
"get_run_details",
|
|
52
|
+
"get_taxonomy",
|
|
53
|
+
"get_velocity",
|
|
54
|
+
"list_agents",
|
|
55
|
+
"list_projects",
|
|
56
|
+
"list_runs",
|
|
57
|
+
"query_analysis_records",
|
|
58
|
+
"query_issues",
|
|
59
|
+
"search_issues",
|
|
60
|
+
"validate_run",
|
|
61
|
+
];
|
|
62
|
+
const REGISTRY_READ_TOOLS = [
|
|
63
|
+
"batch_users",
|
|
64
|
+
"compare_effectiveness",
|
|
65
|
+
"diff_versions",
|
|
66
|
+
"get_definition",
|
|
67
|
+
"get_dependencies",
|
|
68
|
+
"get_dependents",
|
|
69
|
+
"get_diff_impact",
|
|
70
|
+
"get_ecosystem_overview",
|
|
71
|
+
"get_effectiveness",
|
|
72
|
+
"get_evolution",
|
|
73
|
+
"get_execution_stats",
|
|
74
|
+
"get_fork_lineage",
|
|
75
|
+
"get_health",
|
|
76
|
+
"get_language",
|
|
77
|
+
"get_lineage",
|
|
78
|
+
"get_model",
|
|
79
|
+
"get_translation_analytics",
|
|
80
|
+
"get_translator_version",
|
|
81
|
+
"get_user",
|
|
82
|
+
"is_forkable",
|
|
83
|
+
"list_aliases",
|
|
84
|
+
"list_definitions",
|
|
85
|
+
"list_forks",
|
|
86
|
+
"list_languages",
|
|
87
|
+
"list_models",
|
|
88
|
+
"list_providers",
|
|
89
|
+
"list_versions",
|
|
90
|
+
"render_definition",
|
|
91
|
+
"resolve_alias",
|
|
92
|
+
"search_definitions",
|
|
93
|
+
"set_default_type",
|
|
94
|
+
"validate_definition",
|
|
95
|
+
];
|
|
15
96
|
function tomlString(value) {
|
|
16
97
|
return JSON.stringify(value);
|
|
17
98
|
}
|
|
18
|
-
function
|
|
99
|
+
function toolBlock(serverName, toolName) {
|
|
19
100
|
return [
|
|
20
|
-
`[mcp_servers.${
|
|
101
|
+
`[mcp_servers.${serverName}.tools.${toolName}]`,
|
|
102
|
+
`approval_mode = "approve"`,
|
|
103
|
+
].join("\n");
|
|
104
|
+
}
|
|
105
|
+
function serverBlock(name, pkg, apiKey, seedTools) {
|
|
106
|
+
const lines = [
|
|
107
|
+
`[mcp_servers.${name}]`,
|
|
21
108
|
`command = "npx"`,
|
|
22
109
|
`args = ["-y", ${tomlString(pkg)}]`,
|
|
23
110
|
``,
|
|
24
|
-
`[mcp_servers.${
|
|
111
|
+
`[mcp_servers.${name}.env]`,
|
|
25
112
|
`ULUOPS_API_KEY = ${tomlString(apiKey)}`,
|
|
26
|
-
]
|
|
113
|
+
];
|
|
114
|
+
for (const tool of seedTools) {
|
|
115
|
+
lines.push(``, toolBlock(name, tool));
|
|
116
|
+
}
|
|
117
|
+
return lines.join("\n");
|
|
27
118
|
}
|
|
28
119
|
function isServerTableFor(name, table) {
|
|
29
120
|
return table === `mcp_servers.${name}` || table === `mcp_servers."${name}"`;
|
|
30
121
|
}
|
|
122
|
+
function isServerEnvTableFor(name, table) {
|
|
123
|
+
return (table === `mcp_servers.${name}.env` ||
|
|
124
|
+
table === `mcp_servers."${name}".env`);
|
|
125
|
+
}
|
|
31
126
|
function isServerSubtableFor(name, table) {
|
|
32
127
|
const unquotedPrefix = `mcp_servers.${name}.`;
|
|
33
128
|
const quotedPrefix = `mcp_servers."${name}".`;
|
|
34
129
|
return table.startsWith(unquotedPrefix) || table.startsWith(quotedPrefix);
|
|
35
130
|
}
|
|
131
|
+
function isServerToolTableFor(name, table) {
|
|
132
|
+
const unquotedPrefix = `mcp_servers.${name}.tools.`;
|
|
133
|
+
const quotedPrefix = `mcp_servers."${name}".tools.`;
|
|
134
|
+
return table.startsWith(unquotedPrefix) || table.startsWith(quotedPrefix);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Strip the main `[mcp_servers.NAME]` and `[mcp_servers.NAME.env]` blocks
|
|
138
|
+
* but preserve `[mcp_servers.NAME.tools.*]` blocks. Tool overrides may be
|
|
139
|
+
* user customizations — destroying them on every re-install would punish
|
|
140
|
+
* anyone who hand-edited their Codex config to deny a specific tool or to
|
|
141
|
+
* approve one we don't seed.
|
|
142
|
+
*/
|
|
36
143
|
function removeServerConfigBlocks(raw, name) {
|
|
37
144
|
const lines = raw.split("\n");
|
|
38
145
|
const kept = [];
|
|
@@ -40,7 +147,7 @@ function removeServerConfigBlocks(raw, name) {
|
|
|
40
147
|
for (const line of lines) {
|
|
41
148
|
const table = line.trim().match(/^\[([^\]]+)\]$/)?.[1];
|
|
42
149
|
if (table) {
|
|
43
|
-
skipping = isServerTableFor(name, table) ||
|
|
150
|
+
skipping = isServerTableFor(name, table) || isServerEnvTableFor(name, table);
|
|
44
151
|
}
|
|
45
152
|
if (!skipping)
|
|
46
153
|
kept.push(line);
|
|
@@ -61,6 +168,14 @@ function removeServerSubtree(raw, name) {
|
|
|
61
168
|
}
|
|
62
169
|
return kept.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
63
170
|
}
|
|
171
|
+
function hasUserToolEntries(raw, name) {
|
|
172
|
+
for (const line of raw.split("\n")) {
|
|
173
|
+
const table = line.trim().match(/^\[([^\]]+)\]$/)?.[1];
|
|
174
|
+
if (table && isServerToolTableFor(name, table))
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
64
179
|
class CodexMcpConfig {
|
|
65
180
|
async read(path) {
|
|
66
181
|
try {
|
|
@@ -72,10 +187,17 @@ class CodexMcpConfig {
|
|
|
72
187
|
}
|
|
73
188
|
merge(config, apiKey) {
|
|
74
189
|
let raw = typeof config[RAW_TOML] === "string" ? config[RAW_TOML] : "";
|
|
190
|
+
// Detect prior user customization BEFORE we strip anything. A hand-tuned
|
|
191
|
+
// config (any `[mcp_servers.NAME.tools.*]` block present) signals user
|
|
192
|
+
// intent — we replay only main + env, leaving the user's per-tool
|
|
193
|
+
// approval choices untouched. A fresh config gets the seeded read-tool
|
|
194
|
+
// approvals so first-launch UX skips the prompt cascade.
|
|
195
|
+
const trackerHasUserTools = hasUserToolEntries(raw, "uluops-tracker");
|
|
196
|
+
const registryHasUserTools = hasUserToolEntries(raw, "uluops-registry");
|
|
75
197
|
raw = removeServerConfigBlocks(removeServerConfigBlocks(raw, "uluops-tracker"), "uluops-registry");
|
|
76
198
|
const blocks = [
|
|
77
|
-
serverBlock("uluops-tracker", "@uluops/ops-mcp", apiKey),
|
|
78
|
-
serverBlock("uluops-registry", "@uluops/registry-mcp", apiKey),
|
|
199
|
+
serverBlock("uluops-tracker", "@uluops/ops-mcp", apiKey, trackerHasUserTools ? [] : TRACKER_READ_TOOLS),
|
|
200
|
+
serverBlock("uluops-registry", "@uluops/registry-mcp", apiKey, registryHasUserTools ? [] : REGISTRY_READ_TOOLS),
|
|
79
201
|
].join("\n\n");
|
|
80
202
|
return { [RAW_TOML]: [raw.trimEnd(), blocks].filter(Boolean).join("\n\n") + "\n" };
|
|
81
203
|
}
|
|
@@ -102,7 +224,7 @@ const home = join(homedir(), ".codex");
|
|
|
102
224
|
export const codexProfile = {
|
|
103
225
|
name: "codex",
|
|
104
226
|
displayName: "Codex",
|
|
105
|
-
status: "
|
|
227
|
+
status: "stable",
|
|
106
228
|
homeDir: home,
|
|
107
229
|
agentFormat: "toml",
|
|
108
230
|
factoryTarget: "codex",
|
package/dist/harnesses/types.js
CHANGED
|
@@ -22,9 +22,12 @@ export class HarnessNotTestedError extends Error {
|
|
|
22
22
|
constructor(harnessName) {
|
|
23
23
|
super(
|
|
24
24
|
// Keep this list in sync with profiles whose `status === "stable"`.
|
|
25
|
-
// Today: claude-code, opencode, gemini-cli. When a new stable
|
|
26
|
-
// lands, add it here so the error stays actionable.
|
|
27
|
-
|
|
25
|
+
// Today: claude-code, opencode, gemini-cli, codex. When a new stable
|
|
26
|
+
// profile lands, add it here so the error stays actionable. (As of
|
|
27
|
+
// 0.9.0 every shipped profile is stable, so this constructor is
|
|
28
|
+
// currently unreachable — kept as a structural slot for the next
|
|
29
|
+
// experimental harness scaffold.)
|
|
30
|
+
`${harnessName} harness is not yet tested. Use --harness claude-code, --harness opencode, --harness gemini-cli, or --harness codex.`);
|
|
28
31
|
this.name = "HarnessNotTestedError";
|
|
29
32
|
}
|
|
30
33
|
}
|