Skip to main content
nexlem

Nexlem CLI flags — headless-bound standard

HEADLESS-04 canonical reference. This document describes the standardized --input, --output, and --project-dir flag triplet that every headless-bound Nexlem subcommand accepts. Implemented by the shared parser at src/lib/cli-flags.ts (parseHeadlessFlags). Exercised end-to-end by tests/integration/headless-flags.test.ts.

Standard headless-bound flags

FlagTypeDefaultPrecedence vs other sources
--inputabsolute path(unset — command falls back to positional / interactive)— (single source: flag only)
--outputabsolute path(unset — command writes to stdout)— (single source: flag only)
--project-dirabsolute path$NEXLEM_PROJECT_DIR if set, else process.cwd()flag > NEXLEM_PROJECT_DIR env > cwd

All three flags are rejected at parse time when the value is not absolute (path.isAbsolute()). Duplicates of any flag also throw.

Precedence

--project-dir resolves in this order, returning the first source that is set:

  1. --project-dir <abs-path> (CLI flag, this plan)
  2. $NEXLEM_PROJECT_DIR (env var; see docs/env-vars.md)
  3. process.cwd() (Bun process working directory at command entry)

Concrete example:

# Case 1: explicit flag wins over env
NEXLEM_PROJECT_DIR=/foo  nexlem-sdk run --project-dir /bar topic    # projectDir = /bar
 
# Case 2: env wins when flag absent
NEXLEM_PROJECT_DIR=/foo  nexlem-sdk run topic                       # projectDir = /foo
 
# Case 3: cwd fallback
nexlem-sdk run topic                                                # projectDir = $(pwd)

Which commands accept these flags

The v2.3 headless-bound subcommand set is:

  • nexlem-sdk run (with all its sub-flags: --batch, --campaign-id, --group-to-sites, …)
  • nexlem-sdk agent-list
  • nexlem-sdk campaign create
  • nexlem-sdk campaign show <id>
  • nexlem-sdk campaign list

v2.4 will extend the same triplet to additional subcommands (e.g. campaign run, campaign output, status). Because the contract is locked NOW, those extensions will be purely additive — no flag rename, no breaking change.

Path requirements

All three flag values MUST be absolute paths. The parser throws

HEADLESS-04: --<flag> requires an absolute path, got <value>

when given a relative path. The error message is stable and may be matched in shell scripts.

This strictness exists because headless wrappers (claude -p, automation runners, CI jobs) construct argv programmatically; an attacker or buggy caller could inject --project-dir ../../etc and silently re-root execution. The absolute-only requirement makes the boundary explicit and audit-friendly.

--input vs --output vs --json

These three semantically distinct outputs are intentionally orthogonal:

FlagDirectionSink/SourceFormat
--inputreadfileJSON (command-specific schema)
--outputwritefileJSON
--jsonwritestdoutJSON

--output takes precedence over --json when both are set: if you want the JSON in a file, the file wins; stdout falls back to the human-readable form (or stays empty if the command had no human-readable form). --input and --output are independent and can be combined in the same invocation.

Examples

Run a campaign and capture the final status to a file

nexlem-sdk run \
  --project-dir /Users/alice/sites/my-project \
  --output /tmp/run-result.json \
  "Comparativo entre planos de saúde Bradesco e SulAmérica"

The final JSON { campaign_id, topic, site_slug, campaign_type, status, duration_ms } is written to /tmp/run-result.json (in addition to the structured pino logs sent to .nexlem/logs/nexlem.log).

List all queued campaigns to a file (headless pipeline step)

nexlem-sdk campaign list \
  --project-dir /Users/alice/sites/my-project \
  --output /tmp/queued.json \
  --status queued

Emit the agent catalog for a downstream pipeline

nexlem-sdk agent-list \
  --project-dir /Users/alice/sites/my-project \
  --output /tmp/agents.json

Each entry is { name, order, executor, channel, campaign_types }. The schema is locked under the agent-list golden regression (Phase 36 REG-02).

v2.3 partial implementations

The contract surface is locked in v2.3 so v2.4 implementations are purely additive. The current behavior of each --input consumer is:

Subcommand--input behavior in v2.3v2.4 plan
nexlem-sdk runaccepted, logged at debug, NOT consumedHydrate headless campaign overrides (topic, site, type, language)
nexlem-sdk agent-listaccepted, logged at debug, NOT consumed(no input semantics planned — flag exists for uniformity)
nexlem-sdk campaign showaccepted, logged at debug, NOT consumed(no input semantics planned)
nexlem-sdk campaign listaccepted, logged at debug, NOT consumed(potential: load saved filter preset)
nexlem-sdk campaign createCONSUMED — JSON merged into create payload(already wired; v2.4 adds field-level validation)

The "accepted but not consumed" log line uses the literal prefix HEADLESS-04: --input accepted to support grep-based audits of which subcommands deferred consumption to v2.4.

Relative vs absolute path asymmetry

There is an intentional asymmetry between the CLI flag and the env var:

  • --project-dir (CLI flag) — REJECTS relative paths at parse time (parseHeadlessFlags in src/lib/cli-flags.ts). Rationale: in headless contexts argv may be constructed by automated wrappers (claude -p, CI runners), where untrusted callers could inject ../ traversal. The strict check makes the trust boundary explicit and audit-friendly.
  • NEXLEM_PROJECT_DIR (env var) — ACCEPTS relative paths and resolves them against process.cwd() (resolveProjectDir in src/commands/run-paths.ts). Rationale: env vars are set at shell level by the user (interactive ergonomics); the user already has shell-level trust over their own process environment.

Conclusion: in headless contexts ALWAYS pass absolute paths via --project-dir. The env-var leniency is a developer-ergonomics affordance for interactive use only; the strict CLI behavior is the canonical contract for automated pipelines.

HEADLESS-04 traceability

This contract is exercised by tests/integration/headless-flags.test.ts and locked under requirement HEADLESS-04 in .planning/REQUIREMENTS.md. Any change to the flag set, precedence, or path-validation rules must update both this document and the integration test in the same PR.

Edit this page on GitHub