Files
erp/bin/arcodange
Gabriel Radureau 275a59b478 feat(skills,cli): dolibarr-sandbox-checkpoint — manage the sandbox iso-prod checkpoint
A skill + CLI group to drive the ADR-0003 sandbox lifecycle, instead of the manual
kubectl/deno/.env dance:

  arcodange sandbox checkpoint status       # liveness + is the write agent armed?
  arcodange sandbox checkpoint refresh --yes # re-seed iso-prod (DESTRUCTIVE, gated)
  arcodange sandbox checkpoint provision     # re-create ai_agent_sandbox (Playwright) + relink
  arcodange sandbox checkpoint relink-env    # rewrite write skill .env from the key + verify

- refresh wraps ops/sandbox/sandbox-lifecycle.sh; requires --yes (it wipes the agent
  too, since iso-prod overwrites llx_user). --db-only skips the documents sync.
- provision runs test/provisionSandbox.ts (you do the admin login — PROD creds,
  iso-prod) then auto-relinks; relink-env writes .env mode 600 and verifies via
  GET /users/info.
- scripts resolve the repo root from ARCO_ROOT (set by bin/arcodange) or their own
  path, so they work via the CLI or standalone.

Tested: status reports armed/not-armed correctly; refresh refuses without --yes
(exit 3); relink-env errors with no key (exit 1); help/usage wired.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-30 07:19:59 +02:00

396 lines
18 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 <command> [subcommand] [args]
#
# Run `arcodange help` for the full command list.
#
# This is a thin dispatcher: every subcommand delegates to a script under
# .claude/skills/<skill>/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 <command> [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 <invoice-id> JSON facts + PDF mandatory-mention audit
thirdparty Clients + suppliers completeness
audit <socid> 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 <template-id> 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 <qonto|wise> <path> 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 <messageId> [--folder|--save-pdf|--json] Parse PDFs + draft Dolibarr entry
curl <path> 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 <METHOD> <path> [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 <manifest.json> Human-readable review of the change-set
apply <manifest.json> [--target sandbox|prod] Replay it (prod is key+confirm gated)
whoami GET /users/info — confirm auth
ping GET /status — liveness + Dolibarr version
curl <path> 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 <invoice-id> 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 <socid> 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 <template-id> 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 <qonto|wise> <path>" >&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 <qonto|wise> <path> 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 <messageId> [--folder PATH|--save-pdf DIR|--json]
Parse PDF attachments, propose Dolibarr supplier-invoice draft
curl <path> 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 <manifest.json> Human-readable review of the change-set
apply <manifest.json> [--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