@uluops/setup 0.9.7 → 0.9.9

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/dist/cli.js CHANGED
@@ -33,6 +33,7 @@ async function main() {
33
33
  .option("--verify", "Check existing installation health")
34
34
  .option("--uninstall", "Remove all UluOps artifacts")
35
35
  .option("--dry-run", "Show what would happen", false)
36
+ .option("--username <name>", "Set your registry username (slug, required to publish) without prompting")
36
37
  .option("-y, --yes", "Skip confirmations", false);
37
38
  program.parse();
38
39
  const opts = program.opts();
@@ -90,7 +91,6 @@ async function main() {
90
91
  const { checkbox } = await import("@inquirer/prompts");
91
92
  const chosen = await checkbox({
92
93
  message: "Multiple harnesses detected. Which would you like to install into?",
93
- instructions: " (use space to toggle, enter to confirm)",
94
94
  choices: profiles.map((p) => ({
95
95
  name: p.displayName,
96
96
  value: p.name,
@@ -124,6 +124,7 @@ async function main() {
124
124
  }
125
125
  await runSetup({
126
126
  apiKey: opts.apiKey,
127
+ username: opts.username,
127
128
  signup: opts.signup ?? false,
128
129
  scope,
129
130
  localDefs: opts.localDefs,
@@ -19,5 +19,7 @@ interface RunSetupOpts {
19
19
  * CLI prompt is also suppressed downstream (no hook → no CLI gate).
20
20
  */
21
21
  noMetrics?: boolean;
22
+ /** Explicit registry username (slug). Set + confirmed non-interactively when provided. */
23
+ username?: string;
22
24
  }
23
25
  export declare function runSetup(opts: RunSetupOpts): Promise<void>;
@@ -9,6 +9,7 @@ import { acquireInstallLock, } from "../lib/install-lock.js";
9
9
  import { initContext, checkConflicts, configureMcpStep, installAgentsDefs, installCommandsDefs, installSkillsDefs, configureMetricsStep, configureCliStep, configureAgentMetricsCliStep, runHealthCheck, configureShell, } from "./helpers.js";
10
10
  import { ConflictRejectedError } from "./errors.js";
11
11
  import { classifyExit, } from "./per-harness.js";
12
+ import { maybeSetUsername } from "../steps/username.js";
12
13
  export async function runSetup(opts) {
13
14
  if (opts.harnesses.length === 0) {
14
15
  info(chalk.dim("Nothing to install — re-run with at least one harness selected.\n"));
@@ -34,6 +35,17 @@ export async function runSetup(opts) {
34
35
  // === Once-per-run: BEFORE the per-harness loop ===
35
36
  const { env, apiKey } = await initContext(opts);
36
37
  console.log();
38
+ // Optional, never-forced: offer to set a registry username (the one-time
39
+ // prerequisite for creating/publishing definitions). Skipped silently in
40
+ // non-interactive runs unless --username is supplied.
41
+ await maybeSetUsername({
42
+ apiKey,
43
+ username: opts.username,
44
+ interactive: !opts.yes && Boolean(process.stdin.isTTY),
45
+ dryRun: opts.dryRun,
46
+ emit: (msg) => info(msg),
47
+ });
48
+ console.log();
37
49
  // Acquire the install lock before touching any shared state. Skipped on
38
50
  // dry-run (read-only). The lock excludes a second concurrent uluops-setup
39
51
  // from racing the manifest / MCP config / shell-profile / settings.json
@@ -21,11 +21,11 @@
21
21
  * the latest version was reachable.
22
22
  */
23
23
  export declare const OPS_MCP_PACKAGE = "@uluops/ops-mcp";
24
- export declare const OPS_MCP_VERSION = "0.4.4";
25
- export declare const OPS_MCP_SPEC: "@uluops/ops-mcp@0.4.4";
24
+ export declare const OPS_MCP_VERSION = "0.5.0";
25
+ export declare const OPS_MCP_SPEC: "@uluops/ops-mcp@0.5.0";
26
26
  export declare const REGISTRY_MCP_PACKAGE = "@uluops/registry-mcp";
27
- export declare const REGISTRY_MCP_VERSION = "0.2.9";
28
- export declare const REGISTRY_MCP_SPEC: "@uluops/registry-mcp@0.2.9";
27
+ export declare const REGISTRY_MCP_VERSION = "0.2.14";
28
+ export declare const REGISTRY_MCP_SPEC: "@uluops/registry-mcp@0.2.14";
29
29
  /**
30
30
  * Bare package names for the npm availability probe. The probe checks
31
31
  * existence on the registry, not version-specific resolvability, so it
@@ -21,10 +21,10 @@
21
21
  * the latest version was reachable.
22
22
  */
23
23
  export const OPS_MCP_PACKAGE = "@uluops/ops-mcp";
24
- export const OPS_MCP_VERSION = "0.4.4";
24
+ export const OPS_MCP_VERSION = "0.5.0";
25
25
  export const OPS_MCP_SPEC = `${OPS_MCP_PACKAGE}@${OPS_MCP_VERSION}`;
26
26
  export const REGISTRY_MCP_PACKAGE = "@uluops/registry-mcp";
27
- export const REGISTRY_MCP_VERSION = "0.2.9";
27
+ export const REGISTRY_MCP_VERSION = "0.2.14";
28
28
  export const REGISTRY_MCP_SPEC = `${REGISTRY_MCP_PACKAGE}@${REGISTRY_MCP_VERSION}`;
29
29
  /**
30
30
  * Bare package names for the npm availability probe. The probe checks
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Optional username step.
3
+ *
4
+ * Setting a username is the one-time prerequisite the registry enforces before
5
+ * a user can create or publish definitions. We offer it during setup so
6
+ * publishers clear the gate up front — but never force it: consumers who only
7
+ * run definitions never need a username, so the step is skippable and is
8
+ * silently skipped in non-interactive runs unless --username is supplied.
9
+ *
10
+ * Uses native fetch against the platform API with the api key the run already
11
+ * resolved — no SDK dependency. Mirrors the PATCH /auth/profile fold-in, where
12
+ * setting a username also confirms it.
13
+ */
14
+ /**
15
+ * Pure slug check for the interactive prompt. Returns true when valid, or a
16
+ * message string otherwise (inquirer validate contract). Server validation
17
+ * remains authoritative.
18
+ * @internal Exported for testing only.
19
+ */
20
+ export declare function validateUsername(name: string): string | true;
21
+ /**
22
+ * PATCH /auth/profile with the username. Setting it confirms it (one-time).
23
+ * Returns the confirmed username on success. Throws a friendly Error otherwise.
24
+ * @internal Exported for testing only.
25
+ */
26
+ export declare function setUsername(apiKey: string, username: string): Promise<string>;
27
+ export interface MaybeSetUsernameOpts {
28
+ /** Resolved api key for the run; without it there is nothing to authenticate with. */
29
+ apiKey: string | null;
30
+ /** Explicit username from --username (non-interactive path). */
31
+ username?: string;
32
+ /** True when prompting is allowed (a TTY run without --yes). */
33
+ interactive: boolean;
34
+ /** Dry run — describe, don't write. */
35
+ dryRun: boolean;
36
+ /** Emit a line of user-facing output. */
37
+ emit: (msg: string) => void;
38
+ }
39
+ /**
40
+ * Offer to set a username. Resolution order:
41
+ * - no api key → skip (nothing to auth with)
42
+ * - --username supplied → set it (works non-interactively)
43
+ * - interactive, no flag → prompt once; empty input skips
44
+ * - non-interactive → skip silently
45
+ *
46
+ * Never throws: a failure to set the username is surfaced as a warning and
47
+ * does not abort setup (it is an optional, publisher-only prerequisite).
48
+ */
49
+ export declare function maybeSetUsername(opts: MaybeSetUsernameOpts): Promise<void>;
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Optional username step.
3
+ *
4
+ * Setting a username is the one-time prerequisite the registry enforces before
5
+ * a user can create or publish definitions. We offer it during setup so
6
+ * publishers clear the gate up front — but never force it: consumers who only
7
+ * run definitions never need a username, so the step is skippable and is
8
+ * silently skipped in non-interactive runs unless --username is supplied.
9
+ *
10
+ * Uses native fetch against the platform API with the api key the run already
11
+ * resolved — no SDK dependency. Mirrors the PATCH /auth/profile fold-in, where
12
+ * setting a username also confirms it.
13
+ */
14
+ const API_BASE = "https://api.uluops.ai/api/v1";
15
+ // Canonical username / personal-org slug pattern, shared in spirit with
16
+ // ops-api + ops-sdk: 1-40 chars, lowercase alphanumeric with internal hyphens
17
+ // or underscores, must start and end alphanumeric (e.g. "ulu-labs").
18
+ const USERNAME_REGEX = /^[a-z0-9](?:[a-z0-9_-]{0,38}[a-z0-9])?$/;
19
+ /**
20
+ * Pure slug check for the interactive prompt. Returns true when valid, or a
21
+ * message string otherwise (inquirer validate contract). Server validation
22
+ * remains authoritative.
23
+ * @internal Exported for testing only.
24
+ */
25
+ export function validateUsername(name) {
26
+ if (!name.trim())
27
+ return "Username is required";
28
+ if (!USERNAME_REGEX.test(name)) {
29
+ return "1-40 chars, lowercase letters/digits with internal hyphens or underscores (e.g. ulu-labs)";
30
+ }
31
+ return true;
32
+ }
33
+ /**
34
+ * PATCH /auth/profile with the username. Setting it confirms it (one-time).
35
+ * Returns the confirmed username on success. Throws a friendly Error otherwise.
36
+ * @internal Exported for testing only.
37
+ */
38
+ export async function setUsername(apiKey, username) {
39
+ let res;
40
+ try {
41
+ res = await fetch(`${API_BASE}/auth/profile`, {
42
+ method: "PATCH",
43
+ headers: {
44
+ "Content-Type": "application/json",
45
+ Authorization: `Bearer ${apiKey}`,
46
+ },
47
+ body: JSON.stringify({ username }),
48
+ signal: AbortSignal.timeout(15000),
49
+ });
50
+ }
51
+ catch (err) {
52
+ if (err instanceof TypeError) {
53
+ throw new Error("Can't reach api.uluops.ai — check your connection.");
54
+ }
55
+ throw err;
56
+ }
57
+ if (res.ok) {
58
+ const body = await res.json();
59
+ if (typeof body !== "object" || body === null) {
60
+ throw new Error("Unexpected API response shape");
61
+ }
62
+ const confirmed = body.data?.user?.username;
63
+ return typeof confirmed === "string" ? confirmed : username;
64
+ }
65
+ const errorBody = (await res.json().catch(() => null));
66
+ const message = errorBody?.error?.message ?? errorBody?.message ?? `HTTP ${res.status}`;
67
+ if (res.status === 409) {
68
+ throw new Error(`Username unavailable: ${message}`);
69
+ }
70
+ if (res.status === 400) {
71
+ throw new Error(`Username rejected: ${message}`);
72
+ }
73
+ throw new Error(`Couldn't set username (${res.status}): ${message}`);
74
+ }
75
+ /**
76
+ * Offer to set a username. Resolution order:
77
+ * - no api key → skip (nothing to auth with)
78
+ * - --username supplied → set it (works non-interactively)
79
+ * - interactive, no flag → prompt once; empty input skips
80
+ * - non-interactive → skip silently
81
+ *
82
+ * Never throws: a failure to set the username is surfaced as a warning and
83
+ * does not abort setup (it is an optional, publisher-only prerequisite).
84
+ */
85
+ export async function maybeSetUsername(opts) {
86
+ const { apiKey, interactive, dryRun, emit } = opts;
87
+ if (!apiKey)
88
+ return;
89
+ let username = opts.username?.trim();
90
+ if (!username) {
91
+ if (!interactive)
92
+ return; // non-interactive with no flag → skip silently
93
+ const { input } = await import("@inquirer/prompts");
94
+ const answer = await input({
95
+ message: "Registry username (optional — required to publish; press Enter to skip)",
96
+ validate: (v) => (v.trim() === "" ? true : validateUsername(v)),
97
+ });
98
+ username = answer.trim();
99
+ if (!username)
100
+ return; // user skipped
101
+ }
102
+ else {
103
+ const valid = validateUsername(username);
104
+ if (valid !== true) {
105
+ emit(` ⚠ Skipping username — ${valid}`);
106
+ return;
107
+ }
108
+ }
109
+ if (dryRun) {
110
+ emit(` Would set registry username to "${username}".`);
111
+ return;
112
+ }
113
+ try {
114
+ const confirmed = await setUsername(apiKey, username);
115
+ emit(` Registry username set to "${confirmed}".`);
116
+ }
117
+ catch (err) {
118
+ const message = err instanceof Error ? err.message : String(err);
119
+ emit(` ⚠ ${message} You can set it later with "ulu auth update-profile --username <name>".`);
120
+ }
121
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uluops/setup",
3
- "version": "0.9.7",
3
+ "version": "0.9.9",
4
4
  "description": "Zero-friction installer for UluOps agentic harnesses",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -45,16 +45,16 @@
45
45
  "prepublishOnly": "npm run build"
46
46
  },
47
47
  "dependencies": {
48
- "@inquirer/prompts": "7.10.1",
48
+ "@inquirer/prompts": "8.5.2",
49
49
  "@uluops/agent-metrics": "0.4.0",
50
50
  "chalk": "5.6.2",
51
- "commander": "12.1.0",
51
+ "commander": "15.0.0",
52
52
  "jsonc-parser": "3.3.1"
53
53
  },
54
54
  "devDependencies": {
55
- "@types/node": "22.19.15",
56
- "tsx": "4.21.0",
57
- "typescript": "5.9.3",
58
- "vitest": "4.1.8"
55
+ "@types/node": "25.9.3",
56
+ "tsx": "4.22.4",
57
+ "typescript": "6.0.3",
58
+ "vitest": "4.1.9"
59
59
  }
60
60
  }