@uluops/setup 0.9.8 → 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 +2 -0
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +12 -0
- package/dist/lib/mcp-packages.d.ts +4 -4
- package/dist/lib/mcp-packages.js +2 -2
- package/dist/steps/username.d.ts +49 -0
- package/dist/steps/username.js +121 -0
- package/package.json +1 -1
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();
|
|
@@ -123,6 +124,7 @@ async function main() {
|
|
|
123
124
|
}
|
|
124
125
|
await runSetup({
|
|
125
126
|
apiKey: opts.apiKey,
|
|
127
|
+
username: opts.username,
|
|
126
128
|
signup: opts.signup ?? false,
|
|
127
129
|
scope,
|
|
128
130
|
localDefs: opts.localDefs,
|
package/dist/commands/setup.d.ts
CHANGED
|
@@ -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>;
|
package/dist/commands/setup.js
CHANGED
|
@@ -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.
|
|
25
|
-
export declare const OPS_MCP_SPEC: "@uluops/ops-mcp@0.
|
|
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.
|
|
28
|
-
export declare const REGISTRY_MCP_SPEC: "@uluops/registry-mcp@0.2.
|
|
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
|
package/dist/lib/mcp-packages.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
+
}
|