coder-firefly-cli 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,239 @@
1
+ Metadata-Version: 2.4
2
+ Name: coder-firefly-cli
3
+ Version: 1.0.0
4
+ Summary: Firefly III CLI - 个人财务管理命令行工具
5
+ Home-page: https://github.com/joyous-coder/coder-firefly-cli
6
+ Author: coder
7
+ Author-email: coder@example.com
8
+ License: MIT
9
+ Keywords: firefly-iii cli finance personal-finance
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: End Users/Desktop
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Office/Business :: Financial
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: click>=8.0
21
+ Requires-Dist: requests>=2.25
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=7.0; extra == "dev"
24
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
25
+ Requires-Dist: responses>=0.25; extra == "dev"
26
+ Dynamic: author
27
+ Dynamic: author-email
28
+ Dynamic: classifier
29
+ Dynamic: description
30
+ Dynamic: description-content-type
31
+ Dynamic: home-page
32
+ Dynamic: keywords
33
+ Dynamic: license
34
+ Dynamic: provides-extra
35
+ Dynamic: requires-dist
36
+ Dynamic: requires-python
37
+ Dynamic: summary
38
+
39
+ # coder-firefly-cli
40
+
41
+ Firefly III CLI - 个人财务管理命令行工具
42
+
43
+ ## 安装
44
+
45
+ ```bash
46
+ pip install -e .
47
+ ```
48
+
49
+ ## 前提条件
50
+
51
+ - Python 3.10+
52
+ - 运行中的 Firefly III 实例
53
+ - Personal Access Token (PAT)
54
+
55
+ ## 配置
56
+
57
+ ### 环境变量(推荐)
58
+
59
+ ```bash
60
+ export FIREFLY_BASE_URL="https://firefly.yourdomain.com"
61
+ export FIREFLY_PAT="your-personal-access-token"
62
+ ```
63
+
64
+ ### 命令行参数
65
+
66
+ ```bash
67
+ coder-firefly-cli --base-url https://firefly.yourdomain.com --pat your-token accounts list
68
+ ```
69
+
70
+ ## 使用方法
71
+
72
+ ### 账户管理
73
+
74
+ ```bash
75
+ # 列出账户
76
+ coder-firefly-cli accounts list
77
+ coder-firefly-cli accounts list --type asset
78
+
79
+ # 获取账户详情
80
+ coder-firefly-cli accounts get --id 123
81
+
82
+ # 创建账户
83
+ coder-firefly-cli accounts create --name "现金" --type asset --currency-code CNY
84
+
85
+ # 更新账户
86
+ coder-firefly-cli accounts update --id 123 --name "新名称"
87
+
88
+ # 删除账户
89
+ coder-firefly-cli accounts delete --id 123
90
+ ```
91
+
92
+ ### 交易管理
93
+
94
+ ```bash
95
+ # 列出交易
96
+ coder-firefly-cli transactions list
97
+ coder-firefly-cli transactions list --limit 10 --start 2024-01-01
98
+
99
+ # 创建支出
100
+ coder-firefly-cli transactions create --description "超市购物" --amount 100.00 --source-account 1
101
+
102
+ # 创建转账
103
+ coder-firefly-cli transactions create --description "转账" --amount 500.00 --source-account 1 --destination-account 2 --type transfer
104
+
105
+ # 获取交易详情
106
+ coder-firefly-cli transactions get --id 456
107
+
108
+ # 删除交易
109
+ coder-firefly-cli transactions delete --id 456
110
+ ```
111
+
112
+ ### 预算管理
113
+
114
+ ```bash
115
+ # 列出预算
116
+ coder-firefly-cli budgets list
117
+
118
+ # 创建预算
119
+ coder-firefly-cli budgets create --name "餐饮预算"
120
+
121
+ # 设置预算限额
122
+ coder-firefly-cli budgets limit-create --budget-id 1 --amount 2000 --start 2024-01-01 --end 2024-01-31
123
+ ```
124
+
125
+ ### 分类管理
126
+
127
+ ```bash
128
+ # 列出分类
129
+ coder-firefly-cli categories list
130
+
131
+ # 创建分类
132
+ coder-firefly-cli categories create --name "餐饮"
133
+ ```
134
+
135
+ ### 标签管理
136
+
137
+ ```bash
138
+ # 列出标签
139
+ coder-firefly-cli tags list
140
+
141
+ # 创建标签
142
+ coder-firefly-cli tags create --tag "重要"
143
+ ```
144
+
145
+ ### 账单管理
146
+
147
+ ```bash
148
+ # 列出账单
149
+ coder-firefly-cli bills list
150
+
151
+ # 创建账单
152
+ coder-firefly-cli bills create --name "房租" --amount-min 3000 --amount-max 3000 --date 2024-01-01
153
+ ```
154
+
155
+ ### 储蓄罐管理
156
+
157
+ ```bash
158
+ # 列出储蓄罐
159
+ coder-firefly-cli piggy-banks list
160
+
161
+ # 创建储蓄罐
162
+ coder-firefly-cli piggy-banks create --name "旅行基金" --account-id 1 --target-amount 10000
163
+ ```
164
+
165
+ ### 搜索
166
+
167
+ ```bash
168
+ # 搜索交易
169
+ coder-firefly-cli search transactions --query "超市"
170
+ ```
171
+
172
+ ### 洞察报告
173
+
174
+ ```bash
175
+ # 支出洞察
176
+ coder-firefly-cli insights expense --start 2024-01-01 --end 2024-01-31
177
+
178
+ # 收入洞察
179
+ coder-firefly-cli insights income --start 2024-01-01 --end 2024-01-31
180
+ ```
181
+
182
+ ### 系统信息
183
+
184
+ ```bash
185
+ # 获取系统信息
186
+ coder-firefly-cli info about
187
+
188
+ # 检查连接状态
189
+ coder-firefly-cli info status
190
+ ```
191
+
192
+ ## JSON 输出
193
+
194
+ 所有命令都支持 `--json` 参数以结构化格式输出:
195
+
196
+ ```bash
197
+ coder-firefly-cli --json accounts list
198
+ ```
199
+
200
+ ## 故障排除
201
+
202
+ ### 连接失败
203
+
204
+ ```
205
+ 错误: 无法连接到 Firefly III 实例
206
+ ```
207
+
208
+ 检查:
209
+ 1. Firefly III 实例是否正在运行
210
+ 2. 基础 URL 是否正确
211
+ 3. 网络连接是否正常
212
+
213
+ ### 认证失败
214
+
215
+ ```
216
+ 错误: 认证失败: Personal Access Token 无效
217
+ ```
218
+
219
+ 检查:
220
+ 1. PAT 是否正确
221
+ 2. PAT 是否已过期
222
+ 3. 在 Firefly III 的 选项 > 个人资料 > OAuth 中生成新的 PAT
223
+
224
+ ## 开发
225
+
226
+ ```bash
227
+ # 安装依赖
228
+ pip install -e ".[dev]"
229
+
230
+ # 运行测试
231
+ pytest
232
+
233
+ # 代码格式化
234
+ black src/
235
+ ```
236
+
237
+ ## 许可证
238
+
239
+ MIT License
@@ -0,0 +1,16 @@
1
+ commands/__init__.py,sha256=CWZPsSgPbJkjvUJRTnSOoNvHzClpKAHxT1ZsYOfTxXg,33
2
+ commands/accounts.py,sha256=ecNFiVwi2nLE8oqsvGLp5bRVhbVtTSw-vSmIxTU1wOA,3270
3
+ commands/bills.py,sha256=DmjqmNVmBSxxso1cokZOxoGsuU3Xofm0Sg06x7qwv2M,2782
4
+ commands/budgets.py,sha256=S48263TalETyO_4qmUL1qTs7GV1sQFoMA3ZHy4Hhgos,3955
5
+ commands/categories.py,sha256=sOwaIym29mebnSFf8z6spDyPPpPeX-rRuU2tI8E_chY,1946
6
+ commands/info.py,sha256=TAeZOaGSKyQPhCVP40ZwekLxh396T5uXYc2aaRI5ClI,899
7
+ commands/insights.py,sha256=PgsiyE6X2zLn7SSRdCVA5CW0ptYBSLDI5ElANo0YM0Y,2449
8
+ commands/piggy_banks.py,sha256=zsqQyjB8-nfZh2ycr_MiuH6bqxwSzq7jdpJs8D9WtmI,2949
9
+ commands/search.py,sha256=SyBIdrHAXOdA6nhIckWXaFPF4sn9ujqC9qtd2w8lO3I,571
10
+ commands/tags.py,sha256=Gd7ir05CMrOKdpgjqIOdbFS4uVE2BS0p0ltY8T1pOvg,2564
11
+ commands/transactions.py,sha256=QEjtg259YU_qfC5uZ-pmWCuAG-Mxxah-1vdcdjo2gqM,4820
12
+ coder_firefly_cli-1.0.0.dist-info/METADATA,sha256=17ZZimvUAv7ZRsAOTaj_S98hloxf0NxqSKG_-yA02Nk,4759
13
+ coder_firefly_cli-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
14
+ coder_firefly_cli-1.0.0.dist-info/entry_points.txt,sha256=KG7AR_EGrOib73vpbyAP-W79pyIcbsUQRXcZV1lEG_g,47
15
+ coder_firefly_cli-1.0.0.dist-info/top_level.txt,sha256=6dNUbRvZAy7fLWNbD4YeUosLYwGk-SVew-Swd5CfFkg,9
16
+ coder_firefly_cli-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ coder-firefly-cli = cli:main
@@ -0,0 +1 @@
1
+ commands
commands/__init__.py ADDED
@@ -0,0 +1 @@
1
+ # coder-firefly-cli 命令模块
commands/accounts.py ADDED
@@ -0,0 +1,113 @@
1
+ r"""
2
+ 账户管理命令组
3
+ """
4
+
5
+ import click
6
+ from cli import get_client, output
7
+
8
+
9
+ @click.group()
10
+ def accounts():
11
+ """管理账户"""
12
+ pass
13
+
14
+
15
+ @accounts.command(name="list")
16
+ @click.option("--type",
17
+ type=click.Choice(['asset', 'expense', 'revenue', 'liability', 'all']),
18
+ default='all',
19
+ help="按账户类型筛选")
20
+ @click.option("--limit", default=50, help="限制结果数量")
21
+ @click.option("--page", default=1, help="页码")
22
+ def accounts_list(type, limit, page):
23
+ """列出所有账户"""
24
+ client = get_client()
25
+ params = {"limit": limit, "page": page}
26
+ if type != 'all':
27
+ params["type"] = type
28
+
29
+ result = client.get_accounts(params)
30
+ output(result)
31
+
32
+
33
+ @accounts.command(name="get")
34
+ @click.option("--id", required=True, type=int, help="账户 ID")
35
+ def accounts_get(id):
36
+ """获取账户详情"""
37
+ client = get_client()
38
+ result = client.get_account(id)
39
+ output(result)
40
+
41
+
42
+ @accounts.command(name="create")
43
+ @click.option("--name", required=True, help="账户名称")
44
+ @click.option("--type",
45
+ required=True,
46
+ type=click.Choice(['asset', 'expense', 'revenue', 'liability']),
47
+ help="账户类型")
48
+ @click.option("--currency-code", default="USD", help="货币代码 (ISO 4217)")
49
+ @click.option("--opening-balance", default="0", help="初始余额")
50
+ @click.option("--account-role", help="账户角色 (用于资产账户)")
51
+ @click.option("--iban", help="IBAN")
52
+ @click.option("--bic", help="BIC")
53
+ @click.option("--account-number", help="账户号码")
54
+ @click.option("--notes", help="备注")
55
+ def accounts_create(name, type, currency_code, opening_balance, account_role, iban, bic, account_number, notes):
56
+ """创建新账户"""
57
+ client = get_client()
58
+
59
+ data = {
60
+ "name": name,
61
+ "type": type,
62
+ "currency_code": currency_code,
63
+ "opening_balance": opening_balance,
64
+ }
65
+
66
+ if account_role:
67
+ data["account_role"] = account_role
68
+ if iban:
69
+ data["iban"] = iban
70
+ if bic:
71
+ data["bic"] = bic
72
+ if account_number:
73
+ data["account_number"] = account_number
74
+ if notes:
75
+ data["notes"] = notes
76
+
77
+ result = client.create_account(data)
78
+ output(result)
79
+
80
+
81
+ @accounts.command(name="update")
82
+ @click.option("--id", required=True, type=int, help="账户 ID")
83
+ @click.option("--name", help="账户名称")
84
+ @click.option("--opening-balance", help="初始余额")
85
+ @click.option("--notes", help="备注")
86
+ def accounts_update(id, name, opening_balance, notes):
87
+ """更新现有账户"""
88
+ client = get_client()
89
+
90
+ data = {}
91
+ if name:
92
+ data["name"] = name
93
+ if opening_balance:
94
+ data["opening_balance"] = opening_balance
95
+ if notes:
96
+ data["notes"] = notes
97
+
98
+ if not data:
99
+ click.echo("错误: 至少需要提供一个更新字段", err=True)
100
+ return
101
+
102
+ result = client.update_account(id, data)
103
+ output(result)
104
+
105
+
106
+ @accounts.command(name="delete")
107
+ @click.option("--id", required=True, type=int, help="账户 ID")
108
+ @click.confirmation_option(prompt="确定要删除此账户吗?")
109
+ def accounts_delete(id):
110
+ """删除账户"""
111
+ client = get_client()
112
+ result = client.delete_account(id)
113
+ output(result)
commands/bills.py ADDED
@@ -0,0 +1,99 @@
1
+ r"""
2
+ 账单管理命令组
3
+ """
4
+
5
+ import click
6
+ from cli import get_client, output
7
+
8
+
9
+ @click.group()
10
+ def bills():
11
+ """管理账单"""
12
+ pass
13
+
14
+
15
+ @bills.command(name="list")
16
+ @click.option("--limit", default=50, help="限制结果数量")
17
+ @click.option("--page", default=1, help="页码")
18
+ def bills_list(limit, page):
19
+ """列出所有账单"""
20
+ client = get_client()
21
+ params = {"limit": limit, "page": page}
22
+ result = client.get_bills(params)
23
+ output(result)
24
+
25
+
26
+ @bills.command(name="get")
27
+ @click.option("--id", required=True, type=int, help="账单 ID")
28
+ def bills_get(id):
29
+ """获取账单详情"""
30
+ client = get_client()
31
+ result = client.get_bill(id)
32
+ output(result)
33
+
34
+
35
+ @bills.command(name="create")
36
+ @click.option("--name", required=True, help="账单名称")
37
+ @click.option("--amount-min", required=True, help="最小金额")
38
+ @click.option("--amount-max", required=True, help="最大金额")
39
+ @click.option("--date", required=True, help="账单日期 (YYYY-MM-DD)")
40
+ @click.option("--repeat-freq", default="monthly", help="重复频率")
41
+ @click.option("--skip", default=0, help="跳过次数")
42
+ @click.option("--currency-code", default="USD", help="货币代码")
43
+ @click.option("--notes", help="备注")
44
+ def bills_create(name, amount_min, amount_max, date, repeat_freq, skip, currency_code, notes):
45
+ """创建新账单"""
46
+ client = get_client()
47
+
48
+ data = {
49
+ "name": name,
50
+ "amount_min": amount_min,
51
+ "amount_max": amount_max,
52
+ "date": date,
53
+ "repeat_freq": repeat_freq,
54
+ "skip": skip,
55
+ "currency_code": currency_code,
56
+ }
57
+ if notes:
58
+ data["notes"] = notes
59
+
60
+ result = client.create_bill(data)
61
+ output(result)
62
+
63
+
64
+ @bills.command(name="update")
65
+ @click.option("--id", required=True, type=int, help="账单 ID")
66
+ @click.option("--name", help="账单名称")
67
+ @click.option("--amount-min", help="最小金额")
68
+ @click.option("--amount-max", help="最大金额")
69
+ @click.option("--notes", help="备注")
70
+ def bills_update(id, name, amount_min, amount_max, notes):
71
+ """更新现有账单"""
72
+ client = get_client()
73
+
74
+ data = {}
75
+ if name:
76
+ data["name"] = name
77
+ if amount_min:
78
+ data["amount_min"] = amount_min
79
+ if amount_max:
80
+ data["amount_max"] = amount_max
81
+ if notes:
82
+ data["notes"] = notes
83
+
84
+ if not data:
85
+ click.echo("错误: 至少需要提供一个更新字段", err=True)
86
+ return
87
+
88
+ result = client.update_bill(id, data)
89
+ output(result)
90
+
91
+
92
+ @bills.command(name="delete")
93
+ @click.option("--id", required=True, type=int, help="账单 ID")
94
+ @click.confirmation_option(prompt="确定要删除此账单吗?")
95
+ def bills_delete(id):
96
+ """删除账单"""
97
+ client = get_client()
98
+ result = client.delete_bill(id)
99
+ output(result)
commands/budgets.py ADDED
@@ -0,0 +1,147 @@
1
+ r"""
2
+ 预算管理命令组
3
+ """
4
+
5
+ import click
6
+ from cli import get_client, output
7
+
8
+
9
+ @click.group()
10
+ def budgets():
11
+ """管理预算"""
12
+ pass
13
+
14
+
15
+ @budgets.command(name="list")
16
+ @click.option("--limit", default=50, help="限制结果数量")
17
+ @click.option("--page", default=1, help="页码")
18
+ def budgets_list(limit, page):
19
+ """列出所有预算"""
20
+ client = get_client()
21
+ params = {"limit": limit, "page": page}
22
+ result = client.get_budgets(params)
23
+ output(result)
24
+
25
+
26
+ @budgets.command(name="get")
27
+ @click.option("--id", required=True, type=int, help="预算 ID")
28
+ def budgets_get(id):
29
+ """获取预算详情"""
30
+ client = get_client()
31
+ result = client.get_budget(id)
32
+ output(result)
33
+
34
+
35
+ @budgets.command(name="create")
36
+ @click.option("--name", required=True, help="预算名称")
37
+ @click.option("--notes", help="备注")
38
+ def budgets_create(name, notes):
39
+ """创建新预算"""
40
+ client = get_client()
41
+
42
+ data = {"name": name}
43
+ if notes:
44
+ data["notes"] = notes
45
+
46
+ result = client.create_budget(data)
47
+ output(result)
48
+
49
+
50
+ @budgets.command(name="update")
51
+ @click.option("--id", required=True, type=int, help="预算 ID")
52
+ @click.option("--name", help="预算名称")
53
+ @click.option("--notes", help="备注")
54
+ def budgets_update(id, name, notes):
55
+ """更新现有预算"""
56
+ client = get_client()
57
+
58
+ data = {}
59
+ if name:
60
+ data["name"] = name
61
+ if notes:
62
+ data["notes"] = notes
63
+
64
+ if not data:
65
+ click.echo("错误: 至少需要提供一个更新字段", err=True)
66
+ return
67
+
68
+ result = client.update_budget(id, data)
69
+ output(result)
70
+
71
+
72
+ @budgets.command(name="delete")
73
+ @click.option("--id", required=True, type=int, help="预算 ID")
74
+ @click.confirmation_option(prompt="确定要删除此预算吗?")
75
+ def budgets_delete(id):
76
+ """删除预算"""
77
+ client = get_client()
78
+ result = client.delete_budget(id)
79
+ output(result)
80
+
81
+
82
+ # ========== 预算限额 ==========
83
+
84
+ @budgets.command(name="limits")
85
+ @click.option("--budget-id", required=True, type=int, help="预算 ID")
86
+ @click.option("--start", help="开始日期 (YYYY-MM-DD)")
87
+ @click.option("--end", help="结束日期 (YYYY-MM-DD)")
88
+ def budgets_limits(budget_id, start, end):
89
+ """列出预算限额"""
90
+ client = get_client()
91
+ params = {}
92
+ if start:
93
+ params["start"] = start
94
+ if end:
95
+ params["end"] = end
96
+ result = client.get_budget_limits(budget_id, params)
97
+ output(result)
98
+
99
+
100
+ @budgets.command(name="limit-create")
101
+ @click.option("--budget-id", required=True, type=int, help="预算 ID")
102
+ @click.option("--amount", required=True, help="金额")
103
+ @click.option("--start", required=True, help="开始日期 (YYYY-MM-DD)")
104
+ @click.option("--end", required=True, help="结束日期 (YYYY-MM-DD)")
105
+ @click.option("--currency-code", default="USD", help="货币代码")
106
+ def budgets_limit_create(budget_id, amount, start, end, currency_code):
107
+ """创建预算限额"""
108
+ client = get_client()
109
+
110
+ data = {
111
+ "amount": amount,
112
+ "start": start,
113
+ "end": end,
114
+ "currency_id": currency_code,
115
+ }
116
+
117
+ result = client.create_budget_limit(budget_id, data)
118
+ output(result)
119
+
120
+
121
+ @budgets.command(name="limit-update")
122
+ @click.option("--id", required=True, type=int, help="预算限额 ID")
123
+ @click.option("--amount", help="金额")
124
+ def budgets_limit_update(id, amount):
125
+ """更新预算限额"""
126
+ client = get_client()
127
+
128
+ data = {}
129
+ if amount:
130
+ data["amount"] = amount
131
+
132
+ if not data:
133
+ click.echo("错误: 金额是必填项", err=True)
134
+ return
135
+
136
+ result = client.update_budget_limit(id, data)
137
+ output(result)
138
+
139
+
140
+ @budgets.command(name="limit-delete")
141
+ @click.option("--id", required=True, type=int, help="预算限额 ID")
142
+ @click.confirmation_option(prompt="确定要删除此预算限额吗?")
143
+ def budgets_limit_delete(id):
144
+ """删除预算限额"""
145
+ client = get_client()
146
+ result = client.delete_budget_limit(id)
147
+ output(result)
commands/categories.py ADDED
@@ -0,0 +1,79 @@
1
+ r"""
2
+ 分类管理命令组
3
+ """
4
+
5
+ import click
6
+ from cli import get_client, output
7
+
8
+
9
+ @click.group()
10
+ def categories():
11
+ """管理分类"""
12
+ pass
13
+
14
+
15
+ @categories.command(name="list")
16
+ @click.option("--limit", default=50, help="限制结果数量")
17
+ @click.option("--page", default=1, help="页码")
18
+ def categories_list(limit, page):
19
+ """列出所有分类"""
20
+ client = get_client()
21
+ params = {"limit": limit, "page": page}
22
+ result = client.get_categories(params)
23
+ output(result)
24
+
25
+
26
+ @categories.command(name="get")
27
+ @click.option("--id", required=True, type=int, help="分类 ID")
28
+ def categories_get(id):
29
+ """获取分类详情"""
30
+ client = get_client()
31
+ result = client.get_category(id)
32
+ output(result)
33
+
34
+
35
+ @categories.command(name="create")
36
+ @click.option("--name", required=True, help="分类名称")
37
+ @click.option("--notes", help="备注")
38
+ def categories_create(name, notes):
39
+ """创建新分类"""
40
+ client = get_client()
41
+
42
+ data = {"name": name}
43
+ if notes:
44
+ data["notes"] = notes
45
+
46
+ result = client.create_category(data)
47
+ output(result)
48
+
49
+
50
+ @categories.command(name="update")
51
+ @click.option("--id", required=True, type=int, help="分类 ID")
52
+ @click.option("--name", help="分类名称")
53
+ @click.option("--notes", help="备注")
54
+ def categories_update(id, name, notes):
55
+ """更新现有分类"""
56
+ client = get_client()
57
+
58
+ data = {}
59
+ if name:
60
+ data["name"] = name
61
+ if notes:
62
+ data["notes"] = notes
63
+
64
+ if not data:
65
+ click.echo("错误: 至少需要提供一个更新字段", err=True)
66
+ return
67
+
68
+ result = client.update_category(id, data)
69
+ output(result)
70
+
71
+
72
+ @categories.command(name="delete")
73
+ @click.option("--id", required=True, type=int, help="分类 ID")
74
+ @click.confirmation_option(prompt="确定要删除此分类吗?")
75
+ def categories_delete(id):
76
+ """删除分类"""
77
+ client = get_client()
78
+ result = client.delete_category(id)
79
+ output(result)
commands/info.py ADDED
@@ -0,0 +1,36 @@
1
+ r"""
2
+ 系统信息命令组
3
+ """
4
+
5
+ import click
6
+ from cli import get_client, output
7
+
8
+
9
+ @click.group()
10
+ def info():
11
+ """系统信息"""
12
+ pass
13
+
14
+
15
+ @info.command(name="about")
16
+ def info_about():
17
+ """获取 Firefly III 系统信息"""
18
+ client = get_client()
19
+ result = client.get_about()
20
+ output(result)
21
+
22
+
23
+ @info.command(name="status")
24
+ def info_status():
25
+ """检查 Firefly III 连接状态"""
26
+ try:
27
+ client = get_client()
28
+ result = client.get_about()
29
+ click.echo("Firefly III 连接正常")
30
+ if 'data' in result:
31
+ attrs = result['data'].get('attributes', {})
32
+ click.echo(f" 版本: {attrs.get('version', 'N/A')}")
33
+ click.echo(f" API 版本: {attrs.get('api_version', 'N/A')}")
34
+ click.echo(f" 环境: {attrs.get('environment', 'N/A')}")
35
+ except Exception as e:
36
+ click.echo(f"连接失败: {e}", err=True)
commands/insights.py ADDED
@@ -0,0 +1,87 @@
1
+ r"""
2
+ 洞察命令组
3
+ """
4
+
5
+ import click
6
+ from cli import get_client, output
7
+
8
+
9
+ @click.group()
10
+ def insights():
11
+ """查看财务洞察"""
12
+ pass
13
+
14
+
15
+ @insights.command(name="expense")
16
+ @click.option("--start", help="开始日期 (YYYY-MM-DD)")
17
+ @click.option("--end", help="结束日期 (YYYY-MM-DD)")
18
+ @click.option("--accounts", help="账户 ID (逗号分隔)")
19
+ @click.option("--categories", help="分类 ID (逗号分隔)")
20
+ @click.option("--budgets", help="预算 ID (逗号分隔)")
21
+ @click.option("--tags", help="标签 (逗号分隔)")
22
+ def insights_expense(start, end, accounts, categories, budgets, tags):
23
+ """支出洞察"""
24
+ client = get_client()
25
+ params = {}
26
+ if start:
27
+ params["start"] = start
28
+ if end:
29
+ params["end"] = end
30
+ if accounts:
31
+ params["accounts"] = accounts
32
+ if categories:
33
+ params["categories"] = categories
34
+ if budgets:
35
+ params["budgets"] = budgets
36
+ if tags:
37
+ params["tags"] = tags
38
+
39
+ result = client.get_insight("expense", params)
40
+ output(result)
41
+
42
+
43
+ @insights.command(name="income")
44
+ @click.option("--start", help="开始日期 (YYYY-MM-DD)")
45
+ @click.option("--end", help="结束日期 (YYYY-MM-DD)")
46
+ @click.option("--accounts", help="账户 ID (逗号分隔)")
47
+ @click.option("--categories", help="分类 ID (逗号分隔)")
48
+ @click.option("--tags", help="标签 (逗号分隔)")
49
+ def insights_income(start, end, accounts, categories, tags):
50
+ """收入洞察"""
51
+ client = get_client()
52
+ params = {}
53
+ if start:
54
+ params["start"] = start
55
+ if end:
56
+ params["end"] = end
57
+ if accounts:
58
+ params["accounts"] = accounts
59
+ if categories:
60
+ params["categories"] = categories
61
+ if tags:
62
+ params["tags"] = tags
63
+
64
+ result = client.get_insight("income", params)
65
+ output(result)
66
+
67
+
68
+ @insights.command(name="transfer")
69
+ @click.option("--start", help="开始日期 (YYYY-MM-DD)")
70
+ @click.option("--end", help="结束日期 (YYYY-MM-DD)")
71
+ @click.option("--accounts", help="账户 ID (逗号分隔)")
72
+ @click.option("--tags", help="标签 (逗号分隔)")
73
+ def insights_transfer(start, end, accounts, tags):
74
+ """转账洞察"""
75
+ client = get_client()
76
+ params = {}
77
+ if start:
78
+ params["start"] = start
79
+ if end:
80
+ params["end"] = end
81
+ if accounts:
82
+ params["accounts"] = accounts
83
+ if tags:
84
+ params["tags"] = tags
85
+
86
+ result = client.get_insight("transfer", params)
87
+ output(result)
@@ -0,0 +1,99 @@
1
+ r"""
2
+ 储蓄罐管理命令组
3
+ """
4
+
5
+ import click
6
+ from cli import get_client, output
7
+
8
+
9
+ @click.group()
10
+ def piggy_banks():
11
+ """管理储蓄罐"""
12
+ pass
13
+
14
+
15
+ @piggy_banks.command(name="list")
16
+ @click.option("--limit", default=50, help="限制结果数量")
17
+ @click.option("--page", default=1, help="页码")
18
+ def piggy_banks_list(limit, page):
19
+ """列出所有储蓄罐"""
20
+ client = get_client()
21
+ params = {"limit": limit, "page": page}
22
+ result = client.get_piggy_banks(params)
23
+ output(result)
24
+
25
+
26
+ @piggy_banks.command(name="get")
27
+ @click.option("--id", required=True, type=int, help="储蓄罐 ID")
28
+ def piggy_banks_get(id):
29
+ """获取储蓄罐详情"""
30
+ client = get_client()
31
+ result = client.get_piggy_bank(id)
32
+ output(result)
33
+
34
+
35
+ @piggy_banks.command(name="create")
36
+ @click.option("--name", required=True, help="储蓄罐名称")
37
+ @click.option("--account-id", required=True, type=int, help="关联账户 ID")
38
+ @click.option("--target-amount", required=True, help="目标金额")
39
+ @click.option("--current-amount", default="0", help="当前金额")
40
+ @click.option("--start-date", help="开始日期 (YYYY-MM-DD)")
41
+ @click.option("--target-date", help="目标日期 (YYYY-MM-DD)")
42
+ @click.option("--notes", help="备注")
43
+ def piggy_banks_create(name, account_id, target_amount, current_amount, start_date, target_date, notes):
44
+ """创建新储蓄罐"""
45
+ client = get_client()
46
+
47
+ data = {
48
+ "name": name,
49
+ "account_id": account_id,
50
+ "target_amount": target_amount,
51
+ "current_amount": current_amount,
52
+ }
53
+ if start_date:
54
+ data["start_date"] = start_date
55
+ if target_date:
56
+ data["target_date"] = target_date
57
+ if notes:
58
+ data["notes"] = notes
59
+
60
+ result = client.create_piggy_bank(data)
61
+ output(result)
62
+
63
+
64
+ @piggy_banks.command(name="update")
65
+ @click.option("--id", required=True, type=int, help="储蓄罐 ID")
66
+ @click.option("--name", help="储蓄罐名称")
67
+ @click.option("--target-amount", help="目标金额")
68
+ @click.option("--current-amount", help="当前金额")
69
+ @click.option("--notes", help="备注")
70
+ def piggy_banks_update(id, name, target_amount, current_amount, notes):
71
+ """更新现有储蓄罐"""
72
+ client = get_client()
73
+
74
+ data = {}
75
+ if name:
76
+ data["name"] = name
77
+ if target_amount:
78
+ data["target_amount"] = target_amount
79
+ if current_amount:
80
+ data["current_amount"] = current_amount
81
+ if notes:
82
+ data["notes"] = notes
83
+
84
+ if not data:
85
+ click.echo("错误: 至少需要提供一个更新字段", err=True)
86
+ return
87
+
88
+ result = client.update_piggy_bank(id, data)
89
+ output(result)
90
+
91
+
92
+ @piggy_banks.command(name="delete")
93
+ @click.option("--id", required=True, type=int, help="储蓄罐 ID")
94
+ @click.confirmation_option(prompt="确定要删除此储蓄罐吗?")
95
+ def piggy_banks_delete(id):
96
+ """删除储蓄罐"""
97
+ client = get_client()
98
+ result = client.delete_piggy_bank(id)
99
+ output(result)
commands/search.py ADDED
@@ -0,0 +1,25 @@
1
+ r"""
2
+ 搜索命令组
3
+ """
4
+
5
+ import click
6
+ from cli import get_client, output
7
+
8
+
9
+ @click.group()
10
+ def search():
11
+ """搜索交易"""
12
+ pass
13
+
14
+
15
+ @search.command(name="transactions")
16
+ @click.option("--query", required=True, help="搜索关键词")
17
+ @click.option("--limit", default=50, help="限制结果数量")
18
+ @click.option("--page", default=1, help="页码")
19
+ def search_transactions(query, limit, page):
20
+ """搜索交易"""
21
+ client = get_client()
22
+ params = {"limit": limit, "page": page}
23
+
24
+ result = client.search_transactions(query, params)
25
+ output(result)
commands/tags.py ADDED
@@ -0,0 +1,100 @@
1
+ r"""
2
+ 标签管理命令组
3
+ """
4
+
5
+ import click
6
+ from cli import get_client, output
7
+
8
+
9
+ @click.group()
10
+ def tags():
11
+ """管理标签"""
12
+ pass
13
+
14
+
15
+ @tags.command(name="list")
16
+ @click.option("--limit", default=50, help="限制结果数量")
17
+ @click.option("--page", default=1, help="页码")
18
+ def tags_list(limit, page):
19
+ """列出所有标签"""
20
+ client = get_client()
21
+ params = {"limit": limit, "page": page}
22
+ result = client.get_tags(params)
23
+ output(result)
24
+
25
+
26
+ @tags.command(name="get")
27
+ @click.option("--id", required=True, help="标签 ID")
28
+ def tags_get(id):
29
+ """获取标签详情"""
30
+ client = get_client()
31
+ result = client.get_tag(id)
32
+ output(result)
33
+
34
+
35
+ @tags.command(name="create")
36
+ @click.option("--tag", required=True, help="标签名称")
37
+ @click.option("--date", help="日期 (YYYY-MM-DD)")
38
+ @click.option("--description", help="描述")
39
+ @click.option("--latitude", help="纬度")
40
+ @click.option("--longitude", help="经度")
41
+ @click.option("--zoom", help="缩放级别")
42
+ @click.option("--notes", help="备注")
43
+ def tags_create(tag, date, description, latitude, longitude, zoom, notes):
44
+ """创建新标签"""
45
+ client = get_client()
46
+
47
+ data = {"tag": tag}
48
+ if date:
49
+ data["date"] = date
50
+ if description:
51
+ data["description"] = description
52
+ if latitude:
53
+ data["latitude"] = latitude
54
+ if longitude:
55
+ data["longitude"] = longitude
56
+ if zoom:
57
+ data["zoom"] = zoom
58
+ if notes:
59
+ data["notes"] = notes
60
+
61
+ result = client.create_tag(data)
62
+ output(result)
63
+
64
+
65
+ @tags.command(name="update")
66
+ @click.option("--id", required=True, help="标签 ID")
67
+ @click.option("--tag", help="标签名称")
68
+ @click.option("--date", help="日期 (YYYY-MM-DD)")
69
+ @click.option("--description", help="描述")
70
+ @click.option("--notes", help="备注")
71
+ def tags_update(id, tag, date, description, notes):
72
+ """更新现有标签"""
73
+ client = get_client()
74
+
75
+ data = {}
76
+ if tag:
77
+ data["tag"] = tag
78
+ if date:
79
+ data["date"] = date
80
+ if description:
81
+ data["description"] = description
82
+ if notes:
83
+ data["notes"] = notes
84
+
85
+ if not data:
86
+ click.echo("错误: 至少需要提供一个更新字段", err=True)
87
+ return
88
+
89
+ result = client.update_tag(id, data)
90
+ output(result)
91
+
92
+
93
+ @tags.command(name="delete")
94
+ @click.option("--id", required=True, help="标签 ID")
95
+ @click.confirmation_option(prompt="确定要删除此标签吗?")
96
+ def tags_delete(id):
97
+ """删除标签"""
98
+ client = get_client()
99
+ result = client.delete_tag(id)
100
+ output(result)
@@ -0,0 +1,151 @@
1
+ r"""
2
+ 交易管理命令组
3
+ """
4
+
5
+ import click
6
+ from datetime import datetime
7
+ from cli import get_client, output
8
+
9
+
10
+ @click.group()
11
+ def transactions():
12
+ """管理交易"""
13
+ pass
14
+
15
+
16
+ @transactions.command(name="list")
17
+ @click.option("--limit", default=50, help="限制结果数量")
18
+ @click.option("--page", default=1, help="页码")
19
+ @click.option("--start", help="开始日期 (YYYY-MM-DD)")
20
+ @click.option("--end", help="结束日期 (YYYY-MM-DD)")
21
+ @click.option("--type",
22
+ type=click.Choice(['withdrawal', 'deposit', 'transfer']),
23
+ help="交易类型")
24
+ @click.option("--source-account", help="源账户 ID 或名称")
25
+ @click.option("--destination-account", help="目标账户 ID 或名称")
26
+ def transactions_list(limit, page, start, end, type, source_account, destination_account):
27
+ """列出交易"""
28
+ client = get_client()
29
+ params = {"limit": limit, "page": page}
30
+
31
+ if start:
32
+ params["start"] = start
33
+ if end:
34
+ params["end"] = end
35
+ if type:
36
+ params["type"] = type
37
+ if source_account:
38
+ params["source_id"] = source_account
39
+ if destination_account:
40
+ params["destination_id"] = destination_account
41
+
42
+ result = client.get_transactions(params)
43
+ output(result)
44
+
45
+
46
+ @transactions.command(name="get")
47
+ @click.option("--id", required=True, type=int, help="交易 ID")
48
+ def transactions_get(id):
49
+ """获取交易详情"""
50
+ client = get_client()
51
+ result = client.get_transaction(id)
52
+ output(result)
53
+
54
+
55
+ @transactions.command(name="create")
56
+ @click.option("--description", required=True, help="交易描述")
57
+ @click.option("--amount", required=True, help="交易金额")
58
+ @click.option("--source-account", required=True, help="源账户 ID")
59
+ @click.option("--destination-account", help="目标账户 ID (转账时需要)")
60
+ @click.option("--type",
61
+ type=click.Choice(['withdrawal', 'deposit', 'transfer']),
62
+ default='withdrawal',
63
+ help="交易类型")
64
+ @click.option("--date", default=lambda: datetime.now().strftime('%Y-%m-%d'),
65
+ help="交易日期 (YYYY-MM-DD)")
66
+ @click.option("--category", help="分类名称")
67
+ @click.option("--tags", help="标签 (逗号分隔)")
68
+ @click.option("--budget", help="预算名称")
69
+ @click.option("--notes", help="备注")
70
+ def transactions_create(description, amount, source_account, destination_account,
71
+ type, date, category, tags, budget, notes):
72
+ """创建新交易"""
73
+ client = get_client()
74
+
75
+ transaction_data = {
76
+ "type": type,
77
+ "date": date,
78
+ "amount": amount,
79
+ "description": description,
80
+ "source_id": source_account,
81
+ }
82
+
83
+ if destination_account:
84
+ transaction_data["destination_id"] = destination_account
85
+ if category:
86
+ transaction_data["category_name"] = category
87
+ if tags:
88
+ transaction_data["tags"] = [tag.strip() for tag in tags.split(",")]
89
+ if budget:
90
+ transaction_data["budget_name"] = budget
91
+ if notes:
92
+ transaction_data["notes"] = notes
93
+
94
+ data = {
95
+ "error_if_duplicate_hash": True,
96
+ "error_if_duplicate_hash_v2": True,
97
+ "apply_rules": True,
98
+ "fire_webhooks": True,
99
+ "group_title": description,
100
+ "transactions": [transaction_data]
101
+ }
102
+
103
+ result = client.create_transaction(data)
104
+ output(result)
105
+
106
+
107
+ @transactions.command(name="update")
108
+ @click.option("--id", required=True, type=int, help="交易 ID")
109
+ @click.option("--description", help="交易描述")
110
+ @click.option("--amount", help="交易金额")
111
+ @click.option("--category", help="分类名称")
112
+ @click.option("--tags", help="标签 (逗号分隔)")
113
+ @click.option("--notes", help="备注")
114
+ def transactions_update(id, description, amount, category, tags, notes):
115
+ """更新现有交易"""
116
+ client = get_client()
117
+
118
+ transaction_data = {}
119
+ if description:
120
+ transaction_data["description"] = description
121
+ if amount:
122
+ transaction_data["amount"] = amount
123
+ if category:
124
+ transaction_data["category_name"] = category
125
+ if tags:
126
+ transaction_data["tags"] = [tag.strip() for tag in tags.split(",")]
127
+ if notes:
128
+ transaction_data["notes"] = notes
129
+
130
+ if not transaction_data:
131
+ click.echo("错误: 至少需要提供一个更新字段", err=True)
132
+ return
133
+
134
+ data = {
135
+ "apply_rules": True,
136
+ "fire_webhooks": True,
137
+ "transactions": [transaction_data]
138
+ }
139
+
140
+ result = client.update_transaction(id, data)
141
+ output(result)
142
+
143
+
144
+ @transactions.command(name="delete")
145
+ @click.option("--id", required=True, type=int, help="交易 ID")
146
+ @click.confirmation_option(prompt="确定要删除此交易吗?")
147
+ def transactions_delete(id):
148
+ """删除交易"""
149
+ client = get_client()
150
+ result = client.delete_transaction(id)
151
+ output(result)