@revturbine/cli 0.18.0 → 0.18.1

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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +44 -14
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -66,7 +66,7 @@ Commands that read a config name the version explicitly — there is no default:
66
66
  | `init` (alias `create`) | Set up RevTurbine while preserving existing integrations: install missing packages, pin the CLI exactly, create a starter for a fresh integration, and install skills for one agent. Use `--scaffold` to create a missing root starter explicitly; existing files are never overwritten. `--agent claude-code\|cursor\|codex` overrides environment detection; an unknown agent skips skills and prints the selection command. Also supports `--dir`, `--dry-run`, `--no-skills`, `--json`. With no `package.json`, offers a new project (`--yes` for noninteractive creation). Runs the invoked CLI even inside a repo that pins a different one. |
67
67
  | `signup` | Create an account headlessly: email + password, then an emailed one-time code to verify, then a token is stored. |
68
68
  | `login` / `logout` | Device-flow auth; tokens stored at `~/.revturbine/credentials.json` (mode 0600). |
69
- | `whoami` | The resolved instance, tenant, credentials source, and whether the stored token works. |
69
+ | `whoami` | Authentication state first: not logged in, credentials accepted, credentials rejected for the selected instance/tenant, or verification unavailable. Then shows instance, tenant, and credentials source; configured tenant information alone does not prove login. Verification and subsequent telemetry each time out after 3 seconds. Status reports exit 0. `--json` retains its seven fields: `instance`, `tenant`, `tenant_source`, `credentials_dir`, `credentials_source`, `token_present` (boolean), and `token_valid` (`true` for 2xx, `false` for 401/403, `null` when absent or unverified, including redirects, other HTTP errors, timeouts and network failures). |
70
70
  | `schema` | Emit the bundled `RevTurbineConfig` JSON schema (for agents to author against). |
71
71
  | `docs` | Print the canonical documentation URL. |
72
72
  | `download` | Fetch a config version (`--live` / `--draft` / `--release <id>`); `--save`. |
package/dist/cli.js CHANGED
@@ -10705,7 +10705,7 @@ var CLI_UNTRACKED_COMMANDS = /* @__PURE__ */ new Set(["login", "signup", "logout
10705
10705
  function shouldTrackCommandExecution(name, hasUrl) {
10706
10706
  return hasUrl && !CLI_UNTRACKED_COMMANDS.has(name);
10707
10707
  }
10708
- async function trackEvent(rawUrl, explicitTenantId, eventType, payload) {
10708
+ async function trackEvent(rawUrl, explicitTenantId, eventType, payload, signal) {
10709
10709
  try {
10710
10710
  const url = normalizeBaseUrl(rawUrl);
10711
10711
  const cred = getCredential(url);
@@ -10717,7 +10717,8 @@ async function trackEvent(rawUrl, explicitTenantId, eventType, payload) {
10717
10717
  "x-tenant-id": explicitTenantId ?? cred.tenant_id ?? "dev-tenant-001",
10718
10718
  Authorization: `Bearer ${cred.token}`
10719
10719
  },
10720
- body: JSON.stringify({ event_type: eventType, payload })
10720
+ body: JSON.stringify({ event_type: eventType, payload }),
10721
+ ...signal ? { signal } : {}
10721
10722
  });
10722
10723
  } catch {
10723
10724
  }
@@ -10886,6 +10887,20 @@ async function resolveActiveDraft(baseUrl, headers, fetchImpl = fetch) {
10886
10887
  return { ok: res.ok, status: res.status, draft: res.ok ? json.active ?? null : null };
10887
10888
  }
10888
10889
 
10890
+ // src/lib/whoami.ts
10891
+ function classifyToken(hasToken, status) {
10892
+ if (!hasToken || status === null) return null;
10893
+ if (status >= 200 && status < 300) return true;
10894
+ if (status === 401 || status === 403) return false;
10895
+ return null;
10896
+ }
10897
+ function formatAuthentication(hasToken, tokenValid) {
10898
+ if (!hasToken) return "authentication: not logged in \u2014 run `revturbine login`";
10899
+ if (tokenValid === true) return "authentication: credentials accepted for this instance and tenant";
10900
+ if (tokenValid === false) return "authentication: credentials rejected for this instance or tenant \u2014 check access or run `revturbine login`";
10901
+ return "authentication: verification unavailable \u2014 credentials present; retry when the instance is reachable";
10902
+ }
10903
+
10889
10904
  // src/lib/evaluate-local.ts
10890
10905
  import { initRevTurbine } from "@revturbine/sdk/headless";
10891
10906
  function resolvePlacementComponentType(input) {
@@ -11781,6 +11796,7 @@ function queryAnalyticsView(baseUrl, headers, body, fetchImpl) {
11781
11796
  // src/cli.ts
11782
11797
  var DOCS_URL = "https://github.com/revt-eng/revturbine-cli#readme";
11783
11798
  var DEFAULT_URL = "https://revturbine.com/app";
11799
+ var WHOAMI_REQUEST_TIMEOUT_MS = 3e3;
11784
11800
  function loadConfig(file) {
11785
11801
  const resolved = path2.resolve(file);
11786
11802
  if (!existsSync2(resolved)) fail(EXIT.USAGE, `Config not found: ${resolved}`);
@@ -11816,15 +11832,15 @@ function verifyConfig(file) {
11816
11832
  if (check.advisories.length > 0) diagRaw(formatFindings(check.advisories));
11817
11833
  return check.body;
11818
11834
  }
11819
- function connect(rawUrl, explicitTenantId) {
11835
+ function connect(rawUrl, explicitTenantId, diagnostic = diag) {
11820
11836
  const url = normalizeBaseUrl(rawUrl);
11821
11837
  const { dir, source } = resolveConfigDir();
11822
11838
  const cred = getCredential(url);
11823
11839
  const tenantId = explicitTenantId ?? cred?.tenant_id ?? "dev-tenant-001";
11824
11840
  const tenantSource = explicitTenantId ? "--tenant-id" : cred?.tenant_id ? "stored token" : "default";
11825
- diag(`Tenant ${tenantId} (${tenantSource}); credentials: ${dir} [${source}].`);
11841
+ diagnostic(`Tenant ${tenantId} (${tenantSource}); credentials: ${dir} [${source}].`);
11826
11842
  if (source === "global") {
11827
- diag(
11843
+ diagnostic(
11828
11844
  `WARNING: using the global ${dir} - NOT worktree-scoped. If this session is for a specific customer, run from that customer's worktree (with its own .revturbine/) so a stale login can't target the wrong tenant.`
11829
11845
  );
11830
11846
  }
@@ -12092,6 +12108,10 @@ Auth:
12092
12108
  ~/.revturbine/credentials.json (0600); the token's tenant is used by default,
12093
12109
  override with -t/--tenant-id. Mutating commands (discard, restore) prompt
12094
12110
  for confirmation unless --yes.
12111
+ \`whoami\` leads with absent, accepted, rejected, or unverifiable credentials;
12112
+ a configured tenant alone does not prove login. Verification and subsequent
12113
+ telemetry each time out after 3 seconds. Status reports exit 0; --json keeps
12114
+ token_present boolean and token_valid true/false/null (null = unverified).
12095
12115
 
12096
12116
  Full reference: ${DOCS_URL}
12097
12117
  `;
@@ -12102,7 +12122,7 @@ program.hook("postAction", async (_thisCommand, actionCommand) => {
12102
12122
  if (!shouldTrackCommandExecution(actionCommand.name(), Boolean(opts.url))) return;
12103
12123
  await trackEvent(opts.url, opts.tenantId, "cli_command_executed", {
12104
12124
  command: actionCommand.name()
12105
- });
12125
+ }, actionCommand.name() === "whoami" ? globalThis.AbortSignal.timeout(WHOAMI_REQUEST_TIMEOUT_MS) : void 0);
12106
12126
  });
12107
12127
  program.name("revturbine").description("Validate RevTurbine Playbooks and ship them through the playbook-version lifecycle (draft \u2192 Release).").version(`${pkgVersion} (schema ${SCHEMA_VERSION})`, "-V, --version", "Print the revturbine and bundled schema versions").option(NO_LOCAL_FLAG, "Ignore the repo-pinned CLI and run this installation").showHelpAfterError().addHelpText("after", HELP_AFTER);
12108
12128
  function runInstall(manager, args, cwd) {
@@ -12350,13 +12370,22 @@ program.command("logout").description("Remove the stored token for <url>.").argu
12350
12370
  const removed = removeCredential(normalized);
12351
12371
  diag(removed ? `Logged out of ${normalized}.` : `No stored credential for ${normalized}.`);
12352
12372
  });
12353
- program.command("whoami").description("Show the resolved instance, tenant, credentials source, and whether the stored token works.").option("-u, --url <url>", "RevTurbine instance URL", DEFAULT_URL).option("-t, --tenant-id <id>", "x-tenant-id (defaults to the stored token tenant)").option("--json", "Machine-readable output").action(async (opts) => {
12354
- const conn = connect(opts.url, opts.tenantId);
12355
- let tokenValid = null;
12373
+ program.command("whoami").description("Show authentication state first (absent, accepted, rejected, or unverifiable), then instance, tenant, and credentials source.").option("-u, --url <url>", "RevTurbine instance URL", DEFAULT_URL).option("-t, --tenant-id <id>", "x-tenant-id (defaults to the stored token tenant)").option("--json", "Machine-readable output").action(async (opts) => {
12374
+ const diagnostics = [];
12375
+ const conn = connect(opts.url, opts.tenantId, (message) => diagnostics.push(message));
12376
+ let probeStatus = null;
12356
12377
  if (conn.hasToken) {
12357
- const probe = await resolveActiveDraft(conn.url, conn.headers).catch(() => ({ ok: false, status: 0, draft: null }));
12358
- tokenValid = probe.ok || probe.status !== 401 && probe.status !== 403 && probe.status !== 0;
12359
- }
12378
+ probeStatus = await fetch(`${conn.url}/api/optimization/drafts`, {
12379
+ headers: conn.headers,
12380
+ signal: globalThis.AbortSignal.timeout(WHOAMI_REQUEST_TIMEOUT_MS),
12381
+ // A redirected login page returning 200 is not proof of authentication.
12382
+ redirect: "manual"
12383
+ }).then(async (response) => {
12384
+ await response.body?.cancel();
12385
+ return response.status;
12386
+ }).catch(() => null);
12387
+ }
12388
+ const tokenValid = classifyToken(conn.hasToken, probeStatus);
12360
12389
  const data = {
12361
12390
  instance: conn.url,
12362
12391
  tenant: conn.tenantId,
@@ -12370,12 +12399,13 @@ program.command("whoami").description("Show the resolved instance, tenant, crede
12370
12399
  data,
12371
12400
  Boolean(opts.json),
12372
12401
  [
12402
+ formatAuthentication(conn.hasToken, tokenValid),
12373
12403
  `instance: ${data.instance}`,
12374
12404
  `tenant: ${data.tenant} (${data.tenant_source})`,
12375
- `credentials: ${data.credentials_dir} [${data.credentials_source}]`,
12376
- `token: ${data.token_present ? tokenValid ? "present, valid" : "present, NOT accepted" : "absent \u2014 run `revturbine login`"}`
12405
+ `credentials: ${data.credentials_dir} [${data.credentials_source}]`
12377
12406
  ].join("\n")
12378
12407
  );
12408
+ for (const message of diagnostics) diag(message);
12379
12409
  });
12380
12410
  program.command("schema").description("Emit the bundled RevTurbineConfig JSON schema for an agent to author against.").option("--json", "Accepted for symmetry; output is always JSON").action(() => {
12381
12411
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revturbine/cli",
3
- "version": "0.18.0",
3
+ "version": "0.18.1",
4
4
  "description": "revturbine — validate RevTurbine Playbooks and ship them to a RevTurbine instance through the playbook-version lifecycle (draft → Release).",
5
5
  "license": "MIT",
6
6
  "repository": {