@yigemo/choosefire 0.1.0

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.
Files changed (62) hide show
  1. package/README.md +83 -0
  2. package/contracts/choosefire-family-wealth/v2/capability-matrix.v1.json +464 -0
  3. package/contracts/choosefire-family-wealth/v2/golden/performance.v1.json +79 -0
  4. package/contracts/choosefire-family-wealth/v2/golden/r2-contract.v1.json +1070 -0
  5. package/contracts/choosefire-family-wealth/v2/golden/scenarios.v1.json +241 -0
  6. package/contracts/choosefire-family-wealth/v2/golden/transport.v1.json +207 -0
  7. package/contracts/choosefire-family-wealth/v2/manifest.json +58 -0
  8. package/contracts/choosefire-family-wealth/v2/openapi.generated.json +2605 -0
  9. package/contracts/choosefire-family-wealth/v2/runtime-schemas/family-wealth.v2.schema.json +2127 -0
  10. package/contracts/choosefire-family-wealth/v2/taxonomies/account-types.v1.json +41 -0
  11. package/contracts/choosefire-family-wealth/v2/taxonomies/asset-classes.v1.json +45 -0
  12. package/contracts/choosefire-family-wealth/v2/taxonomies/transaction-types.v1.json +57 -0
  13. package/contracts/choosefire-financial-workspace/v1/field-registry.generated.json +137 -0
  14. package/contracts/choosefire-financial-workspace/v1/field-registry.manifest.json +11 -0
  15. package/contracts/choosefire-financial-workspace/v1/golden/commands.invalid.json +583 -0
  16. package/contracts/choosefire-financial-workspace/v1/golden/commands.valid.json +201 -0
  17. package/contracts/choosefire-financial-workspace/v1/golden/cursors.invalid.json +82 -0
  18. package/contracts/choosefire-financial-workspace/v1/golden/errors.invalid.json +191 -0
  19. package/contracts/choosefire-financial-workspace/v1/golden/errors.valid.json +298 -0
  20. package/contracts/choosefire-financial-workspace/v1/golden/fingerprints.json +105 -0
  21. package/contracts/choosefire-financial-workspace/v1/golden/projections.invalid.json +3543 -0
  22. package/contracts/choosefire-financial-workspace/v1/golden/projections.valid.json +2953 -0
  23. package/contracts/choosefire-financial-workspace/v1/manifest.json +69 -0
  24. package/contracts/choosefire-financial-workspace/v1/openapi.generated.json +1987 -0
  25. package/contracts/choosefire-financial-workspace/v1/runtime-schemas/financial-workspace.v1.schema.json +1496 -0
  26. package/dist/args.d.ts +5 -0
  27. package/dist/args.js +60 -0
  28. package/dist/args.js.map +1 -0
  29. package/dist/auth.d.ts +55 -0
  30. package/dist/auth.js +480 -0
  31. package/dist/auth.js.map +1 -0
  32. package/dist/commands.d.ts +4 -0
  33. package/dist/commands.js +637 -0
  34. package/dist/commands.js.map +1 -0
  35. package/dist/config.d.ts +10 -0
  36. package/dist/config.js +74 -0
  37. package/dist/config.js.map +1 -0
  38. package/dist/family-wealth-v2-contracts.d.ts +276 -0
  39. package/dist/family-wealth-v2-contracts.js +77 -0
  40. package/dist/family-wealth-v2-contracts.js.map +1 -0
  41. package/dist/family-wealth-v2-http.d.ts +65 -0
  42. package/dist/family-wealth-v2-http.js +340 -0
  43. package/dist/family-wealth-v2-http.js.map +1 -0
  44. package/dist/financial-workspace-contracts.d.ts +191 -0
  45. package/dist/financial-workspace-contracts.js +282 -0
  46. package/dist/financial-workspace-contracts.js.map +1 -0
  47. package/dist/http.d.ts +76 -0
  48. package/dist/http.js +446 -0
  49. package/dist/http.js.map +1 -0
  50. package/dist/index.d.ts +2 -0
  51. package/dist/index.js +50 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/output.d.ts +18 -0
  54. package/dist/output.js +111 -0
  55. package/dist/output.js.map +1 -0
  56. package/dist/token-store.d.ts +27 -0
  57. package/dist/token-store.js +408 -0
  58. package/dist/token-store.js.map +1 -0
  59. package/dist/types.d.ts +65 -0
  60. package/dist/types.js +5 -0
  61. package/dist/types.js.map +1 -0
  62. package/package.json +43 -0
@@ -0,0 +1,637 @@
1
+ import { getBooleanFlag, getRepeatedStringFlag, getStringFlag, } from './args.js';
2
+ import { authStatus, login, logout } from './auth.js';
3
+ import { getAccountFinancialWorkspaceActivity, getAccountFinancialWorkspaceSnapshot, getAccountFinancialWorkspaceSummary, initializeAccountFinancialWorkspace, recordAccountFinancialWorkspaceFact, requestChooseFire, } from './http.js';
4
+ import { randomUUID } from 'node:crypto';
5
+ import { getFamilyWealthV2Activity, getFamilyWealthV2Overview, getFamilyWealthV2Summary, archiveFamilyWealthV2AssetAccount, createFamilyWealthV2AssetAccount, updateFamilyWealthV2AssetAccount, appendFamilyWealthV2Valuation, replaceFamilyWealthV2Classification, issueFamilyWealthV2McpDelegation, initializeFamilyWealthV2, revokeFamilyWealthV2McpDelegation, } from './family-wealth-v2-http.js';
6
+ export async function runCommand(config, parsed, dependencies = {}) {
7
+ const [resource, action, maybeId] = parsed.positionals;
8
+ if (!resource || resource === 'help' || getBooleanFlag(parsed.flags, 'help')) {
9
+ return help();
10
+ }
11
+ if (resource === 'auth') {
12
+ return runAuthCommand(config, action, parsed);
13
+ }
14
+ if (resource === 'workspace') {
15
+ return runWorkspaceCommand(config, action, parsed, dependencies);
16
+ }
17
+ if (resource === 'household') {
18
+ return runHouseholdCommand(config, action, parsed, dependencies);
19
+ }
20
+ if (resource === 'activity') {
21
+ return runFamilyActivityCommand(config, action, parsed, dependencies);
22
+ }
23
+ if (resource === 'wealth') {
24
+ return runWealthCommand(config, action, parsed, dependencies);
25
+ }
26
+ if (resource === 'mcp') {
27
+ return runMcpCommand(config, action, maybeId, parsed, dependencies);
28
+ }
29
+ if (resource === 'plan') {
30
+ return runPlanCommand(config, action, maybeId, parsed);
31
+ }
32
+ if (resource === 'asset') {
33
+ return runAssetCommand(config, action, parsed);
34
+ }
35
+ if (resource === 'visual') {
36
+ return runVisualCommand(config, action, parsed);
37
+ }
38
+ throw new Error(`Unknown command: ${parsed.positionals.join(' ')}`);
39
+ }
40
+ async function runMcpCommand(config, action, maybeAction, parsed, dependencies) {
41
+ if (action !== 'token' || !maybeAction || parsed.positionals.length !== 3) {
42
+ throw new Error(`Unknown mcp command: ${action || ''}`);
43
+ }
44
+ if (maybeAction === 'issue') {
45
+ assertAllowedFlags(parsed, ['clientId', 'ttlSeconds']);
46
+ const ttlRaw = getStringFlag(parsed.flags, 'ttlSeconds');
47
+ const result = await issueFamilyWealthV2McpDelegation(config, {
48
+ clientId: requireFlag(parsed, 'clientId', '--client-id'),
49
+ ...(ttlRaw === undefined ? {} : { ttlSeconds: requiredIntegerFlag(parsed, 'ttlSeconds') }),
50
+ }, dependencies);
51
+ return getBooleanFlag(parsed.flags, 'json')
52
+ ? result
53
+ : [
54
+ 'ChooseFIRE MCP 只读令牌已创建',
55
+ `客户端:${result.clientId}`,
56
+ `工作区:${result.workspaceId}`,
57
+ `有效期:${result.expiresIn} 秒`,
58
+ `令牌:${result.accessToken}`,
59
+ ].join('\n');
60
+ }
61
+ if (maybeAction === 'revoke') {
62
+ assertAllowedFlags(parsed, ['accessToken']);
63
+ const result = await revokeFamilyWealthV2McpDelegation(config, requireFlag(parsed, 'accessToken', '--access-token'), dependencies);
64
+ return getBooleanFlag(parsed.flags, 'json') ? result : 'ChooseFIRE MCP 只读令牌已撤销';
65
+ }
66
+ throw new Error(`Unknown mcp token command: ${maybeAction}`);
67
+ }
68
+ async function runHouseholdCommand(config, action, parsed, dependencies) {
69
+ if (parsed.positionals.length !== 2) {
70
+ throw new Error('Household commands do not accept additional positional arguments');
71
+ }
72
+ if (action === 'init') {
73
+ assertAllowedFlags(parsed, [
74
+ 'commandId',
75
+ 'idempotencyKey',
76
+ 'baseCurrency',
77
+ 'timezone',
78
+ ]);
79
+ const result = await initializeFamilyWealthV2(config, {
80
+ kind: 'workspace.initialize.v2',
81
+ commandId: getStringFlag(parsed.flags, 'commandId') ?? randomUUID(),
82
+ idempotencyKey: requireFlag(parsed, 'idempotencyKey', '--idempotency-key'),
83
+ expectedWorkspaceVersion: 0,
84
+ source: { clientId: 'choosefire-cli', channel: 'cli' },
85
+ payload: {
86
+ baseCurrency: requireFlag(parsed, 'baseCurrency', '--base-currency'),
87
+ timezone: requireFlag(parsed, 'timezone', '--timezone'),
88
+ },
89
+ }, dependencies);
90
+ return getBooleanFlag(parsed.flags, 'json')
91
+ ? result
92
+ : formatInitializeReceipt(result);
93
+ }
94
+ if (action === 'summary') {
95
+ assertAllowedFlags(parsed, []);
96
+ const result = await getFamilyWealthV2Summary(config, dependencies);
97
+ return getBooleanFlag(parsed.flags, 'json')
98
+ ? result
99
+ : formatWorkspaceSummary(result);
100
+ }
101
+ throw new Error(`Unknown household command: ${action || ''}`);
102
+ }
103
+ async function runFamilyActivityCommand(config, action, parsed, dependencies) {
104
+ if (action !== 'list' || parsed.positionals.length !== 2) {
105
+ throw new Error(`Unknown activity command: ${action || ''}`);
106
+ }
107
+ assertAllowedFlags(parsed, ['limit', 'cursor']);
108
+ const cursor = getStringFlag(parsed.flags, 'cursor');
109
+ const result = await getFamilyWealthV2Activity(config, {
110
+ limit: requiredIntegerFlag(parsed, 'limit'),
111
+ ...(cursor === undefined ? {} : { cursor }),
112
+ }, dependencies);
113
+ return getBooleanFlag(parsed.flags, 'json')
114
+ ? result
115
+ : formatActivityPage(result);
116
+ }
117
+ async function runWealthCommand(config, action, parsed, dependencies) {
118
+ if (parsed.positionals.length !== 2)
119
+ throw new Error('Wealth commands do not accept additional positional arguments');
120
+ if (action === 'overview') {
121
+ assertAllowedFlags(parsed, []);
122
+ const result = await getFamilyWealthV2Overview(config, dependencies);
123
+ return getBooleanFlag(parsed.flags, 'json') ? result : formatWealthOverview(result);
124
+ }
125
+ if (action === 'account-create') {
126
+ assertWealthFlags(parsed, [
127
+ 'commandId', 'idempotencyKey', 'expectedWorkspaceVersion', 'accountId',
128
+ 'name', 'accountType', 'ownerSubjectId', 'currency', 'visibility', 'maintenanceMode',
129
+ ]);
130
+ const command = {
131
+ kind: 'asset_account.create.v2',
132
+ commandId: getStringFlag(parsed.flags, 'commandId') ?? randomUUID(),
133
+ idempotencyKey: requireFlag(parsed, 'idempotencyKey', '--idempotency-key'),
134
+ expectedWorkspaceVersion: requiredIntegerFlag(parsed, 'expectedWorkspaceVersion'),
135
+ expectedObjectRevision: null,
136
+ source: { clientId: 'choosefire-cli', channel: 'cli' },
137
+ payload: {
138
+ accountId: requireFlag(parsed, 'accountId', '--account-id'),
139
+ account: {
140
+ name: requireFlag(parsed, 'name', '--name'),
141
+ accountType: requireFlag(parsed, 'accountType', '--account-type'),
142
+ ownerSubjectId: requireFlag(parsed, 'ownerSubjectId', '--owner-subject-id'),
143
+ visibility: (getStringFlag(parsed.flags, 'visibility') ?? 'household'),
144
+ aggregationConsent: !getBooleanFlag(parsed.flags, 'noAggregationConsent'),
145
+ currency: requireFlag(parsed, 'currency', '--currency'),
146
+ maintenanceMode: (getStringFlag(parsed.flags, 'maintenanceMode') ?? 'simplified'),
147
+ },
148
+ },
149
+ };
150
+ const result = await createFamilyWealthV2AssetAccount(config, command, dependencies);
151
+ return getBooleanFlag(parsed.flags, 'json') ? result : formatMutationReceipt(result);
152
+ }
153
+ if (action === 'account-update') {
154
+ assertWealthFlags(parsed, [
155
+ 'commandId', 'idempotencyKey', 'expectedWorkspaceVersion', 'expectedObjectRevision',
156
+ 'accountId', 'name', 'accountType', 'ownerSubjectId', 'currency', 'visibility',
157
+ 'maintenanceMode',
158
+ ]);
159
+ const command = {
160
+ kind: 'asset_account.update.v2',
161
+ commandId: getStringFlag(parsed.flags, 'commandId') ?? randomUUID(),
162
+ idempotencyKey: requireFlag(parsed, 'idempotencyKey', '--idempotency-key'),
163
+ expectedWorkspaceVersion: requiredIntegerFlag(parsed, 'expectedWorkspaceVersion'),
164
+ expectedObjectRevision: requiredIntegerFlag(parsed, 'expectedObjectRevision'),
165
+ source: { clientId: 'choosefire-cli', channel: 'cli' },
166
+ payload: {
167
+ accountId: requireFlag(parsed, 'accountId', '--account-id'),
168
+ account: {
169
+ name: requireFlag(parsed, 'name', '--name'),
170
+ accountType: requireFlag(parsed, 'accountType', '--account-type'),
171
+ ownerSubjectId: requireFlag(parsed, 'ownerSubjectId', '--owner-subject-id'),
172
+ visibility: (getStringFlag(parsed.flags, 'visibility') ?? 'household'),
173
+ aggregationConsent: !getBooleanFlag(parsed.flags, 'noAggregationConsent'),
174
+ currency: requireFlag(parsed, 'currency', '--currency'),
175
+ maintenanceMode: (getStringFlag(parsed.flags, 'maintenanceMode') ?? 'simplified'),
176
+ },
177
+ },
178
+ };
179
+ const result = await updateFamilyWealthV2AssetAccount(config, command, dependencies);
180
+ return getBooleanFlag(parsed.flags, 'json') ? result : formatMutationReceipt(result);
181
+ }
182
+ if (action === 'account-archive') {
183
+ assertWealthFlags(parsed, [
184
+ 'commandId', 'idempotencyKey', 'expectedWorkspaceVersion', 'expectedObjectRevision',
185
+ 'accountId', 'reason',
186
+ ]);
187
+ const command = {
188
+ kind: 'asset_account.archive.v2',
189
+ commandId: getStringFlag(parsed.flags, 'commandId') ?? randomUUID(),
190
+ idempotencyKey: requireFlag(parsed, 'idempotencyKey', '--idempotency-key'),
191
+ expectedWorkspaceVersion: requiredIntegerFlag(parsed, 'expectedWorkspaceVersion'),
192
+ expectedObjectRevision: requiredIntegerFlag(parsed, 'expectedObjectRevision'),
193
+ source: { clientId: 'choosefire-cli', channel: 'cli' },
194
+ payload: {
195
+ accountId: requireFlag(parsed, 'accountId', '--account-id'),
196
+ reason: requireFlag(parsed, 'reason', '--reason'),
197
+ },
198
+ };
199
+ const result = await archiveFamilyWealthV2AssetAccount(config, command, dependencies);
200
+ return getBooleanFlag(parsed.flags, 'json') ? result : formatMutationReceipt(result);
201
+ }
202
+ if (action === 'valuation-append') {
203
+ assertWealthFlags(parsed, [
204
+ 'commandId', 'idempotencyKey', 'expectedWorkspaceVersion', 'snapshotId',
205
+ 'scopeId', 'amount', 'currency', 'asOf', 'quality',
206
+ ]);
207
+ const command = {
208
+ kind: 'valuation_snapshot.append.v2',
209
+ commandId: getStringFlag(parsed.flags, 'commandId') ?? randomUUID(),
210
+ idempotencyKey: requireFlag(parsed, 'idempotencyKey', '--idempotency-key'),
211
+ expectedWorkspaceVersion: requiredIntegerFlag(parsed, 'expectedWorkspaceVersion'),
212
+ expectedObjectRevision: null,
213
+ source: { clientId: 'choosefire-cli', channel: 'cli' },
214
+ payload: {
215
+ snapshotId: requireFlag(parsed, 'snapshotId', '--snapshot-id'),
216
+ snapshot: {
217
+ scopeType: 'account',
218
+ scopeId: requireFlag(parsed, 'scopeId', '--scope-id'),
219
+ value: {
220
+ amount: requireFlag(parsed, 'amount', '--amount'),
221
+ currency: requireFlag(parsed, 'currency', '--currency'),
222
+ },
223
+ asOf: requireFlag(parsed, 'asOf', '--as-of'),
224
+ quality: (getStringFlag(parsed.flags, 'quality') ?? 'confirmed'),
225
+ source: { clientId: 'choosefire-cli', channel: 'cli' },
226
+ },
227
+ },
228
+ };
229
+ const result = await appendFamilyWealthV2Valuation(config, command, dependencies);
230
+ return getBooleanFlag(parsed.flags, 'json') ? result : formatMutationReceipt(result);
231
+ }
232
+ if (action === 'classify') {
233
+ assertWealthFlags(parsed, [
234
+ 'commandId', 'idempotencyKey', 'expectedWorkspaceVersion', 'expectedObjectRevision',
235
+ 'classificationRevisionId', 'scopeId', 'effectiveFrom',
236
+ ], ['assetClass', 'purpose']);
237
+ const command = {
238
+ kind: 'classification.replace.v2',
239
+ commandId: getStringFlag(parsed.flags, 'commandId') ?? randomUUID(),
240
+ idempotencyKey: requireFlag(parsed, 'idempotencyKey', '--idempotency-key'),
241
+ expectedWorkspaceVersion: requiredIntegerFlag(parsed, 'expectedWorkspaceVersion'),
242
+ expectedObjectRevision: requiredIntegerFlag(parsed, 'expectedObjectRevision', true),
243
+ source: { clientId: 'choosefire-cli', channel: 'cli' },
244
+ payload: {
245
+ classificationRevisionId: requireFlag(parsed, 'classificationRevisionId', '--classification-revision-id'),
246
+ classification: {
247
+ scopeType: 'account',
248
+ scopeId: requireFlag(parsed, 'scopeId', '--scope-id'),
249
+ assetClasses: readAllocationLines(parsed, 'assetClass', '--asset-class'),
250
+ purposeBuckets: readAllocationLines(parsed, 'purpose', '--purpose'),
251
+ effectiveFrom: requireFlag(parsed, 'effectiveFrom', '--effective-from'),
252
+ },
253
+ },
254
+ };
255
+ const result = await replaceFamilyWealthV2Classification(config, command, dependencies);
256
+ return getBooleanFlag(parsed.flags, 'json') ? result : formatMutationReceipt(result);
257
+ }
258
+ throw new Error(`Unknown wealth command: ${action || ''}`);
259
+ }
260
+ function formatMutationReceipt(receipt) {
261
+ return [
262
+ receipt.replayed ? '家庭财富变更已重放' : '家庭财富变更已提交',
263
+ `版本:${receipt.baseWorkspaceVersion} -> ${receipt.resultWorkspaceVersion}`,
264
+ `回执:${receipt.receiptId}`,
265
+ ].join('\n');
266
+ }
267
+ function formatWealthOverview(overview) {
268
+ return [
269
+ `家庭净资产:${overview.totals.netWorth.amount} ${overview.baseCurrency}`,
270
+ `总资产:${overview.totals.grossAssets.amount} ${overview.baseCurrency}`,
271
+ `负债:${overview.totals.liabilities.amount} ${overview.baseCurrency}`,
272
+ `可见账户:${overview.coverage.visibleAccountCount}`,
273
+ overview.warnings.length > 0 ? `提示:${overview.warnings.join(', ')}` : '数据状态:完整',
274
+ ].join('\n');
275
+ }
276
+ function readAllocationLines(parsed, name, label) {
277
+ const values = getRepeatedStringFlag(parsed.flags, name);
278
+ if (values.length === 0)
279
+ throw new Error(`At least one ${label} value is required`);
280
+ const lines = values.map((value) => {
281
+ const [code, rawBasisPoints] = splitAssignment(value);
282
+ const basisPoints = Number(rawBasisPoints);
283
+ if (!/^[a-z][a-z0-9._-]{0,63}$/.test(code) || !Number.isInteger(basisPoints) || basisPoints < 0 || basisPoints > 10_000) {
284
+ throw new Error(`Invalid ${label} assignment: ${value}`);
285
+ }
286
+ return { code, basisPoints };
287
+ });
288
+ if (new Set(lines.map((line) => line.code)).size !== lines.length || lines.reduce((sum, line) => sum + line.basisPoints, 0) !== 10_000) {
289
+ throw new Error(`${label} values must total 10000 basis points without duplicate codes`);
290
+ }
291
+ return lines;
292
+ }
293
+ function assertWealthFlags(parsed, allowed, repeated = []) {
294
+ const allowedFlags = new Set([...allowed, ...repeated, 'json', 'noAggregationConsent']);
295
+ const unexpected = Object.keys(parsed.flags).find((flag) => !allowedFlags.has(flag));
296
+ if (unexpected)
297
+ throw new Error(`Unknown wealth flag: --${flagName(unexpected)}`);
298
+ for (const flag of allowed) {
299
+ const value = parsed.flags[flag];
300
+ if (value !== undefined && typeof value !== 'string')
301
+ throw new Error(`--${flagName(flag)} must be provided exactly once with a value`);
302
+ }
303
+ for (const flag of repeated) {
304
+ if (parsed.flags[flag] !== undefined && getRepeatedStringFlag(parsed.flags, flag).length === 0)
305
+ throw new Error(`--${flagName(flag)} must include a value`);
306
+ }
307
+ if (parsed.flags.json !== undefined && parsed.flags.json !== true)
308
+ throw new Error('--json must be provided without a value');
309
+ if (parsed.flags.noAggregationConsent !== undefined && parsed.flags.noAggregationConsent !== true)
310
+ throw new Error('--no-aggregation-consent must be provided without a value');
311
+ }
312
+ function formatInitializeReceipt(receipt) {
313
+ const workspace = receipt.affectedObjects.find((item) => item.objectType === 'workspace');
314
+ return [
315
+ receipt.replayed ? '家庭财富工作区已确认(幂等重放)' : '家庭财富工作区已建立',
316
+ `工作区:${workspace?.objectId ?? '-'}`,
317
+ `版本:${receipt.resultWorkspaceVersion}`,
318
+ `回执:${receipt.receiptId}`,
319
+ ].join('\n');
320
+ }
321
+ function formatWorkspaceSummary(summary) {
322
+ return [
323
+ '家庭财富工作区',
324
+ `家庭:${summary.authority.householdId}`,
325
+ `工作区:${summary.authority.workspaceId}`,
326
+ `角色:${summary.authority.role}`,
327
+ `基准币种:${summary.baseCurrency}`,
328
+ `时区:${summary.timezone}`,
329
+ `版本:${summary.workspaceVersion}`,
330
+ '当前为空,可以开始建立第一项资产。',
331
+ ].join('\n');
332
+ }
333
+ function formatActivityPage(page) {
334
+ if (page.items.length === 0)
335
+ return '暂无新的工作区活动。';
336
+ return page.items
337
+ .map((item) => `${item.committedAt} ${item.operation} ${item.source.channel} v${item.resultWorkspaceVersion}`)
338
+ .join('\n');
339
+ }
340
+ async function runWorkspaceCommand(config, action, parsed, dependencies) {
341
+ if (parsed.positionals.length !== 2) {
342
+ throw new Error('Workspace commands do not accept additional positional arguments');
343
+ }
344
+ if (action === 'initialize') {
345
+ assertAllowedFlags(parsed, ['idempotencyKey']);
346
+ return initializeAccountFinancialWorkspace(config, {
347
+ kind: 'financial_workspace_initialize_command.v1',
348
+ idempotencyKey: requireFlag(parsed, 'idempotencyKey', '--idempotency-key'),
349
+ }, dependencies);
350
+ }
351
+ if (action === 'snapshot') {
352
+ assertAllowedFlags(parsed, []);
353
+ return getAccountFinancialWorkspaceSnapshot(config, dependencies);
354
+ }
355
+ if (action === 'summary') {
356
+ assertAllowedFlags(parsed, []);
357
+ return getAccountFinancialWorkspaceSummary(config, dependencies);
358
+ }
359
+ if (action === 'activity') {
360
+ assertAllowedFlags(parsed, ['limit', 'cursor']);
361
+ const cursor = getStringFlag(parsed.flags, 'cursor');
362
+ return getAccountFinancialWorkspaceActivity(config, {
363
+ limit: requiredIntegerFlag(parsed, 'limit'),
364
+ ...(cursor === undefined ? {} : { cursor }),
365
+ }, dependencies);
366
+ }
367
+ if (action === 'fact-record') {
368
+ assertAllowedFlags(parsed, [
369
+ 'expectedVersion',
370
+ 'idempotencyKey',
371
+ 'field',
372
+ 'amount',
373
+ 'currency',
374
+ 'status',
375
+ 'asOf',
376
+ ]);
377
+ const status = getStringFlag(parsed.flags, 'status');
378
+ const asOf = getStringFlag(parsed.flags, 'asOf');
379
+ return recordAccountFinancialWorkspaceFact(config, {
380
+ kind: 'financial_workspace_fact_record_command.v1',
381
+ expectedVersion: requiredIntegerFlag(parsed, 'expectedVersion', true),
382
+ idempotencyKey: requireFlag(parsed, 'idempotencyKey', '--idempotency-key'),
383
+ field: requireFlag(parsed, 'field', '--field'),
384
+ value: {
385
+ kind: 'money.v1',
386
+ amount: requireFlag(parsed, 'amount', '--amount'),
387
+ currency: requireFlag(parsed, 'currency', '--currency'),
388
+ },
389
+ ...(status === undefined ? {} : { status: status }),
390
+ ...(asOf === undefined ? {} : { asOf }),
391
+ }, dependencies);
392
+ }
393
+ throw new Error(`Unknown workspace command: ${action || ''}`);
394
+ }
395
+ async function runAuthCommand(config, action, parsed) {
396
+ if (action === 'status') {
397
+ return authStatus(config);
398
+ }
399
+ if (action === 'login') {
400
+ return login(config, {
401
+ port: numberFlag(parsed, 'port') ?? 8787,
402
+ openBrowser: !getBooleanFlag(parsed.flags, 'noOpen'),
403
+ });
404
+ }
405
+ if (action === 'logout') {
406
+ return logout();
407
+ }
408
+ throw new Error(`Unknown auth command: ${action || ''}`);
409
+ }
410
+ async function runPlanCommand(config, action, maybeId, parsed) {
411
+ if (action === 'list') {
412
+ return requestChooseFire(config, '/choosefire/fire-plans');
413
+ }
414
+ if (action === 'get') {
415
+ const id = requireValue(maybeId, 'plan id');
416
+ return requestChooseFire(config, `/choosefire/fire-plans/${encodeURIComponent(id)}`);
417
+ }
418
+ if (action === 'update-inputs') {
419
+ const id = requireValue(maybeId, 'plan id');
420
+ const patch = readInputPatch(parsed);
421
+ return requestChooseFire(config, `/choosefire/fire-plans/${encodeURIComponent(id)}/commands/update-inputs`, {
422
+ method: 'POST',
423
+ source: 'cli',
424
+ body: commandBody(parsed, { patch }),
425
+ });
426
+ }
427
+ if (action === 'duplicate') {
428
+ const id = requireValue(maybeId, 'plan id');
429
+ const name = requireValue(getStringFlag(parsed.flags, 'name'), '--name');
430
+ return requestChooseFire(config, `/choosefire/fire-plans/${encodeURIComponent(id)}/commands/duplicate`, {
431
+ method: 'POST',
432
+ source: 'cli',
433
+ body: commandBody(parsed, {
434
+ name,
435
+ patch: readSetPatch(parsed),
436
+ }),
437
+ });
438
+ }
439
+ if (action === 'set-default') {
440
+ const id = requireValue(maybeId, 'plan id');
441
+ return requestChooseFire(config, `/choosefire/fire-plans/${encodeURIComponent(id)}/commands/set-default`, {
442
+ method: 'POST',
443
+ source: 'cli',
444
+ body: commandBody(parsed, {}),
445
+ });
446
+ }
447
+ if (action === 'archive') {
448
+ const id = requireValue(maybeId, 'plan id');
449
+ return requestChooseFire(config, `/choosefire/fire-plans/${encodeURIComponent(id)}/commands/archive`, {
450
+ method: 'POST',
451
+ source: 'cli',
452
+ body: commandBody(parsed, {}),
453
+ });
454
+ }
455
+ throw new Error(`Unknown plan command: ${action || ''}`);
456
+ }
457
+ async function runAssetCommand(config, action, parsed) {
458
+ const planId = requireValue(getStringFlag(parsed.flags, 'plan'), '--plan');
459
+ if (action === 'upsert') {
460
+ const account = readAssetAccountPatch(parsed);
461
+ return requestChooseFire(config, `/choosefire/fire-plans/${encodeURIComponent(planId)}/commands/upsert-asset-account`, {
462
+ method: 'POST',
463
+ source: 'cli',
464
+ body: commandBody(parsed, {
465
+ accountId: getStringFlag(parsed.flags, 'accountId'),
466
+ account,
467
+ }),
468
+ });
469
+ }
470
+ if (action === 'remove') {
471
+ return requestChooseFire(config, `/choosefire/fire-plans/${encodeURIComponent(planId)}/commands/remove-asset-account`, {
472
+ method: 'POST',
473
+ source: 'cli',
474
+ body: commandBody(parsed, {
475
+ accountId: requireValue(getStringFlag(parsed.flags, 'accountId'), '--account-id'),
476
+ }),
477
+ });
478
+ }
479
+ throw new Error(`Unknown asset command: ${action || ''}`);
480
+ }
481
+ async function runVisualCommand(config, action, parsed) {
482
+ if (action !== 'dataset') {
483
+ throw new Error(`Unknown visual command: ${action || ''}`);
484
+ }
485
+ const planId = requireValue(getStringFlag(parsed.flags, 'plan'), '--plan');
486
+ const kind = getStringFlag(parsed.flags, 'kind') || 'overview';
487
+ return requestChooseFire(config, `/choosefire/fire-plans/${encodeURIComponent(planId)}/visual-dataset?kind=${encodeURIComponent(kind)}`);
488
+ }
489
+ function commandBody(parsed, body) {
490
+ return {
491
+ ...body,
492
+ dryRun: !getBooleanFlag(parsed.flags, 'apply'),
493
+ expectedUpdatedAt: getStringFlag(parsed.flags, 'expectedUpdatedAt'),
494
+ reason: getStringFlag(parsed.flags, 'reason'),
495
+ };
496
+ }
497
+ function readInputPatch(parsed) {
498
+ const patch = {};
499
+ setNumberIfPresent(patch, 'currentSavings', getStringFlag(parsed.flags, 'currentSavings'));
500
+ setNumberIfPresent(patch, 'annualReturn', getStringFlag(parsed.flags, 'annualReturn'));
501
+ setNumberIfPresent(patch, 'annualInflation', getStringFlag(parsed.flags, 'annualInflation'));
502
+ setNumberIfPresent(patch, 'annualExpenses', getStringFlag(parsed.flags, 'annualExpenses'));
503
+ setNumberIfPresent(patch, 'monthlyContribution', getStringFlag(parsed.flags, 'monthlyContribution'));
504
+ setNumberIfPresent(patch, 'safeWithdrawalRate', getStringFlag(parsed.flags, 'safeWithdrawalRate'));
505
+ for (const assignment of getRepeatedStringFlag(parsed.flags, 'set')) {
506
+ const [key, rawValue] = splitAssignment(assignment);
507
+ setNumberIfPresent(patch, key, rawValue);
508
+ }
509
+ if (Object.keys(patch).length === 0) {
510
+ throw new Error('No input patch provided');
511
+ }
512
+ return patch;
513
+ }
514
+ function readSetPatch(parsed) {
515
+ const patch = {};
516
+ for (const assignment of getRepeatedStringFlag(parsed.flags, 'set')) {
517
+ const [key, rawValue] = splitAssignment(assignment);
518
+ setNumberIfPresent(patch, key, rawValue);
519
+ }
520
+ return Object.keys(patch).length > 0 ? patch : undefined;
521
+ }
522
+ function readAssetAccountPatch(parsed) {
523
+ const account = {};
524
+ setStringIfPresent(account, 'name', getStringFlag(parsed.flags, 'name'));
525
+ setStringIfPresent(account, 'assetClass', getStringFlag(parsed.flags, 'assetClass'));
526
+ setStringIfPresent(account, 'riskLevel', getStringFlag(parsed.flags, 'riskLevel'));
527
+ setStringIfPresent(account, 'liquidityLevel', getStringFlag(parsed.flags, 'liquidityLevel'));
528
+ setStringIfPresent(account, 'notes', getStringFlag(parsed.flags, 'notes'));
529
+ setNumberIfPresent(account, 'amount', getStringFlag(parsed.flags, 'amount'));
530
+ setNumberIfPresent(account, 'expectedReturn', getStringFlag(parsed.flags, 'expectedReturn'));
531
+ if (Object.keys(account).length === 0) {
532
+ throw new Error('No asset account patch provided');
533
+ }
534
+ return account;
535
+ }
536
+ function setNumberIfPresent(target, key, value) {
537
+ if (value === undefined)
538
+ return;
539
+ const numericValue = Number(value);
540
+ if (!Number.isFinite(numericValue)) {
541
+ throw new Error(`${key} must be a finite number`);
542
+ }
543
+ target[key] = numericValue;
544
+ }
545
+ function setStringIfPresent(target, key, value) {
546
+ if (value === undefined)
547
+ return;
548
+ target[key] = value;
549
+ }
550
+ function splitAssignment(value) {
551
+ const index = value.indexOf('=');
552
+ if (index <= 0) {
553
+ throw new Error(`Invalid --set assignment: ${value}`);
554
+ }
555
+ return [value.slice(0, index), value.slice(index + 1)];
556
+ }
557
+ function requireValue(value, label) {
558
+ if (!value) {
559
+ throw new Error(`Missing ${label}`);
560
+ }
561
+ return value;
562
+ }
563
+ function numberFlag(parsed, name) {
564
+ const value = getStringFlag(parsed.flags, name);
565
+ if (value === undefined)
566
+ return undefined;
567
+ const numericValue = Number(value);
568
+ if (!Number.isInteger(numericValue) || numericValue <= 0) {
569
+ throw new Error(`--${name} must be a positive integer`);
570
+ }
571
+ return numericValue;
572
+ }
573
+ function requiredIntegerFlag(parsed, name, allowZero = false) {
574
+ const raw = requireFlag(parsed, name, `--${flagName(name)}`);
575
+ const value = Number(raw);
576
+ if (!Number.isSafeInteger(value) || (allowZero ? value < 0 : value <= 0)) {
577
+ throw new Error(`--${flagName(name)} must be ${allowZero ? 'a non-negative' : 'a positive'} integer`);
578
+ }
579
+ return value;
580
+ }
581
+ function requireFlag(parsed, name, label) {
582
+ return requireValue(getStringFlag(parsed.flags, name), label);
583
+ }
584
+ function assertAllowedFlags(parsed, allowed) {
585
+ const allowedFlags = new Set([...allowed, 'json']);
586
+ const unexpected = Object.keys(parsed.flags).find((flag) => !allowedFlags.has(flag));
587
+ if (unexpected)
588
+ throw new Error(`Unknown workspace flag: --${flagName(unexpected)}`);
589
+ for (const flag of allowed) {
590
+ const value = parsed.flags[flag];
591
+ if (value !== undefined && typeof value !== 'string') {
592
+ throw new Error(`--${flagName(flag)} must be provided exactly once with a value`);
593
+ }
594
+ }
595
+ const json = parsed.flags.json;
596
+ if (json !== undefined && json !== true) {
597
+ throw new Error('--json must be provided at most once without a value');
598
+ }
599
+ }
600
+ function flagName(value) {
601
+ return value.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
602
+ }
603
+ function help() {
604
+ return {
605
+ usage: [
606
+ 'choosefire auth status --json',
607
+ 'choosefire auth login --json',
608
+ 'choosefire auth logout --json',
609
+ 'choosefire workspace initialize --idempotency-key <key> --json',
610
+ 'choosefire workspace snapshot --json',
611
+ 'choosefire workspace summary --json',
612
+ 'choosefire workspace activity --limit <1-100> [--cursor <cursor>] --json',
613
+ 'choosefire workspace fact-record --expected-version <n> --idempotency-key <key> --field <field> --amount <decimal> --currency <code> [--status confirmed|estimated] [--as-of <iso>] --json',
614
+ 'choosefire household init --idempotency-key <key> --base-currency CNY --timezone Asia/Shanghai [--command-id <uuid>] [--json]',
615
+ 'choosefire household summary [--json]',
616
+ 'choosefire activity list --limit <1-100> [--cursor <cursor>] [--json]',
617
+ 'choosefire wealth overview [--json]',
618
+ 'choosefire wealth account-create --account-id <uuid> --name <name> --account-type <type> --owner-subject-id <uuid> --currency <code> --expected-workspace-version <n> --idempotency-key <key> [--visibility household|private] [--maintenance-mode simplified|detailed] [--no-aggregation-consent] [--json]',
619
+ 'choosefire wealth account-update --account-id <uuid> --name <name> --account-type <type> --owner-subject-id <uuid> --currency <code> --expected-object-revision <n> --expected-workspace-version <n> --idempotency-key <key> [--visibility household|private] [--maintenance-mode simplified|detailed] [--no-aggregation-consent] [--json]',
620
+ 'choosefire wealth account-archive --account-id <uuid> --reason <reason> --expected-object-revision <n> --expected-workspace-version <n> --idempotency-key <key> [--json]',
621
+ 'choosefire wealth valuation-append --snapshot-id <uuid> --scope-id <uuid> --amount <decimal> --currency <code> --as-of <iso> --expected-workspace-version <n> --idempotency-key <key> [--quality confirmed|estimated|unknown] [--json]',
622
+ 'choosefire wealth classify --classification-revision-id <uuid> --scope-id <uuid> --expected-object-revision <n> --expected-workspace-version <n> --effective-from <iso> --asset-class <code=basis-points> --purpose <code=basis-points> --idempotency-key <key> [--json]',
623
+ 'choosefire mcp token issue --client-id <client-id> [--ttl-seconds 300] [--json]',
624
+ 'choosefire mcp token revoke --access-token <token> [--json]',
625
+ 'choosefire plan list --json',
626
+ 'choosefire plan get <plan-id> --json',
627
+ 'choosefire plan update-inputs <plan-id> --annual-return 0.035 --expected-updated-at <iso> --dry-run --json',
628
+ 'choosefire plan duplicate <plan-id> --name "Conservative" --set annualReturn=0.035 --expected-updated-at <iso> --dry-run --json',
629
+ 'choosefire plan set-default <plan-id> --expected-updated-at <iso> --dry-run --json',
630
+ 'choosefire plan archive <plan-id> --expected-updated-at <iso> --reason "cleanup" --dry-run --json',
631
+ 'choosefire asset upsert --plan <plan-id> --account-id asset-bank --amount 300000 --expected-return 0.02 --expected-updated-at <iso> --dry-run --json',
632
+ 'choosefire asset remove --plan <plan-id> --account-id asset-bank --reason "duplicate account" --expected-updated-at <iso> --dry-run --json',
633
+ 'choosefire visual dataset --plan <plan-id> --kind overview --json',
634
+ ],
635
+ };
636
+ }
637
+ //# sourceMappingURL=commands.js.map