Commitc8e15b57Recorded29 Mar 2026Repositorytally
Migrate CLI to (sigil args) declarative command pattern
Message
Replace manual find-flag/positional-args CLI parsing with the declarative command/option/run-command pattern from (sigil args), matching the approach used by tube. All existing subcommands and flags are preserved.
Changed
package.sgl | 1 +
src/tally/main.sgl | 229 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------------------------------------------------------
2 files changed, 128 insertions(+), 102 deletions(-)Diff
package.sglmodified
@@ -45,6 +45,7 @@
45
(from-git url: sigil-repo package: "sigil-tls") 46
(from-git url: sigil-repo package: "sigil-http") 47
(from-git url: sigil-repo package: "sigil-json")+48
(from-git url: sigil-repo package: "sigil-args") 49
(from-git url: sigil-repo package: "sigil-mcp") 50
(from-git url: sigil-repo package: "sigil-log") 51
(from-git url: wise-repo package: "sigil-wise")src/tally/main.sglmodified
@@ -1,12 +1,13 @@
1
;;; (tally main) - Entry point for tally CLI and MCP server. 2
;;; 3
;;; Dispatches between MCP server mode (`tally serve`) and CLI mode−4
;;; (`tally <command> <args>`). Follows the fjo dual-mode pattern.+4
;;; (`tally <command> <args>`). Uses (sigil args) for CLI parsing. 5
6
(define-library (tally main) 7
(import (sigil core) 8
(sigil string) 9
(sigil process)+10
(sigil args) 11
(sigil mcp server) 12
(wise) 13
(ledger)@@ -22,56 +23,12 @@
23
(register-tally-tools! server config) 24
(mcp-server-run server))) 25
−25
(define (print-usage)−26
(display "tally - Personal finance management tool\n\n")−27
(display "Usage:\n")−28
(display " tally serve Start MCP server\n")−29
(display " tally pull [--since DATE] [--until DATE] [--profile TYPE]\n")−30
(display " Pull transactions from Wise\n")−31
(display " tally import [--journal FILE] [--since DATE] [--until DATE]\n")−32
(display " Pull and write to journal\n")−33
(display " tally import-qbo <csv-file> [--output FILE] [--account-map FILE]\n")−34
(display " Import QBO CSV export to journal\n")−35
(display " tally balance [--profile TYPE] Show Wise balances\n")−36
(display " tally report [--journal FILE] [--type bal|reg|is] [QUERY]\n")−37
(display " Run hledger reports\n")−38
(display " tally categorize [--journal FILE] List uncategorized transactions\n\n")−39
(display "Environment:\n")−40
(display " WISE_API_TOKEN Wise personal API token (required)\n")−41
(display " TALLY_JOURNAL Default journal file path\n")−42
(display " TALLY_PROFILE Default Wise profile type (personal/business)\n"))−43
−44
;;; Find a --flag value in an argument list.−45
(define (find-flag args flag default)−46
(let loop ((rest args))−47
(cond−48
((null? rest) default)−49
((and (string=? (car rest) flag)−50
(not (null? (cdr rest))))−51
(cadr rest))−52
(else (loop (cdr rest))))))−53
−54
;;; Collect non-flag arguments (positional args).−55
(define (positional-args args)−56
(let loop ((rest args) (acc '()))−57
(cond−58
((null? rest) (reverse acc))−59
((and (> (string-length (car rest)) 1)−60
(char=? (string-ref (car rest) 0) #\-)−61
(char=? (string-ref (car rest) 1) #\-))−62
;; Skip --flag and its value−63
(if (null? (cdr rest))−64
(reverse acc)−65
(loop (cddr rest) acc)))−66
(else−67
(loop (cdr rest) (cons (car rest) acc))))))−68
−69
(define (cli-pull config args since-default until-default)+26
(define (cli-pull config opts since-default until-default) 27
(let* ((client (require-wise-client config))−71
(since (find-flag args "--since" since-default))−72
(until (find-flag args "--until" until-default))−73
(profile-type (find-flag args "--profile"−74
(tally-config-profile-type config)))+28
(since (or (alist-get 'since opts) since-default))+29
(until (or (alist-get 'until opts) until-default))+30
(profile-type (or (alist-get 'profile opts)+31
(tally-config-profile-type config))) 32
(profile-id (resolve-profile-id client profile-type)) 33
(txns (tally-pull-transactions client profile-id since until))) 34
(display (string-append@@ -81,24 +38,24 @@
38
(newline)) 39
txns))) 40
−84
(define (cli-import config args since-default until-default)+41
(define (cli-import config opts since-default until-default) 42
(let* ((client (require-wise-client config))−86
(journal (find-flag args "--journal"−87
(tally-config-journal-path config)))−88
(since (find-flag args "--since" since-default))−89
(until (find-flag args "--until" until-default))−90
(profile-type (find-flag args "--profile"−91
(tally-config-profile-type config)))+43
(journal (or (alist-get 'journal opts)+44
(tally-config-journal-path config)))+45
(since (or (alist-get 'since opts) since-default))+46
(until (or (alist-get 'until opts) until-default))+47
(profile-type (or (alist-get 'profile opts)+48
(tally-config-profile-type config))) 49
(profile-id (resolve-profile-id client profile-type)) 50
(count (tally-import client profile-id since until journal))) 51
(display (string-append 52
"Imported " (number->string count) 53
" new transactions to " journal ".\n")))) 54
−98
(define (cli-balance config args)+55
(define (cli-balance config opts) 56
(let* ((client (require-wise-client config))−100
(profile-type (find-flag args "--profile"−101
(tally-config-profile-type config)))+57
(profile-type (or (alist-get 'profile opts)+58
(tally-config-profile-type config))) 59
(profile-id (resolve-profile-id client profile-type)) 60
(balances (wise-balances client profile-id))) 61
(if (null? balances)@@ -108,24 +65,22 @@
65
(newline)) 66
balances)))) 67
−111
(define (cli-report config args)−112
(let* ((journal (find-flag args "--journal"−113
(tally-config-journal-path config)))−114
(report-type (find-flag args "--type" "bal"))−115
(query-args (positional-args args)))−116
(display (run-hledger-report report-type journal query-args))+68
(define (cli-report config opts args)+69
(let* ((journal (or (alist-get 'journal opts)+70
(tally-config-journal-path config)))+71
(report-type (or (alist-get 'type opts) "bal")))+72
(display (run-hledger-report report-type journal args)) 73
(newline))) 74
−119
(define (cli-import-qbo config args)−120
(let* ((pos-args (positional-args args))−121
(csv-path (if (pair? pos-args)−122
(car pos-args)+75
(define (cli-import-qbo config opts args)+76
(let* ((csv-path (if (pair? args)+77
(car args) 78
(begin 79
(display "Error: CSV file path required.\n") 80
(display "Usage: tally import-qbo <csv-file> [--output FILE] [--account-map FILE]\n") 81
(exit 1))))−127
(output-path (find-flag args "--output" #f))−128
(map-path (find-flag args "--account-map" #f))+82
(output-path (alist-get 'output opts))+83
(map-path (alist-get 'account-map opts)) 84
(account-map (if map-path 85
(load-account-map map-path) 86
default-account-map))@@ -135,46 +90,116 @@
90
"Converted " (number->string count) " transactions from QBO export.\n")) 91
(if output-path 92
(display (string-append "Written to " output-path "\n"))−138
;; No output file — print to stdout 93
(begin 94
(display "\n") 95
(display (format-transactions txns)) 96
(newline))))) 97
−144
(define (cli-categorize config args)−145
(let* ((journal-path (find-flag args "--journal"−146
(tally-config-journal-path config)))+98
(define (cli-categorize config opts)+99
(let* ((journal-path (or (alist-get 'journal opts)+100
(tally-config-journal-path config))) 101
(params #{ journal: journal-path }) 102
(result (tool-categorize config params))) 103
(display result) 104
(newline))) 105
+106
;; ============================================================+107
;; Shared option builders+108
;; ============================================================+109
+110
(define (since-option)+111
(option name: 'since long: "since"+112
value: "DATE"+113
description: "Start date (ISO 8601)"))+114
+115
(define (until-option)+116
(option name: 'until long: "until"+117
value: "DATE"+118
description: "End date (ISO 8601)"))+119
+120
(define (profile-option)+121
(option name: 'profile long: "profile"+122
value: "TYPE"+123
description: "Wise profile type (personal/business)"))+124
+125
(define (journal-option)+126
(option name: 'journal long: "journal"+127
value: "FILE"+128
description: "Journal file path"))+129
+130
;; ============================================================+131
;; CLI Definition+132
;; ============================================================+133
+134
(define (make-cli config since-default until-default)+135
(command+136
name: "tally"+137
description: "Personal finance management tool"+138
subcommands: (list+139
(command+140
name: "serve"+141
description: "Start MCP server"+142
handler: (lambda (opts args) (run-server config)))+143
+144
(command+145
name: "pull"+146
description: "Pull transactions from Wise"+147
options: (list (since-option) (until-option) (profile-option))+148
handler: (lambda (opts args)+149
(cli-pull config opts since-default until-default)))+150
+151
(command+152
name: "import"+153
description: "Pull and write to journal"+154
options: (list (journal-option) (since-option)+155
(until-option) (profile-option))+156
handler: (lambda (opts args)+157
(cli-import config opts since-default until-default)))+158
+159
(command+160
name: "balance"+161
description: "Show Wise balances"+162
options: (list (profile-option))+163
handler: (lambda (opts args) (cli-balance config opts)))+164
+165
(command+166
name: "report"+167
description: "Run hledger reports"+168
options: (list+169
(journal-option)+170
(option name: 'type long: "type"+171
value: "TYPE" default: "bal"+172
description: "Report type (bal, reg, is)"+173
choices: '("bal" "reg" "is")))+174
handler: (lambda (opts args) (cli-report config opts args)))+175
+176
(command+177
name: "import-qbo"+178
description: "Import QBO CSV export to journal"+179
options: (list+180
(option name: 'output long: "output"+181
value: "FILE"+182
description: "Output journal file")+183
(option name: 'account-map long: "account-map"+184
value: "FILE"+185
description: "Account mapping file"))+186
handler: (lambda (opts args) (cli-import-qbo config opts args)))+187
+188
(command+189
name: "categorize"+190
description: "List uncategorized transactions"+191
options: (list (journal-option))+192
handler: (lambda (opts args) (cli-categorize config opts))))))+193
+194
;; ============================================================+195
;; Entry Point+196
;; ============================================================+197
198
(define (main)−153
(let ((config (load-tally-config))−154
(args (cdr (command-line)))−155
(since-default (default-since-date))−156
(until-default (default-until-date)))−157
(cond−158
((null? args)−159
(print-usage)−160
(exit 1))−161
((string=? (car args) "serve")−162
(run-server config))−163
((string=? (car args) "pull")−164
(cli-pull config (cdr args) since-default until-default))−165
((string=? (car args) "import")−166
(cli-import config (cdr args) since-default until-default))−167
((string=? (car args) "balance")−168
(cli-balance config (cdr args)))−169
((string=? (car args) "report")−170
(cli-report config (cdr args)))−171
((string=? (car args) "import-qbo")−172
(cli-import-qbo config (cdr args)))−173
((string=? (car args) "categorize")−174
(cli-categorize config (cdr args)))−175
(else−176
(display (string-append "Unknown command: " (car args) "\n\n"))−177
(print-usage)−178
(exit 1)))))+199
(let* ((config (load-tally-config))+200
(since-default (default-since-date))+201
(until-default (default-until-date))+202
(cli (make-cli config since-default until-default)))+203
(run-command cli (cdr (command-line))))) 204
205
))