Meta CLI and package manager for the Paperworlds text- stack.
One install, one CLI surface. Bootstraps tools, manages workflows, and gives your repos — and the agents working on them — a shared communication layer: threads, decisions, specs, and ideas, all queryable from the terminal.
pipx install textworkspace
tw init
tw shell install --fish # or --bash / --zshtw init discovers existing tools, prompts for missing ones, and bootstraps Go
binaries from GitHub releases. The shell wrapper is required for commands that
need to modify shell state (tw switch, ta switch).
Optional — enable tab completion:
env _TW_COMPLETE=fish_source tw > ~/.config/fish/completions/tw.fish
env _TEXTFORUMS_COMPLETE=fish_source textforums > ~/.config/fish/completions/textforums.fishtw init # guided first-run, bootstraps binaries
tw status # unified view: profile, proxy, servers, sessions
tw doctor # health + stale paths + spec conformance across repos
tw up / tw down # bring the whole MCP fleet up / down via textserve
tw update [tool] # update managed binaries and packages
tw dev install # install every tool from its local repo (editable mode)
tw sync # reinstall after pulling; also reconciles combos
tw which <tool> # where is this binary installed, what version
Each installed tool also becomes a passthrough: tw proxy <anything>,
tw serve <anything>, tw read <anything>, tw accounts <anything>,
tw map <anything> forward unknown subcommands to the real binary — so
tw is one muscle-memory entry point, not a shim that hides features.
A workspace is a named join of profile + servers + project — one command to switch context. Combos are YAML recipes for multi-step workflows.
tw start data # switch profile, start servers, open a session
tw start data my-session # same, custom session name
tw stop data
tw workspaces list / status / add / edit
tw <combo> [args] # any combo name becomes a top-level command
tw --dry-run <combo> # preview without executing
tw combos list / edit / export / update / remove
tw combos install gh:paperworlds/textcombos/workdayExample combo:
combos:
workday:
description: Start work environment
args: [profile]
steps:
- run: accounts switch {profile}
- run: proxy start
skip_if: proxy.running
- run: servers start --tag defaulttw keeps a registry of repos you work on — personal, work, scratch — so
every cross-repo command (forums, ideas, specs) agrees on what "mono" or
"textread" means. Filter by profile to separate work from personal.
tw repo add mono /Users/projects/paradigm/mono --profile work
tw repo list [--profile work]
tw repo move mono /new/path # updates config + ~/.claude/projects + every tool's own config
tw repo import # pull repos from any tool that exposes themWhen a folder moves, tw repo move orchestrates: rename on disk,
rewrite config.yaml, rename ~/.claude/projects/<encoded>, and call
each installed tool's <tool> repo move to update downstream state.
tw doctor aggregates STALE <name> <path> warnings from every tool
that implements the contract.
Async notes, bug reports, cross-repo coordination — agents and humans in
the same channel. Threads live in ~/.textforums/<slug>/thread.yaml.
textforums new --title "proxy status broken" --repo textworkspace --tag bug
textforums list --repo textworkspace --status open
textforums show <slug>
textforums add <slug> --content "reproduced on 0.4.2"
textforums close <slug> --content "shipped in 0.4.3"Also available as tw forums <sub> — same commands, same semantics.
Pins — mark a thread urgent so it surfaces above everything else:
textforums pin <slug> # priority=high
textforums pin <slug> --until 2026-12-31 # auto-expire
textforums new --pin --title "…" # pin on createDecisions — promote a concluded thread to canonical, ADR-lite:
textforums decide <slug> --summary "Use protobuf for the wire format."
tw forums decisions list [--repo X] [--query T] [--since YYYY-MM-DD]
tw forums decisions show <slug>
tw forums decisions supersede <old> <new> # ADR-history patternOnce decided, the thread is frozen (title + decision fields) and drops out of inbox by default — decisions are the law, not the queue.
Decisions → textmap — promote decided threads into a queryable graph:
tw forums decisions export # write decision-<slug>.md files
tw forums decisions ingest # export + `textmap ingest` in oneEach decided thread becomes a decision node in textmap; superseded-by
chains become replaces edges (direction inverted to match textmap
convention, OLD marked deprecated), context.repos become applies_to
edges, context.spec becomes an implements edge, tags + repos become
labels. The forum stays the source of truth — export is a full rewrite,
stale files are pruned.
Inbox — per-repo mailbox with unread state, one-stop agent onboarding:
tw forums inbox # this repo, CWD-inferred
tw forums inbox --as reviewer # address-filtered
tw forums inbox --profile work # aggregate across all work repos
tw forums inbox --format prompt # paste-ready dump for handoff
tw forums inbox --mark-read # once you've processed it--format prompt is the key primitive for agent handoffs — a fresh
session can resume purely by reading the thread.
Every thread carries rich context (repos, paths, spec, to,
mentions, commit) so filters compose:
textforums new --title "migration plan" \
--repo textworkspace --repo textaccounts \
--spec textaccounts-api \
--to reviewer --mention deployer \
--tag planning --pinRun tw forums quickstart for a 30-second agent onboarding, or
tw forums example for an annotated lifecycle.
Backup & restore — plain-directory export, git-friendly, no archives:
textforums export ~/snapshots/forums # idempotent copy of every thread.yaml
textforums export ~/snapshots/forums --status resolved --tag textworkspace
textforums import ~/snapshots/forums # restore (skip on conflict by default)
textforums import ~/snapshots/forums --on-conflict overwrite --dry-runexport writes a .manifest.yaml with {exported_at, count, source_root, host}. Wire it into a daily just/cron recipe pointed at a git repo and
you get full history + a one-line restore on a fresh host (git clone … && textforums import …). No tarballs, no DB.
Owned by one repo, followed by others, checked automatically.
Specs live in the owner repo at docs/specs/<slug>.md (YAML frontmatter
- markdown body); consumers declare what they follow in
docs/SPECS.yamland mark implementations with# SPEC: <slug>comments in source.
tw forums spec new <slug> --owner <repo> --title "..."
tw forums spec list [--owner R] [--consumer R] [--status S]
tw forums spec show <slug>
tw forums spec refs <slug> # grep `# SPEC:` markers across repos
tw forums spec check [--repo R] # conformance check
tw forums spec adopt <slug> # freeze frontmatter (slug/owner/version/…)
tw forums spec supersede <old> <new>
tw forums spec brief [--repo R] # agent-ready 'what do I own / follow' brief
tw forums spec explain # the full format reference, inlineDrift is surfaced by tw doctor — missing implementations, pinned
versions out of sync with upstream, frozen fields mutated post-adoption.
Discover and aggregate IDEAS.yaml (or directory of per-file YAMLs) from
every registered repo. Personal repos drop a docs/IDEAS.yaml; work
repos can use .files/ideas/*.yaml where each file is one idea.
tw ideas list # profile | repo | id | status | title
tw ideas list --profile work
tw ideas list --status planned --query forums
tw ideas show mono deploy-notifications # full reasoning, not just summary
tw ideas threads mono deploy-notifications # forum threads tagged idea:mono/deploy-notifications
tw ideas quickstart # onboardingIdea → thread link is by tag convention (idea:<repo>/<id>), so
expansion / proposal / decision loops use the same forums primitives.
A playbook is a frozen-when-adopted YAML spec encoding a sequence of
agent actions. tw is the registry/discovery side; textprompts'
pp playbook run is the executor. Playbooks live at
<owner-repo>/docs/specs/playbooks/<slug>.yaml and follow the same
adopt → freeze → supersede chain as cross-repo specs.
# docs/specs/playbooks/triage-stale-pr.yaml
slug: triage-stale-pr
owner: textworkspace
status: draft
version: 0.1.0
persona: pr-reviewer
description: Triage a single PR — fetch state, classify, post verdict.
inputs:
- name: pr_number
type: int
required: true
- name: repo
type: string
required: true
steps:
- id: fetch
kind: run
run: gh pr view ${inputs.pr_number} --repo ${inputs.repo} --json author,updatedAt
out: pr
- id: classify
kind: persona_turn # one LLM turn, bound to the playbook persona
persona_turn: |
Decide CLOSE | PING | LEAVE — context: ${steps.fetch.out}
out: verdict
- id: post
kind: run
skip_if: "${steps.classify.out} startswith 'LEAVE'"
run: textforums new --tag playbook:triage-stale-pr --content "..."tw playbook list # registry across registered repos
tw playbook show triage-stale-pr
tw playbook adopt triage-stale-pr # promote draft → adopted (frozen)
tw playbook export-textmap # project as textmap protocol nodes
tw run triage-stale-pr -i pr_number=42 -i repo=foo/bar # delegates to pp playbook runEach run produces a forum thread tagged playbook:<slug> / run:<id>,
with one structured entry per step (step_id, status, output_summary,
agent_feedback, agent_ideas). Statically validated against
docs/specs/playbooks/_schema.json. See
docs/specs/playbook-format.md and
docs/audit.yaml for the full contract.
Read-only queries over the run threads playbooks produce, plus an
aggregator for agent_ideas entries that haven't been promoted yet.
tw runs list # all playbook runs
tw runs list --playbook triage-stale-pr
tw runs show <run-slug> # parsed per-step audit trail
tw runs ideas list --unread # agent suggestions across all runs
tw runs ideas show <run-slug> <step-id>
tw runs ideas promote <run> <step> <idx> --into mono # graduate into the curated inbox
# with from_run / from_step / from_playbook provenanceThe aggregator is on-demand-scan + mtime cache (no index file). Promoted
entries cross-reference the curated tw ideas inbox via provenance
fields, so --unread derives state without persisting it.
tw brief # all workspaces
tw brief -w personal # filter textmap nodes to one workspace
tw brief -n 5 # cap items per section (default 10)One-screen synthesis pulled from textmap (active initiatives, open problems,
active recurring routines) and textforums (open threads). Sources that are
unavailable are silently skipped — partial output is fine. Useful at session
start, after tw go, or whenever you want to land in context without
manually running four queries.
When an initiative has references→thread edges in textmap (add via
textmap edge add <init> <thread> references), brief inlines the linked
thread slugs under the initiative and excludes them from the standalone
"Open forum threads" section — so each thread surfaces exactly once,
under the initiative that owns the conversation.
A daily is just a playbook (slug daily by convention). tw daily
is a thin scheduler: run-it-once-per-day, record completion, refuse
to re-run without --force.
tw daily # run today's sweep if not already done
tw daily --dry-run # preview the steps
tw daily --force # re-run regardless
tw daily --slug morning # alternate playbookThe shipped daily playbook does an 8-step no-LLM sweep
(docs/specs/playbooks/daily.yaml):
textmap inbox → all open forums → work block (inbox + ideas) → personal
block → cross-profile run-ideas → personas review. Ordering is
deliberate (work first, then personal). Each step's output lands as a
structured forum entry; you read the resulting thread top-to-bottom in
30 seconds.
State at ~/.local/state/textworkspace/daily.yaml. Failures don't mark
the day complete — re-runs retry, never silently skip a broken sweep.
textprompts personas (slug, description, allowed tools, system prompt,
typed inputs) are exactly the shape of a Claude Code SKILL.md. tw skill
is a thin renderer — one source of truth, two execution modes:
tw skill list # delegates to `pp persona list`
tw skill export pr-reviewer # ~/.claude/skills/pr-reviewer/SKILL.md
tw skill export pr-reviewer --dry-run # print the rendered file
tw skill export pr-reviewer --out ./.claude/skills # project-scopedThe persona YAML is the truth — exported SKILL.md inherits description
and allowed-tools from frontmatter, body is the system prompt, and any
declared inputs: block becomes a documented "Inputs" appendix. Re-run
to refresh; nothing else has to know about the duplication.
# 1. Set the stage
tw init && tw shell install --fish
tw repo add mono ~/work/mono --profile work
# 2. See what's pending
tw forums inbox --profile work --format prompt | pbcopy # paste into next session
# 3. Capture an idea at work
mkdir -p ~/work/mono/.files/ideas
cat > ~/work/mono/.files/ideas/deploy-notifications.yaml <<'YAML'
title: "Deploy notifications for all services in #eng-team-delta"
status: brainstorm
priority: 2
summary: Extend the paradex-backend→Jenkins→Slack hook to the whole stack.
YAML
tw ideas list --profile work
# 4. Open discussion, pin it, decide
textforums new \
--title "expand: deploy notifications" \
--repo mono --to lead \
--tag idea:mono/deploy-notifications --pin
textforums add <slug> --content "Proposal A: central Jenkins listener…"
textforums decide <slug> --summary "Going with Proposal A"
# 5. Promote to a cross-repo spec once it's durable
tw forums spec new mono-deploy-notifs --owner mono --title "Deploy notif protocol"
# 6. Each morning — one command sweeps everything
tw daily # textmap inbox + open forums + work/personal blocks + agent ideas + personas~/.config/paperworlds/config.yaml # tools, versions, defaults, repos, workspaces
~/.config/paperworlds/combos.yaml # user combos
~/.config/paperworlds/combos.d/ # community combos
~/.textforums/<slug>/thread.yaml # forum threads + per-repo last_read state
$TEXTFORUMS_ROOT overrides the forums root; config.forums.root overrides
the default per install. $TEXTFORUMS_AUTHOR / config.forums.author set the
default author for new entries.
tw init interrogates the system for each known tool — Python packages via
uv, Go binaries via GitHub Releases — and records versions and binary
paths in config.yaml. Go tools (textproxy, textserve) ship as pre-built
archives verified against a .sha256 sidecar and unpacked into
~/.local/share/textworkspace/bin/; symlinks point at the active version.
Combos are loaded at startup from all YAML files in combos.yaml and
combos.d/. Each combo declares steps using a small DSL (run, skip_if,
args) and the combo name becomes a top-level tw subcommand via dynamic
dispatch.
Passthrough groups (tw proxy, tw serve, tw read, tw accounts,
tw map) forward unknown subcommands to the underlying binary with a
custom Click group — so the full tool surface is always reachable without
re-implementing every subcommand here.
Cross-repo features (forums, specs, ideas) share a repo registry — the
union of config.repos and a scan of dev_root. Registered entries win
on conflict and can carry a profile tag used by --profile filters.
Shell integration writes fish/bash/zsh wrapper functions via
tw shell install. The wrappers handle commands that modify shell state
(tw switch, ta switch) which cannot work as subprocess calls.
tw dev install builds every tool from local repo checkouts in editable
mode (uv tool install -e), then records the git hash in the version
string so tw doctor can detect stale installs without re-running.
Recently shipped:
-
tw ideas expand <repo> <id>— pp worker opens a thread with 2–3 proposals; backlink lands on the idea viaidea:<repo>/<id>tag - Playbooks v0.1 —
tw playbook list/show/adopt/export-textmap,tw run,tw runs list/show,tw runs ideas list/show/promote, andtw daily(sections 7–9 above) -
pp persona run -i k=vmirrorspp playbook run -i k=v— sameInputshape,--ideakept as back-compat alias -
tw skill list/export— render textprompts personas as Claude Code SKILL.md without duplicating metadata (section 11) -
textforums export/import— plain-directory backup, git-friendly, idempotent; one-line restore on a fresh host (section 4) -
tw briefinitiative→thread inlining — threads referenced by an initiative surface under it once and drop out of the open list -
_SUPPORT_TOOLStier intw doctor+tw dev install— optional personal/local tools (textbridge, …) render as info, not FAIL
In flight:
- Live forums → textmap dual-write on
decide/supersede(today: batch viatw forums decisions ingest) - Extract
textforumsinto its own repo (current home: inside textworkspace) - Publish to PyPI
- Playbooks v2:
for_each:/when:/sub_playbook(schema already reserves these; runners reject in v1) - Run-time output validation (today: spec declares
outputs:, runtime doesn't enforce shape) -
tw forums decisions import— pull paper ADRs from any repo that ships them
textworkspace is part of Paperworlds — an open org building tools and games around AI agents and text interfaces.