# CLI reference

> Full command surface from loopany --help: init, artifact/*, refs, trace, domain, followups, search, reindex, factory, kind list, doctor, migrate, and JSON stdout conventions.

- Repository: superdesigndev/loopany
- GitHub: https://github.com/superdesigndev/loopany
- Human docs: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8
- Complete Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/llms-full.txt

## Source Files

- `src/cli.ts`
- `src/version.ts`
- `src/core/operations.ts`
- `src/commands/argv.ts`
- `test/cli.e2e.test.ts`
- `test/helpers/cli.ts`

---

---
title: "CLI reference"
description: "Full command surface from loopany --help: init, artifact/*, refs, trace, domain, followups, search, reindex, factory, kind list, doctor, migrate, and JSON stdout conventions."
---

The `loopany` binary (currently **0.2.0**, workspace schema **0.2.0**) is a Bun entrypoint at `src/cli.ts` that dispatches hand-parsed argv to command modules, bootstraps `$LOOPANY_HOME` (default `~/loopany`), and prints **pretty-printed JSON** on stdout for most mutating and query operations. Human prose is reserved for `init`, default `doctor`, `migrate`, and the long-running `factory` server.

```text
loopany [--help|-h] [--version|version]
        init
        kind list
        artifact create|get|list|append|status|set
        refs <id> | refs add
        trace <id>
        followups
        domain list|enable|disable
        search <query> | reindex
        factory
        doctor | migrate [<name>]
```

<Note>
There is no global `--json` flag. JSON vs text is per command (see [Stdout conventions](#stdout-conventions)). Flags always take a value (`--flag value`); exceptions are bare switches on `reindex` (`--force`, `--no-embed`) and `factory` (`--no-open`).
</Note>

## Runtime and workspace

| Input | Effect |
| --- | --- |
| `LOOPANY_HOME` | Workspace root; default `~/loopany` |
| `LOOPANY_SKIP_VERSION_CHECK=1` | Skips `config.yaml` `schemaVersion` guard (migration scripts) |
| `LOOPANY_EDITOR` | Used by factory UI when opening artifact files (not by core CLI) |

Bootstrap (`bootstrap()` in `src/core/engine.ts`) requires `kinds/` under the workspace. If `schemaVersion` ≠ binary `SCHEMA_VERSION` and the skip flag is unset, commands fail with `SchemaVersionMismatchError` and a pointer to `loopany migrate`. `doctor` and `migrate` bypass that guard so they can report or plan upgrades.

Every completed invocation appends one line to `audit.jsonl` with `op` (dotted, e.g. `artifact.create`), `actor: cli`, `duration_ms`, and command-specific metadata; failures include `error`. Body content is never logged.

## Argv parsing

`src/commands/argv.ts` implements a minimal parser: positional tokens first, then repeating `--key value` pairs. Unknown flags on `artifact create` produce a field list derived from the kind definition. Kebab-case flags map to camelCase frontmatter (`--check-at` → `checkAt`). Comma-separated values become `string[]` (`--mentions alice,bob`).

## Command reference

### `loopany init`

Scaffolds the workspace: `kinds/`, `artifacts/`, `config.yaml` with current `schemaVersion`, and copies bundled kinds from `skills/loopany-core/kinds/`. Idempotent — existing files are left untouched.

| Stream | Shape |
| --- | --- |
| stdout | Human lines: path, created count, optional ONBOARDING hint |
| stderr | — |
| exit | `0` |

Onboarding hint appears when no `artifacts/missions/*.md` exists.

### `loopany kind list`

| Stream | Shape |
| --- | --- |
| stdout | JSON array of `{ kind, dirName, slugLayout, indexedFields, hasStatusMachine }` |
| exit | `0`; non-zero if workspace missing |

Domain pack kinds appear only when the domain is enabled (`domain enable`).

### Artifact commands

All artifact subcommands except `get` (default) emit JSON. Per-kind fields come from `~/loopany/kinds/<kind>.md`; reserved flags on create: `--kind`, `--slug`, `--domain`, `--content`, `--content-file`.

#### `artifact create`

```
loopany artifact create --kind <K> [--slug <id>] [--domain <D>]
  [--content <text> | --content-file <path|->]
  [--<field> <value> ...]
```

<ParamField body="--kind" type="string" required>
Kind name; must exist in the loaded registry.
</ParamField>

<ParamField body="--slug" type="string">
Stable id for `[[citations]]`. If omitted: slugified `--title` with collision suffixes, or timestamp-based id when no title.
</ParamField>

<ParamField body="--content / --content-file" type="string">
Body text. `-` on `--content-file` reads stdin. Inline `--content` rejects literal `\n` without real newlines (shell footgun guard).
</ParamField>

<ResponseField name="stdout" type="object">
`{ "id", "kind", "path" }` — path is the on-disk markdown file.
</ResponseField>

<Warning>
`--kind journal` is rejected; journal entries are auto-managed when other artifacts are created.
</Warning>

#### `artifact get <id>`

<ParamField body="--format" type="md | json" default="md">
`md`: raw file contents. `json`: `{ id, kind, path, frontmatter, body, audit }` where `audit` lists mutation ops for this id (timestamps as local `YYYY-MM-DD HH:MM`, no `actor`/`duration_ms`).
</ParamField>

#### `artifact list`

```
loopany artifact list [--kind K] [--status S] [--domain D]
  [--<field> V ...] [--contains Q]
```

<ResponseField name="stdout" type="array">
`[{ id, kind, path, frontmatter }, ...]`. With `--kind`, extra `--field` filters use the per-kind index when declared in `indexedFields`; otherwise linear scan. `--contains` is case-insensitive on body and string/`string[]` frontmatter; applied last.
</ResponseField>

#### `artifact append <id>`

Requires `--section` and (`--content` or `--content-file`). Returns `{ "id" }`.

#### `artifact status <id> <new-status>`

<ParamField body="--reason" type="string">
Recorded in audit, not appended to the artifact body.
</ParamField>

<ParamField body="--addressed-by" type="string">
Required when transitioning to `addressed`; emits an `addresses` edge from that id to this artifact.
</ParamField>

<ResponseField name="stdout" type="object">
`{ id, status, reason?, addressedBy?, edgesEmitted }`.
</ResponseField>

Status transitions are enforced by each kind’s status machine in its kind file.

#### `artifact set <id>`

```
loopany artifact set <id> --field <name> --value <value>
```

Updates one non-status frontmatter field. Use `artifact status` for `status`.

<ResponseField name="stdout" type="object">
`{ id, field, value }`.
</ResponseField>

### Graph commands

#### `refs <id>`

BFS over the in-memory graph (persisted `references.jsonl` plus implicit edges from `mentions` frontmatter and body `[[id]]` links).

| Flag | Default | Meaning |
| --- | --- | --- |
| `--direction` | `out` | `in`, `out`, or `both` |
| `--depth` | `1` | Positive integer hop count |
| `--relation` | — | Filter by relation verb |
| `--domain` | — | Both endpoints must have this `domain` frontmatter |

<ResponseField name="stdout" type="array">
`Edge[]`: `{ ts, from, to, relation, actor, implicit? }`.
</ResponseField>

#### `refs add`

```
loopany refs add --from <id> --to <id> --relation <R>
```

Appends one row to `references.jsonl`. Returns the new edge object.

#### `trace <id>`

Walks causal predicates to a fixed point (not plain BFS depth). Default relations: `led-to`, `addresses`, `supersedes`, `follows-up`, `cites` (`mentions` excluded).

| Flag | Default |
| --- | --- |
| `--direction` | `both` (`forward`, `backward`, `both`) |
| `--relations` | default set (comma-separated override) |
| `--max-depth` | unlimited |

<ResponseField name="stdout" type="object">
`{ root, nodes: [{ ...ArtifactMeta, distance }], edges: Edge[] }` — `distance` negative = causes, `0` = root, positive = effects; nodes sorted by distance.
</ResponseField>

### `followups`

```
loopany followups [--due today|overdue|next-7d] [--include-done true] [--domain D]
```

<ResponseField name="stdout" type="array">
`ArtifactMeta[]` with `checkAt` due per mode. Terminal statuses (no outgoing transitions in the kind machine) are excluded unless `--include-done true`.
</ResponseField>

### Domain commands

| Command | stdout |
| --- | --- |
| `domain list` | `{ enabled: string[], observed_only: string[] }` — observed = domains seen on artifacts but not in `config.yaml` |
| `domain enable <name>` | `{ enabled: string[] }` |
| `domain disable <name>` | `{ enabled: string[] }` |

Enabling loads `domains/<name>/kinds/*.md` into the registry on next bootstrap.

### Search and index

#### `reindex`

```
loopany reindex [--force] [--no-embed]
```

Rebuilds `$LOOPANY_HOME/search.db` from all artifacts. Incremental by file mtime unless `--force` (deletes DB first). `--no-embed` uses FTS5-only (`embedder: "noop"`).

<ResponseField name="stdout" type="object">
`{ indexed, skipped, removed, embedder: "transformers" | "noop" }`.
</ResponseField>

Artifact writes do not auto-reindex; run `reindex` after bulk edits.

#### `search <query>`

```
loopany search <query> [--kind K] [--domain D] [--status S] [--limit N]
```

Default `--limit` is `10`. Hybrid FTS5 + embeddings when the embedder is available.

<ResponseField name="stdout" type="array">
`SearchResult[]`: `{ artifactId, kind, domain, status, path, section, snippet, score }`.
</ResponseField>

If `search.db` is missing: exit `0`, stdout `[]`, stderr warns to run `reindex` first.

### `factory`

```
loopany factory [--port N] [--host H] [--no-open]
```

Starts the pixel-factory UI (default port **4242**, host **127.0.0.1**). Prints URL and workspace path; opens the browser unless `--no-open`. Runs until SIGINT/SIGTERM — no JSON stdout.

### `doctor`

```
loopany doctor [--format json]
```

Checks: workspace, schema version, kinds parse, artifact frontmatter validation, reference integrity (no dangling edges), onboarding (`self` person + active mission), mission coverage (warn), domain coverage (warn).

| `--format` | stdout | exit |
| --- | --- | --- |
| (default) | Human checklist (`✓` / `⚠` / `✗`) | `1` if any check `fail` |
| `json` | `{ workspace, checks: [{ name, status, detail, problems? }], ok }` | same |

Schema version mismatch is reported as `fail` with `loopany migrate …` in `problems`.

### `migrate`

```
loopany migrate
loopany migrate <name>   # e.g. v0.1.0-to-v0.2.0
```

Does **not** run migration scripts — discovery and documentation only. Scripts live under `skills/migrations/v<from>-to-v<to>/scripts/` and are run with `bun` by the agent.

| Invocation | stdout |
| --- | --- |
| no args | Human: workspace vs binary schema, next migration hint, available list |
| `<name>` | Human: migration metadata, script paths, full `SKILL.md` body |

Uses `bootstrap({ skipVersionCheck: true })`.

### Version

`loopany --version` or `loopany version` → `loopany 0.2.0\n` (plain text).

## Stdout conventions

```mermaid
flowchart LR
  subgraph human["Human stdout"]
    init[init]
    doctorH[doctor default]
    migrate[migrate]
    factory[factory server]
    version[--version]
  end
  subgraph json["JSON stdout pretty-printed"]
    kinds[kind list]
    artifacts[artifact mutators + list]
    graph[refs trace followups]
    domains[domain *]
    searchCmd[search reindex]
    doctorJ[doctor --format json]
    getJ[artifact get --format json]
  end
  cli[src/cli.ts dispatch] --> human
  cli --> json
```

| Pattern | Commands |
| --- | --- |
| `JSON.stringify(x, null, 2) + '\n'` | `kind list`, `artifact` (except default `get`), `refs`, `trace`, `followups`, `domain *`, `search`, `reindex` |
| Raw markdown | `artifact get` (default) |
| Human prose | `init`, `migrate`, default `doctor`, `factory`, `--version` |
| stderr only (success) | `search` when index missing |

<RequestExample>
```bash
LOOPANY_HOME=/tmp/brain loopany artifact create \
  --kind task --title "Follow up" --status todo --priority high
```
</RequestExample>

<ResponseExample>
```json
{
  "id": "follow-up",
  "kind": "task",
  "path": "/tmp/brain/artifacts/tasks/follow-up.md"
}
```
</ResponseExample>

## Errors and exit codes

| Error type | stderr | exit |
| --- | --- | --- |
| `WorkspaceNotFoundError` | `No loopany workspace at … Run loopany init` | `1` |
| `SchemaVersionMismatchError` | Version mismatch + migrate hint | `1` |
| `ZodError` | `Invalid input:` with `path: message` lines (not raw Zod JSON) | `1` |
| Other `Error` | `error.message` | `1` |
| `doctor` with failing checks | Human or JSON report | `1` |
| Unknown command | `Unknown command` + help hint | `1` |

Failed operations still append an audit row with `error`.

## Invocation examples

<CodeGroup>
```bash title="Development"
bun run src/cli.ts init
LOOPANY_HOME=~/loopany bun run src/cli.ts doctor
```

```bash title="Compiled binary"
bun run build
./bin/loopany init
loopany kind list
```
</CodeGroup>

```bash
# Graph + scheduling
loopany refs add --from task-a --to task-b --relation led-to
loopany trace task-a --direction forward --max-depth 3
loopany followups --due overdue

# Search pipeline
loopany reindex --no-embed
loopany search "billing refactor" --kind note --limit 5
```

## Related pages

<CardGroup>
<Card title="Artifact commands" href="/artifact-commands">
Per-subcommand flags, kind fields, body input, and Outcome rules.
</Card>
<Card title="Graph, search, and scheduling" href="/graph-search-commands">
refs/trace depth, followups modes, reindex and factory details.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`config.yaml`, `LOOPANY_HOME`, schema version guard.
</Card>
<Card title="Doctor and troubleshooting" href="/doctor-and-troubleshooting">
Check list, common errors, index recovery.
</Card>
<Card title="Schema migration" href="/schema-migration">
`loopany migrate` and v0.1→v0.2 scripts.
</Card>
<Card title="Quickstart" href="/quickstart">
First session command sequence.
</Card>
</CardGroup>
