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
))