Nexlem CLI flags — headless-bound standard
HEADLESS-04 canonical reference. This document describes the standardized
--input,--output, and--project-dirflag triplet that every headless-bound Nexlem subcommand accepts. Implemented by the shared parser atsrc/lib/cli-flags.ts(parseHeadlessFlags). Exercised end-to-end bytests/integration/headless-flags.test.ts.
Standard headless-bound flags
| Flag | Type | Default | Precedence vs other sources |
|---|---|---|---|
--input | absolute path | (unset — command falls back to positional / interactive) | — (single source: flag only) |
--output | absolute path | (unset — command writes to stdout) | — (single source: flag only) |
--project-dir | absolute 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:
--project-dir <abs-path>(CLI flag, this plan)$NEXLEM_PROJECT_DIR(env var; seedocs/env-vars.md)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-listnexlem-sdk campaign createnexlem-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:
| Flag | Direction | Sink/Source | Format |
|---|---|---|---|
--input | read | file | JSON (command-specific schema) |
--output | write | file | JSON |
--json | write | stdout | JSON |
--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 queuedEmit the agent catalog for a downstream pipeline
nexlem-sdk agent-list \
--project-dir /Users/alice/sites/my-project \
--output /tmp/agents.jsonEach 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.3 | v2.4 plan |
|---|---|---|
nexlem-sdk run | accepted, logged at debug, NOT consumed | Hydrate headless campaign overrides (topic, site, type, language) |
nexlem-sdk agent-list | accepted, logged at debug, NOT consumed | (no input semantics planned — flag exists for uniformity) |
nexlem-sdk campaign show | accepted, logged at debug, NOT consumed | (no input semantics planned) |
nexlem-sdk campaign list | accepted, logged at debug, NOT consumed | (potential: load saved filter preset) |
nexlem-sdk campaign create | CONSUMED — 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 (parseHeadlessFlagsinsrc/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 againstprocess.cwd()(resolveProjectDirinsrc/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.