Create encrypted HTML plans. Keep them hosted while they change, then publish them as 1Sat Ordinals when they should be permanent.
The CLI validates a self-contained HTML document and encrypts it with standard
@bsv/sdk and wallet operations. A hosted draft stores the sealed envelope on
bitplan.dev and costs no BSV. When the plan is ready, the BRC-100 wallet can
publish it as a 1Sat Ordinal. On-chain updates spend the current draft coin with
a new inscription, so one origin identifies the plan and its version history.
A running BRC-100 wallet on this machine. bitplan never receives identity
private keys: BRC-2 wrapping, unwrapping, signing, and funding are wallet calls.
The shared document key exists transiently in the CLI. BSV
Desktop serves the JSON API on
127.0.0.1:3321, which is where bitplan looks by default.
If no wallet answers, the command fails and says so.
Upload a draft:
npx bitplan upload ./plan.html
bunx bitplan upload ./plan.htmlBy default, BitPlan notifies 1Sat after the wallet publishes so ORDFS can capture the transaction as quickly as possible. Opt out when needed:
npx bitplan upload ./plan.html --no-relayNormally, the wallet publishes first. Afterward, bitplan sends the
wallet-returned Atomic BEEF through 1Sat, which attempts to capture it for
ORDFS and forwards the transaction to Arcade. This may make the viewer
available sooner. A relay failure is only a warning because the wallet publish
may already have succeeded. --no-relay skips only this notification.
Attach an optional description (shown in bitplan list and the My drafts page).
Re-running with --description updates it; omitting it leaves the existing one
untouched:
npx bitplan upload ./plan.html --description "Q3 warehouse migration plan"Confirm the wallet is there:
npx bitplan auth
npx bitplan whoami
npx bitplan versionShare the next version with one or more wallet identity public keys, local contacts, or local teams. Existing raw-key readers remain authorized unless you explicitly publish a private version:
npx bitplan upload ./plan.html --share-with <identity-key>
npx bitplan upload ./plan.html \
--share-with <identity-key-a> \
--share-with <identity-key-b>
npx bitplan upload ./plan.html --private
npx bitplan upload ./plan.html --linkbitplan whoami prints the connected wallet's identity key. A shared version
stores the encrypted document once, then asks the wallet to wrap its 32-byte
document key for each reader. Identity keys and the access list are public, and
--private only affects the new version; older shared inscriptions remain
readable by their original recipients. Security level 2 lets the wallet ask for
permission for each new reader.
Give public identity keys memorable local names, then group those contacts:
npx bitplan contact set alice <identity-key>
npx bitplan contact set bob <identity-key>
npx bitplan team set acme-dev alice bob
npx bitplan upload ./plan.html --share-with acme-devcontact list and team list [name] show the address book; both accept
--json. Use team add, team remove, or team set to change membership.
The next version of a locally tracked draft using that team resolves its
members again, so removed members are left out of that version. Versions that
were already shared remain readable by their original readers.
Save one or more default readers for every new plan:
npx bitplan config --share-with <identity-key>
npx bitplan config --share-with acme-dev
npx bitplan config --clear-share-withUpdates keep their existing readers. Use --share-with to add a reader to one
plan, or --private to publish the new version for this wallet only.
List the drafts this wallet holds. The default table shortens origins and
outpoints. --verbose switches to one labeled detail block per draft so full
identifiers and timestamps do not stretch the table:
npx bitplan list
npx bitplan list --verboseRead one back (HTML to stdout, metadata to stderr with --meta). Metadata
includes the envelope version, access mode, and public reader list:
npx bitplan fetch <origin> --meta > plan.html
npx bitplan fetch https://bitplan.dev/d/<origin>
npx bitplan fetch <origin> --version 2Agents and scripts can ask for one JSON value instead of parsing the text made for people:
npx bitplan upload ./plan.html --yes --json
npx bitplan fetch <origin> --jsonThe upload result includes the origin, current outpoint, version, access list,
relay status, and viewer URL. --json still requires --yes; it never treats a
machine-readable response as permission to publish. Fetch JSON includes both
the decrypted HTML and its metadata, so --meta is not needed.
bitplan upload plan.html --hosted seals the same envelope, then stores it on
bitplan.dev instead of inscribing it. There is no transaction and no BSV. A
wallet is still required to create and update the plan. Re-uploading the same
file writes a new hosted version. bitplan inscribe <h_id|file> puts the latest
hosted version on the chain; --all-versions replays the whole history. After
inscribe, the hosted viewer URL redirects to the chain origin.
npx bitplan upload ./plan.html --hosted
npx bitplan inscribe h_xxxxxxxxxxxxxxxxxxxx
npx bitplan fetch h_xxxxxxxxxxxxxxxxxxxx
npx bitplan fetch https://bitplan.dev/d/h_xxxxxxxxxxxxxxxxxxxxbitplan list includes hosted drafts marked (hosted, not on chain). Pass
--site-url on upload, inscribe, and fetch to point at a different
bitplan.dev origin.
The hosted ID is a random locator, not a decryption key. bitplan.dev stores the ciphertext and public envelope header. The separate write secret stays in the CLI's local state; the server stores only its hash.
The CLI stores optional config and file-to-origin mappings in ~/.bitplan.
Pass --wallet-url to point at a different BRC-100 endpoint; upload and
fetch also accept --ordfs-url.
Every publish prints the draft's origin, its version, and a viewer URL:
Origin: <txid>_0
Outpoint: <txid>_0
Version: 1
Viewer: https://bitplan.dev/d/<txid>_0
Before an on-chain confirmation, the CLI reads Arcade's public /policy and
shows a payload-only cost at the current network floor. It uses the estimated
encrypted payload size for uploads and the sum of independently rounded
payload costs for multi-version inscribes. This is not a wallet quote: it
excludes transaction overhead and wallet rate or ancestor differences. If the
policy cannot be read, the floor is shown as unavailable and publishing still
continues. Hosted uploads and quiet --json output do not request the policy.
bitplan upload <file>
--draft <origin> Update a specific draft
--new Always create a new draft
--hosted Store the encrypted draft on bitplan.dev instead of the chain
--site-url <url> bitplan.dev origin for hosted drafts
--description <text> Set a short description
--share-with <reader> Add a key, contact, or team (repeatable)
--private Make the new version wallet-only
--link Add a reader link anyone can open
--no-relay Skip the default 1Sat notification for ORDFS capture
-y, --yes Skip the confirmation prompt
--json Print one JSON result (requires --yes)
--allow-finding <id> Waive one secret-scanner finding (repeatable)
bitplan inscribe <h_id|file>
--all-versions Inscribe every hosted version, not only the latest
--site-url <url>
-y, --yes
--json
bitplan list
--json
-v, --verbose One detailed block per draft
--limit <n>
bitplan fetch <origin|url>
--meta
--json Print the HTML and metadata as JSON
--version <n>
--site-url <url>
bitplan config
--share-with <reader> Default key, contact, or team (repeatable)
--clear-share-with Clear default readers
bitplan contact set <name> <identity-key>
bitplan contact remove <name>
bitplan contact list [--json]
bitplan team set <name> <contacts...>
bitplan team add <name> <contacts...>
bitplan team remove <name> <contacts...>
bitplan team delete <name>
bitplan team list [name] [--json]
bitplan version
In a non-interactive shell, upload requires --yes; otherwise it stops before
encryption or publishing rather than assuming consent.
bitplan upload plan.html --link adds a reader anyone can use: a throwaway
secp256k1 key whose public half is an ordinary reader slot and whose private
half travels in the viewer URL fragment. Later versions keep the same link
until you publish with --private. bitplan fetch can open that URL without
a wallet. Anyone with the complete link can read, so treat it like a password.
npx bitplan upload ./plan.html --link
npx bitplan fetch 'https://bitplan.dev/d/<origin>#k=...'- Encryption. Each draft is the bitplan envelope: the SDK's
SymmetricKeyencrypts the document once, then BRC-2-wraps that 32-byte key for the publisher and each invited reader. A plan with no invited readers has one slot, the publisher's. The CLI never handles identity private keys or implements its own cipher. See ENVELOPE.md for the byte layout. - Versioning. The first publish inscribes a 1-satoshi output. Later publishes spend that satoshi back to you carrying a new envelope. Only the wallet holding the coin can publish the next one.
- Propagation. By default, the CLI sends the wallet-returned Atomic BEEF to
1Sat. 1Sat attempts to store it for ORDFS and forwards the leaf transaction to
Arcade. The wallet remains the publisher. Pass
--no-relayto opt out. - Metadata. The cleartext MAP on chain is three fields:
{ app: "bitplan", type: "plan", enc: "1" }. Titles, descriptions and git provenance live inside the ciphertext.
bitplan upload refuses to spend until the document passes two gates.
HTML policy (ported from postplan,
MIT): no <form>, <iframe>, <object>, <embed>, <applet>, <base> or
<link>; no external script sources; no inline event handlers; no srcdoc; no
javascript: / vbscript: / file: URLs; no meta refresh; 5 MB cap.
Secret scanner: private key blocks, cloud and SaaS token shapes, JWTs,
database URLs carrying credentials, long secrets behind key= / token= /
secret=, and absolute home paths. Findings block the upload. Waive one at a
time with the id the scanner prints:
npx bitplan upload plan.html --allow-finding home-path-macos-3f2a91c40b7dThe scan runs on the plaintext even though the output is encrypted.
~/.bitplan/ (directory 0700, files 0600):
config.json: optional wallet/ORDFS overrides, default readers, contacts, and teams. Contacts contain public identity keys only.drafts.json: which local file maps to which draft: origin, keyID, latest outpoint, latest version, and any local contact/team references used for its access list. Hosted drafts also store the write secret so this machine can update them.
Neither file holds identity private keys. Losing drafts.json costs
convenience only for chain drafts: origins are on chain, and each draft's
keyID is in its envelope header. A hosted draft cannot be updated from this
machine without its secret.
MIT. Bundled dependency notices are in THIRD_PARTY_NOTICES.md.