npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@uluops/cli

v0.47.0

Published

Unified CLI for UluOps — runs, findings, issues and the definition registry from a single command

Readme

UluOps · The operations layer for agentic work


@uluops/cli

npm version License: MIT Node.js TypeScript

Unified CLI for UluOps — runs, findings, issues, the definition registry, and executing agents, commands, workflows and pipelines against your AI provider (ulu exec), from a single command. Wraps both the ops-sdk and registry-sdk into an ergonomic terminal interface.

See the changelog for release history. The npm badge above tracks the published version.

Quick Start

Requires Node.js 20.3 or higher.

# Install
npm install -g @uluops/cli

# Authenticate
export ULUOPS_API_KEY=ulr_your-api-key-here

# Create a project and save a validation run
ulu projects create my-project
ulu runs save --file results.json
# → Run #1 saved for my-project (3 agents, 12 recommendations)

# Browse issues
ulu issues list my-project --status open --priority critical

# Check analytics
ulu analytics burndown --project my-project --days 30

# Run a validator agent (requires ANTHROPIC_API_KEY; parent options like
# --project work before OR after the subcommand, like --model)
export ANTHROPIC_API_KEY=sk-ant-your-key-here
ulu exec --project my-project agent code-validator -t ./src --model sonnet
# → code-validator: PASS (score 92) — results tracked under my-project

Table of Contents

Features

  • Unified interface: Single ulu command covers both the tracker (runs, findings, issues) and the definition registry APIs
  • The second history: ulu log interleaves a project's runs and decisions newest-first (--stat for the rollup, --orgs across every org you belong to); ulu orgs audit-feed for an org's member-visible activity
  • Command aliases: ulu p (projects), ulu r (runs), ulu i (issues), ulu a (analytics), ulu x (exec), ulu def (definitions)
  • Flexible authentication: API key, session token, or email/password — same credential chain as the SDKs
  • Machine-friendly output: --json flag on every command for scripting and CI/CD integration
  • Shell completion: Tab completion for bash, zsh, and fish
  • Contextual error hints: Actionable suggestions on auth failures, 404s, rate limits, and network errors — usage and examples shown on every error
  • Security-event warnings: Surfaces ⚠ Security: notices on stderr for a blocked upstream redirect (possible MITM), a rejected credential, or a failed token refresh — even on best-effort paths where the command itself does not error (suppress with -q)
  • Honest tracking signals: When a run succeeds but recording it to the tracker fails (e.g. a free-tier project-limit cap), the result surfaces Run not recorded: … with an upgrade link instead of silently dropping the dashboard link. When the run is saved but the tracker rejects some agent's analysis, Analysis rejected for <agent>: … says which
  • Spinner feedback: Progress indicators for long operations (suppress with -q)

Installation

# npm (global)
npm install -g @uluops/cli

# npx (no install)
npx @uluops/cli --help

# From source (monorepo)
cd packages/-uluops-cli
npm run build
node dist/cli.js --help

Requirements:

  • Node.js 20.3.0 or higher

Every model provider ulu exec supports — Anthropic, OpenAI, Google and OpenRouter — ships with the CLI; there is nothing else to install. Set the provider's API key to use it (see Providers and shell access).

Authentication

The CLI supports three authentication methods, resolved in priority order:

1. API Key (Recommended)

# Via flag
ulu projects list --api-key ulr_your-key

# Via environment variable (preferred)
export ULUOPS_API_KEY=ulr_your-api-key-here
ulu projects list

2. Session Token (Login)

ulu auth login
# Prompts for email and password, stores session in ~/.uluops/credentials.json

ulu auth whoami
# Shows current authenticated user

3. Email/Password (Environment)

export [email protected]
export ULUOPS_PASSWORD=your-password
ulu projects list

Credential Priority Chain

The CLI resolves credentials in this order:

  1. --api-key flag
  2. ULUOPS_API_KEY environment variable
  3. ULUOPS_EMAIL + ULUOPS_PASSWORD environment variables
  4. Session token stored by ulu auth login
  5. Profile credentials in ~/.uluops/credentials.json
  6. Local .env file in the current directory

Configuration

| File | Purpose | |------|---------| | ~/.uluops/credentials.json | Credentials per profile (API keys, session tokens) | | ./.env | Project-level environment overrides |

Global Options

Which org a command acts in

Every org-scoped command sends X-Org-Slug for the org it acts in (orgs audit-feed is the exception — the org is its argument, not a scope). Resolution, highest first: --org <slug>; the nearest .uluops.json above the current directory ({ "org": "ulu-labs" } — put one at the root of a work checkout; { "org": "personal" } in a personal repo nested under it stops the walk); ULUOPS_ORG_SLUG — from your shell, or from ./.env / ~/.uluops/.env, which the CLI loads at startup; else your personal org. The API never infers an org from a project name. ulu runs save prints where the run landed. ulu exec hands the same org to @uluops/core. Registry commands (ulu def, versions, forks, deps, …) send the org on definition writes only (create, update, publish, deprecate, archive, delete, fork); registry reads carry no org header and return the marketplace view, with your org's private definitions included by membership — filter by namespace (in --json output) to tell orgs' same-named rows apart. Before 0.33.0 registry commands ignored the org entirely. Prompts that name the source (projects rehome) print its provenance in full — the path of the .uluops.json that answered, or env-file when the variable came from a file rather than the shell — so a stray .env in a checkout cannot pass as "my shell".

Every command accepts these flags:

--api-key <key>      Override API key (env: ULUOPS_API_KEY)
--profile <name>     Config profile to use (default: 'default')
--base-url <url>     Override the base URL of the API the command talks to (tracker or registry)
--timeout <ms>       Request timeout in milliseconds (default: 30000 for ops/registry, 600000 for exec)
--json               Output raw JSON for scripting
--json-envelope      Wrap --json output in the versioned stability envelope (same as ULU_JSON_SCHEMA=1)
--debug              Enable debug output
-q, --quiet          Suppress spinners and non-essential output
-V, --version        Show CLI version
-h, --help           Show help for any command

Security warnings

The CLI surfaces security-relevant events (from @uluops/sdk-core) as ⚠ Security: notices on stderr, so stdout stays clean for --json and pipes. They complement the normal error — you get both the plain-language notice and the detailed error — and are most valuable on best-effort paths (e.g. result tracking) where the command itself does not error, so the event would otherwise be invisible.

| Notice | Meaning | |--------|---------| | Blocked redirect | The configured API origin returned a 3xx the CLI refuses to follow — a possible man-in-the-middle, moved endpoint, or captive portal. The request is not sent to the redirect target (its body, which can carry credentials on login, is never replayed). | | Credential rejected (401) | The server rejected your credential — expired, revoked, or invalid. Includes the server request id for correlation. | | Token refresh failed | A session token could not be refreshed. Re-authenticate with ulu auth login. |

Suppress all of them with -q, --quiet.

Command Reference

Auth

Authentication and credential management.

ulu auth login                    # Login with email/password
ulu auth logout                   # Revoke all sessions
ulu auth whoami                   # Show current user
ulu auth register                 # Register new account
ulu auth forgot-password          # Request password reset email
ulu auth reset-password           # Reset password with the emailed token (or the whole link)
ulu auth set-password             # Set a FIRST password on an account that has none (API key / OAuth-only)
ulu auth change-password          # Change current password (needs the current one)
ulu auth profile                  # View user profile
ulu auth update-profile           # Update profile (display name, bio, avatar)
ulu auth sessions list            # List active sessions
ulu auth sessions revoke <id>     # Revoke a session
ulu auth api-keys list            # List API keys
ulu auth api-keys create          # Create new API key (--name, --scope <read|write>, --expires <ISO date>)
ulu auth api-keys rotate <id>     # Rotate a key: re-mint preserving name+scope, revoke the old
ulu auth api-keys revoke <id>     # Revoke an API key

Examples:

# Register and create an API key
ulu auth register --email [email protected] --password mypassword
ulu auth api-keys create --name "CI Pipeline"
ulu auth api-keys create --name "CI Pipeline" --expires 2026-12-31  # optional expiry
ulu auth api-keys create --name "readonly-briefing" --scope read     # a key that cannot write
# Save this key: ulr_abc123... (shown once)

# Login interactively
ulu auth login --email [email protected] --password mypassword

# Check who you are
ulu auth whoami

Projects

Names that start with - (e.g. a project named after the repo -uluops-core) look like options to the argument parser: ulu projects get -uluops-core fails with unknown option. Put the name after --, with any flags before it: ulu projects get -- -uluops-core, ulu issues list --status open -- -uluops-core. Quoting does not help (the shell removes the quotes); a flag that takes the name accepts it with = (--project=-uluops-core). Since 0.33.1 the error itself prints the command to run.

Project lifecycle management. Alias: p.

ulu projects list                 # List all projects
ulu projects get <name>           # Get project details
ulu projects create <name>        # Create new project
ulu projects delete <name>        # Soft delete (--force for hard delete)
ulu projects restore <name>       # Restore soft-deleted project
ulu projects summary <name>       # Project summary with issue counts
ulu projects trends <name>        # Issue trends over time
ulu projects rename <name>        # Rename project (--new-name required)
ulu projects rehome <name>        # Move project to another org (--to required; source = --org)
ulu projects bulk-update-issues <name>   # Batch update issue statuses
ulu projects merge-issues <name>         # Merge duplicate issues

Examples:

# Create a project and check its summary
ulu projects create my-app
ulu projects summary my-app

# View trends for the last 90 days
ulu projects trends my-app --days 90

# Rename a project
ulu projects rename old-name --new-name new-name

# Soft delete and restore (delete prompts for confirmation; pass -y to skip)
ulu projects delete my-app -y
ulu projects restore my-app

# Move a project (and its whole history) from org acme to org ulu-labs.
# The SOURCE is the org this command is scoped to (--org, else the nearest
# .uluops.json, else ULUOPS_ORG_SLUG, else your personal org) — the project is
# looked up THERE. Needs admin/owner in both orgs.
ulu projects rehome my-app --org acme --to ulu-labs --reason "team took it over"

rehome prompts with both orgs and the source's provenance (move project "my-app" from org acme (workspace /path/to/.uluops.json) to org ulu-labs at …?); pass -y to skip — the prompt is the only place the source is shown before the write, so with -y the success line repeats base URL and provenance and is the record a script keeps. --to takes the target's real slug (personal is refused). After the move the old address in the source org is a tombstone — an org-less write there answers 410 PROJECT_REHOMED naming the new org instead of forking a new project; moving back is the same command the other way. A re-run of the same command after the move answers 404, because the project is looked up in the source org where it no longer is — same_org ("already there") is the answer on the admin path, whose lookup is by id; the CLI's hints say which is which. Other refusals name their reason and the CLI hints each: name_collision / soft_deleted_conflict / rehomed_away_conflict (the name is taken in the target), project_soft_deleted, export_in_progress, moved_during_request; 402 PROJECT_LIMIT means the target org is at its project cap (not a subscription gate); 403 ORG_ACCESS_DENIED on a move is usually about the target.


Orgs

Reads an org's member-visible activity. Org creation, membership and invitations are dashboard surfaces. --org does not apply here — the org is the positional argument.

ulu orgs audit-feed <slug>        # Org-visible audit feed (any member); ACTOR = user id prefix, system (org purge) or unknown; --limit 1–100, --cursor, --json

The feed carries the rows an org's writers marked visible to every member — today, projects that left the org for someone's personal org (an org admin may do that; the feed is how the org's owner sees it). Each row is one line: when, who, and "billing" moved to alexself2 (personal org) — reason. Page with the --cursor the previous page printed; --json emits the raw envelope (data.entries[], count, hasMore, nextCursor). The feed carries only moves into a personal org (D19) — an admin's org→org move is not in it. Rendered text is another member's free text (the reason, the project name): the CLI strips terminal control sequences and flattens newlines before printing it.


Log — the project's second history

Git remembers what changed. ulu log remembers what was decided: runs (what was examined) and decisions (what was decided, with reasons) interleaved newest first, plus what came back.

ulu log [project]                 # The stream, newest first, 50 events a page (-n 1-500; --cursor to continue)
ulu log [project] --stat          # The rollup: examined / found / decided / came back / activity
ulu log --orgs                    # The rollup per org you belong to, with a per-project table (implies --stat)
ulu log --since <iso> --until <iso> --kind run,decision,regression --workflow <type> --agent <name> --include-archived
ulu log --no-collapse             # One line per issue instead of collapsing same-second identical decisions
ulu log --json                    # The API's page or rollup, byte-for-byte; never collapsed

Which project. --project <name> / -p, then the argument, then the project key of the nearest .uluops.json — the same file that names the org, so both come from one place — then ULUOPS_PROJECT, then an error listing your projects. The file's project key governs reads only: ulu exec keeps its own resolution (--project, ULUOPS_PROJECT, the directory name) and can track under a different name than ulu log reads — a walked file never decides where a run lands, only what is shown. A file carrying project must also carry org ("personal" for no org) or it is refused, so a project-only file can never shadow an outer org. A refused file (owned by another user, unparseable, an unknown key) stops resolution with its own message. When the nearest file names an org but no project and ULUOPS_PROJECT answers, one stderr line says so.

Before you add project to a shared checkout: every tool that reads .uluops.json in that tree must be on @uluops/ops-sdk ≥ 6.5.0 — older readers refuse the key for every command, not only ulu log. This CLI (≥ 0.31.0, with @uluops/core ≥ 0.43.5) is; the tracker MCP is from the release that pins ops-sdk 6.5.0. Check with find <tree> -path '*/@uluops/ops-sdk/dist/config/workspace-org.js' and read each copy's package.json version.

Reading the stream. run #47 2026-09-13 14:02 security-audit [email protected] +2 score 82, gates ok then its counts (new 3 . recurring 1 . regression 1; - on runs saved before counts were recorded). regressed is a finding a run re-detected, with via run #N (via run ? when that run is archived). decided is a ledger row: deferred <- open "reason"; no reason recorded is the ledger's silence, not a person's; by agent prints only when the ledger says so — nothing printed means unattributed, never human; open <- wontfix without a run is reopened by decision. Same-second identical decisions collapse into one line with an issue count (a bulk status change); the footer says how many, and --no-collapse expands them. Collapse is per page: a group across a page boundary shows as two lines. Run times are as reported by the saving client; within a second, order is as recorded — for events recorded before millisecond timestamps (migration 080) it is not meaningful. Times are UTC. Long reasons are cut at 120 characters on the page; --json carries the full text.

Reading --stat. Two frames on two clocks: Decided is the current status of findings first seen in the window (run times); Activity is what changed in the window (ledger times). With no window both say all time. completed prints as fixed. Came back counts distinct findings — caught by re-running (a run re-detected it; any window for the last date) and reopened by decision. Under Activity, reopened counts transitions into open.

--orgs lists every org you belong to and prints each org's rollup with a per-project table (name, runs, findings, fixed, regressions; last run first, capped at 100). The org rollup is cached 60 s server-side — the as of time is when the numbers were computed. A key bound to one org lists all your orgs but can read only its own; the others are noted on stderr and skipped.


Runs

Validation run management — save, compare, and archive pipeline results. Alias: r.

ulu runs list <project>           # List runs (--workflow, --limit)
ulu runs get <runId>              # Get run by UUID
ulu runs latest <project>         # Get latest run (--workflow)
ulu runs details <project>        # Detailed run with agents/recommendations (-n for a specific run number)
ulu runs save                     # Save run from JSON (--file or --stdin; -p/-w override input fields)
ulu runs validate                 # Dry run — preview against the live tracker, incl. analysis a save would reject (requires auth; -p/-w override)
ulu runs diff <project>           # Compare two runs (--base, --compare)
ulu runs archive <project>        # Archive old runs (--before-run, --before-date, --keep-last, --reason)
ulu runs update <project>         # Update run metadata or repair its analysis (--number, --score, --passed, --file, --preview, --record-mode, --allow-retire)
ulu runs delete <runId>           # Delete a run

Examples:

# Save a validation run from a JSON file
ulu runs save --file validation-results.json

# Save from stdin (pipe from another tool)
cat results.json | ulu runs save --stdin

# Override the project or workflow type in the JSON without editing the file (handy in CI)
ulu runs save --file results.json -p override-project -w post-implementation

# Dry run to preview what would happen
ulu runs validate --file results.json

# Compare run 1 vs run 5
ulu runs diff my-project --base 1 --compare 5

# Get latest run details
ulu runs latest my-project --workflow post-implementation

# Archive all but the last 10 runs
ulu runs archive my-project --keep-last 10

# Update token counts on a run after the fact
ulu runs update my-project --number 5 --file token-update.json

# Set the gates-passed flag on a run after the fact
ulu runs update my-project --number 5 --passed true

# Repair analysis the tracker rejected on save (see "Repairing rejected analysis" below)
ulu runs update my-project --number 5 --file fix.json --preview   # nothing written
ulu runs update my-project --number 5 --file fix.json

Note: runs update --score is rejected on finalized runs ("Cannot rewrite averageScore"). Use the --file form to patch per-agent fields. Each agent entry in the file must include name and decision (any other fields like tokens are merged into the existing record).

Repairing rejected analysis

A save keeps the run when one agent's analysis is invalid and rejects that agent's analysis as a unit; ulu runs save and ulu exec then print Analysis rejected for <agent>: … with the server's issues. ulu runs validate prints the same verdict before you save (Would reject analysis for <agent>: …). Under --json the lines go to stderr and stdout stays the JSON. The exit code stays 0: the run was saved. Do not save the run again, which creates a second run (an identical resubmission is deduplicated instead, and then the printed rejections are the earlier save's, not a verdict on the new input). The whole unit was dropped, including entries the printed issues do not name, so put each rejected agent's complete records and summary in a file. Use the runs save field names, and include only the rejected agents' analysis: no agents, and nothing for agents whose analysis was stored (naming them would replace their analysis too):

{
  "analysis_records": [
    { "agent_name": "agent-h", "record_type": "finding", "record_id": "h-1", "title": "…", "data": { "note": "…" } }
  ],
  "analysis_summary": [{ "agent_name": "agent-h", "decision": "PASS" }]
}

The same fields work in runs save input. snake_case or camelCase both work.

| entry | field | required | notes | |---|---|---|---| | record | record_type | yes | your category (finding, fear, assumption, …); up to 50 chars | | record | record_id | yes | short stable slug per agent, up to 100 chars; the key merge and replace match on | | record | title | yes | up to 500 chars | | record | data | yes | object; nested at most 8 levels | | record | agent_name | on multi-agent runs | names the agent the record belongs to | | record | classification | no | up to 50 chars | | record | severity | no | critical, high, medium, low or info | | summary | decision | yes | the agent's decision (PASS, VITAL, …); up to 50 chars | | summary | agent_name | on multi-agent runs | | | summary | score | no | 0–100 | | summary | decision_vocabulary, system_metrics, category_scores, epistemic_assessment, audit_implications, exploration_maps | no | as the agent reports them; category_scores entries are { name, weight, score } with integer weight ≥ 1 and integer score |

One update carries at most 100 records and 20 summaries; send a larger set in parts with --record-mode merge. When a save rejects an entry, the issue printed under Analysis rejected for … names the field (records[1].data: …).

Send the file with ulu runs update <project> -n <run> --file fix.json. runs save prints that command with the run filled in. After ulu exec, the run is the one in the Dashboard link. ulu runs latest can return a different run on a busy project. Records are written per agent, under one of two modes:

| --record-mode | records of the named agents | summaries | |---|---|---| | replace (default) | superseded; any record left out of the file is retired | replaced | | merge | only the record_ids sent are superseded; nothing is retired | replaced |

A rejected agent has no stored analysis, so either mode repairs it. On an agent that does have records, a replace write retires (stops counting as live analysis; only superseded-history reads still show them) every record the file leaves out. So the cli previews every analysis write first, and refuses a replace write that would retire records, naming them, unless you pass --allow-retire. With it, the retired ids are printed after the write. The refusal names the alternatives and what each costs: send the missing records too; or --record-mode merge, which keeps them live, so a record whose record_id you changed stays beside its new version. --preview shows the full plan (what each agent's write would create, supersede and retire) and writes nothing. Unlike a save, an update is refused whole if any entry is invalid, so a bad entry costs nothing.

The preview also catches a tracker that ignores --record-mode: the write is then not attempted ("Nothing was written"). If the cli instead reports that the update was applied but the server's echo did not match, it exits 5: do not re-run it (that applies it again); check the run with ulu runs details. The SDK's own explanation is printed beneath. Under --json, both the refusal and the mismatch are JSON on stderr, with code (WOULD_RETIRE_RECORDS, ANALYSIS_ECHO_MISMATCH) and applied. A successful analysis update's JSON is { run, analysisWrite, retiredRecordIds } (kind run.updateAnalysis); --preview --json is the server's plan (run.updatePreview).

Run Input Format

The JSON file for ulu runs save follows this structure (analysis_records and analysis_summary are optional; their fields are under Repairing rejected analysis):

{
  "project": "my-project",
  "workflow_type": "post-implementation",
  "agents": [
    {
      "name": "code-validator",
      "score": 85,
      "decision": "PASS",
      "model": "sonnet",
      "tokens": { "input_tokens": 1000, "output_tokens": 500 }
    }
  ],
  "recommendations": [
    {
      "agent": "code-validator",
      "title": "Missing error handling",
      "priority": "suggested",
      "severity": "medium",
      "file_path": "src/api/client.ts",
      "line_number": 42
    }
  ],
  "summary": {
    "average_score": 85,
    "all_gates_passed": true
  }
}

Issues

Issue tracking and management. Alias: i.

ulu issues list <project>         # List issues with filters
ulu issues get <id>               # Get issue (--full for occurrences/notes)
ulu issues search                 # Search across projects (--query, --projects to scope)
ulu issues create                 # Create user-submitted issue (--failure-code, --domain link to taxonomy)
ulu issues update <id>            # Update status (--status, --reason)
ulu issues close <id>             # Mark as completed (--reason)
ulu issues edit <id>              # Edit metadata (--title, --severity, etc.)
ulu issues add-note <id>          # Add note (--message, --type)
ulu issues history <id>                          # Show timeline by UUID
ulu issues history <fingerprint> --project <slug> # Resolve fingerprint then show timeline
ulu issues history --project <slug>              # Picker: list recent issues (--limit), then drill in
ulu issues undo <id>              # Undo last status change
ulu issues restore <id>           # Restore soft-deleted issue
ulu issues bulk-update            # Bulk update statuses (--ids, --status)
ulu issues by-fingerprint <fp> --project <name>        # Get issue by SHA-256 fingerprint
ulu issues update-by-fingerprint <fp> --project <name> # Update by fingerprint

Examples:

# List open critical issues
ulu issues list my-project --status open --priority critical

# Search across all projects (or scope to specific ones with --projects)
ulu issues search --query "authentication" --status open
ulu issues search --query "timeout" --projects my-app,my-api

# Create an issue manually. Pass --failure-code and --domain to link it into
# the failure taxonomy — without them the issue is excluded from domain/code analytics.
ulu issues create --project my-project \
  --title "SQL injection in login" \
  --priority critical \
  --severity critical \
  --type security \
  --failure-code SEM-INC/C \
  --domain SEM \
  --file-path src/auth/login.ts \
  --line 45

# Close an issue with a reason
ulu issues close abc123 --reason "Fixed in PR #42"

# Add a resolution note
ulu issues add-note abc123 --message "Root cause: race condition" --type resolution

# Undo an accidental status change
ulu issues undo abc123

# Bulk close multiple issues
ulu issues bulk-update --ids id1,id2,id3 --status completed --reason "Batch fix"

# Browse recent issues then drill into one (picker mode, v0.13.0)
ulu issues history --project my-project          # list recent issues
ulu issues history a1b2c3d4 --project my-project # then drill in by fingerprint

v0.13.0 breaking change (--json): ulu issues history --json now emits the IssueHistoryEnvelope shape { issueId, events, totalEvents, truncated } instead of a flat StatusHistory[] array. Scripts doing result[0] or Array.isArray(result) must migrate to result.events[0]. The events[] array contains discriminated entries with type: 'occurrence' | 'status' | 'note' — narrow on type before accessing event-specific fields. See CHANGELOG for the full migration story.

Filter options for issues list:

| Flag | Values | |------|--------| | --status | open, completed, deferred, wontfix, all | | --all | Shorthand for --status all | | --priority | critical, high, suggested, backlog | | --severity | critical, high, medium, low, info | | --agent | Any agent name (e.g., code-validator) | | --domain | STR, SEM, PRA, EPI | | --include-resolved | Include resolved (completed/wontfix/deferred) issues in results | | --limit | Max results (default: 50) |


Analytics

Validation analytics and trend metrics. Alias: a.

ulu analytics agents              # Agent performance (avg score, pass rate)
ulu analytics reliability         # Agent reliability (false-positive, declined, resolution rates)
ulu analytics hotspots            # Files with most issues
ulu analytics burndown            # Taxonomy burndown time series
ulu analytics velocity            # Rate of change per failure mode
ulu analytics discovery           # New vs recurring issues timeline
ulu analytics matrix              # Agent-taxonomy coverage matrix (--min-issues to threshold)
ulu analytics resolution          # Issue resolution rates by project (cross-project; no --project flag)
ulu analytics taxonomy            # Taxonomy distribution
ulu analytics full-taxonomy       # Full taxonomy analytics breakdown
ulu analytics trends              # Trend summary metrics

Examples:

# Which agents are most effective?
ulu analytics agents --project my-project --days 30

# Which files keep generating issues?
ulu analytics hotspots --project my-project --limit 10

# Are we closing issues faster than opening them?
ulu analytics burndown --project my-project --days 90 --granularity weekly

# Which failure modes are growing fastest?
ulu analytics velocity --project my-project --threshold 50

# New issues vs recurring — is validation finding new problems?
ulu analytics discovery --project my-project --group-by week

# Coverage gaps — which domains lack agent coverage?
ulu analytics matrix --project my-project
ulu analytics matrix --project my-project --min-issues 3  # only failure modes seen 3+ times

# Reliability — which agents have high false positive rates?
ulu analytics reliability --days 90

Taxonomy

Inspect the failure taxonomy schema.

ulu taxonomy get                  # Display domains, severity codes, statuses

Displays the four failure domains (STR, SEM, PRA, EPI), their failure modes, severity levels, and priority tiers.


Definitions

Workflow definition management (registry API). Alias: def.

ulu definitions list              # List definitions (--type, --status, --search, --domain, --visibility, --limit, --offset)
ulu definitions get <type> <name> [version]   # Get definition (--yaml, --rendered, --target, --render-profile, --include-runtime, -m/--model, -o)
ulu definitions create <type> <name>          # Create draft (--file, --visibility public|private)
ulu definitions update <type> <name> <ver>    # Update draft (--file, --display-name, --description, --visibility); --change-type major|minor|patch creates a new version from a published one
ulu definitions publish <type> <name> <ver>   # Publish definition
ulu definitions deprecate <type> <name> <ver> # Deprecate (--reason, --successor)
ulu definitions validate [type]               # Validate YAML (--file, type auto-detected)
ulu definitions render [type]                 # Render YAML preview (--file, --render-profile core|uluops-full, type auto-detected)
ulu definitions delete <type> <name> <ver>    # Delete draft (--yes)

Definition types: agent, command, workflow, pipeline

Examples:

# List all published agents
ulu definitions list --type agent --status published

# Get a definition (YAML output, latest version)
ulu def get agent code-validator --yaml

# Get a specific version
ulu def get agent code-validator 1.10.2 --yaml

# Get rendered markdown for a published definition
ulu def get agent code-validator --rendered

# Render for a specific harness (claude-code, opencode, codex, gemini-cli)
ulu def get agent code-validator --rendered --target opencode

# Render for a harness with a model-specific override
ulu def get agent code-validator --rendered --target gemini-cli -m gemini-2.5-pro

# Write rendered output to file instead of stdout
ulu def get agent code-validator --rendered -o ./code-validator.md

# Validate and render a local YAML file (type auto-detected from filename)
ulu def validate --file my-agent.agent.yaml
ulu def render --file my-agent.agent.yaml

# Render the declared-only core profile: only what the definition declares, with no
# factory-supplied output contract, scoring instruction or tone (registry on definition-factory
# >= 0.81.0). Omitting --render-profile renders the registry's stored profile, uluops-full.
ulu def render --file my-agent.agent.yaml --render-profile core

# Create, validate, and publish a new agent
ulu def validate agent --file my-agent.yaml
ulu def create agent my-agent --file my-agent.yaml
ulu def publish agent my-agent 1.0.0

# Deprecate an old version
ulu def deprecate agent old-agent 1.0.0 \
  --reason "Replaced by new-agent" \
  --successor [email protected]

Versions

Definition version history and comparison.

ulu versions list <type> <name>                  # List version history
ulu versions diff <type> <name> <from> <to>      # Compare two versions

Deps

Dependency graph inspection.

ulu deps get <type> <name> <version>         # Show dependency graph (--max-depth, --tree)
ulu deps dependents <type> <name> <version>  # Show reverse dependencies

Examples:

# Flat indented dependency list (default; each line tagged with its depth)
ulu deps get workflow ship 1.0.0

# Recursive tree view with [context] edge labels (v0.13.0)
ulu deps get workflow ship 1.0.0 --tree

# Limit traversal depth
ulu deps get workflow ship 1.0.0 --max-depth 2

# Reverse lookup with ← context attribution
ulu deps dependents agent code-validator 1.0.0

v0.13.0 changes: deps get output format changed (no longer prints Edges: N or cycle warnings — the registry never tracked those). The new --tree flag renders the recursive DependencyNode graph with [context] labels per edge (e.g., [invokes.agent], [stage "Final Checks"]). deps dependents now shows ← context arrows so operators can see how each consumer references the target.


Forks

Definition forking and lineage.

ulu forks list <type> <name> <version>       # List forks
ulu forks create <type> <name> <version>     # Fork definition (--fork-name, --visibility, --display-name, --description)
ulu forks check <type> <name> <version>      # Check if forkable
ulu forks lineage <type> <name> <version>    # Show fork ancestry chain

Languages

Definition language schemas. Alias: lang.

ulu lang                          # List all languages with versions
ulu lang adl                      # Get ADL metadata
ulu lang adl --json               # Full output with JSON Schema content
ulu lang adl -o adl-schema.json   # Write JSON Schema to file

Models

AI model catalog.

ulu models list                   # List models (--provider, --tier, --capability vision|tools|reasoning, --status)
ulu models get <provider> <id>    # Get model details
ulu models get openrouter deepseek/deepseek-v4-flash   # ids containing "/" work too
ulu models providers              # List providers
ulu models aliases                # List model aliases
ulu models resolve <alias>        # Resolve alias to concrete model

Note: ulu models providers lists the providers the platform catalog knows about. ulu exec executes against any provider whose API key is set (see Providers and shell access); the catalog listing does not imply a key is configured.


Exec

Execute agents, commands, workflows, and pipelines. Alias: x.

# Auto-detect definition type and execute
ulu exec run <name> <target>

# Execute an agent
ulu exec agent code-validator -t ./src --model sonnet

# Execute multiple agents in parallel (default concurrency: 5)
ulu exec agent code-validator security-analyst test-architect -t ./src

# Execute a saved command configuration
ulu exec command my-command ./src

# Execute a multi-phase workflow
ulu exec workflow post-implementation ./

# Execute a multi-stage pipeline
ulu exec --project my-project pipeline foundations ./

# List available definitions
ulu exec list                          # All definitions
ulu exec list --type agent             # Filter by type (agent, command, workflow, pipeline)
ulu exec list --domain security        # Filter by domain

# Inspect a definition's metadata
ulu exec describe code-validator
ulu exec describe socrates-explorer --type agent       # Disambiguate when name exists across types
ulu exec describe [email protected]                  # Inspect a specific version (@version suffix)
ulu exec describe code-validator -v 1.2.0               # Same — explicit -v flag
ulu exec describe code-validator --def-version 1.2.0    # Same — long form is --def-version (the global -V/--version shadows a plain --version)
ulu exec describe                                       # No name → list all definitions
ulu exec describe --type pipeline                       # No name + --type → filter the list

Tracking notice. When a run executes successfully but recording it to the tracker fails (e.g. a free-tier 402 PROJECT_LIMIT or SUBSCRIPTION_REQUIRED), the result prints a non-fatal Run not recorded: <message> line — with — upgrade: <url> appended for cap/tier failures — in place of the dashboard link. The run itself still succeeds.

Analysis notice. A run can be saved while the tracker rejects one agent's analysis (an invalid record or summary): the run, its agents and its recommendations are kept, and that agent's analysis is not. The result then prints Analysis rejected for <agent>: <reason> (<n> records, <m> summaries) per rejected agent, and a line saying the run is saved. Do not run it again to fix this, which creates a second run; repair the analysis on the same run with ulu runs update <project> -n <run> --file <analysis.json> (see Repairing rejected analysis). ulu runs save prints the same lines.

Parent options (apply to all subcommands):

| Option | Description | |--------|-------------| | --local-definitions <dir> | Local YAML definitions directory | | --project <name> | Project name for result tracking | | --no-tracking | Disable validation service submission | | --no-safety-warnings | Suppress risk warnings and runtime advisories | | --registry-url <url> | Override the registry URL definitions are resolved from | | --extended-thinking / --no-extended-thinking | Turn extended thinking on or off for this run (every subcommand). Off by default; overrides ULUOPS_EXTENDED_THINKING. Thinking tokens are billed as output. Works on OpenAI, Google and OpenRouter models; on direct Anthropic models (the default sonnet) it prints "requested but not applied: no-mapping", because that mapping is not built yet — use an openrouter:anthropic/… model for Claude thinking. What each run actually did is recorded on the result (runConditions). |

Parent options work before OR after the subcommand (since 0.21.2). They belong to ulu exec, not the subcommand, but the CLI relocates them automatically — both ulu exec --project foo agent code-validator ./src and ulu exec agent code-validator ./src --project foo work. (Placed after the subcommand they were once silently ignored, then loudly rejected; now they just work, like --model.)

Tracking requires a named project. When tracking is on and no project is resolved (--project or ULUOPS_PROJECT), the CLI shows the project name the run would be tracked under and asks you to confirm it — rather than silently inventing one from the target directory. In a non-interactive context (CI, piped stdin) it fails closed (exit 1) instead of prompting; pass --project <name>, set ULUOPS_PROJECT, or --no-tracking.

Safety advisories. Registry definitions carry a publish-time safety scan; ulu def get renders it, and exec agent prints a pre-run advisory when the definition has risk signals or when the scan cannot be trusted as clean: "Safety scan incomplete — could not determine" (sync scan aborted, 0.22.3) and "Deep safety analysis failed — verdict is sync-only" (background deep audit errored, 0.23.0). Absence of signals is not a clean verdict in either state; an actual sync risk signal outranks both advisories. --no-safety-warnings suppresses them.

Shared options (all exec subcommands):

| Option | Description | |--------|-------------| | -p, --prompt <text> | Operator directive or context for the agent | | -m, --model <model> | Model override (alias, tier, or provider:modelId, e.g. openai:gpt-5, openrouter:deepseek/deepseek-v4-flash). The provider's API key must be set; see Providers and shell access. | | --hash <sha256:...> | Optional. Pin the expected YAML hash (from a trusted channel). Verifies the resolved definition source + config before executing; refuses on mismatch (exit 4). Available on run, agent, command, workflow, and pipeline (requires @uluops/[email protected]). | | --prompt-hash <sha256:...> | Optional. Pin the expected rendered-prompt hash. Pair with --hash for full agent/command executed-prompt integrity. Only run, agent, and command — workflows/pipelines have no rendered prompt, so supplying it for one is refused as "unavailable" (exit 4). |

Agent-specific options (exec agent only):

| Option | Description | |--------|-------------| | -t, --target <path> | Required. Target directory to analyze | | --max-tokens <n> | Maximum response tokens | | --max-steps <n> | Maximum tool loop iterations (default: 50) | | --temperature <n> | Generation temperature 0-1 (default: 0) | | --exec-timeout <ms> | Execution timeout in milliseconds | | -c, --concurrency <n> | Max concurrent agents for parallel execution (default: 5) | | --threshold-pass <n> | Pass threshold (agents): a score below it caps a positive verdict to conditional. The agent's own decision text is kept; the category gates | | --threshold-warn <n> | Warn threshold (agents): a score below it caps the verdict to negative | | --report [path] | Write a human-readable, publication-quality report to file (single agent only). With no path, defaults to ./<agent>-report-<YYYYMMDDTHHmmss>.md in cwd. Injects a report-mode directive into the agent's prompt and disables structured-output enforcement so the model can emit prose. Disables tracker submission (implies --no-tracking): the schema-validated path the tracker depends on is no longer guaranteed under report mode, so --report silently wins over --project rather than erroring. With --project (or ULUOPS_PROJECT) set, a "tracking disabled" notice prints even under -q. Run without --report for tracker submission. | | -o, --output <path> | Explicit output path for --report (overrides the --report argument and the default) | | --features-list <path> | Write structured features/recommendations to file (single agent only) |

Run completeness. Agent output shows a Completeness: badge next to the decision when a run did not fully finish its work — PARTIAL or FAILED (clean runs stay uncluttered). This is separate from the decision: a PASS · PARTIAL means a positive verdict reached on incomplete coverage. Run with --debug to list the underlying degradation markers (e.g. budget.forced-wrap-up, steps.near-exhaustion, extraction.low-confidence) and why each fired. Requires @uluops/[email protected].

Concurrency. -c/--concurrency caps how many agent definitions run in parallel. A separate, engine-wide ceiling on concurrent in-flight LLM calls (across workflow phases, parallel steps, and inline agents) is set by the ULUOPS_MAX_CONCURRENCY env var (default 8).

Examples:

# Run code-validator with a specific model
ulu exec --project my-project agent code-validator -t . --model sonnet

# Generator: tell the agent what to create (generators produce artifacts, not
# scores — the result has no score; `--json` shows score: null)
ulu exec agent aristotle-generator -t ./src \
  -p "Create a health check endpoint for the Express API"

# Validator: provide focus context
ulu exec agent security-analyst -t ./src \
  -p "Focus on the authentication middleware and JWT handling"

# Use local definitions instead of registry
ulu exec --project my-project --local-definitions ./agent-defs \
  agent my-validator -t ./src

# Produce a publication-quality report (cwd default destination)
ulu exec agent wittgenstein-analyst -t ./docs --report

# Same, with explicit destination
ulu exec agent wittgenstein-analyst -t ./docs --report -o ~/my-report.md

# Execute without tracking results
ulu exec --no-tracking workflow ship ./packages/api

# Inspect what a definition expects
ulu exec describe code-validator

# Pin integrity hashes (from a trusted channel) — refuses with exit 4 on mismatch
ulu exec agent code-validator -t ./src \
  --hash sha256:… --prompt-hash sha256:…

# Pin a pipeline or command the same way (CI: pin what you run, especially
# with bash enabled — the YAML pin covers the whole workflow/pipeline runtime)
ulu exec pipeline api-server-validate ./src --hash sha256:…
ulu exec command validate ./src --hash sha256:… --prompt-hash sha256:…

Integrity pins are optional. --hash verifies the YAML (source + config); --prompt-hash verifies the rendered prompt (agents/commands). Use both for full agent/command integrity. On mismatch — or if --prompt-hash is given for a definition with no rendered prompt — execution is refused with exit code 4 (distinct from 1 usage/config and 2 API/runtime). exec workflow/pipeline have no rendered prompt; pin their YAML with --hash only — for WDL/PDL the YAML is the runtime, so that pin alone fully covers execution (stage/phase refs resolve downstream and are not individually pinned).


Providers and shell access

ulu exec runs on any provider whose API key is set in the environment (ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY, OPENROUTER_API_KEY, …); pick one with --model provider:modelId. Anthropic, OpenAI, Google and OpenRouter are bundled.

OpenRouter (experimental). Bundled since CLI 0.34.1 — set the key and pick a model:

export OPENROUTER_API_KEY=your_openrouter_key
ulu exec agent code-validator -t ./src --model openrouter:deepseek/deepseek-v4-flash

The CLI carries @openrouter/ai-sdk-provider at the exact version its @uluops/core is built against (2.10.0, which targets the AI SDK 6 that core runs; the package's npm latest is 3.x and targets AI SDK 7). If you installed it globally for an earlier CLI (npm install -g @openrouter/[email protected]), that copy is no longer used and can be removed with npm uninstall -g @openrouter/ai-sdk-provider. Using @uluops/core as a library instead of the CLI? There you still install the provider yourself — see core's notes linked below. OpenRouter stays opt-in: nothing is sent to it unless OPENROUTER_API_KEY is set and you select an openrouter: model. Everything the agent reads, and any shell output, goes to OpenRouter and on to whichever upstream serves the request. Since 0.36.0 (core 0.49.0) the CLI asks OpenRouter only for upstreams that do not retain or train on prompts (provider.data_collection: 'deny'). A model that only data-collecting upstreams serve — most :free models — then fails with a "Filter by Data Policy" error. To allow collection for one command, prefix it: OPENROUTER_DATA_COLLECTION=allow ulu exec …. Avoid exporting it from a shell profile: it then applies to every later run, on private code too, and the only sign is a warning on each run's first OpenRouter request. deny is OpenRouter's per-provider policy flag, not zero data retention; your OpenRouter account's privacy settings (openrouter.ai/settings/privacy) are the setting that actually enforces it. See @uluops/core's OpenRouter notes.

Cost. Each run prints Cost: $<total> (<basis>) under Token Usage. The basis says what the total is made of: billed (the provider's own bill — OpenRouter reports one per request, including BYOK, where it is OpenRouter's fee plus your upstream key's charge), estimated (tokens priced at the registry's catalog rates), or mixed. When the bill and the estimate differ, an Estimated cost line follows for reconciliation. Cost: unknown means a model could not be priced or an agent failed after spending; runs that call no model print no cost. Costs print to six decimals, since one OpenRouter request can cost $0.000003. --json carries the raw costUsd, costUsdBilled, costUsdTotal and costBasis fields. Cost is shown locally only: it is not sent to the Tracker.

When OpenRouter refuses. Out of credit (402) stops an exec pipeline run and fails it (exit 1, later stages skipped, the provider's text printed). Since 0.37.0 (core 0.50.0) the agents the stop cut off are recorded ABORTED with no finding, so no tracker issue is filed for them; the agent that received the 402 keeps its crash record; if the refusal says the request's worst case exceeds the balance, a lower --max-tokens is enough. An unknown slug is reported as unknown (check ulu models list --provider openrouter), and a rate limit names how long to wait when OpenRouter says.

Shell access. Agents that declare Bash get a shell only when you allow it: ULUOPS_ALLOWED_TOOLS=bash (any case; a comma-separated list), including from a ./.env or ~/.uluops/.env the CLI loads. When a shell is active, core prints a warning once per run. Commands run via sh -c, start in the target directory but are not confined to it, run without a sandbox, and do not see your API keys or tokens. Allowing bash allows it for every agent that declares it. Providers with a shell: Anthropic, OpenAI and OpenRouter; on others the agent runs without one and core warns. Before @uluops/core 0.46.0 (CLI 0.33.0) this setting had no effect at all; if you set it long ago, check it is still what you want.

Pipeline steps (--allow-steps). A pipeline's steps: are shell commands from its definition, and core runs them only when told to. ulu exec pipeline <name> <target> --allow-steps (also on exec run) runs them for that invocation and prints a notice on stderr; without it they do not run. Since core 0.54.0 a pipeline whose conditions read step output is refused without steps (STEP_OUTPUT_STEPS_DISABLED), as is a steps stage behind a blocking gate; the refusal names --allow-steps. Nine corpus pipelines read step output, pre-implementation, ship, post-implementation and consumer-validate among them. There is no command allowlist: the flag trusts the pipeline's author with your shell, registry-resolved pipelines included, so pin what you run in CI (--hash). ULUOPS_ALLOW_STAGE_STEPS=true turns steps on for every run instead.

Executions

Execution tracking for definitions.

ulu executions record <type> <name> <version>   # Record execution (--source, --run-id for idempotency)
ulu executions stats <type> <name> <version>     # Get statistics (--window)

Translation

Definition translation and legacy upgrades.

A successful legacy upgrade prints the requested type/name with the returned new version and previous version. With --json, it emits the upgrade response, including translation artifact metadata. Registry API 0.59.4 or newer provides these semantics. Current-format drafts and definitions with translation artifacts cannot use legacy upgrade; follow the API guidance to publish or retranslate. If response validation fails after a write, read the definition and its versions before retrying—the write may have committed.

ulu translation version                          # Get translator version
ulu translation retranslate <type> <name> <ver>  # Re-translate (--new-version)
ulu translation upgrade <type> <name>            # Upgrade legacy YAML (--file)

Completion

Generate shell completion scripts.

# Bash — add to ~/.bashrc
echo 'eval "$(ulu completion bash)"' >> ~/.bashrc

# Zsh — add to ~/.zshrc
echo 'eval "$(ulu completion zsh)"' >> ~/.zshrc

# Fish
ulu completion fish > ~/.config/fish/completions/ulu.fish

Output Modes

| Mode | Flag | Description | |------|------|-------------| | Human | (default) | Pretty-printed tables and key-value output | | JSON | --json | Raw JSON for scripting and piping | | Quiet | -q, --quiet | Suppress spinners and non-essential output | | Debug | --debug | Include request/response details |

# Pipe JSON output to jq
ulu issues list my-project --json | jq '.[] | .title'

# Quiet mode for scripts
ulu runs save --file results.json -q

# Debug a failing command
ulu projects get my-project --debug

Destructive commands in scripts and CI. projects delete, projects rehome, runs delete, and definitions delete prompt for confirmation at an interactive terminal. In a non-interactive context (CI, piped stdin, automated agent harness) there is no prompt to answer, so they fail closed: without --yes/-y they print an error to stderr and exit non-zero rather than silently skipping the deletion with a success code. Always pass -y when scripting a delete.

JSON Output Stability Contract

--json output shapes are part of this CLI's public API. Automated and CI consumers parse them, and they cannot easily refuse an upgrade — so the contract is explicit:

  • A change to any default --json output shape is a breaking change and requires a major version bump. Adding fields, removing fields, re-nesting, or turning a bare array into an object (or vice-versa) all count.
  • The default --json output is frozen at its current shape. New stability machinery is added additively and never alters the default bytes.

Detecting shape changes at runtime (opt-in envelope)

Set ULU_JSON_SCHEMA=1 (or pass the global --json-envelope flag) to wrap every --json payload in a versioned envelope so a script can detect a shape change instead of breaking silently:

ULU_JSON_SCHEMA=1 ulu deps get workflow ship 1.0.0 --json
{
  "schema": "uluops.cli/v1",   // envelope format id
  "cliVersion": "0.15.0",
  "kind": "deps.get",           // stable logical name of this output
  "schemaVersion": 2,           // per-output shape version — bumps on a breaking change
  "data": { /* the exact payload the default --json mode emits */ }
}

Pin kind + schemaVersion in your CI. The data field is byte-for-byte what the default --json mode emits, so you can opt in without changing how you read the payload — only how you guard it. (kind values such as issue.history and deps.get are already at schemaVersion: 2, recording the breaking change they shipped in v0.13.0.)

For maintainers — changing a --json shape

The source of truth is the SCHEMA_VERSIONS registry in src/formatters/json.ts; all --json output flows through the single emitJson() chokepoint. To change an output shape you must:

  1. Bump that kind's schemaVersion in SCHEMA_VERSIONS.
  2. Add a CHANGELOG entry marked BREAKING and ship it under a major bump.
  3. Update the contract-anchor test for that surface (e.g. deps get / issues list / issues history) — these tests pin the shape and will fail CI on an unacknowledged change, which is the point.

Environment Variables

| Variable | Description | Default | |----------|-------------|---------| | ULUOPS_API_KEY | API key for authentication | - | | ULUOPS_PROJECT | Project name for ulu exec result tracking when --project is not passed (useful in CI) | - | | ULUOPS_EMAIL | Email for session auth | - | | ULUOPS_PASSWORD | Password for session auth | - | | ULUOPS_ORG_SLUG | Org a command acts in when no --org flag or .uluops.json answers — see Which org a command acts in | personal org | | ULUOPS_BASE_URL | Tracker API base URL (the --base-url flag overrides it) | https://api.uluops.ai/api/v1 | | ULUOPS_REGISTRY_URL | Registry API base URL (the --base-url flag overrides it on registry commands) | https://api.uluops.ai/api/v1/registry | | ULUOPS_SUBMISSION_URL | Where ulu exec submits tracked results, when it should differ from the tracker base URL | the tracker base URL | | ULUOPS_DEBUG | Enable debug logging (also expands the global unhandled-error handler's output) | false | | ULU_JSON_SCHEMA | Set to 1 to wrap --json output in the versioned stability envelope | - | | ANTHROPIC_API_KEY | API key for Anthropic models (the default provider for ulu exec) | - | | OPENAI_API_KEY, GOOGLE_API_KEY, OPENROUTER_API_KEY, … | API key for that provider's models; any <PROVIDER>_API_KEY that is set makes the provider available to ulu exec (GOOGLE_GENERATIVE_AI_API_KEY also works for Google) | - | | ULUOPS_ALLOWED_TOOLS | Comma-separated tool allowlist for ulu exec; bash (any case) gives agents that declare Bash a shell — see Providers and shell access | bash denied | | ULUOPS_MAX_CONCURRENCY | Engine-wide cap on concurrent in-flight LLM calls for ulu exec (distinct from exec agent -c/--concurrency); honored by @uluops/core | 8 | | ULUOPS_EXTENDED_THINKING | on or off: extended thinking for every ulu exec run (the flags above win). Anything else is off with a warning. A value in ./.env or ~/.uluops/.env applies to every run in that directory or machine — the CLI prints a caution when that is where it came from | off | | ULUOPS_THINKING_BUDGET | Thinking budget in tokens when thinking is on (Google, OpenRouter; ignored by OpenAI, which takes an effort). Whole numbers only — anything else is ignored with a warning. Capped by core so the answer keeps at least half of --max-tokens | core default (10000) |

Create a .env file in your project directory:

ULUOPS_API_KEY=ulr_your-api-key-here

Error Handling

The CLI provides contextual error messages with actionable hints:

| Error | Hint | |-------|------| | 401 Unauthorized | Check ULUOPS_API_KEY or run ulu auth login | | 403 Forbidden | Insufficient permissions — contact admin | | 404 Not Found | Verify the resource name or ID | | 400 Validation | Check command arguments — run with --help; a 400/409 that carries a business reason (re-home's same_org, name_collision, …) is explained as state instead | | 402 Project limit | The destination org is at its project cap — a re-home/create refusal, not a subscription gate | | 410 Re-homed | The project moved orgs; the hint names the org to pass as --org | | 429 Rate Limited | Wait and retry — the CLI shows the retry delay | | Network Error | Check your network connection and server status |

All errors include the HTTP status code and server error code when available. Use --debug for full request/response details.

Exit codes

| code | meaning | |---|---| | 0 | success; also any completed exec agent, command or workflow run, whatever its decision (a verdict is not an exit code), and a pipeline whose decision is PASS or WARN | | 1 | usage, configuration or execution error (including a pipeline stopped by a deadline or a provider credit limit) | | 2 | API or runtime error | | 3 | exec pipeline (or exec run on a pipeline) completed and its decision is FAIL: a stage failed behind abort or fail, an ERRORED stage behind a blocking gate, an accepted CONDITIONAL, a gate whose member reported INVALID (the run could not judge; since 0.47.0, core 0.59.0), or a cancel. Since 0.42.0; before it, only an abort gate made a pipeline exit non-zero (1, through core's throw) | | 4 | execution refused by an integrity pin | | 5 | runs update with analysis: the write was applied but its echo did not match the request. Do not retry (a retry applies it again); check the run. A refused update, which wrote nothing, exits 1. Since 0.46.0 |

ulu exec pipeline ship . && deploy therefore still blocks on a failed gate. A stage with no gate never fails a pipeline (it WARNs, exit 0).

Troubleshooting

"Authentication required" on every command

# Verify your credentials are set
echo $ULUOPS_API_KEY
ulu auth whoami

"Connection refused" errors

# Verify the service is reachable and your credentials are valid
ulu auth whoami

Shell completion not working

# Regenerate and source the completion script
ulu completion bash >> ~/.bashrc
source ~/.bashrc

Related Packages

| Package | Description | |---------|-------------| | @uluops/ops-sdk | TypeScript SDK for the tracker API — runs, findings, issues, analytics | | @uluops/registry-sdk | TypeScript SDK for the definition registry API |

License

MIT License - see LICENSE for details.