# CLI reference

> last30days.py positional topic, all argparse flags, --emit choices, targeting flags, and direct CLI vs slash-command constraints.

- Repository: mvanhorn/last30days-skill
- GitHub: https://github.com/mvanhorn/last30days-skill
- Human docs: https://grok-wiki.com/public/docs/mvanhorn-last30days-skill-50b5421a8cca
- Complete Markdown: https://grok-wiki.com/public/docs/mvanhorn-last30days-skill-50b5421a8cca/llms-full.txt

## Source Files

- `skills/last30days/scripts/last30days.py`
- `CONFIGURATION.md`
- `tests/test_regression.py`
- `skills/last30days/scripts/verify_v3.py`
- `AGENTS.md`
- `pyproject.toml`

---

---
title: "CLI reference"
description: "last30days.py positional topic, all argparse flags, --emit choices, targeting flags, and direct CLI vs slash-command constraints."
---

`skills/last30days/scripts/last30days.py` is the v3 research engine: a Python 3.12+ `argparse` program that accepts a multi-word positional topic, fans out retrieval through `lib.pipeline`, and prints a rendered report on stdout while progress, promos, saves, and store writes go to stderr. The Agent Skills slash command (`/last30days`) is the primary product surface; direct CLI invocation is the supported path for scripting, cron, watchlist, verification, and engine testing.

## Invocation surfaces

```text
  Harness (Claude Code, Codex, Cursor, Gemini, …)
       │
       │  User: /last30days <topic>   (no shell pipes; model maps intent → flags)
       ▼
  SKILL.md contract ──► Bash: ${LAST30DAYS_PYTHON} ${SKILL_DIR}/scripts/last30days.py …
       │
       │  Operator / cron / watchlist / pytest
       ▼
  python3 skills/last30days/scripts/last30days.py "<topic>" [flags…]
       │
       ▼
  stdout: rendered --emit payload
  stderr: banner, promos, saves, store counts, quality nudges
```

<Warning>
Slash commands do not pass shell mechanics through. `/last30days OpenClaw --emit=html | pbcopy` is invalid in any harness. Use natural language in the slash form and let the hosting model translate intent into engine flags, or run the full `python3 …` command in a real shell when you need pipes, redirection, or explicit flags.
</Warning>

| Surface | When to use | Typical command shape |
| --- | --- | --- |
| Slash `/last30days` | Interactive research in an agent harness | Topic only; model adds `--emit=compact`, `--plan`, targeting, `--save-dir` per SKILL.md |
| Direct CLI | Cron, CI, watchlist, local debugging | Full flag list; stdout captured or redirected |
| Topic `setup` | First-run credential wizard | `last30days.py setup` plus unknown args (see Setup) |

From a repo checkout (dev/fallback):

```bash
python3 skills/last30days/scripts/last30days.py "openclaw skills" --emit=compact
uv run pytest tests/test_cli_v3.py   # CLI contract tests
```

Installed skill layout (production):

```bash
"${LAST30DAYS_PYTHON}" "${SKILL_DIR}/scripts/last30days.py" "$TOPIC" --emit=compact --save-dir="${LAST30DAYS_MEMORY_DIR}" --save-suffix=v3
```

`SKILL_DIR` must be the directory containing the `SKILL.md` the harness loaded. `LAST30DAYS_PYTHON` must be Python 3.12+ (resolved once per SKILL.md session).

## Positional topic

<ParamField body="topic" type="string (0+ tokens)" required>
Multi-word research query. Tokens are joined with spaces: `biosecurity ai agents` → `"biosecurity ai agents"`.
</ParamField>

| Case | Behavior |
| --- | --- |
| Normal topic | Required unless `--diagnose` alone (see Diagnostics) |
| Empty topic | Usage printed to stderr; exit code `2` |
| `setup` (case-insensitive) | Routes to setup wizard instead of research |
| `… vs …` / `… versus …` | Auto-enters comparison fan-out when the planner splits ≥2 entities (see Comparison) |

## `--emit` modes

Default: `compact`. All modes write human-oriented output to **stdout**; operational messages use **stderr**.

| Value | Renderer | Primary use |
| --- | --- | --- |
| `compact` | `render.render_compact` | Default skill path; badge + synthesis-oriented envelope |
| `md` | Same as `compact` | Debug/inspection; SKILL.md disallows as primary user-facing emit in harness runs |
| `json` | `schema.to_dict(report)` | Automation, tests, watchlist, `verify_v3.py` |
| `context` | `render.render_context` | Trimmed agent context (cluster-limited) |
| `html` | `html_render.render_html` | Shareable offline brief; pairs with `--synthesis-file` |

Comparison runs (`--competitors`, explicit list, or vs-topic) use parallel emit helpers: JSON wraps `{comparison, entities, reports[]}`; compact/md/context/html use multi-entity renderers.

<Note>
`--synthesis-file` is only honored when `--emit=html`. Otherwise the engine logs a warning and ignores the file.
</Note>

## Depth and source selection

| Flag | Effect |
| --- | --- |
| `--quick` | `depth="quick"` — lower latency (`per_stream_limit` 6, `pool_limit` 15, `rerank_limit` 12) |
| *(none)* | `depth="default"` |
| `--deep` | `depth="deep"` — higher recall (limits 20 / 60 / 60) |

<ParamField body="--search" type="comma-separated sources">
Restricts retrieval to named sources. Aliases normalized via `SEARCH_ALIAS` (`hn`→`hackernews`, `web`→`grounding`, `bsky`→`bluesky`, `truth`→`truthsocial`, `xhs`→`xiaohongshu`). Unknown or empty lists exit with an error.
</ParamField>

Valid source keys (also used by `--mock`): `reddit`, `x`, `youtube`, `tiktok`, `instagram`, `hackernews`, `bluesky`, `truthsocial`, `polymarket`, `grounding`, `xiaohongshu`, `github`, `perplexity`, `threads`, `pinterest`, `xquik`, `digg`.

## Targeting flags

Per-run targeting merges into `pipeline.run()` and is recorded in `report.artifacts["resolved"]`. When any of these are set, the renderer can emit a pre-research status warning if Step 0.55 was skipped in harness runs.

| Flag | Purpose |
| --- | --- |
| `--x-handle` | Primary X handle (leading `@` stripped) |
| `--x-related` | Comma-separated related handles (lower weight) |
| `--subreddits` | Comma-separated subs (`r/` prefix stripped) |
| `--tiktok-hashtags` | Hashtags without `#` |
| `--tiktok-creators` | Creator handles |
| `--ig-creators` | Instagram handles |
| `--github-user` | GitHub username for person-mode |
| `--github-repo` | Comma-separated `owner/repo` (canonicalized unless from `--auto-resolve`) |
| `--polymarket-keywords` | Comma-separated title filters for Polymarket disambiguation |
| `--days` / `--lookback-days` | Lookback window (default `30`; watchlist uses `90`) |

### Plan and auto-resolve

<ParamField body="--plan" type="JSON string or file path">
External query plan; skips the internal LLM planner when valid JSON loads. Invalid JSON logs to stderr and leaves planning unset.
</ParamField>

<ParamField body="--auto-resolve" type="boolean">
Engine-side Step 0.55/0.75 for hosts without WebSearch: discovers subreddits, X handle, GitHub user/repos before planning. Skipped when `--plan` is already provided.
</ParamField>

Harness models with WebSearch are expected to resolve targeting themselves and pass `--plan` or per-entity `--competitors-plan` instead of relying on `--auto-resolve`.

## Web backends and deep research

<ParamField body="--web-backend" type="enum" default="auto">
Choices: `auto`, `brave`, `exa`, `serper`, `parallel`, `none`. `auto` tries Brave → Exa → Serper → Parallel per config.
</ParamField>

| Flag | Requirement |
| --- | --- |
| `--deep-research` | Sets `OPENROUTER_API_KEY`; auto-appends `perplexity` to `INCLUDE_SOURCES`; exits `1` if key missing |

## Comparison mode

Comparison activates when any of these is true:

1. `--competitors` or `--competitors-list`
2. Topic contains ` vs ` or ` versus ` and splits into ≥2 entities

<ParamField body="--competitors" type="optional int (1–6)" default="2 when bare">
`--competitors` alone → discover **2** peers (3-way: topic + 2). `--competitors 4` → four discovered peers. Counts &lt; 1 exit `2`; counts &gt; 6 clamp with stderr warning.
</ParamField>

<ParamField body="--competitors-list" type="comma-separated names">
Skips discovery; list length wins over numeric `--competitors`. Empty list exits `2`. More than six entries clamp.
</ParamField>

<ParamField body="--competitors-plan" type="JSON object or file path">
Per-entity Step 0.55 targeting when comparison mode is already active. Top-level map: `{entity_name: {x_handle?, x_related?, subreddits?, github_user?, github_repos?, context?}}`. Keys normalized to lowercase; unknown fields warned and dropped. Invalid JSON or non-dict top-level exits `2`.
</ParamField>

Discovery requires a web backend or `--mock`; otherwise stderr prints the recommended WebSearch + `--competitors-plan` path and exits `2`. Fewer than two surviving sub-runs exits `1`.

Vs-topic routing sets the first entity as the main topic and remaining segments as the explicit competitor list. With `--save-dir`, comparison HTML saves one combined file plus per-entity raw files for peers.

## Output persistence

| Flag | Behavior |
| --- | --- |
| `--save-dir` | Writes slug-named file under directory; collision same day → date-stamped name |
| `--save-suffix` | Inserts suffix into filename (`topic-raw-SUFFIX.md`) |
| `--store` | Persists findings to SQLite (`scripts/store.py`) |

`LAST30DAYS_STORE=1` in environment or `~/.config/last30days/.env` enables store writes without the flag (same hybrid pattern as `LAST30DAYS_DEBUG`).

Save extensions follow emit: `.json`, `.html` (`raw-html` infix), else `.md`. Footer display uses `~/…` posix paths under the user home.

## Diagnostics, mock, and debug

| Flag | Behavior |
| --- | --- |
| `--diagnose` | Prints JSON availability report (`pipeline.diagnose`); **no topic required**; exit `0` |
| `--mock` | Fixture retrieval; bypasses live APIs |
| `--debug` | Sets `LAST30DAYS_DEBUG=1` for HTTP logging |

Preflight refusals (class-1 traps) honor `LAST30DAYS_SKIP_PREFLIGHT` and return exit `2` with message on stderr.

## Setup subcommand

`parse_known_args` leaves trailing argv available for setup-only flags:

| Invocation | Extra argv | Result |
| --- | --- | --- |
| `last30days.py setup` | *(none)* | `run_auto_setup`; writes config; human-readable status on stderr |
| `last30days.py setup` | `--device-auth` | JSON device-auth results on stdout |
| `last30days.py setup` | `--github` | JSON GitHub auth results |
| `last30days.py setup` | `--openclaw` | JSON OpenClaw setup results |

## Exit codes

| Code | Typical cause |
| --- | --- |
| `0` | Success; diagnose-only; setup completed |
| `1` | Comparison fan-out &lt; 2 entities; `--deep-research` without `OPENROUTER_API_KEY` |
| `2` | Missing topic; preflight refusal; competitor parse/validation errors; unreadable `--synthesis-file` |

Unsupported `--emit` values raise `SystemExit` inside emit helpers (parser choices prevent this in normal use).

## Flag inventory

### Output and persistence

| Flag | Type | Default |
| --- | --- | --- |
| `--emit` | `compact\|json\|context\|md\|html` | `compact` |
| `--save-dir` | path | — |
| `--save-suffix` | string | — |
| `--synthesis-file` | path | — (html only) |
| `--store` | boolean | off (unless env) |

### Retrieval profile

| Flag | Type |
| --- | --- |
| `--quick` | flag |
| `--deep` | flag |
| `--search` | string |
| `--mock` | flag |
| `--lookback-days` / `--days` | int (30) |
| `--web-backend` | enum (`auto`) |
| `--deep-research` | flag |

### Targeting and planning

| Flag | Type |
| --- | --- |
| `--plan` | JSON or file |
| `--auto-resolve` | flag |
| `--x-handle`, `--x-related` | string |
| `--subreddits` | string |
| `--tiktok-hashtags`, `--tiktok-creators` | string |
| `--ig-creators` | string |
| `--github-user`, `--github-repo` | string |
| `--polymarket-keywords` | string |

### Comparison

| Flag | Type |
| --- | --- |
| `--competitors` | optional int (const 2) |
| `--competitors-list` | string |
| `--competitors-plan` | JSON or file |

### Operations

| Flag | Type |
| --- | --- |
| `--diagnose` | flag |
| `--debug` | flag |

## Examples

<CodeGroup>

```bash title="Smoke test (repo root)"
python3 skills/last30days/scripts/last30days.py "test topic" --mock --emit=json
```

```bash title="Availability probe"
python3 skills/last30days/scripts/last30days.py --diagnose
```

```bash title="Headless comparison with explicit peers"
python3 skills/last30days/scripts/last30days.py "OpenAI" \
  --competitors-list "Anthropic,xAI" \
  --emit=json --mock
```

```bash title="HTML brief with saved synthesis"
python3 skills/last30days/scripts/last30days.py "OpenClaw" \
  --emit=html \
  --synthesis-file /tmp/synthesis.md \
  --save-dir ~/Documents/Last30Days
```

```bash title="Cron-friendly quick pass"
python3 skills/last30days/scripts/last30days.py "british airways middle east" \
  --quick --search=grounding,hackernews --emit=json
```

</CodeGroup>

<RequestExample>

```bash
python3 skills/last30days/scripts/last30days.py "openclaw vs nanoclaw" --mock --emit=json
```

</RequestExample>

<ResponseExample>

```json
{
  "comparison": true,
  "entities": ["openclaw", "nanoclaw"],
  "reports": [
    { "entity": "openclaw", "report": { "topic": "openclaw", "query_plan": { } } },
    { "entity": "nanoclaw", "report": { "topic": "nanoclaw", "query_plan": { } } }
  ]
}
```

</ResponseExample>

## Harness vs CLI constraints

<Steps>

<Step title="Slash command (default)">
User supplies topic and intent in natural language. The model reads SKILL.md, resolves `SKILL_DIR` and `LAST30DAYS_PYTHON`, runs Step 0.55 / plan generation when required, and invokes the engine with `--emit=compact` (not `md` as the primary path), plus `--save-dir` / `--save-suffix=v3` and targeting flags. Output is passed through verbatim (badge, evidence envelope, footer).
</Step>

<Step title="Direct CLI (scripting)">
Operator supplies every flag explicitly. Shell pipes, env exports, and cron wrappers are valid. Use `--emit=json` or `--mock` for automation; use `--diagnose` before debugging missing sources.
</Step>

</Steps>

<Tip>
New engine flags are incomplete until SKILL.md documents them — harness models only learn flags present in the skill contract. For env-backed defaults and save paths, see the configuration reference rather than duplicating every variable here.
</Tip>

## Related pages

<CardGroup>

<Card title="Skill, engine, and harness" href="/skill-engine-harness">
Boundaries between the Agent Skills package, Python engine, and multi-harness runtimes.
</Card>

<Card title="Output contract" href="/output-contract">
Badge line, LAWs voice rules, evidence envelopes, and emit semantics the harness must pass through.
</Card>

<Card title="Configuration reference" href="/configuration-reference">
Environment variables, `.env` lookup order, reasoning and web-backend priority, and store toggles.
</Card>

<Card title="Comparison mode" href="/comparison-mode">
Discovery fan-out, `--competitors-plan` schema, and multi-entity synthesis expectations.
</Card>

<Card title="Develop and test" href="/develop-and-test">
`uv run pytest`, `verify_v3.py`, and regression fixtures for the CLI surface.
</Card>

</CardGroup>
