@uluops/setup 0.6.0 → 0.6.3

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 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** — Validates your API key (or creates an account with `--signup`)
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
- You'll be prompted for your API key (get one at [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.
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
- **New to UluOps?** Create an account without leaving the terminal:
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
- # New user create account + install in one shot
132
+ # Defaultasks "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 name (supports aliases)
55
- const harnessName = resolveHarnessName(opts.harness);
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,
@@ -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 (opts.signup) {
59
+ if (creatingAccount) {
23
60
  info("Create your UluOps account\n");
24
61
  const auth = await signup();
25
62
  apiKey = auth.apiKey;
@@ -49,7 +49,7 @@ class OpenCodeMcpConfig {
49
49
  const existing = (typeof raw === "object" && raw !== null ? raw : {});
50
50
  const tracker = {
51
51
  type: "local",
52
- command: ["npx", "-y", "uluops-tracker-mcp-client"],
52
+ command: ["npx", "-y", "@uluops/ops-mcp"],
53
53
  enabled: true,
54
54
  timeout: 30000,
55
55
  environment: {
@@ -59,7 +59,7 @@ class OpenCodeMcpConfig {
59
59
  };
60
60
  const registry = {
61
61
  type: "local",
62
- command: ["npx", "-y", "uluops-registry-mcp-client"],
62
+ command: ["npx", "-y", "@uluops/registry-mcp"],
63
63
  enabled: true,
64
64
  timeout: 30000,
65
65
  environment: {
@@ -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-tracker-mcp-client", "uluops-registry-mcp-client"];
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 = [];
@@ -55,7 +55,7 @@ export function mergeUluopsMcp(config, apiKey, trust = false) {
55
55
  ...existing,
56
56
  "uluops-tracker": {
57
57
  command: "npx",
58
- args: ["-y", "uluops-tracker-mcp-client"],
58
+ args: ["-y", "@uluops/ops-mcp"],
59
59
  env: {
60
60
  ULUOPS_BASE_URL: "https://api.uluops.ai/api/v1",
61
61
  ULUOPS_API_KEY: apiKey,
@@ -64,7 +64,7 @@ export function mergeUluopsMcp(config, apiKey, trust = false) {
64
64
  },
65
65
  "uluops-registry": {
66
66
  command: "npx",
67
- args: ["-y", "uluops-registry-mcp-client"],
67
+ args: ["-y", "@uluops/registry-mcp"],
68
68
  env: {
69
69
  ULUOPS_REGISTRY_URL: "https://api.uluops.ai/api/v1/registry",
70
70
  ULUOPS_API_KEY: apiKey,
@@ -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
  */
@@ -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 = join(homedir(), ".uluops", "credentials.json");
68
+ const credsPath = credentialsPath();
52
69
  let raw;
53
70
  try {
54
71
  raw = await readFile(credsPath, "utf-8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uluops/setup",
3
- "version": "0.6.0",
3
+ "version": "0.6.3",
4
4
  "description": "Zero-friction installer for UluOps agentic harnesses",
5
5
  "license": "MIT",
6
6
  "repository": {