@uluops/setup 0.2.0 → 0.4.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 +56 -53
- package/assets/agents/anxiety-reader-agent.md +464 -0
- package/assets/commands/agents/anxiety-reader.md +160 -0
- package/assets/commands/agents/api-contract.md +1 -0
- package/assets/commands/agents/architect.md +1 -0
- package/assets/commands/agents/aristotle-analyst.md +1 -0
- package/assets/commands/agents/aristotle-explorer.md +1 -0
- package/assets/commands/agents/aristotle-forecaster.md +1 -0
- package/assets/commands/agents/aristotle-validator.md +1 -0
- package/assets/commands/agents/assumption-excavator.md +1 -0
- package/assets/commands/agents/audit.md +1 -0
- package/assets/commands/agents/{validate.md → code-validate.md} +6 -5
- package/assets/commands/agents/docs-validate.md +1 -0
- package/assets/commands/agents/frontend.md +1 -0
- package/assets/commands/agents/mcp-validate.md +1 -0
- package/assets/commands/agents/optimize.md +1 -0
- package/assets/commands/agents/pattern-analyzer.md +1 -0
- package/assets/commands/agents/prompt-quality.md +1 -0
- package/assets/commands/agents/prompt-validate.md +1 -0
- package/assets/commands/agents/public-interface.md +1 -0
- package/assets/commands/agents/release.md +1 -0
- package/assets/commands/agents/security.md +1 -0
- package/assets/commands/agents/test-review.md +1 -0
- package/assets/commands/agents/type-safety.md +1 -0
- package/assets/commands/agents/workflow-synthesis.md +1 -0
- package/assets/commands/pipelines/aristotle.md +143 -0
- package/assets/commands/pipelines/ship.md +188 -0
- package/assets/commands/workflows/prompt-audit.md +37 -747
- package/dist/cli.js +251 -207
- package/dist/harnesses/claude-code.d.ts +8 -0
- package/dist/harnesses/claude-code.js +72 -0
- package/dist/harnesses/codex.d.ts +15 -0
- package/dist/harnesses/codex.js +53 -0
- package/dist/harnesses/gemini-cli.d.ts +16 -0
- package/dist/harnesses/gemini-cli.js +54 -0
- package/dist/harnesses/index.d.ts +18 -0
- package/dist/harnesses/index.js +45 -0
- package/dist/harnesses/opencode.d.ts +14 -0
- package/dist/harnesses/opencode.js +130 -0
- package/dist/harnesses/types.d.ts +87 -0
- package/dist/harnesses/types.js +24 -0
- package/dist/lib/agent-transform.d.ts +12 -0
- package/dist/lib/agent-transform.js +129 -0
- package/dist/lib/asset-catalog.d.ts +9 -0
- package/dist/lib/asset-catalog.js +56 -0
- package/dist/lib/atomic-write.d.ts +11 -0
- package/dist/lib/atomic-write.js +28 -0
- package/dist/lib/config-merger.d.ts +7 -1
- package/dist/lib/config-merger.js +34 -5
- package/dist/lib/display.d.ts +14 -0
- package/dist/lib/display.js +66 -0
- package/dist/lib/file-ops.d.ts +6 -0
- package/dist/lib/file-ops.js +22 -1
- package/dist/lib/hash.d.ts +1 -0
- package/dist/lib/hash.js +1 -0
- package/dist/lib/health.d.ts +2 -0
- package/dist/lib/health.js +10 -0
- package/dist/lib/manifest.d.ts +22 -5
- package/dist/lib/manifest.js +148 -13
- package/dist/lib/paths.d.ts +15 -3
- package/dist/lib/paths.js +71 -13
- package/dist/lib/settings-merger.d.ts +9 -1
- package/dist/lib/settings-merger.js +45 -17
- package/dist/steps/agents.d.ts +5 -1
- package/dist/steps/agents.js +59 -9
- package/dist/steps/auth.js +26 -10
- package/dist/steps/commands.d.ts +6 -1
- package/dist/steps/commands.js +87 -9
- package/dist/steps/detect.d.ts +3 -0
- package/dist/steps/detect.js +7 -0
- package/dist/steps/mcp.d.ts +6 -2
- package/dist/steps/mcp.js +46 -21
- package/dist/steps/metrics.d.ts +14 -10
- package/dist/steps/metrics.js +59 -89
- package/dist/steps/shell.d.ts +2 -0
- package/dist/steps/shell.js +16 -9
- package/dist/steps/signup.d.ts +6 -3
- package/dist/steps/signup.js +26 -14
- package/dist/steps/verify.d.ts +2 -2
- package/dist/steps/verify.js +84 -117
- package/package.json +32 -7
- package/assets/commands/workflows/aristotle.md +0 -543
- package/assets/commands/workflows/ship.md +0 -721
- package/dist/test/auth.test.d.ts +0 -1
- package/dist/test/auth.test.js +0 -43
- package/dist/test/config-io.test.d.ts +0 -1
- package/dist/test/config-io.test.js +0 -56
- package/dist/test/config-merger.test.d.ts +0 -1
- package/dist/test/config-merger.test.js +0 -94
- package/dist/test/detect.test.d.ts +0 -1
- package/dist/test/detect.test.js +0 -25
- package/dist/test/file-ops.test.d.ts +0 -1
- package/dist/test/file-ops.test.js +0 -100
- package/dist/test/hash.test.d.ts +0 -1
- package/dist/test/hash.test.js +0 -14
- package/dist/test/manifest.test.d.ts +0 -1
- package/dist/test/manifest.test.js +0 -78
- package/dist/test/paths.test.d.ts +0 -1
- package/dist/test/paths.test.js +0 -30
- package/dist/test/settings-merger.test.d.ts +0 -1
- package/dist/test/settings-merger.test.js +0 -167
- package/dist/test/shell-profile.test.d.ts +0 -1
- package/dist/test/shell-profile.test.js +0 -40
- package/dist/test/shell.test.d.ts +0 -1
- package/dist/test/shell.test.js +0 -71
- package/dist/test/signup.test.d.ts +0 -1
- package/dist/test/signup.test.js +0 -83
package/dist/cli.js
CHANGED
|
@@ -13,41 +13,99 @@ import { installCommands, uninstallCommands } from "./steps/commands.js";
|
|
|
13
13
|
import { writeShellExport, removeShellExport } from "./steps/shell.js";
|
|
14
14
|
import { verify } from "./steps/verify.js";
|
|
15
15
|
import { installMetrics, uninstallMetrics } from "./steps/metrics.js";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import { probeHookSupport } from "./lib/settings-merger.js";
|
|
17
|
+
import { loadManifest, saveManifest, deleteManifest, validateManifest, } from "./lib/manifest.js";
|
|
18
|
+
import { ASSETS_DIR, findProjectRoot } from "./lib/paths.js";
|
|
19
|
+
import { getHealthTimeout } from "./lib/health.js";
|
|
20
|
+
import { ok, warn, fail, info, printSetupSummary, printAgentList } from "./lib/display.js";
|
|
21
|
+
import { getProfile, resolveHarnessName, listHarnesses, HarnessNotTestedError, } from "./harnesses/index.js";
|
|
18
22
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
23
|
async function getVersion() {
|
|
20
24
|
const pkgPath = join(__dirname, "..", "package.json");
|
|
21
25
|
const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
|
|
22
26
|
return pkg.version;
|
|
23
27
|
}
|
|
24
|
-
const ok = (msg) => console.log(` ${chalk.green("✓")} ${msg}`);
|
|
25
|
-
const warn = (msg) => console.log(` ${chalk.yellow("⚠")} ${msg}`);
|
|
26
|
-
const fail = (msg) => console.log(` ${chalk.red("✗")} ${msg}`);
|
|
27
|
-
const info = (msg) => console.log(` ${msg}`);
|
|
28
28
|
async function runSetup(opts) {
|
|
29
29
|
const version = await getVersion();
|
|
30
|
+
const profile = getProfile(opts.harness);
|
|
30
31
|
console.log();
|
|
31
32
|
console.log(` ${chalk.dim("⟨u⟩")} ${chalk.cyan.bold("ulu")}${chalk.bold("·ops")}`);
|
|
32
33
|
console.log(` ${chalk.dim("operating intelligence as infrastructure")}`);
|
|
33
34
|
console.log();
|
|
34
|
-
console.log(` Setup v${version}`);
|
|
35
|
+
console.log(` Setup v${version} — ${chalk.bold(profile.displayName)}`);
|
|
35
36
|
console.log();
|
|
36
37
|
if (opts.dryRun) {
|
|
37
38
|
info(chalk.dim("(dry run — no changes will be made)\n"));
|
|
38
39
|
}
|
|
39
|
-
|
|
40
|
+
const { env, apiKey } = await initContext(opts);
|
|
41
|
+
console.log();
|
|
42
|
+
// Load existing manifest for update detection
|
|
43
|
+
const existingManifest = await loadManifest();
|
|
44
|
+
const existingHarness = existingManifest?.harnesses[profile.name];
|
|
45
|
+
if (existingManifest && existingManifest.version !== version) {
|
|
46
|
+
info(`Updating ${chalk.dim(existingManifest.version)} → ${chalk.green(version)}`);
|
|
47
|
+
console.log();
|
|
48
|
+
}
|
|
49
|
+
else if (existingHarness) {
|
|
50
|
+
info(chalk.dim(`Already at v${version} — checking for changes`));
|
|
51
|
+
console.log();
|
|
52
|
+
}
|
|
53
|
+
// Check for conflicts on first install for this harness
|
|
54
|
+
if (!existingHarness && !opts.yes && !opts.dryRun) {
|
|
55
|
+
await checkConflicts(profile, opts.localDefs);
|
|
56
|
+
}
|
|
57
|
+
const mcpResult = await configureMcpStep(profile, apiKey, opts);
|
|
58
|
+
const agentsResult = await installAgentsDefs(profile, opts, existingHarness?.agents);
|
|
59
|
+
const commandsResult = await installCommandsDefs(profile, opts, existingHarness?.commands);
|
|
60
|
+
const metricsResult = await configureMetricsStep(profile, opts);
|
|
61
|
+
await runHealthCheck(opts);
|
|
62
|
+
const shellModified = await configureShell(env, apiKey, opts);
|
|
63
|
+
// Save manifest
|
|
64
|
+
if (!opts.dryRun) {
|
|
65
|
+
const now = new Date().toISOString();
|
|
66
|
+
const harnessEntry = {
|
|
67
|
+
installedAt: now,
|
|
68
|
+
setupVersion: version,
|
|
69
|
+
mcpScope: opts.scope,
|
|
70
|
+
mcpConfigPath: mcpResult.configPath,
|
|
71
|
+
defsScope: opts.localDefs ? "local" : "global",
|
|
72
|
+
defsPath: opts.localDefs
|
|
73
|
+
? join(await findProjectRoot(), "uluops")
|
|
74
|
+
: profile.paths.home,
|
|
75
|
+
agents: agentsResult.files,
|
|
76
|
+
commands: commandsResult.files,
|
|
77
|
+
hooksInstalled: metricsResult.hookConfigured,
|
|
78
|
+
};
|
|
79
|
+
const manifest = existingManifest ?? {
|
|
80
|
+
version,
|
|
81
|
+
installedAt: now,
|
|
82
|
+
shellModified: false,
|
|
83
|
+
harnesses: {},
|
|
84
|
+
};
|
|
85
|
+
manifest.version = version;
|
|
86
|
+
manifest.installedAt = now;
|
|
87
|
+
manifest.shellModified = shellModified || manifest.shellModified;
|
|
88
|
+
manifest.harnesses[profile.name] = harnessEntry;
|
|
89
|
+
await saveManifest(manifest);
|
|
90
|
+
}
|
|
91
|
+
await printSetupSummary({
|
|
92
|
+
profile,
|
|
93
|
+
agentCount: agentsResult.files.length,
|
|
94
|
+
commandCount: commandsResult.files.length,
|
|
95
|
+
apiKey,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
// --- extracted helpers ---
|
|
99
|
+
/** Resolve API key via flag, env, file, signup, or interactive prompt. Returns env detection + key. */
|
|
100
|
+
async function initContext(opts) {
|
|
40
101
|
const env = await detect();
|
|
41
|
-
// Resolve API key — via signup or existing key
|
|
42
102
|
let apiKey;
|
|
43
|
-
let email = null;
|
|
44
103
|
try {
|
|
45
104
|
if (opts.signup) {
|
|
46
105
|
info("Create your UluOps account\n");
|
|
47
106
|
const auth = await signup();
|
|
48
107
|
apiKey = auth.apiKey;
|
|
49
|
-
|
|
50
|
-
ok(`Account created (${email})`);
|
|
108
|
+
ok(`Account created (${auth.email})`);
|
|
51
109
|
ok(`API key generated`);
|
|
52
110
|
}
|
|
53
111
|
else {
|
|
@@ -57,193 +115,137 @@ async function runSetup(opts) {
|
|
|
57
115
|
interactive: !opts.yes && !opts.apiKey && !process.env["ULUOPS_API_KEY"],
|
|
58
116
|
});
|
|
59
117
|
apiKey = auth.apiKey;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
else if (opts.skipValidation) {
|
|
118
|
+
if (auth.email)
|
|
119
|
+
ok(`Key validated (${auth.email})`);
|
|
120
|
+
else if (opts.skipValidation)
|
|
65
121
|
ok("Key accepted (validation skipped)");
|
|
66
|
-
|
|
67
|
-
else {
|
|
122
|
+
else
|
|
68
123
|
ok("Key validated");
|
|
69
|
-
}
|
|
70
124
|
}
|
|
71
125
|
}
|
|
72
126
|
catch (err) {
|
|
73
127
|
fail(err instanceof Error ? err.message : String(err));
|
|
74
128
|
process.exit(1);
|
|
75
129
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
if (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
130
|
+
return { env, apiKey };
|
|
131
|
+
}
|
|
132
|
+
/** Write MCP server entries to harness config and report warnings. */
|
|
133
|
+
async function configureMcpStep(profile, apiKey, opts) {
|
|
134
|
+
const res = await installMcp(profile, apiKey, opts.scope, opts.dryRun);
|
|
135
|
+
ok(`MCP config → ${res.configPath} (2 servers)`);
|
|
136
|
+
for (const w of res.packageWarnings)
|
|
137
|
+
warn(w);
|
|
138
|
+
return res;
|
|
139
|
+
}
|
|
140
|
+
/** Copy agent definitions from assets to harness directory. */
|
|
141
|
+
async function installAgentsDefs(profile, opts, prev) {
|
|
142
|
+
const res = await installAgents(profile, opts.localDefs, opts.dryRun, prev);
|
|
143
|
+
const parts = [];
|
|
144
|
+
if (res.copied > 0)
|
|
145
|
+
parts.push(`${res.copied} copied`);
|
|
146
|
+
if (res.skipped > 0)
|
|
147
|
+
parts.push(`${res.skipped} unchanged`);
|
|
148
|
+
if (res.removed > 0)
|
|
149
|
+
parts.push(`${res.removed} removed`);
|
|
150
|
+
const dest = opts.localDefs
|
|
151
|
+
? "./uluops/agents/"
|
|
152
|
+
: `${profile.paths.agentsDir.replace(process.env["HOME"] ?? "", "~")}/`;
|
|
153
|
+
ok(`${res.files.length} agents → ${dest}${parts.length ? ` (${parts.join(", ")})` : ""}`);
|
|
154
|
+
return res;
|
|
155
|
+
}
|
|
156
|
+
/** Copy slash-command definitions from assets (Claude Code only). */
|
|
157
|
+
async function installCommandsDefs(profile, opts, prev) {
|
|
158
|
+
const res = await installCommands(profile, opts.localDefs, opts.dryRun, prev);
|
|
159
|
+
if (res.skippedReason === "not-supported") {
|
|
160
|
+
info(chalk.dim(`Commands not yet supported for ${profile.displayName} (coming soon)`));
|
|
161
|
+
return res;
|
|
162
|
+
}
|
|
163
|
+
const total = res.agentCommands + res.workflowCommands + res.pipelineCommands;
|
|
164
|
+
const parts = [];
|
|
165
|
+
if (total > 0)
|
|
166
|
+
parts.push(`${total} copied`);
|
|
167
|
+
if (res.skipped > 0)
|
|
168
|
+
parts.push(`${res.skipped} unchanged`);
|
|
169
|
+
if (res.removed > 0)
|
|
170
|
+
parts.push(`${res.removed} removed`);
|
|
171
|
+
const dest = opts.localDefs
|
|
172
|
+
? "./uluops/commands/"
|
|
173
|
+
: `${profile.paths.commandsDir.replace(process.env["HOME"] ?? "", "~")}/`;
|
|
174
|
+
ok(`${res.files.length} commands → ${dest}${parts.length ? ` (${parts.join(", ")})` : ""}`);
|
|
175
|
+
return res;
|
|
176
|
+
}
|
|
177
|
+
/** Install agent-metrics hook and tool files (Claude Code only). */
|
|
178
|
+
async function configureMetricsStep(profile, opts) {
|
|
179
|
+
if (!profile.hooks) {
|
|
180
|
+
info(chalk.dim(`Metrics hooks not supported for ${profile.displayName}`));
|
|
181
|
+
return { toolFilesCopied: 0, hookConfigured: false };
|
|
182
|
+
}
|
|
183
|
+
const probe = probeHookSupport();
|
|
184
|
+
if (probe.warning)
|
|
185
|
+
warn(probe.warning);
|
|
186
|
+
const res = await installMetrics(profile, opts.dryRun);
|
|
187
|
+
if (res.hookConfigured) {
|
|
121
188
|
const parts = [];
|
|
122
|
-
if (
|
|
123
|
-
parts.push(`${
|
|
189
|
+
if (res.toolFilesCopied > 0)
|
|
190
|
+
parts.push(`${res.toolFilesCopied} files`);
|
|
124
191
|
parts.push("hook configured");
|
|
125
|
-
|
|
192
|
+
const toolPath = profile.paths.toolsDir?.replace(process.env["HOME"] ?? "", "~");
|
|
193
|
+
ok(`Agent metrics → ${toolPath}/ (${parts.join(", ")})`);
|
|
126
194
|
}
|
|
127
195
|
else {
|
|
128
196
|
warn("Agent metrics hook not configured (tool files not found)");
|
|
129
197
|
}
|
|
130
|
-
|
|
198
|
+
return res;
|
|
199
|
+
}
|
|
200
|
+
/** Ping tracker and registry health endpoints. */
|
|
201
|
+
async function runHealthCheck(opts) {
|
|
131
202
|
if (!opts.skipValidation && !opts.dryRun) {
|
|
132
203
|
try {
|
|
133
204
|
const [trackerOk, registryOk] = await Promise.all([
|
|
134
205
|
checkEndpoint("https://api.uluops.ai/api/v1/health"),
|
|
135
206
|
checkEndpoint("https://api.uluops.ai/api/v1/registry/health"),
|
|
136
207
|
]);
|
|
137
|
-
if (trackerOk && registryOk)
|
|
208
|
+
if (trackerOk && registryOk)
|
|
138
209
|
ok("Health check passed — both APIs reachable");
|
|
139
|
-
|
|
140
|
-
else {
|
|
210
|
+
else
|
|
141
211
|
warn("Some APIs unreachable (MCP tools may have limited functionality)");
|
|
142
|
-
}
|
|
143
212
|
}
|
|
144
213
|
catch {
|
|
145
214
|
warn("Health check skipped (network issue)");
|
|
146
215
|
}
|
|
147
216
|
}
|
|
148
|
-
|
|
149
|
-
|
|
217
|
+
}
|
|
218
|
+
/** Optionally write ULUOPS_API_KEY export to shell profile. */
|
|
219
|
+
async function configureShell(env, apiKey, opts) {
|
|
220
|
+
let modified = false;
|
|
150
221
|
if (opts.shell && env.shellProfile) {
|
|
222
|
+
if (!opts.yes && !opts.dryRun) {
|
|
223
|
+
const confirmed = await confirmShellWrite(env.shellProfile);
|
|
224
|
+
if (!confirmed) {
|
|
225
|
+
warn("Skipped writing API key to shell profile");
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
151
229
|
await writeShellExport(env.shellProfile, apiKey, opts.dryRun);
|
|
152
230
|
ok(`ULUOPS_API_KEY added to ${env.shellProfile}`);
|
|
153
|
-
|
|
231
|
+
warn("API key stored in plaintext in shell profile. Consider rotating if shared machine.");
|
|
232
|
+
modified = true;
|
|
154
233
|
}
|
|
155
234
|
else if (opts.shell) {
|
|
156
235
|
warn("--shell requested but no supported shell detected ($SHELL). Skipping.");
|
|
157
236
|
}
|
|
158
|
-
|
|
159
|
-
if (!opts.dryRun) {
|
|
160
|
-
await saveManifest({
|
|
161
|
-
version,
|
|
162
|
-
installedAt: new Date().toISOString(),
|
|
163
|
-
mcpScope: opts.scope,
|
|
164
|
-
mcpConfigPath: mcpResult.configPath,
|
|
165
|
-
defsScope: opts.localDefs ? "local" : "global",
|
|
166
|
-
defsPath: opts.localDefs
|
|
167
|
-
? join(process.cwd(), "uluops")
|
|
168
|
-
: getClaudeHome(),
|
|
169
|
-
shellModified,
|
|
170
|
-
agents: agentsResult.files,
|
|
171
|
-
commands: commandsResult.files,
|
|
172
|
-
metricsHookInstalled: metricsResult.hookConfigured,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
printSetupSummary({
|
|
176
|
-
agentCount: agentsResult.files.length,
|
|
177
|
-
commandCount: cmdTotal,
|
|
178
|
-
apiKey,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
// MCP tool count across both servers. Update when server toolsets change.
|
|
182
|
-
const TOOL_COUNT = 73;
|
|
183
|
-
const AGENT_LIST = [
|
|
184
|
-
["/agents:validate", "Code quality", "sonnet"],
|
|
185
|
-
["/agents:type-safety", "TypeScript", "sonnet"],
|
|
186
|
-
["/agents:test-review", "Test quality", "sonnet"],
|
|
187
|
-
["/agents:optimize", "Performance", "sonnet"],
|
|
188
|
-
["/agents:frontend", "React/a11y", "sonnet"],
|
|
189
|
-
["/agents:mcp-validate", "MCP compliance", "sonnet"],
|
|
190
|
-
["/agents:architect", "Design review", "sonnet"],
|
|
191
|
-
["/agents:audit", "Runtime bugs", "opus"],
|
|
192
|
-
["/agents:security", "OWASP", "sonnet"],
|
|
193
|
-
["/agents:api-contract", "API alignment", "sonnet"],
|
|
194
|
-
["/agents:release", "Publish ready", "sonnet"],
|
|
195
|
-
["/agents:public-interface", "README/exports", "sonnet"],
|
|
196
|
-
["/agents:docs-validate", "Documentation", "sonnet"],
|
|
197
|
-
["/agents:prompt-validate", "Prompt review", "sonnet"],
|
|
198
|
-
["/agents:prompt-quality", "Prompt quality", "sonnet"],
|
|
199
|
-
["/agents:pattern-analyzer", "Patterns", "sonnet"],
|
|
200
|
-
["/agents:aristotle-explorer", "Categories", "opus"],
|
|
201
|
-
["/agents:aristotle-analyst", "Four causes", "opus"],
|
|
202
|
-
["/agents:aristotle-validator", "Teleology", "opus"],
|
|
203
|
-
["/agents:aristotle-forecaster", "Potentiality", "opus"],
|
|
204
|
-
["/agents:assumption-excavator", "Assumptions", "sonnet"],
|
|
205
|
-
["/agents:workflow-synthesis", "Cross-agent synthesis", "opus"],
|
|
206
|
-
];
|
|
207
|
-
function printSetupSummary(opts) {
|
|
208
|
-
console.log();
|
|
209
|
-
console.log(` ${chalk.dim("━".repeat(46))}`);
|
|
210
|
-
console.log();
|
|
211
|
-
console.log(` ${chalk.bold("Setup complete!")} ${TOOL_COUNT} MCP tools · ${opts.agentCount} agents · ${opts.commandCount} slash commands · metrics`);
|
|
212
|
-
console.log();
|
|
213
|
-
printAgentList();
|
|
214
|
-
info("For SDK/CLI usage, add to your shell profile:");
|
|
215
|
-
info(` ${chalk.cyan(`export ULUOPS_API_KEY="${opts.apiKey}"`)}`);
|
|
216
|
-
console.log();
|
|
217
|
-
info(`Run again to update: ${chalk.cyan("npx @uluops/setup")}`);
|
|
218
|
-
console.log();
|
|
219
|
-
// Restart warning — last and prominent
|
|
220
|
-
console.log(` ${chalk.dim("━".repeat(46))}`);
|
|
221
|
-
console.log();
|
|
222
|
-
console.log(` ${chalk.yellow.bold("Restart Claude Code to load agents.")}`);
|
|
223
|
-
console.log();
|
|
224
|
-
info("After restart, verify with:");
|
|
225
|
-
info(` ${chalk.cyan("/agents:validate --help")}`);
|
|
226
|
-
console.log();
|
|
227
|
-
info("Then try:");
|
|
228
|
-
info(` ${chalk.cyan("/workflows:post-implementation .")}`);
|
|
229
|
-
console.log();
|
|
237
|
+
return modified;
|
|
230
238
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
for (const [cmd, desc, model] of AGENT_LIST) {
|
|
242
|
-
info(` ${chalk.cyan(cmd.padEnd(34))}${desc.padEnd(17)}${chalk.dim(model)}`);
|
|
243
|
-
}
|
|
244
|
-
console.log();
|
|
245
|
-
info(chalk.dim(` This is the starter set. Browse 135+ agents at registry.uluops.ai`));
|
|
246
|
-
console.log();
|
|
239
|
+
/** Interactive y/N confirmation before writing API key to shell profile. */
|
|
240
|
+
async function confirmShellWrite(profilePath) {
|
|
241
|
+
const readline = await import("node:readline/promises");
|
|
242
|
+
const rl = readline.createInterface({
|
|
243
|
+
input: process.stdin,
|
|
244
|
+
output: process.stdout,
|
|
245
|
+
});
|
|
246
|
+
const answer = await rl.question(`Write ULUOPS_API_KEY to ${profilePath}? (y/N) `);
|
|
247
|
+
rl.close();
|
|
248
|
+
return answer.trim().toLowerCase() === "y";
|
|
247
249
|
}
|
|
248
250
|
async function runUninstall(opts) {
|
|
249
251
|
const version = await getVersion();
|
|
@@ -258,59 +260,87 @@ async function runUninstall(opts) {
|
|
|
258
260
|
warn("No manifest found — nothing to uninstall.");
|
|
259
261
|
return;
|
|
260
262
|
}
|
|
261
|
-
|
|
262
|
-
if (!
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
// Remove commands
|
|
270
|
-
if (!opts.dryRun) {
|
|
271
|
-
const removed = await uninstallCommands(manifest.commands, manifest.defsPath);
|
|
272
|
-
ok(`Removed ${removed} command(s)`);
|
|
273
|
-
}
|
|
274
|
-
else {
|
|
275
|
-
ok(`Would remove ${manifest.commands.length} command(s)`);
|
|
276
|
-
}
|
|
277
|
-
// Remove MCP config
|
|
278
|
-
if (!opts.dryRun) {
|
|
279
|
-
await uninstallMcp(manifest.mcpConfigPath);
|
|
280
|
-
ok(`Removed MCP servers from ${manifest.mcpConfigPath}`);
|
|
263
|
+
const validation = await validateManifest(manifest);
|
|
264
|
+
if (!validation.valid) {
|
|
265
|
+
fail("Manifest references paths that no longer exist:");
|
|
266
|
+
for (const err of validation.errors)
|
|
267
|
+
info(` ${err}`);
|
|
268
|
+
console.log();
|
|
269
|
+
info("Uninstall may be incomplete. Proceeding with what's available.");
|
|
270
|
+
console.log();
|
|
281
271
|
}
|
|
282
|
-
|
|
283
|
-
|
|
272
|
+
if (validation.warnings.length > 0) {
|
|
273
|
+
for (const w of validation.warnings)
|
|
274
|
+
warn(w);
|
|
275
|
+
console.log();
|
|
284
276
|
}
|
|
285
|
-
|
|
286
|
-
|
|
277
|
+
for (const [harnessName, hm] of Object.entries(manifest.harnesses)) {
|
|
278
|
+
let profile;
|
|
279
|
+
try {
|
|
280
|
+
profile = getProfile(harnessName);
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
warn(`Unknown harness "${harnessName}" in manifest — skipping`);
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
info(chalk.bold(profile.displayName));
|
|
287
|
+
if (!opts.dryRun) {
|
|
288
|
+
const agentCount = await uninstallAgents(hm.agents, hm.defsPath);
|
|
289
|
+
ok(`Removed ${agentCount} agent(s)`);
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
ok(`Would remove ${hm.agents.length} agent(s)`);
|
|
293
|
+
}
|
|
294
|
+
if (hm.commands.length > 0) {
|
|
295
|
+
if (!opts.dryRun) {
|
|
296
|
+
const cmdCount = await uninstallCommands(hm.commands, hm.defsPath);
|
|
297
|
+
ok(`Removed ${cmdCount} command(s)`);
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
ok(`Would remove ${hm.commands.length} command(s)`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
287
303
|
if (!opts.dryRun) {
|
|
288
|
-
|
|
289
|
-
|
|
304
|
+
try {
|
|
305
|
+
await uninstallMcp(profile, hm.mcpConfigPath);
|
|
306
|
+
ok(`Removed MCP servers from ${hm.mcpConfigPath}`);
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
warn(`Could not remove MCP servers from ${hm.mcpConfigPath}`);
|
|
310
|
+
}
|
|
290
311
|
}
|
|
291
312
|
else {
|
|
292
|
-
ok(
|
|
313
|
+
ok(`Would remove MCP servers from ${hm.mcpConfigPath}`);
|
|
314
|
+
}
|
|
315
|
+
if (hm.hooksInstalled) {
|
|
316
|
+
if (!opts.dryRun) {
|
|
317
|
+
await uninstallMetrics(profile, false);
|
|
318
|
+
ok("Removed agent-metrics hook and tool files");
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
ok("Would remove agent-metrics hook and tool files");
|
|
322
|
+
}
|
|
293
323
|
}
|
|
324
|
+
console.log();
|
|
294
325
|
}
|
|
295
326
|
// Remove shell export
|
|
296
327
|
if (manifest.shellModified) {
|
|
297
328
|
const { getShellProfile } = await import("./lib/paths.js");
|
|
298
|
-
const
|
|
299
|
-
if (
|
|
300
|
-
await removeShellExport(
|
|
301
|
-
ok(`Removed export from ${
|
|
329
|
+
const shellProfile = getShellProfile();
|
|
330
|
+
if (shellProfile && !opts.dryRun) {
|
|
331
|
+
await removeShellExport(shellProfile.path);
|
|
332
|
+
ok(`Removed export from ${shellProfile.path}`);
|
|
302
333
|
}
|
|
303
|
-
else if (
|
|
304
|
-
ok(`Would remove export from ${
|
|
334
|
+
else if (shellProfile) {
|
|
335
|
+
ok(`Would remove export from ${shellProfile.path}`);
|
|
305
336
|
}
|
|
306
337
|
}
|
|
307
|
-
// Delete manifest
|
|
308
338
|
if (!opts.dryRun) {
|
|
309
339
|
await deleteManifest();
|
|
310
340
|
ok("Manifest deleted");
|
|
311
341
|
}
|
|
312
342
|
console.log();
|
|
313
|
-
info("UluOps has been removed. Restart
|
|
343
|
+
info("UluOps has been removed. Restart your harness to complete.");
|
|
314
344
|
console.log();
|
|
315
345
|
}
|
|
316
346
|
async function runVerify() {
|
|
@@ -337,9 +367,12 @@ async function runVerify() {
|
|
|
337
367
|
console.log();
|
|
338
368
|
process.exit(result.ok ? 0 : 1);
|
|
339
369
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
const
|
|
370
|
+
/** Warn if existing agent files will be overwritten and prompt for confirmation. */
|
|
371
|
+
async function checkConflicts(profile, localDefs) {
|
|
372
|
+
const destDir = localDefs
|
|
373
|
+
? join(await findProjectRoot(), "uluops", "agents")
|
|
374
|
+
: profile.paths.agentsDir;
|
|
375
|
+
const srcDir = join(ASSETS_DIR, "agents", profile.name);
|
|
343
376
|
let existingFiles;
|
|
344
377
|
let assetFiles;
|
|
345
378
|
try {
|
|
@@ -347,7 +380,7 @@ async function checkConflicts(localDefs) {
|
|
|
347
380
|
assetFiles = await readdir(srcDir);
|
|
348
381
|
}
|
|
349
382
|
catch {
|
|
350
|
-
return;
|
|
383
|
+
return;
|
|
351
384
|
}
|
|
352
385
|
const conflicts = assetFiles.filter((f) => existingFiles.includes(f));
|
|
353
386
|
if (conflicts.length === 0)
|
|
@@ -368,9 +401,12 @@ async function checkConflicts(localDefs) {
|
|
|
368
401
|
process.exit(0);
|
|
369
402
|
}
|
|
370
403
|
}
|
|
404
|
+
/** Fetch a URL and return true if the response is OK, false on any failure. */
|
|
371
405
|
async function checkEndpoint(url) {
|
|
372
406
|
try {
|
|
373
|
-
const res = await fetch(url, {
|
|
407
|
+
const res = await fetch(url, {
|
|
408
|
+
signal: AbortSignal.timeout(getHealthTimeout()),
|
|
409
|
+
});
|
|
374
410
|
return res.ok;
|
|
375
411
|
}
|
|
376
412
|
catch {
|
|
@@ -381,12 +417,13 @@ async function main() {
|
|
|
381
417
|
const version = await getVersion();
|
|
382
418
|
const program = new Command()
|
|
383
419
|
.name("uluops-setup")
|
|
384
|
-
.description("Zero-friction installer for UluOps
|
|
420
|
+
.description("Zero-friction installer for UluOps agentic harnesses")
|
|
385
421
|
.version(version)
|
|
386
422
|
.option("--api-key <key>", "API key (skip prompt)")
|
|
387
423
|
.option("--signup", "Create a new account (email + password, no browser)")
|
|
424
|
+
.option("--harness <name>", `Target harness: ${listHarnesses().join(", ")} (aliases: claude, oc, gemini)`, "claude-code")
|
|
388
425
|
.option("--scope <mode>", 'MCP config scope: "global" or "local"', "global")
|
|
389
|
-
.option("--local-defs", "Save agents/commands locally instead of
|
|
426
|
+
.option("--local-defs", "Save agents/commands locally instead of harness global dir", false)
|
|
390
427
|
.option("--shell", "Write API key export to shell profile", false)
|
|
391
428
|
.option("--skip-validation", "Accept API key without verifying", false)
|
|
392
429
|
.option("--list", "Show available agents and workflows without installing")
|
|
@@ -400,7 +437,7 @@ async function main() {
|
|
|
400
437
|
console.log();
|
|
401
438
|
console.log(` ${chalk.dim("⟨u⟩")} ${chalk.cyan.bold("ulu")}${chalk.bold("·ops")} v${version} — available agents and workflows`);
|
|
402
439
|
console.log();
|
|
403
|
-
printAgentList();
|
|
440
|
+
await printAgentList();
|
|
404
441
|
info(`Install with: ${chalk.cyan("npx @uluops/setup")}`);
|
|
405
442
|
console.log();
|
|
406
443
|
return;
|
|
@@ -418,6 +455,8 @@ async function main() {
|
|
|
418
455
|
process.exit(1);
|
|
419
456
|
}
|
|
420
457
|
const scope = opts.scope === "local" ? "local" : "global";
|
|
458
|
+
// Resolve harness name (supports aliases)
|
|
459
|
+
const harnessName = resolveHarnessName(opts.harness);
|
|
421
460
|
await runSetup({
|
|
422
461
|
apiKey: opts.apiKey,
|
|
423
462
|
signup: opts.signup ?? false,
|
|
@@ -427,9 +466,14 @@ async function main() {
|
|
|
427
466
|
skipValidation: opts.skipValidation,
|
|
428
467
|
dryRun: opts.dryRun,
|
|
429
468
|
yes: opts.yes,
|
|
469
|
+
harness: harnessName,
|
|
430
470
|
});
|
|
431
471
|
}
|
|
432
472
|
main().catch((err) => {
|
|
473
|
+
if (err instanceof HarnessNotTestedError) {
|
|
474
|
+
console.error(chalk.yellow(`\n ${err.message}\n`));
|
|
475
|
+
process.exit(1);
|
|
476
|
+
}
|
|
433
477
|
const msg = err instanceof Error ? err.message : String(err);
|
|
434
478
|
console.error(chalk.red(`\n Error: ${msg}\n`));
|
|
435
479
|
process.exit(1);
|