#!/usr/bin/env bash # arcodange — operational CLI for the Arcodange Dolibarr ERP. Read-only on prod; # host-guarded WRITE ops on the sandbox via `arcodange sandbox ...`. # # Usage: arcodange [subcommand] [args] # # Run `arcodange help` for the full command list. # # This is a thin dispatcher: every subcommand delegates to a script under # .claude/skills//scripts/. The skills (markdown SKILL.md files) # remain the source of behaviour documentation; this CLI is the human- # friendly entry point so you don't have to spell out 5-component paths. set -euo pipefail # --- Locate the project root ---------------------------------------------- # Strategy: # 1. git rev-parse --show-toplevel (works when CWD is inside the repo). # 2. Walk up from this script's directory to find a sibling .claude/skills. # Either approach handles being run from a worktree without surprises. if SROOT=$(git rev-parse --show-toplevel 2>/dev/null) && [[ -d "${SROOT}/.claude/skills" ]]; then : else SROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" if [[ ! -d "${SROOT}/.claude/skills" ]]; then echo "arcodange: cannot find project root (no .claude/skills/ found)" >&2 exit 2 fi fi SKILLS="${SROOT}/.claude/skills" DOLC="${SKILLS}/dolibarr/scripts/dol-curl.sh" # --- Help text ----------------------------------------------------------- usage() { cat <<'EOF' arcodange — read-only Arcodange Dolibarr CLI. USAGE arcodange [subcommand] [args...] COMMANDS tva French TVA monthly preparation collect [--year|--since|--until] TVA collectée by month × rate (CA3 A1/A4/E2) collect-detail [--year|--since|--until] Per-line audit, customer side deductible [--year|--since|--until] TVA déductible by month × rate (CA3 19/20/17+24) deductible-detail [--year|--since|--until] Per-line audit, supplier side summary [--year|--since|--until] Composite CA3-ready monthly summary invoice Customer invoices (KissMetrics) list [--since YYYY-MM-DD] Table of KM invoices with payment state audit JSON facts + PDF mandatory-mention audit thirdparty Clients + suppliers completeness audit Country-aware audit for one thirdparty audit-all [--clients-only|--suppliers-only] Audit every visible thirdparty payments Cash receipts (KissMetrics-side) state [--since YYYY-MM-DD] Per-invoice TTC vs payments reconciliation timeline [--year|--since|--until] Payment timeline with cumulative balance by-month [--year|--all-clients] Monthly aggregation templates Recurring invoice templates list [--max-id N] Enumerate templates (probes ids) inspect Full template audit with health checks snapshot [--out FILE|--print-only] Bundle full read-only state into one JSON bank Bank-side data (Qonto + Wise) + Dolibarr reconciliation probe Auth + discovery (org slug, profile id, balance ids) qonto-transactions [--month|--since|--until] Qonto transactions table (incoming + outgoing) wise-transactions [--month|--since|--until|--type|--enrich] Wise activities (incoming + outgoing) match [--month|--since|--until|--window-days N|--enrich] Match bank ↔ Dolibarr (split buckets) balance Live balances + Dolibarr cross-check per fk_account curl Raw read-only curl through bank-curl.sh email Supplier-invoice emails from the Zoho mailbox list [--folder|--limit|--candidates-only|--all-folders] List candidates inspect [--folder|--save-pdf|--json] Parse PDFs + draft Dolibarr entry curl Raw read-only curl through zoho-curl.sh sandbox WRITE ops on erp-sandbox ONLY (host-guarded; JSON on stdin) thirdparty Create a client/supplier fiche invoice Customer/supplier invoice + product/service lines payment Record a règlement (transaction_id=bank tx) → bank_transaction_id creditnote Create an avoir — customer or supplier (kind) accounts List bank accounts (id/label) to pick account_id write [body] Raw host-guarded write checkpoint status|refresh|provision|relink-env Manage the iso-prod checkpoint promote Replay a reviewed change-set sandbox -> prod (ADR-0003) plan Human-readable review of the change-set apply [--target sandbox|prod] Replay it (prod is key+confirm gated) whoami GET /users/info — confirm auth ping GET /status — liveness + Dolibarr version curl Raw read-only curl through dol-curl.sh help [command] Show this help (or subcommand help) CREDENTIALS Reads .claude/skills/dolibarr/.env (mode 600, gitignored). See .claude/skills/dolibarr/README.md for one-time setup. EXAMPLES arcodange ping arcodange whoami arcodange invoice list arcodange invoice audit 12 arcodange tva summary --year 2026 arcodange thirdparty audit-all arcodange snapshot --out /tmp/erp.json echo '{"name":"OVH","role":"supplier"}' | arcodange sandbox thirdparty EOF } # --- Dispatch ------------------------------------------------------------ cmd="${1:-help}" shift || true case "${cmd}" in tva) sub="${1:-help}"; shift || true case "${sub}" in collect) exec "${SKILLS}/dolibarr-tva-reconciliation/scripts/tva-by-month.sh" "$@" ;; collect-detail) exec "${SKILLS}/dolibarr-tva-reconciliation/scripts/tva-line-detail.sh" "$@" ;; deductible) exec "${SKILLS}/dolibarr-tva-deductible/scripts/deductible-by-month.sh" "$@" ;; deductible-detail) exec "${SKILLS}/dolibarr-tva-deductible/scripts/deductible-line-detail.sh" "$@" ;; summary) exec "${SKILLS}/dolibarr-tva-summary/scripts/tva-summary.sh" "$@" ;; help|-h|--help) cat <<'EOF' arcodange tva — French TVA monthly preparation. collect TVA collectée by month × rate (CA3 A1/A4/E2) collect-detail Per-line audit, customer side deductible TVA déductible by month × rate (CA3 19/20/17+24) deductible-detail Per-line audit, supplier side summary CA3-ready monthly net summary (collectée − déductible) All accept --year YYYY, --since YYYY-MM-DD, --until YYYY-MM-DD. EOF ;; *) echo "arcodange tva: unknown subcommand '${sub}' (try 'arcodange tva help')" >&2; exit 2 ;; esac ;; invoice) sub="${1:-help}"; shift || true case "${sub}" in list) exec "${SKILLS}/dolibarr-invoice-audit/scripts/list-km-invoices.sh" "$@" ;; audit) exec "${SKILLS}/dolibarr-invoice-audit/scripts/audit-invoice.sh" "$@" ;; help|-h|--help) cat <<'EOF' arcodange invoice — customer (KissMetrics) invoice operations. list [--since YYYY-MM-DD] Table of KM invoices with payment state audit JSON facts + PDF mandatory-mention audit EOF ;; *) echo "arcodange invoice: unknown subcommand '${sub}' (try 'arcodange invoice help')" >&2; exit 2 ;; esac ;; thirdparty) sub="${1:-help}"; shift || true case "${sub}" in audit) exec "${SKILLS}/dolibarr-thirdparty-completeness/scripts/audit-thirdparty.sh" "$@" ;; audit-all) exec "${SKILLS}/dolibarr-thirdparty-completeness/scripts/audit-all-thirdparties.sh" "$@" ;; help|-h|--help) cat <<'EOF' arcodange thirdparty — clients + suppliers completeness. audit Country-aware audit for one thirdparty audit-all [--clients-only|--suppliers-only] Audit every visible thirdparty EOF ;; *) echo "arcodange thirdparty: unknown subcommand '${sub}'" >&2; exit 2 ;; esac ;; payments) sub="${1:-help}"; shift || true case "${sub}" in state) exec "${SKILLS}/dolibarr-payments-state/scripts/km-payment-state.sh" "$@" ;; timeline) exec "${SKILLS}/dolibarr-payments-state/scripts/km-payment-timeline.sh" "$@" ;; by-month) exec "${SKILLS}/dolibarr-payments-state/scripts/payments-by-month.sh" "$@" ;; help|-h|--help) cat <<'EOF' arcodange payments — KissMetrics cash-receipt tracking. state [--since YYYY-MM-DD] Per-invoice TTC vs payments reconciliation timeline [--year|--since|--until] Payment timeline with cumulative balance by-month [--year|--all-clients] Monthly aggregation EOF ;; *) echo "arcodange payments: unknown subcommand '${sub}'" >&2; exit 2 ;; esac ;; templates) sub="${1:-help}"; shift || true case "${sub}" in list) exec "${SKILLS}/dolibarr-recurring-templates/scripts/list-templates.sh" "$@" ;; inspect) exec "${SKILLS}/dolibarr-recurring-templates/scripts/inspect-template.sh" "$@" ;; help|-h|--help) cat <<'EOF' arcodange templates — recurring invoice templates. list [--max-id N] Enumerate templates (probes ids) inspect Full audit with health checks EOF ;; *) echo "arcodange templates: unknown subcommand '${sub}'" >&2; exit 2 ;; esac ;; snapshot) exec "${SKILLS}/dolibarr-data-snapshot/scripts/snapshot.sh" "$@" ;; bank) sub="${1:-help}"; shift || true case "${sub}" in probe) exec "${SKILLS}/arcodange-bank-reco/scripts/bank-probe.sh" "$@" ;; qonto-transactions) exec "${SKILLS}/arcodange-bank-reco/scripts/qonto-transactions.sh" "$@" ;; wise-transactions) exec "${SKILLS}/arcodange-bank-reco/scripts/wise-transactions.sh" "$@" ;; match) exec "${SKILLS}/arcodange-bank-reco/scripts/bank-match.sh" "$@" ;; balance) exec "${SKILLS}/arcodange-bank-reco/scripts/bank-balance.sh" "$@" ;; curl) if [[ $# -lt 2 ]]; then echo "arcodange bank curl: usage: bank curl " >&2; exit 2 fi exec "${SKILLS}/arcodange-bank-reco/scripts/bank-curl.sh" "$@" ;; help|-h|--help) cat <<'EOF' arcodange bank — bank-side data (Qonto + Wise) and Dolibarr reconciliation. probe Auth + discovery (org slug, profile id, balance ids) qonto-transactions [--month|--since|--until] Qonto transactions table wise-transactions [--month|--since|--until|--type|--enrich] Wise activities (incoming + outgoing) match [--month|--since|--until|--window-days N] Match bank ↔ Dolibarr (3 buckets) balance Live balances + Dolibarr cross-check per fk_account curl Raw read-only curl through bank-curl.sh Requires QONTO_LOGIN, QONTO_SECRET_KEY, QONTO_ORG_SLUG, WISE_API_TOKEN, WISE_PROFILE_ID in .env. See arcodange-bank-reco/SKILL.md for setup. EOF ;; *) echo "arcodange bank: unknown subcommand '${sub}' (try 'arcodange bank help')" >&2; exit 2 ;; esac ;; email) sub="${1:-help}"; shift || true case "${sub}" in list) exec "${SKILLS}/arcodange-email-ingest/scripts/email-list.sh" "$@" ;; inspect) exec "${SKILLS}/arcodange-email-ingest/scripts/email-inspect.sh" "$@" ;; curl) exec "${SKILLS}/arcodange-email-ingest/scripts/zoho-curl.sh" "$@" ;; help|-h|--help) cat <<'EOF' arcodange email — supplier-invoice ingestion from the Zoho mailbox. list [--folder PATH|--limit N|--candidates-only|--all-folders] List messages (default: /Inbox/books) inspect [--folder PATH|--save-pdf DIR|--json] Parse PDF attachments, propose Dolibarr supplier-invoice draft curl Raw read-only call through zoho-curl.sh Requires ZOHO_CLIENT_ID, ZOHO_CLIENT_SECRET, ZOHO_REFRESH_TOKEN, ZOHO_DC in .env. See arcodange-email-ingest/SKILL.md for OAuth setup. EOF ;; *) echo "arcodange email: unknown subcommand '${sub}' (try 'arcodange email help')" >&2; exit 2 ;; esac ;; sandbox) sub="${1:-help}"; shift || true case "${sub}" in thirdparty) exec "${SKILLS}/dolibarr-sandbox-write/scripts/thirdparty-create.sh" "$@" ;; invoice) exec "${SKILLS}/dolibarr-sandbox-write/scripts/invoice-create.sh" "$@" ;; payment) exec "${SKILLS}/dolibarr-sandbox-write/scripts/payment-record.sh" "$@" ;; creditnote) exec "${SKILLS}/dolibarr-sandbox-write/scripts/creditnote-create.sh" "$@" ;; accounts) exec "${SKILLS}/dolibarr-sandbox-write/scripts/bank-accounts.sh" "$@" ;; write) exec "${SKILLS}/dolibarr-sandbox-write/scripts/dol-write.sh" "$@" ;; checkpoint) export ARCO_ROOT="${SROOT}" csub="${1:-status}"; shift || true case "${csub}" in status) exec "${SKILLS}/dolibarr-sandbox-checkpoint/scripts/checkpoint-status.sh" "$@" ;; refresh) exec "${SKILLS}/dolibarr-sandbox-checkpoint/scripts/checkpoint-refresh.sh" "$@" ;; provision) exec "${SKILLS}/dolibarr-sandbox-checkpoint/scripts/checkpoint-provision.sh" "$@" ;; relink-env) exec "${SKILLS}/dolibarr-sandbox-checkpoint/scripts/checkpoint-relink-env.sh" "$@" ;; help|-h|--help) cat <<'EOF' arcodange sandbox checkpoint — manage the erp-sandbox iso-prod checkpoint (ADR-0003). status Liveness + whether the write agent is armed (key authenticates) refresh --yes [--db-only] Re-seed iso-prod from prod (DESTRUCTIVE; wipes the agent too) provision Re-create ai_agent_sandbox (Playwright; you log in) + relink .env relink-env Rewrite the write skill .env from test/.ai_agent_sandbox.key + verify Typical reset: arcodange sandbox checkpoint refresh --yes then ... provision (provision opens a browser for the admin login — use the PROD admin creds, iso-prod — and auto-relinks the .env) EOF ;; *) echo "arcodange sandbox checkpoint: unknown '${csub}' (try 'arcodange sandbox checkpoint help')" >&2; exit 2 ;; esac ;; help|-h|--help) cat <<'EOF' arcodange sandbox — WRITE operations against erp-sandbox (rehearsal ONLY). Every write goes through dol-write.sh, which REFUSES any non-sandbox host. Each subcommand reads a JSON object on stdin (or a file path arg). thirdparty client/supplier fiche echo '{"name":"OVH","role":"supplier"}' | arcodange sandbox thirdparty invoice customer/supplier invoice with product/service lines (+ ref_supplier) echo '{"socid":42,"validate":true,"lines":[{"desc":"X","qty":1,"price_ht":100,"tva":20,"type":"service"}]}' | arcodange sandbox invoice payment règlement on a validated invoice echo '{"invoice_id":19,"mode":"VIR","account_id":1}' | arcodange sandbox payment creditnote avoir (credit note) referencing a source invoice echo '{"socid":42,"source_invoice":19,"validate":true,"lines":[...]}' | arcodange sandbox creditnote write raw host-guarded write arcodange sandbox write POST /thirdparties '{"name":".."}' checkpoint manage the iso-prod checkpoint (status|refresh|provision|relink-env) arcodange sandbox checkpoint status Needs .claude/skills/dolibarr-sandbox-write/.env (DOLIBARR_SANDBOX_URL + _API_KEY). See dolibarr-sandbox-write/SKILL.md and dolibarr-sandbox-checkpoint/SKILL.md. EOF ;; *) echo "arcodange sandbox: unknown subcommand '${sub}' (try 'arcodange sandbox help')" >&2; exit 2 ;; esac ;; promote) sub="${1:-help}"; shift || true case "${sub}" in plan) exec "${SKILLS}/dolibarr-sandbox-write/scripts/promote-plan.sh" "$@" ;; apply) exec "${SKILLS}/dolibarr-sandbox-write/scripts/promote-apply.sh" "$@" ;; help|-h|--help) cat <<'EOF' arcodange promote — replay a reviewed sandbox change-set onto a target. A manifest is a JSON array of write ops with symbolic refs (@name) instead of ids, so the same file replays on sandbox or prod (an invoice refs @tp1, its just-created thirdparty). See dolibarr-sandbox-write/examples/promote-manifest.json. plan Human-readable review of the change-set apply [--target sandbox|prod] Replay it (sandbox = rehearse; prod = real) PROD apply is gated: requires DOLIBARR_PROD_WRITE_KEY + ARCO_PROMOTE_CONFIRM= I-UNDERSTAND-THIS-WRITES-PROD in the environment (the prod key is never stored). EOF ;; *) echo "arcodange promote: unknown subcommand '${sub}' (try 'arcodange promote help')" >&2; exit 2 ;; esac ;; whoami) exec "${DOLC}" /users/info ;; ping) exec "${DOLC}" /status ;; curl) if [[ $# -lt 1 ]]; then echo "arcodange curl: missing path. Example: arcodange curl /invoices/12" >&2 exit 2 fi exec "${DOLC}" "$@" ;; help|-h|--help|"") if [[ $# -gt 0 ]]; then exec "$0" "$1" help fi usage ;; *) echo "arcodange: unknown command '${cmd}'" >&2 echo " Try 'arcodange help' for the list." >&2 exit 2 ;; esac