Cycles
Create, inspect, edit, and safely delete spending periods.
A cycle is a spending period with its own dates, categories, and budget.
The CLI and Spendly Web use the same records. Check spendly cycles --help
for commands supported by your installed version; older releases only support
list and current. Cycle writes require a compatible backend too.
Find and inspect a cycle
spendly cycles list
spendly cycles current
spendly cycles current --date 2026-09-05
spendly cycles get CYCLE_IDReplace CYCLE_ID with an ID returned by the CLI. get includes the current
revision used for safe edits. If no cycle contains the date, current returns
no cycle; it does not choose the newest cycle instead.
Dates use YYYY-MM-DD. The start is inclusive and the end is exclusive:
September 1 through September 30 uses --start-date 2026-09-01 and
--end-date-exclusive 2026-10-01. Adjacent cycles may share that boundary;
cycles cannot overlap. Cycle names need not be unique, so use IDs for selectors.
Create a cycle
Preview a guided form:
spendly cycles add --interactive --dry-runOr provide the name and both dates explicitly:
spendly cycles add --name "September 2026" \
--start-date 2026-09-01 --end-date-exclusive 2026-10-01 --dry-runRemove --dry-run to save. Guided creation shows a preview and asks for
confirmation. A cycle starts with no categories unless you request copying.
Creation does not attach existing unassigned expenses to the new cycle.
Copy categories and budgets
Copy all categories from an owned cycle, including hidden categories:
spendly cycles add --name "October 2026" \
--start-date 2026-10-01 --end-date-exclusive 2026-11-01 \
--copy-from-cycle-id SOURCE_CYCLE_ID --include-planned-amounts --dry-runWithout --include-planned-amounts, copied categories have no planned amounts,
unless you supply overrides. Their names, order, icons, visibility, and category
types are preserved; new category IDs are created. Expenses are never copied.
Choose a subset by repeating --copy-category-id. Override or clear individual
plans using the source category IDs:
spendly cycles add --name "October 2026" \
--start-date 2026-10-01 --end-date-exclusive 2026-11-01 \
--copy-from-cycle-id SOURCE_CYCLE_ID \
--copy-category-id FOOD_ID --copy-category-id TRAVEL_ID \
--include-planned-amounts --planned-amount FOOD_ID=200 \
--clear-planned-amount TRAVEL_ID --dry-runPlanned amounts are nonnegative decimals, including zero. Each category may
have only one override; overrides take precedence over copied amounts. Only
categories belonging to the selected source cycle may be selected or overridden.
To explicitly copy none, use --without-categories instead of selection or
budget flags. Guided mode offers all/subset/none choices and explicit
keep/set/clear choices for plans, including accessible static prompts.
An empty subset selection also copies none.
Copying writes automatically obtain and commit the same snapshot in guided,
human, and direct modes. For a separately reviewed dry run, capture
data.copySnapshot and pass it as --if-copy-snapshot HASH on commit with
identical source, category selections, and plan inputs. The snapshot is a
64-character lowercase hex SHA256 binding the source ID and every effective
persisted category-copy field: selected IDs, names, types, plans, icons, hidden
state, and order. It is not the source cycle revision. A source with explicit
copy-none still gets a guard for the empty effective selection; no-source
creation remains unchanged.
--if-copy-snapshot requires --copy-from-cycle-id and cannot be combined with
--dry-run. For example, review the first result, then replace HASH with its
data.copySnapshot and CREATE_KEY with a fresh key:
spendly --agent --json --non-interactive cycles add --name "October 2026" \
--start-date 2026-10-01 --end-date-exclusive 2026-11-01 \
--copy-from-cycle-id SOURCE_CYCLE_ID --include-planned-amounts --dry-run
spendly --agent --json --non-interactive cycles add --name "October 2026" \
--start-date 2026-10-01 --end-date-exclusive 2026-11-01 \
--copy-from-cycle-id SOURCE_CYCLE_ID --include-planned-amounts \
--if-copy-snapshot HASH --idempotency-key CREATE_KEYEdit a cycle
spendly cycles edit CYCLE_ID --name "Updated name" --dry-run
spendly cycles edit CYCLE_ID --end-date-exclusive 2026-10-05 --dry-run
spendly cycles edit --interactive --dry-runOnly supplied fields change. Changing dates still must satisfy the non-overlap rule. Existing expenses are not reassigned or recategorized. They remain linked to their original cycle, even if their dates fall outside the new range. A later expense edit rechecks date/category compatibility. Category copying is available at creation only; manage existing category plans separately.
Delete a cycle
spendly cycles delete CYCLE_ID --dry-runDeletion is permanent and removes all categories in the cycle. It is blocked while any expense is linked to the cycle, including uncategorized expenses and expenses outside an edited date range. There is no force-delete option.
A deletion preview returns the cycle revision, category count, confirmation token, and expiry time. It saves only a short-lived confirmation record, not a financial change. Tokens expire after five minutes and are bound to the user, cycle revision, and category snapshot. Changes to the cycle or its categories require a fresh preview. Restrictions are checked again when deleting.
For human confirmation, run spendly cycles delete CYCLE_ID in a terminal.
Scripts and agents
Use --agent --json --non-interactive for agent calls. Writes require a unique
--idempotency-key for each intended operation. Edits and deletions also require
--if-revision; use the revision returned by get or the relevant preview.
spendly --agent --json --non-interactive cycles get CYCLE_ID
spendly --agent --json --non-interactive cycles edit CYCLE_ID \
--name "Updated name" --if-revision REVISION --idempotency-key EDIT_KEY
spendly --agent --json --non-interactive cycles delete CYCLE_ID --dry-run
spendly --agent --json --non-interactive cycles delete CYCLE_ID \
--confirmation-token TOKEN --if-revision REVISION --idempotency-key DELETE_KEYReplace the uppercase placeholders with actual returned values and fresh keys.
Creation uses the same flags as the human example plus an idempotency key.
A dry run needs no key. Successful creation returns data.cycle and
data.copiedCategories; editing returns the cycle directly. Deletion returns
data.deleted, the former cycle, and data.deletedCategoryCount.
CYCLE_OVERLAP, CYCLE_HAS_EXPENSES, CYCLE_REVISION_CONFLICT, and
CYCLE_COPY_CONFLICT use conflict exit code 5. A stale-copy failure must stop
for a fresh preview and approval; do not blindly retry or change the payload.
Invalid inputs use exit code 2; missing or foreign records use
RESOURCE_NOT_FOUND (4); invalid/expired confirmation tokens use exit code 6.
Read again and review a fresh preview after a conflict. Do not retry writes
blindly. If a response is lost, preserve the exact inputs and original key.
Human errors print the resolved recovery inputs, including an automatically
generated key and any copy snapshot/revision/token. After checking current state
and explicitly choosing recovery, use those inputs with --non-interactive and
the same agent mode. This bypasses a fresh preview/read so the backend can replay a saved
result even after an edit changed the revision or a deletion removed the cycle.
For copying creation, an explicit snapshot with --non-interactive makes no new
preview/source read: backend idempotency returns a saved result before checking
the guard, even if the source later changed or was deleted.
For the same uncertain write, keep its original snapshot/revision/token/key;
do not substitute new values. See Troubleshooting.
See spending and budget
spendly summary --current
spendly summary --cycle-id CYCLE_ID
spendly expenses list --cycle-id CYCLE_IDThe summary shows spent, planned, and remaining amounts for the whole cycle, with a category breakdown. The expense list is paginated.
Continue to Categories to inspect category plans.