Metadata-Version: 2.1
Name: infinite-craft-cli
Home-page: https://github.com/hacker6284/infinite-craft-cli
License: MIT
Description-Content-Type: text/markdown
Summary: Interactive CLI for Infinite Craft — combine elements from the terminal
Project-URL: Homepage, https://github.com/hacker6284/infinite-craft-cli
Project-URL: Repository, https://github.com/hacker6284/infinite-craft-cli
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Topic :: Games/Entertainment
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Requires-Dist: curl-cffi>=0.15
Version: 2.4.1

# infinite-craft-cli

[![PyPI](https://img.shields.io/pypi/v/infinite-craft-cli)](https://pypi.org/project/infinite-craft-cli/)
[![Downloads](https://img.shields.io/pypi/dm/infinite-craft-cli)](https://pypi.org/project/infinite-craft-cli/)
[![License](https://img.shields.io/badge/License-MIT-red?labelColor=black)](LICENSE)
[![Python](https://img.shields.io/badge/Python-3.10+-blue)](https://python.org)
[![Tests](https://github.com/hacker6284/infinite-craft-cli/actions/workflows/test.yml/badge.svg)](https://github.com/hacker6284/infinite-craft-cli/actions/workflows/test.yml)
[![Publish](https://github.com/hacker6284/infinite-craft-cli/actions/workflows/publish.yml/badge.svg)](https://github.com/hacker6284/infinite-craft-cli/actions/workflows/publish.yml)
[![Chrome Web Store](https://img.shields.io/chrome-web-store/v/gaonnldioeddnfopgejohbhoajoagbnd)](https://chromewebstore.google.com/detail/infinite-craft-trainer/gaonnldioeddnfopgejohbhoajoagbnd)

Interactive CLI for [Infinite Craft](https://neal.fun/infinite-craft/) — combine elements from the terminal. Also available as a [browser extension](https://chromewebstore.google.com/detail/infinite-craft-trainer/gaonnldioeddnfopgejohbhoajoagbnd) and [web trainer](https://hacker6284.github.io/infinite-craft-cli/).

Originally built on [infinite-craft](https://github.com/sqdnoises/infinite-craft) by [@sqdnoises](https://github.com/sqdnoises). As of v1.0, uses [curl_cffi](https://github.com/lexiforest/curl_cffi) directly.

## Installation

```bash
pip install infinite-craft-cli
```

### Browser extension

Install the [Infinite Craft Trainer](https://chromewebstore.google.com/detail/infinite-craft-trainer/gaonnldioeddnfopgejohbhoajoagbnd) from the Chrome Web Store — it loads automatically on neal.fun/infinite-craft and fetches the current trainer (`trainer.min.js`) from [GitHub Pages](https://hacker6284.github.io/infinite-craft-cli/) with `cache: 'no-store'`, so feature updates ship without waiting for a Chrome Web Store release. The extension manifest version (`extension/manifest.json`) is independent of the Python CLI package version (git tags). See [PRIVACY.md](PRIVACY.md) for the remote-script trust model. Works in Edge, Brave, and other Chromium browsers.

**Manual QA (extension):** After changes to `extension/loader.js`, load the unpacked extension on live [neal.fun/infinite-craft](https://neal.fun/infinite-craft) and confirm the trainer UI appears and IndexedDB-backed commands work (e.g. `/list`).

For other install methods (console snippet, userscript), see the [web trainer page](https://hacker6284.github.io/infinite-craft-cli/).

## Usage

### Interactive mode

```bash
infinite-craft
```

This opens a REPL where you can combine elements, search discoveries, and more:

```
=== Infinite Craft CLI ===

craft> Water + Fire
  💨 Water + 🔥 Fire = 💨 Steam

craft> /search steam
  💨 Steam

craft> /target Steam
  Target set: Steam — you'll be asked whether to continue the batch when this is crafted.

craft> /help
```

Sticky chrome under the log always shows the pair-API **rate** bar (next-slot wait + remaining budget). While a job runs it also shows **running** command + progress, or **◆ confirm** with the reason (pair count, target hit). `y` / `n` is only on `confirm [y/n]>`.

### Non-interactive mode

Most REPL commands are available as subcommands (shorthand operators like `+`, `++`, and `*` are REPL-only; `script` runs a script non-interactively):

```bash
infinite-craft combine "Water" "Fire"
infinite-craft search "steam"
infinite-craft list
infinite-craft recipe "Steam"
infinite-craft import "Steam"
infinite-craft export
infinite-craft fill
infinite-craft unfilled
infinite-craft prune
infinite-craft exhaust "Water"
infinite-craft crawl "Water" "Fire"
infinite-craft permute "w*"
infinite-craft with "Water" "fire*"
infinite-craft cross "fire*" "water*"
infinite-craft lucky 25
infinite-craft --version
```

## Commands

### Combine & crawl

| Shorthand | Slash command | Description |
|-----------|---------------|-------------|
| `<element> + <element>` | `/combine <element> <element>` | Combine two elements |
| `<element> ++ <element>` | `/crawl <element> <element>` | Crawl: generations over a growing pool until one adds nothing to it |

### Bulk combine

| Shorthand | Slash command | Description |
|-----------|---------------|-------------|
| `<query> * <query>` | `/cross <query> <query>` | Cross-combine matches from both queries |
| | `/permute <query>` | Combine all matching elements with each other |
| | `/permutate <query>` | Permute repeatedly until no new discoveries |
| | `/exhaust <query>` | Each match combined with all discoveries |
| | `/lucky [count]` | Try random untried pairs — entropy mining (default 10). Pairs that produced Nothing aren't persisted, so later sessions may re-draw them |

### Query syntax

Used by `/search`, `/with`, `/permute`, `/permutate`, `/cross`, `/exhaust`, and script patterns:

| Syntax | Meaning |
|--------|---------|
| `substring` | Case-insensitive substring (default) |
| `*` `?` `[]` | fnmatch wildcards (e.g. `fire*`, `mu?`) |
| `/pattern/` | Regex, case-insensitive; real alternation (`/steam\|mist/`), grouping, anchors |
| `!<query>` | Exclude matches (e.g. `!fire*` = everything except `fire*`) |
| `!` | All elements (exclude nothing) |
| `^<query>` | First discoveries only (e.g. `^fire*` = new `fire*` matches) |
| `^` | All first discoveries |

### Other commands

| Command | Description |
|---------|-------------|
| `/search <query>` | Search discoveries |
| `/recipe <element>` | Show shortest recipe from base elements |
| `/list` | List all discovered elements |
| `/import <element\|file.ic>` | Import from Infinibrowser or `.ic` save file |
| `/fill` | Fetch missing recipes from Infinibrowser |
| `/unfilled` | List elements without recipes |
| `/prune` | Remove orphan elements Infinibrowser can't fill |
| `/export [path]` | Export discoveries as `.ic` save file |
| `/history` | Show combinations tried this session |
| `/target <element>` | Watch for a result; ask y/n to continue the batch on hit |
| `/target` | Show current target |
| `/target clear` | Clear target |
| `/auto [on|off]` | Auto-approve bulk y/n confirms — bare `/auto` toggles |
| `/relay [on|off|status]` | Hive mind: shared pair cache with other users — bare `/relay` toggles, on by default |
| `/queue` | Show running and pending commands (status also appears in chrome) |
| `/help` | Show help |
| `/quit` | Exit |

### Scripting

Every non-slash line in the REPL is a script (the old shorthands are one-statement scripts). Statements are separated by `;`; whitespace-delimited operators are structure and everything attached is pattern material, so `mountain range + ship` still combines exactly what it says. Bare words are element references (error if unknown); patterns with metacharacters (`* ? [] /regex/ ! ^`) are queries; quoted strings are exact element references.

```text
targets := ^(fire* / water*) ;          # bind first-discoveries having a fire*+water* recipe
(targets)* -> |[]| < 2 ;                # permute passes until a pass yields < 2 new
[] * (earth* / fire*)                   # cross what that made against filtered earth*
```

Pure, loop-free scripts (bare queries, unions, filters, walrus bindings) run immediately and interleave with a running bulk command, like `/search`; mutating scripts and loops queue on the pair lane.

Highlights: `,` union, `-` difference, `&` intersect, `/`/`%` known-recipe filters, `(expr)*`/`(expr)**`/`(expr)!` permute/permutate/exhaust, `(expr)100` first-100 (`(expr)(|x*|)` dynamic counts), `(expr)100?` random-100, `(expr)?` shuffle, `A * B` cross, `A ++ B` crawl, `[ expr ]`/`[]` new-element sets, `set @x body` for-each, `body -> cond` do-until, `body ~ cond` while, `cond ? a : b` ternary. Conditions are statically pure — mutations inside a condition are parse errors. Every mutating operation's value is the set it produced, so pipelines chain. Save scripts as `.ice` files and run them with `/script <path>` (file picker in the trainer) or `infinite-craft script -f path`. Non-interactive runs have no y/n prompt: bulk operations over the warn threshold announce their pair count and proceed; script failures exit non-zero. Full spec: `docs/superpowers/specs/2026-08-20-script-language-v0.6.md`.

Breaking changes in 2.0: `+|` is removed (use `*`; `/with` remains), bare words in old `*`//`+|` positions were substring queries and are now element references (write `*fire*` for substring), bare pattern lines print matches instead of erroring, `A + B + C` chains combines, and `/permutate` no longer stops at 50 rounds.

### Queues and confirm (Python REPL + trainer)

Pair-API work (combine, crawl, permute, exhaust, …) and Infinibrowser work (`/fill`, `/prune`, `/import`) use **independent queues** so one lane can run while the other is busy. Local commands (`/help`, `/search`, `/list`, `/recipe`, `/history`, `/clear`, `/unfilled`, `/queue`, `/target`) run immediately — their output may interleave with a running job.

Large batches and `/target` hits pause on `confirm [y/n]>`: **y** continues, **n** / Esc / Stop cancels remaining work. The job row states the reason (`331 pairs`, `target hit`); keybindings are not repeated in the log.

| Key | Action |
|-----|--------|
| Esc | Skip current command, continue to next in that lane (TTY / Stop in the trainer) |
| Ctrl+C | While running: stop and discard remaining queue; at confirm: decline only |

Deferred commands print `Queued:` when that lane is already running.

## Hive mind (shared pair cache)

Neal's server caches every pair anyone has ever combined — only genuinely novel
discoveries cost real inference — so there is no reason for two players to spend
rate-limit slots asking the same question twice. The CLI and trainer share a
small relay (`relay/`, a free Render web service) holding the union of every
connected user's results. Cache order everywhere is **local → hive → neal.fun**:
a rate-limit slot is committed only after both cache tiers miss, and fresh
results are contributed back automatically. Bulk runs sweep the whole batch
against the hive in one request before spending their first slot.

**Bounty board.** When a bulk run overflows your own rate budget, the extra
pairs go on a shared board. Idle clients elsewhere pick them up *within their
own rate limit* and drop the results into the cache, which your run re-sweeps
and absorbs for free — so the neal.fun rate limit is pooled across willing
users instead of stranding your backlog. Working bounties is on by default and
symmetric: while you're idle at the prompt, your client serves the hive too.

**Peer review.** A cache entry is trusted only after a second, independent
client re-asks neal.fun and gets the same answer (review bounties never accept
a cached answer — that would defeat the check). A conflicting claim against an
unreviewed entry drops it and re-opens the pair, so the network self-heals
around a bad result rather than trusting last-writer.

**Same-IP fairness.** The relay sees each client's public IP and tells everyone
how many sessions on that IP are actively spending neal budget; each client
divides its per-IP window by that count. Two laptops on one home Wi-Fi each
settle to half the limit *before* hitting a 429, not after. Bounty work is
offered only to IPs where nobody is running, so serving the hive never contests
your own household's runs.

**429 = stand down.** neal.fun's 429 is an hours-long IP ban, not a backoff
signal, so a client that trips one stops making neal requests entirely (hive
lookups still work) and broadcasts the cooldown to every session on its IP.

The tier is on by default and fails open — if the relay is down or cold, the
CLI just talks to neal.fun as before. `/relay` toggles it and `/relay status`
shows connection, hits/contributions/bounties, the current budget split, and
any cooldown. `IC_RELAY=off` disables it at startup; `IC_RELAY_URL` points at a
different relay. The relay keeps its cache in RAM and is re-seeded by clients
from their own recipe stores on connect (plus an optional Upstash Redis
snapshot across restarts), so a cold instance refills within minutes.

The rate bar shows all of this: a pulsing 🐝 counter beside the bar for pairs
served to you by the hive (they cost no slots), honey-gold cells inside the bar
for slots you've lent to bounty work, and a cooldown note when you're banned.

## Data storage

Discoveries and recipes are stored in `~/.infinite-craft-cli/`:

- `discoveries.json` -- all discovered elements
- `recipes.json` -- known element combinations
- `export.ic` -- default export location

## Browser extension / bookmarklet

The [browser trainers](https://hacker6284.github.io/infinite-craft-cli/) share the same command syntax, query matching, and script language as the Python CLI (wildcards, `/regex/`, `!` exclude, `^` first discoveries, `/combine`, `/with`, `/cross`, `/target`, and scripts). Browser-only additions: `/clear` to clear the output panel, and IndexedDB storage instead of `~/.infinite-craft-cli/`. Rate, job, and queue status live in the sticky panel above the input (visual-only; no `/queue` command). Local commands such as `/search` may interleave output with a running queued command.

**Long runs in a background tab:** the trainer holds a [Web Lock](https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API) while a run is active, which keeps Chrome's Memory Saver / Energy Saver from freezing the tab under current heuristics. If a backgrounded run still pauses (the exemption is a heuristic, not a guarantee), add `neal.fun` to `chrome://settings/performance` → "Always keep these sites active", or keep the tab in a partially visible window. Truly long unattended jobs are what the Python CLI is for.

## Development

After editing `bookmarklet/trainer.src.mjs`, rebuild the bundles:

```bash
bazel build //bookmarklet:site
```

Build the Python wheel:

```bash
bazel build //release:wheel.dist
```

Run all tests:
```bash
bazel test //...
```

This includes `//sudo:craft_lockstep_test`, which runs the kernel's own test
suite against both the generated Python and JavaScript and diffs the two.
`//tests/...` alone skips it.

Run integration tests (hits the real API):
```bash
bazel test //tests:test_integration --test_env=INTEGRATION_TESTS=1
```

## License

MIT

