{"id":"86fdb043-8f5e-4b35-bb05-94639551301c","slug":"metamuse-cli","kind":"skill","name":"metamuse-cli","summary":"MetaMuse CLI — local-first social evidence, Muse discussion and drafting tool (pre-alpha, unofficial)","owner":"TED-H-com","repoUrl":"https://github.com/TED-H-com/metamuse-cli","license":"Apache-2.0","licenseTier":"permissive","trust":"community","stars":2,"match":null,"fewStars":true,"format":"skill","createdAt":"2026-10-13T21:39:38.000Z","addedAt":"2026-10-21T18:17:08.490Z","logoUrl":"https://avatars.githubusercontent.com/u/342602074?v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["writing"],"tags":[],"runtime":"python","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-26T18:15:31.829Z","sample":false,"url":"https://conduits.dev/tools/metamuse-cli","language":"Python","transports":["stdio"],"install":{"pypi":"metamuse-cli"},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"# MetaMuse CLI\n\nTurn comments and transcripts into a recap you can check against the originals.\n\nHave event feedback to review, or a video transcript to quote? MetaMuse keeps your permitted source material, finds matching passages, and saves drafts with their citations. You or your AI assistant writes the recap; MetaMuse keeps the originals and the export together on your computer.\n\nUse it from a terminal, or connect it to an AI assistant that can run CLI or MCP tools. The tool runs on your machine.\n\n[Try the example](#try-it) · [繁體中文上手指南](https://github.com/TED-H-com/metamuse-cli/blob/main/docs/GETTING_STARTED.md) · [Connect accounts and publish](https://github.com/TED-H-com/metamuse-cli/blob/main/docs/CONNECTED_WORKFLOW.md) · [Connect your assistant](https://github.com/TED-H-com/metamuse-cli/blob/main/docs/dev/SKILL.md)\n\n## What can I do with it?\n\n- **Review event feedback:** find comments about a request, then have your assistant draft a recap with links to the original comments.\n- **Find a passage in a transcript:** import local SRT or VTT subtitles and search for words you want to quote.\n- **Keep a checkable draft:** save your text with source references and export Markdown, text, CSV, JSONL or a bundle.\n\nSearch currently matches literal text. Your assistant handles interpretation and writing; a saved citation still needs your review.\n\n## Try it\n\nInstall [uv](https://docs.astral.sh/uv/getting-started/installation/), then install the alpha package and run the bundled example. These instructions target `0.1.0a2`; [PyPI lists the available releases](https://pypi.org/project/metamuse-cli/#history). uv manages the Python version and creates an isolated environment for the command.\n\n```sh\nuv tool install --python 3.12.13 'metamuse-cli==0.1.0a2'\nmetamuse demo\n```\n\nInstallation downloads Python and dependencies. The example itself runs locally with **synthetic comments and a prewritten recap**. It needs no account, Muse connection or AI service.\n\nYou will import three records, search for `字幕` (subtitles), and export a Markdown recap with the matching comment and its source reference. The terminal prints `Export directory:`; open the `.md` files there to see the result. This is a demonstration of the data workflow, not an AI-generated analysis.\n\nEvery run creates a separate workspace outside the checkout, under your system's user data directory (`metamuse/demos/`). It stays there so you can inspect it. Delete the workspace printed by the example when you no longer need it.\n\nThe [getting started guide (繁體中文)](https://github.com/TED-H-com/metamuse-cli/blob/main/docs/GETTING_STARTED.md) shows the expected result and how to move on to your own files. The sample inputs are in [src/metamuse/data/examples/event-comments.jsonl](https://github.com/TED-H-com/metamuse-cli/blob/main/src/metamuse/data/examples/event-comments.jsonl).\n\n## Use it with your AI assistant\n\nConnect the installed CLI using the [Skill setup guide](https://github.com/TED-H-com/metamuse-cli/blob/main/docs/dev/SKILL.md), or configure the [MCP server](https://github.com/TED-H-com/metamuse-cli/blob/main/docs/dev/MCP.md). Loading the Skill supplies instructions; your assistant also needs access to the working CLI or MCP backend.\n\nOnce connected, try a request like this:\n\n> Use MetaMuse to review the event comments I provide and have permission to use. Find feedback about subtitles, group the requests, and draft a recap with links to the original comments. Show me the sources and gaps before saving. Export Markdown and keep this work local.\n\nMuse is an optional collaborator for further questions. Local import, search, drafts and exports work without a Muse account. A real Muse connection needs separate configuration and human approval. Version `0.1.0a2` includes the compatibility fixes tested with two real Muse turns using synthetic input, restart/read-back and cited draft exports. That test crossed candidate builds; it was not a fresh end-to-end test of one unchanged release. The [Muse guide](https://github.com/TED-H-com/metamuse-cli/blob/main/docs/dev/MUSE.md) records the scope. Version `0.1.0a1` lacks these fixes.\n\nUse `metamuse connect muse`, `connect facebook`, or `connect publishing` to validate your\nprivate credential file and bind the account without editing TOML. Add `--enable-network`\nto explicitly enable network use; each external action still needs its own approval.\nThe [connected workflow guide (中文)](https://github.com/TED-H-com/metamuse-cli/blob/main/docs/CONNECTED_WORKFLOW.md) walks through setup,\ncollecting one post, Muse follow-ups, a cited draft and Page publication. This complete\nsequence has synthetic integration coverage; real-account acceptance is pending.\n\n## What can I use today?\n\nStart with files on your computer. You can search, save drafts and export without a Muse account. Your assistant handles interpretation and writing once connected.\n\n| What you want to do | Availability | What you get |\n|---|---|---|\n| Import saved comments and subtitles | Ready with local files | Bring together the saved comments, articles and subtitle files you already have, so you can find them later. |\n| Find a comment or transcript passage | Ready with local files | Search for a word such as subtitles, then read the matching text and its source. |\n| Keep a draft with its sources | Ready with local files | Save text you or your assistant wrote, together with the original material you cited. |\n| Export a file you can edit | Ready with local files | Keep your draft and material as text documents or spreadsheet-friendly files for further editing. |\n| Work with your AI assistant | Connect your assistant first | Connect your assistant to MetaMuse, then ask it to find material and draft a recap with citations. |\n| Ask Muse follow-up questions | Requires 0.1.0a2 and account setup | Select material to share, ask Muse a question, then follow up in the same conversation. A bounded synthetic-input test with a real account succeeded across corrected candidate builds; `0.1.0a2` includes those fixes. |\n| Collect a Facebook post or remote video | Needs extra setup; real-account use is unverified | Additional setup and your approval are required. These collectors have not been verified with real accounts. |\n| Publish a text post to a Facebook Page | Requires 0.1.0a2 and account setup | Approve the exact text and Page, then send and check the receipt. One real test post and read-only reconciliation succeeded across corrected candidate builds; `0.1.0a2` includes the read-back fix. Text-only and experimental; [setup and limits](https://github.com/TED-H-com/metamuse-cli/blob/main/docs/dev/PUBLISHING.md). |\n\nSee the [getting started guide](https://github.com/TED-H-com/metamuse-cli/blob/main/docs/GETTING_STARTED.md) to try the local example,\nor [connect your assistant](https://github.com/TED-H-com/metamuse-cli/blob/main/docs/dev/SKILL.md) to use your own material.\nDevelopers can find the [implementation and verification inventory](https://github.com/TED-H-com/metamuse-cli/blob/main/docs/dev/STATUS.md)\nin the development documentation.\n\n<details>\n<summary>Advanced: ask Muse follow-up questions with one bounded approval</summary>\n\nThe implemented [bounded research workflow](https://github.com/TED-H-com/metamuse-cli/blob/main/docs/dev/MUSE.md#bounded-research-workflow-synthetic-evidence) lets a human approve a fixed packet, topic, expiry and question/read budget. Your assistant can then ask varied questions within that scope, keep the discussion across restarts, and save a draft citing sources and Muse observations. Changed data or scope needs new consent. The guide distinguishes fixture coverage from the bounded real-account test and documents the fixes included in `0.1.0a2`.\n\n</details>\n\n<details>\n<summary>Advanced: full command-by-command offline example, including fixture Muse</summary>\n\n## Local offline walkthrough\n\nRun from the repository root after `uv sync --python 3.12`. This synthetic walkthrough collects data, previews a packet, opens a fixture Muse discussion, asks a question and a follow-up in the same discussion, saves host-written text and exports five formats. The host shows the human each `E_SCOPE_REQUIRED` approval preview. Only the human runs `operator permit approve`, reviews its stderr (including expiry and uses), and repeats the unchanged call with the returned grant. The agent never approves. Evidence is OFFLINE/FIXTURE, with no network or accounts.\n\nThese Bash blocks run in order. Python only edits configuration and copies public JSON fields; every product operation uses the shipped CLI. `tests/test_readme_walkthrough.py` executes these exact blocks in a temporary workspace.\n\n```bash\nexport UV_OFFLINE=1\nmkdir -p \"$HOME/agent-work/tmp\"\nexport SLICE_DIR=\"${SLICE_DIR:-$(mktemp -d \"$HOME/agent-work/tmp/metamuse-at042-XXXXXX\")}\"\nexport SLICE_WS=\"$SLICE_DIR/workspace\"\nmkdir \"$SLICE_DIR/inputs\"\nmm() { uv run metamuse --workspace \"$SLICE_WS\" --json --non-interactive \"$@\"; }\noperator() { uv run metamuse --workspace \"$SLICE_WS\" --json \"$@\"; }\nfield() { uv run python -c 'import json,sys; print(json.load(open(sys.argv[1]))[\"data\"][sys.argv[2]])' \"$1\" \"$2\"; }\napproval_field() { uv run python -c 'import json,sys; print(json.load(open(sys.argv[1]))[\"approval\"][sys.argv[2]])' \"$1\" \"$2\"; }\npreview() {\n  local output=\"$1\"; shift\n  local result=0\n  mm \"$@\" > \"$output\" || result=$?\n  test \"$result\" -eq 3\n  uv run python -c 'import json,sys; e=json.load(open(sys.argv[1])); assert e[\"error\"][\"code\"] == \"E_SCOPE_REQUIRED\" and e[\"approval\"]; print(e[\"approval\"][\"summary\"]); print(e[\"approval\"][\"next_action\"])' \"$output\"\n}\nsave_request() {\n  uv run python -c 'import json,sys; a=json.load(open(sys.argv[1]))[\"approval\"]; json.dump(a[\"request\"],open(a[\"request_file\"],\"w\"),ensure_ascii=False)' \"$1\"\n}\n\nmm init > \"$SLICE_DIR/init.json\"\nuv run python - <<'PYTHON'\nimport json, os\nfrom pathlib import Path\nroot = Path(os.environ['SLICE_DIR'])\nconfig = Path(os.environ['SLICE_WS']) / 'metamuse.toml'\ntext = config.read_text().replace('[app]\\n', '[app]\\nimport_roots = ' + json.dumps([str(root / 'inputs')]) + '\\n')\ntext = text.replace('[muse]\\nenabled = false', '[muse]\\nenabled = true\\nbackend = \"fixture\"')\nconfig.write_text(text)\ncorpus = Path('tests/fixtures/corpus/import/comments_tree.jsonl')\n(root / 'inputs/comments.jsonl').write_text('\\n'.join(corpus.read_text().splitlines()[:3]) + '\\n')\nPYTHON\nmm doctor > \"$SLICE_DIR/doctor.json\"\nmm project create --name 'Synthetic slice' --request-key project > \"$SLICE_DIR/project.json\"\nexport SLICE_PROJECT=$(field \"$SLICE_DIR/project.json\" id)\nmm add --project \"$SLICE_PROJECT\" --file \"$SLICE_DIR/inputs/comments.jsonl\" --kind jsonl --request-key import --run > \"$SLICE_DIR/import.json\"\nmm search --project \"$SLICE_PROJECT\" --query '這是' > \"$SLICE_DIR/search.json\"\nuv run python - <<'PYTHON'\nimport json, os\nfrom pathlib import Path\nroot = Path(os.environ['SLICE_DIR'])\nhits = json.loads((root / 'search.json').read_text())['data']['results']\n(root / 'inputs/selection.json').write_text(json.dumps([hit['ref'] for hit in hits]))\nPYTHON\nmm packet prepare --project \"$SLICE_PROJECT\" --selection-file \"$SLICE_DIR/inputs/selection.json\" --request-key packet > \"$SLICE_DIR/packet.json\"\nexport SLICE_PACKET=$(field \"$SLICE_DIR/packet.json\" id)\nmm packet preview \"$SLICE_PACKET\" > \"$SLICE_DIR/packet-preview.json\"\ncat \"$SLICE_DIR/packet-preview.json\"\n```\n\nCall without a grant, show the review and its human command, save the public request, then approve the exact digest. Credential-like values are redacted separately in every text field. A redacted or edited request fails the original digest; it must be reviewed again. Expired, revoked, exhausted or mismatched grants return `E_SCOPE_DENIED` without a preview. Repeat without a grant to get a fresh review.\n\n```bash\npreview \"$SLICE_DIR/open-preview.json\" muse open --project \"$SLICE_PROJECT\" --topic 'Synthetic activity review' --request-key open\nsave_request \"$SLICE_DIR/open-preview.json\"\noperator permit approve --operation muse.open --request-file \"$(approval_field \"$SLICE_DIR/open-preview.json\" request_file)\" --exact-digest \"$(approval_field \"$SLICE_DIR/open-preview.json\" action_digest)\" --request-key open-grant > \"$SLICE_DIR/open-grant.json\"\nmm muse open --project \"$SLICE_PROJECT\" --topic 'Synthetic activity review' --access-grant \"$(field \"$SLICE_DIR/open-grant.json\" id)\" --request-key open > \"$SLICE_DIR/open.json\"\nexport SLICE_DISCUSSION=$(field \"$SLICE_DIR/open.json\" discussion_id)\nmm jobs get \"$(field \"$SLICE_DIR/open.json\" job_id)\"\nmm worker run --once\nmm jobs get \"$(field \"$SLICE_DIR/open.json\" job_id)\"\n\nprintf '%s' 'List evidence and gaps.' > \"$SLICE_DIR/inputs/question-1.txt\"\nprintf '%s' 'Which missing replies would change those conclusions?' > \"$SLICE_DIR/inputs/question-2.txt\"\nfor exchange in 1 2; do\n  preview \"$SLICE_DIR/send-preview-$exchange.json\" muse send --discussion \"$SLICE_DISCUSSION\" --packet \"$SLICE_PACKET\" --question-file \"$SLICE_DIR/inputs/question-$exchange.txt\" --request-key \"send-$exchange\"\n  mm packet preview \"$SLICE_PACKET\"\n  save_request \"$SLICE_DIR/send-preview-$exchange.json\"\n  operator permit approve --operation muse.send --request-file \"$(approval_field \"$SLICE_DIR/send-preview-$exchange.json\" request_file)\" --exact-digest \"$(approval_field \"$SLICE_DIR/send-preview-$exchange.json\" action_digest)\" --request-key \"share-grant-$exchange\" > \"$SLICE_DIR/share-grant-$exchange.json\"\n  mm muse send --discussion \"$SLICE_DISCUSSION\" --packet \"$SLICE_PACKET\" --question-file \"$SLICE_DIR/inputs/question-$exchange.txt\" --share-grant \"$(field \"$SLICE_DIR/share-grant-$exchange.json\" id)\" --request-key \"send-$exchange\" > \"$SLICE_DIR/send-$exchange.json\"\n  export SLICE_EXCHANGE=$(field \"$SLICE_DIR/send-$exchange.json\" exchange_id)\n  mm jobs get \"$(field \"$SLICE_DIR/send-$exchange.json\" job_id)\"\n  mm worker run --once\n  mm jobs get \"$(field \"$SLICE_DIR/send-$exchange.json\" job_id)\"\n  preview \"$SLICE_DIR/read-preview-$exchange.json\" muse read --exchange \"$SLICE_EXCHANGE\" --refresh\n  save_request \"$SLICE_DIR/read-preview-$exchange.json\"\n  operator permit approve --operation muse.read --request-file \"$(approval_field \"$SLICE_DIR/read-preview-$exchange.json\" request_file)\" --exact-digest \"$(approval_field \"$SLICE_DIR/read-preview-$exchange.json\" action_digest)\" --request-key \"read-grant-$exchange\" > \"$SLICE_DIR/read-grant-$exchange.json\"\n  mm muse read --exchange \"$SLICE_EXCHANGE\" --refresh --access-grant \"$(field \"$SLICE_DIR/read-grant-$exchange.json\" id)\" > \"$SLICE_DIR/refresh-$exchange.json\"\n  mm worker run --once\n  mm muse read --exchange \"$SLICE_EXCHANGE\" > \"$SLICE_DIR/read-$exchange.json\"\n  mm muse close-exchange \"$SLICE_EXCHANGE\" --outcome needs_more_data --request-key \"close-$exchange\" > \"$SLICE_DIR/close-$exchange.json\"\ndone\n```\n\nThe fixture replies are progress, so the operator closes each exchange as `needs_more_data`. Draft text comes from the host and cites observations from both exchanges. CLI invocations have already restarted the application between steps; the separate MCP acceptance test also restarts a real stdio server and checks that no send repeats.\n\n```bash\nuv run python - <<'PYTHON'\nimport json, os\nfrom pathlib import Path\nroot = Path(os.environ['SLICE_DIR'])\nfacts = json.loads((root / 'inputs/selection.json').read_text())\ncitations = []\nfor exchange in (1, 2):\n    read = json.loads((root / f'read-{exchange}.json').read_text())['data']\n    assistant_ids = {message['id'] for message in read['messages'] if message['role'] == 'assistant'}\n    observed = next(o for o in read['observations'] if o['message_id'] in assistant_ids and o['raw_event'].get('payload',{}).get('role') == 'assistant')\n    assert read['mock'] and read['research_complete'] is False\n    facts.append(observed['evidence_ref'])\n    citations.append(observed['id'])\n(root / 'inputs/facts.json').write_text(json.dumps(facts))\nhost_text = '=活動回顧（host 撰寫）\\r\\n首輪列出缺口；追問指出仍需要留言與子回覆。😀 e\\u0301\\n'\n(root / 'inputs/draft.txt').write_bytes((host_text + '\\n引用：' + ', '.join(citations) + '\\n').encode())\nPYTHON\nmm draft save --project \"$SLICE_PROJECT\" --text-file \"$SLICE_DIR/inputs/draft.txt\" --platform generic --facts-file \"$SLICE_DIR/inputs/facts.json\" --request-key draft > \"$SLICE_DIR/draft.json\"\nexport SLICE_DRAFT=$(field \"$SLICE_DIR/draft.json\" draft_id)\nmm draft preview \"$SLICE_DRAFT\" --revision 1 > \"$SLICE_DIR/preview.json\"\nuv run python - <<'PYTHON'\nimport json, os\nfrom pathlib import Path\n(Path(os.environ['SLICE_DIR']) / 'inputs/export-refs.json').write_text(json.dumps([{'kind':'draft_revision','id':os.environ['SLICE_DRAFT'],'revision':1}]))\nPYTHON\nfor format in bundle jsonl markdown text csv; do\n  mm export --project \"$SLICE_PROJECT\" --refs-file \"$SLICE_DIR/inputs/export-refs.json\" --format \"$format\" --request-key \"export-$format\" > \"$SLICE_DIR/export-$format.json\"\ndone\ncat \"$SLICE_DIR/preview.json\" \"$SLICE_DIR/export-bundle.json\"\nif [ \"${SLICE_KEEP:-0}\" != 1 ]; then rm -rf -- \"$SLICE_DIR\"; fi\n```\n\n</details>\n\n## Skill\n\nThe canonical Skill tree is [skills/metamuse/](https://github.com/TED-H-com/metamuse-cli/blob/main/skills/metamuse/SKILL.md). The wheel ships the same files, and `metamuse skill export --dest DIR` writes them to a directory the operator names. It is not an MCP tool. Installation steps are in [SKILL.md](https://github.com/TED-H-com/metamuse-cli/blob/main/docs/dev/SKILL.md). The Skill requires an installed, reachable MetaMuse backend (CLI or MCP server) to execute operations; when the backend is unavailable, operations cannot be simulated.\n\n## Development and project details\n\nOne package (`metamuse`) and one shared application serve the CLI, MCP server and Skill. See the [design baseline](https://github.com/TED-H-com/metamuse-cli/blob/main/docs/design/README.md), [core API](https://github.com/TED-H-com/metamuse-cli/blob/main/docs/dev/CORE_API.md), [collection formats](https://github.com/TED-H-com/metamuse-cli/blob/main/docs/dev/COLLECT.md), and [full offline walkthrough](https://github.com/TED-H-com/metamuse-cli/blob/main/docs/dev/OFFLINE_SLICE.md).\n\nFor development, use Python 3.12 managed by uv:\n\n```sh\npytest-disktmp uv run pytest -q\nuv run ruff check .\n```\n\nRuntime workspaces belong outside the checkout. Credentials, account settings, real collected data and LIVE evidence stay outside this repository and are supplied by the operator at runtime.\n\nThis is an unofficial project, unaffiliated with Meta, Facebook or Muse. It does not claim official API access, terms-of-service compliance or protection against account restrictions.\n\nLicense: [Apache-2.0](https://github.com/TED-H-com/metamuse-cli/blob/main/LICENSE). Dependency notices: [THIRD_PARTY_NOTICES.md](https://github.com/TED-H-com/metamuse-cli/blob/main/THIRD_PARTY_NOTICES.md).\n"}