@uluops/setup 0.6.0 → 0.6.4
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 +8 -10
- package/dist/cli.js +38 -3
- package/dist/commands/helpers.js +39 -2
- package/dist/harnesses/opencode.js +4 -4
- package/dist/lib/config-merger.js +7 -5
- package/dist/steps/auth.d.ts +6 -0
- package/dist/steps/auth.js +19 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ npx @uluops/setup --harness gemini-cli
|
|
|
47
47
|
|
|
48
48
|
The installer runs these steps in sequence:
|
|
49
49
|
|
|
50
|
-
1. **Authenticate** —
|
|
50
|
+
1. **Authenticate** — Asks whether you're creating a new account. New users sign up with email + password; returning users paste an API key. Skip the question with `--api-key`, `--signup`, `--yes`, or `ULUOPS_API_KEY`.
|
|
51
51
|
2. **MCP config** — Writes tracker and registry server entries to the harness config
|
|
52
52
|
3. **Definitions** — Copies pre-rendered agent definition files
|
|
53
53
|
4. **Metrics hook** — Configures a post-agent hook for automatic run capture (Claude Code and Gemini CLI)
|
|
@@ -62,15 +62,9 @@ The installer runs these steps in sequence:
|
|
|
62
62
|
npx @uluops/setup
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
Setup will first ask whether you're creating a new UluOps account. Pick **Y** to sign up with an email and password (account + API key created automatically); pick **n** to paste an existing API key from [app.uluops.ai/settings/api-keys](https://app.uluops.ai/settings/api-keys). Everything else uses smart defaults — no other prompts. See [What it does](#what-it-does) for the full list of changes made.
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
```
|
|
70
|
-
npx @uluops/setup --signup
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
You'll be prompted for email and password. Account + API key are created automatically.
|
|
67
|
+
> The account question is automatically skipped if you've already saved credentials, set `ULUOPS_API_KEY`, or passed `--api-key`/`--yes`/`--signup`. You can also pass `--signup` to skip the question and go straight to signup.
|
|
74
68
|
|
|
75
69
|
> **🛑 IMPORTANT:** You must restart your harness (e.g., restart Claude Code) after setup to load the new agents and commands.
|
|
76
70
|
|
|
@@ -135,7 +129,11 @@ Validates your current installation against the local manifest and checks API co
|
|
|
135
129
|
### Examples
|
|
136
130
|
|
|
137
131
|
```bash
|
|
138
|
-
#
|
|
132
|
+
# Default — asks "creating a new account?" then either signs you up or
|
|
133
|
+
# prompts for an existing API key
|
|
134
|
+
npx @uluops/setup
|
|
135
|
+
|
|
136
|
+
# Skip the account question and go straight to signup
|
|
139
137
|
npx @uluops/setup --signup
|
|
140
138
|
|
|
141
139
|
# Install for OpenCode
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import { Command } from "commander";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { info, printAgentList } from "./lib/display.js";
|
|
5
5
|
import { getVersion } from "./lib/version.js";
|
|
6
|
-
import { resolveHarnessName, listHarnesses, HarnessNotTestedError, } from "./harnesses/index.js";
|
|
6
|
+
import { resolveHarnessName, listHarnesses, detectHarnesses, HarnessNotTestedError, } from "./harnesses/index.js";
|
|
7
7
|
import { runSetup } from "./commands/setup.js";
|
|
8
8
|
import { runUninstall } from "./commands/uninstall.js";
|
|
9
9
|
import { runVerify } from "./commands/verify.js";
|
|
@@ -51,8 +51,43 @@ async function main() {
|
|
|
51
51
|
process.exit(1);
|
|
52
52
|
}
|
|
53
53
|
const scope = opts.scope === "local" ? "local" : "global";
|
|
54
|
-
// Resolve harness
|
|
55
|
-
|
|
54
|
+
// Resolve harness: if --harness was passed explicitly, honor it as-is.
|
|
55
|
+
// Otherwise auto-detect — single match wins silently, multiple matches
|
|
56
|
+
// prompt the user, no matches falls back to the default (claude-code)
|
|
57
|
+
// to preserve the landing-page "just run npx @uluops/setup" promise.
|
|
58
|
+
const harnessExplicit = program.getOptionValueSource("harness") === "cli";
|
|
59
|
+
let harnessName;
|
|
60
|
+
if (harnessExplicit) {
|
|
61
|
+
harnessName = resolveHarnessName(opts.harness);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
const detected = detectHarnesses();
|
|
65
|
+
if (detected.length === 1) {
|
|
66
|
+
harnessName = detected[0].name;
|
|
67
|
+
if (harnessName !== "claude-code") {
|
|
68
|
+
info(chalk.dim(`Detected ${chalk.cyan(detected[0].displayName)} — using as target (pass --harness to override)`));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else if (detected.length > 1) {
|
|
72
|
+
const isInteractive = !opts.yes && !opts.apiKey && !process.env["ULUOPS_API_KEY"] && process.stdin.isTTY;
|
|
73
|
+
if (isInteractive) {
|
|
74
|
+
const { select } = await import("@inquirer/prompts");
|
|
75
|
+
harnessName = await select({
|
|
76
|
+
message: "Multiple harnesses detected — which one are you setting up?",
|
|
77
|
+
choices: detected.map((p) => ({ name: p.displayName, value: p.name })),
|
|
78
|
+
default: detected[0].name,
|
|
79
|
+
});
|
|
80
|
+
console.log();
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
harnessName = detected[0].name;
|
|
84
|
+
info(chalk.dim(`Multiple harnesses detected (${detected.map((p) => p.displayName).join(", ")}); defaulting to ${chalk.cyan(detected[0].displayName)} — pass --harness to choose`));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
harnessName = resolveHarnessName(opts.harness);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
56
91
|
await runSetup({
|
|
57
92
|
apiKey: opts.apiKey,
|
|
58
93
|
signup: opts.signup ?? false,
|
package/dist/commands/helpers.js
CHANGED
|
@@ -3,7 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
import { readdir } from "node:fs/promises";
|
|
4
4
|
import { detect } from "../steps/detect.js";
|
|
5
5
|
import { signup } from "../steps/signup.js";
|
|
6
|
-
import { resolveApiKey } from "../steps/auth.js";
|
|
6
|
+
import { resolveApiKey, hasCredentialsFile } from "../steps/auth.js";
|
|
7
7
|
import { installMcp } from "../steps/mcp.js";
|
|
8
8
|
import { installAgents } from "../steps/agents.js";
|
|
9
9
|
import { installCommands } from "../steps/commands.js";
|
|
@@ -14,12 +14,49 @@ import { probeHookSupport } from "../lib/settings-merger.js";
|
|
|
14
14
|
import { findProjectRoot, ASSETS_DIR } from "../lib/paths.js";
|
|
15
15
|
import { getHealthTimeout } from "../lib/health.js";
|
|
16
16
|
import { ok, warn, fail, info } from "../lib/display.js";
|
|
17
|
+
/**
|
|
18
|
+
* Decide whether to ask the user "Are you creating a new account?".
|
|
19
|
+
* The prompt is the new-user friction-reducer — it should fire only when
|
|
20
|
+
* the user has provided no other signal about who they are.
|
|
21
|
+
*
|
|
22
|
+
* Skip the prompt when:
|
|
23
|
+
* - `--signup` is set (forces signup path)
|
|
24
|
+
* - `--api-key` flag is provided (user already has a key)
|
|
25
|
+
* - `ULUOPS_API_KEY` env var is set (CI/automation)
|
|
26
|
+
* - `--yes` is passed (non-interactive)
|
|
27
|
+
* - stdin is not a TTY (piped / background)
|
|
28
|
+
* - a credentials file already exists (returning user)
|
|
29
|
+
*/
|
|
30
|
+
async function shouldPromptForAccount(opts) {
|
|
31
|
+
if (opts.signup)
|
|
32
|
+
return false;
|
|
33
|
+
if (opts.apiKey)
|
|
34
|
+
return false;
|
|
35
|
+
if (process.env["ULUOPS_API_KEY"])
|
|
36
|
+
return false;
|
|
37
|
+
if (opts.yes)
|
|
38
|
+
return false;
|
|
39
|
+
if (!process.stdin.isTTY)
|
|
40
|
+
return false;
|
|
41
|
+
if (await hasCredentialsFile())
|
|
42
|
+
return false;
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
17
45
|
/** Resolve API key via flag, env, file, signup, or interactive prompt. Returns env detection + key. */
|
|
18
46
|
export async function initContext(opts) {
|
|
19
47
|
const env = await detect();
|
|
20
48
|
let apiKey;
|
|
49
|
+
let creatingAccount = opts.signup;
|
|
50
|
+
if (!opts.signup && (await shouldPromptForAccount(opts))) {
|
|
51
|
+
const { confirm } = await import("@inquirer/prompts");
|
|
52
|
+
creatingAccount = await confirm({
|
|
53
|
+
message: "Are you creating a new UluOps account?",
|
|
54
|
+
default: true,
|
|
55
|
+
});
|
|
56
|
+
console.log();
|
|
57
|
+
}
|
|
21
58
|
try {
|
|
22
|
-
if (
|
|
59
|
+
if (creatingAccount) {
|
|
23
60
|
info("Create your UluOps account\n");
|
|
24
61
|
const auth = await signup();
|
|
25
62
|
apiKey = auth.apiKey;
|
|
@@ -47,23 +47,23 @@ class OpenCodeMcpConfig {
|
|
|
47
47
|
merge(config, apiKey) {
|
|
48
48
|
const raw = config["mcp"];
|
|
49
49
|
const existing = (typeof raw === "object" && raw !== null ? raw : {});
|
|
50
|
+
// Backend URLs resolved by @uluops/ops-mcp and @uluops/registry-mcp via
|
|
51
|
+
// their bundled SDKs. See lib/config-merger.ts for rationale.
|
|
50
52
|
const tracker = {
|
|
51
53
|
type: "local",
|
|
52
|
-
command: ["npx", "-y", "uluops-
|
|
54
|
+
command: ["npx", "-y", "@uluops/ops-mcp"],
|
|
53
55
|
enabled: true,
|
|
54
56
|
timeout: 30000,
|
|
55
57
|
environment: {
|
|
56
|
-
ULUOPS_BASE_URL: "https://api.uluops.ai/api/v1",
|
|
57
58
|
ULUOPS_API_KEY: apiKey,
|
|
58
59
|
},
|
|
59
60
|
};
|
|
60
61
|
const registry = {
|
|
61
62
|
type: "local",
|
|
62
|
-
command: ["npx", "-y", "uluops
|
|
63
|
+
command: ["npx", "-y", "@uluops/registry-mcp"],
|
|
63
64
|
enabled: true,
|
|
64
65
|
timeout: 30000,
|
|
65
66
|
environment: {
|
|
66
|
-
ULUOPS_REGISTRY_URL: "https://api.uluops.ai/api/v1/registry",
|
|
67
67
|
ULUOPS_API_KEY: apiKey,
|
|
68
68
|
},
|
|
69
69
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { atomicWrite } from "./atomic-write.js";
|
|
3
|
-
const MCP_PACKAGES = ["uluops-
|
|
3
|
+
const MCP_PACKAGES = ["@uluops/ops-mcp", "@uluops/registry-mcp"];
|
|
4
4
|
/** Check whether the UluOps MCP client packages exist on the npm registry. Returns lists of available and missing packages. */
|
|
5
5
|
export async function checkMcpPackageAvailability() {
|
|
6
6
|
const available = [];
|
|
@@ -49,24 +49,26 @@ export async function readConfig(path) {
|
|
|
49
49
|
export function mergeUluopsMcp(config, apiKey, trust = false) {
|
|
50
50
|
const existing = config.mcpServers ?? {};
|
|
51
51
|
const trustField = trust ? { trust: true } : {};
|
|
52
|
+
// Backend URLs are resolved by the respective MCP servers (@uluops/ops-mcp
|
|
53
|
+
// and @uluops/registry-mcp) against their bundled SDKs. Stamping
|
|
54
|
+
// ULUOPS_BASE_URL / ULUOPS_REGISTRY_URL here would override that resolution
|
|
55
|
+
// with a value that could go stale if production endpoints ever shift.
|
|
52
56
|
return {
|
|
53
57
|
...config,
|
|
54
58
|
mcpServers: {
|
|
55
59
|
...existing,
|
|
56
60
|
"uluops-tracker": {
|
|
57
61
|
command: "npx",
|
|
58
|
-
args: ["-y", "uluops-
|
|
62
|
+
args: ["-y", "@uluops/ops-mcp"],
|
|
59
63
|
env: {
|
|
60
|
-
ULUOPS_BASE_URL: "https://api.uluops.ai/api/v1",
|
|
61
64
|
ULUOPS_API_KEY: apiKey,
|
|
62
65
|
},
|
|
63
66
|
...trustField,
|
|
64
67
|
},
|
|
65
68
|
"uluops-registry": {
|
|
66
69
|
command: "npx",
|
|
67
|
-
args: ["-y", "uluops
|
|
70
|
+
args: ["-y", "@uluops/registry-mcp"],
|
|
68
71
|
env: {
|
|
69
|
-
ULUOPS_REGISTRY_URL: "https://api.uluops.ai/api/v1/registry",
|
|
70
72
|
ULUOPS_API_KEY: apiKey,
|
|
71
73
|
},
|
|
72
74
|
...trustField,
|
package/dist/steps/auth.d.ts
CHANGED
|
@@ -2,6 +2,12 @@ export interface AuthResult {
|
|
|
2
2
|
apiKey: string;
|
|
3
3
|
email: string | null;
|
|
4
4
|
}
|
|
5
|
+
/**
|
|
6
|
+
* Returns true if a credentials file exists at the default path.
|
|
7
|
+
* Used by the setup flow to skip the "new account?" prompt for returning users.
|
|
8
|
+
* Existence-only — does not validate the file's shape or contents.
|
|
9
|
+
*/
|
|
10
|
+
export declare function hasCredentialsFile(): Promise<boolean>;
|
|
5
11
|
/**
|
|
6
12
|
* Resolve API key from flags, env, credentials file, or interactive prompt.
|
|
7
13
|
*/
|
package/dist/steps/auth.js
CHANGED
|
@@ -1,9 +1,26 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
1
|
+
import { readFile, access } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
function getKeyPrefix() {
|
|
5
5
|
return process.env["ULUOPS_KEY_PREFIX"] ?? "ulr_";
|
|
6
6
|
}
|
|
7
|
+
function credentialsPath() {
|
|
8
|
+
return join(homedir(), ".uluops", "credentials.json");
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Returns true if a credentials file exists at the default path.
|
|
12
|
+
* Used by the setup flow to skip the "new account?" prompt for returning users.
|
|
13
|
+
* Existence-only — does not validate the file's shape or contents.
|
|
14
|
+
*/
|
|
15
|
+
export async function hasCredentialsFile() {
|
|
16
|
+
try {
|
|
17
|
+
await access(credentialsPath());
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
7
24
|
/**
|
|
8
25
|
* Resolve API key from flags, env, credentials file, or interactive prompt.
|
|
9
26
|
*/
|
|
@@ -48,7 +65,7 @@ export async function resolveApiKey(options) {
|
|
|
48
65
|
return { apiKey, email: null };
|
|
49
66
|
}
|
|
50
67
|
async function readCredentialsFile() {
|
|
51
|
-
const credsPath =
|
|
68
|
+
const credsPath = credentialsPath();
|
|
52
69
|
let raw;
|
|
53
70
|
try {
|
|
54
71
|
raw = await readFile(credsPath, "utf-8");
|