allsquare 0.1.1
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.
- package/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +234 -0
- package/dist/api/client.d.ts +46 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +162 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/contract.d.ts +549 -0
- package/dist/api/contract.d.ts.map +1 -0
- package/dist/api/contract.js +49 -0
- package/dist/api/contract.js.map +1 -0
- package/dist/api/model.d.ts +627 -0
- package/dist/api/model.d.ts.map +1 -0
- package/dist/api/model.js +378 -0
- package/dist/api/model.js.map +1 -0
- package/dist/auth/auth-service.d.ts +34 -0
- package/dist/auth/auth-service.d.ts.map +1 -0
- package/dist/auth/auth-service.js +221 -0
- package/dist/auth/auth-service.js.map +1 -0
- package/dist/auth/browser.d.ts +12 -0
- package/dist/auth/browser.d.ts.map +1 -0
- package/dist/auth/browser.js +50 -0
- package/dist/auth/browser.js.map +1 -0
- package/dist/auth/credential-store.d.ts +28 -0
- package/dist/auth/credential-store.d.ts.map +1 -0
- package/dist/auth/credential-store.js +75 -0
- package/dist/auth/credential-store.js.map +1 -0
- package/dist/auth/installation-store.d.ts +14 -0
- package/dist/auth/installation-store.d.ts.map +1 -0
- package/dist/auth/installation-store.js +122 -0
- package/dist/auth/installation-store.js.map +1 -0
- package/dist/auth/model.d.ts +54 -0
- package/dist/auth/model.d.ts.map +1 -0
- package/dist/auth/model.js +24 -0
- package/dist/auth/model.js.map +1 -0
- package/dist/auth/oauth-callback.d.ts +17 -0
- package/dist/auth/oauth-callback.d.ts.map +1 -0
- package/dist/auth/oauth-callback.js +94 -0
- package/dist/auth/oauth-callback.js.map +1 -0
- package/dist/auth/oauth-client.d.ts +43 -0
- package/dist/auth/oauth-client.d.ts.map +1 -0
- package/dist/auth/oauth-client.js +191 -0
- package/dist/auth/oauth-client.js.map +1 -0
- package/dist/auth/pkce.d.ts +9 -0
- package/dist/auth/pkce.d.ts.map +1 -0
- package/dist/auth/pkce.js +12 -0
- package/dist/auth/pkce.js.map +1 -0
- package/dist/command.d.ts +3 -0
- package/dist/command.d.ts.map +1 -0
- package/dist/command.js +24 -0
- package/dist/command.js.map +1 -0
- package/dist/commands/agent-guide.d.ts +5 -0
- package/dist/commands/agent-guide.d.ts.map +1 -0
- package/dist/commands/agent-guide.js +226 -0
- package/dist/commands/agent-guide.js.map +1 -0
- package/dist/commands/auth.d.ts +5 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +68 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/balances.d.ts +5 -0
- package/dist/commands/balances.d.ts.map +1 -0
- package/dist/commands/balances.js +106 -0
- package/dist/commands/balances.js.map +1 -0
- package/dist/commands/config.d.ts +4 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +16 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/expense-input.d.ts +17 -0
- package/dist/commands/expense-input.d.ts.map +1 -0
- package/dist/commands/expense-input.js +78 -0
- package/dist/commands/expense-input.js.map +1 -0
- package/dist/commands/expenses.d.ts +5 -0
- package/dist/commands/expenses.d.ts.map +1 -0
- package/dist/commands/expenses.js +419 -0
- package/dist/commands/expenses.js.map +1 -0
- package/dist/commands/groups.d.ts +5 -0
- package/dist/commands/groups.d.ts.map +1 -0
- package/dist/commands/groups.js +149 -0
- package/dist/commands/groups.js.map +1 -0
- package/dist/commands/recurring-expenses.d.ts +5 -0
- package/dist/commands/recurring-expenses.d.ts.map +1 -0
- package/dist/commands/recurring-expenses.js +416 -0
- package/dist/commands/recurring-expenses.js.map +1 -0
- package/dist/commands/settlements.d.ts +5 -0
- package/dist/commands/settlements.d.ts.map +1 -0
- package/dist/commands/settlements.js +225 -0
- package/dist/commands/settlements.js.map +1 -0
- package/dist/commands/shared.d.ts +6 -0
- package/dist/commands/shared.d.ts.map +1 -0
- package/dist/commands/shared.js +35 -0
- package/dist/commands/shared.js.map +1 -0
- package/dist/commands/spend-summary.d.ts +5 -0
- package/dist/commands/spend-summary.d.ts.map +1 -0
- package/dist/commands/spend-summary.js +107 -0
- package/dist/commands/spend-summary.js.map +1 -0
- package/dist/commands/update.d.ts +9 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +78 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +52 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +87 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +183 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/interaction.d.ts +16 -0
- package/dist/interaction.d.ts.map +1 -0
- package/dist/interaction.js +14 -0
- package/dist/interaction.js.map +1 -0
- package/dist/layers.d.ts +13 -0
- package/dist/layers.d.ts.map +1 -0
- package/dist/layers.js +17 -0
- package/dist/layers.js.map +1 -0
- package/dist/output.d.ts +31 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +26 -0
- package/dist/output.js.map +1 -0
- package/dist/update/update-service.d.ts +41 -0
- package/dist/update/update-service.d.ts.map +1 -0
- package/dist/update/update-service.js +231 -0
- package/dist/update/update-service.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/package.json +41 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
First public beta of the AllSquare CLI.
|
|
6
|
+
|
|
7
|
+
- Groups, expenses, recurring expenses, balances, settlements, and spend
|
|
8
|
+
summaries over the AllSquare `/api/v1` API
|
|
9
|
+
- OAuth login with PKCE and OS keychain storage; read and write access by
|
|
10
|
+
default, `--read-only` available
|
|
11
|
+
- Stable JSON output, error codes with a `retryable` flag, and distinct exit
|
|
12
|
+
codes for agents
|
|
13
|
+
- Safe retries with `--idempotency-key`, including replayed results after a
|
|
14
|
+
lost response
|
|
15
|
+
- `allsquare agent-guide` for AI agents; guarded self-update with
|
|
16
|
+
`allsquare update`
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Swann&Co. LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# AllSquare CLI
|
|
2
|
+
|
|
3
|
+
Agent-friendly command-line access to AllSquare through the public `/api/v1`
|
|
4
|
+
HTTP API.
|
|
5
|
+
|
|
6
|
+
## Status
|
|
7
|
+
|
|
8
|
+
**Beta.** Commands, the JSON envelope, error codes, and exit codes are intended
|
|
9
|
+
to stay compatible; new fields and commands will be added. Breaking changes, if
|
|
10
|
+
needed, will come in a minor version with release notes.
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
- Node.js 22 or newer
|
|
15
|
+
- An AllSquare account
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm install --global allsquare
|
|
21
|
+
allsquare auth login
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`allsquare update` upgrades a global npm, pnpm, Yarn, or Bun installation.
|
|
25
|
+
|
|
26
|
+
## Current commands
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
allsquare --help
|
|
30
|
+
allsquare --version
|
|
31
|
+
allsquare agent-guide [--json]
|
|
32
|
+
allsquare update --check [--json]
|
|
33
|
+
allsquare update [--yes] [--json]
|
|
34
|
+
allsquare auth login
|
|
35
|
+
allsquare auth login --read-only
|
|
36
|
+
allsquare auth login --no-browser
|
|
37
|
+
allsquare auth status
|
|
38
|
+
allsquare auth refresh
|
|
39
|
+
allsquare auth logout
|
|
40
|
+
allsquare groups list
|
|
41
|
+
allsquare groups members <group-id>
|
|
42
|
+
allsquare groups create --name TEXT --currency CODE [--member-email EMAIL]
|
|
43
|
+
allsquare groups add-member <group-id> --email EMAIL
|
|
44
|
+
allsquare balances list [--currency USD]
|
|
45
|
+
allsquare balances show <group-id> [--currency USD]
|
|
46
|
+
allsquare expenses list [--group-id ID] [--query TEXT] [--from YYYY-MM-DD] [--to YYYY-MM-DD]
|
|
47
|
+
allsquare expenses list [--paid-by-id MEMBER_ID] [--is-recurring true|false] [--is-settled true|false]
|
|
48
|
+
allsquare expenses list [--limit 20] [--cursor CURSOR]
|
|
49
|
+
allsquare expenses get <expense-id>
|
|
50
|
+
allsquare expenses create <group-id> --description TEXT --amount DECIMAL --currency CODE
|
|
51
|
+
allsquare expenses create <group-id> [--paid-by-id ID] [--split-with ID] [--split-type amount|percentage|exact]
|
|
52
|
+
allsquare expenses create <group-id> [--percentage-split ID=PERCENT] [--exact-split ID=AMOUNT] [--date YYYY-MM-DD]
|
|
53
|
+
allsquare expenses update <expense-id> [--description TEXT] [--amount DECIMAL] [--currency CODE]
|
|
54
|
+
allsquare expenses update <expense-id> [--paid-by-id ID] [--split-with ID] [--split-type amount|percentage|exact]
|
|
55
|
+
allsquare expenses delete <expense-id> [--yes]
|
|
56
|
+
allsquare recurring-expenses list [--group-id ID]
|
|
57
|
+
allsquare recurring-expenses get <recurring-expense-id>
|
|
58
|
+
allsquare recurring-expenses create <group-id> --description TEXT --amount DECIMAL --currency CODE --frequency FREQUENCY
|
|
59
|
+
allsquare recurring-expenses create <group-id> [--start-date YYYY-MM-DD] [--paid-by-id ID] [--split-with ID]
|
|
60
|
+
allsquare recurring-expenses update <recurring-expense-id> [--description TEXT] [--amount DECIMAL] [--frequency FREQUENCY]
|
|
61
|
+
allsquare recurring-expenses update <recurring-expense-id> [--paid-by-id ID] [--split-with ID] [--split-type amount|percentage|exact]
|
|
62
|
+
allsquare recurring-expenses pause <recurring-expense-id>
|
|
63
|
+
allsquare recurring-expenses resume <recurring-expense-id>
|
|
64
|
+
allsquare recurring-expenses delete <recurring-expense-id> [--yes]
|
|
65
|
+
allsquare settlements list [--group-id ID] [--limit 20]
|
|
66
|
+
allsquare settlements preview <group-id> --expense-id ID [--expense-id ID] [--currency USD]
|
|
67
|
+
allsquare settlements settle <group-id> --expense-id ID [--expense-id ID] [--currency USD] [--yes]
|
|
68
|
+
allsquare spend-summary show [--group-id ID] [--from YYYY-MM-DD] [--to YYYY-MM-DD]
|
|
69
|
+
allsquare spend-summary show [--query TEXT] [--currency USD]
|
|
70
|
+
allsquare config show
|
|
71
|
+
allsquare config show --json
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Add `--json` to any leaf command for machine-readable output. Login opens the
|
|
75
|
+
system browser by default and completes through an ephemeral loopback callback.
|
|
76
|
+
Use `--no-browser` to print the authorization URL without opening it.
|
|
77
|
+
|
|
78
|
+
Each CLI installation registers its own OAuth public client. Authorization uses
|
|
79
|
+
PKCE, access tokens refresh automatically shortly before expiry, and refreshes
|
|
80
|
+
are serialized across concurrent processes so agents cannot accidentally reuse
|
|
81
|
+
a rotated refresh token. Plain `auth login` requests the CLI's normal read and
|
|
82
|
+
write access in one consent flow. Use `auth login --read-only` to request only
|
|
83
|
+
read access; write commands then fail with `AUTH_REQUIRED` rather than asking
|
|
84
|
+
for more access. If a write command runs with no session at all from an
|
|
85
|
+
interactive terminal, the CLI opens consent, waits for approval, then continues
|
|
86
|
+
that same command. With `--json` or without a terminal it fails immediately with
|
|
87
|
+
`AUTH_REQUIRED` (exit 3) so scripts and agents never wait on a browser. The
|
|
88
|
+
older `auth login --write` flag remains accepted as an alias for the default; it
|
|
89
|
+
is not required. Logging in again, including with `--read-only`, revokes the
|
|
90
|
+
previous session's tokens; if that revocation cannot be confirmed, the login
|
|
91
|
+
still succeeds and reports `replacedSessionRevocation: "unconfirmed"`.
|
|
92
|
+
|
|
93
|
+
Refresh tokens are stored in the operating system credential vault (macOS
|
|
94
|
+
Keychain, Windows Credential Manager, or a Linux Secret Service provider).
|
|
95
|
+
Linux installations therefore need a working Secret Service implementation
|
|
96
|
+
such as GNOME Keyring, KWallet, or KeePassXC. Non-secret client registration
|
|
97
|
+
metadata is stored in the OS-standard application config directory with private
|
|
98
|
+
permissions.
|
|
99
|
+
|
|
100
|
+
The default profile retains its existing keychain account. Setting
|
|
101
|
+
`ALLSQUARE_CONFIG_DIR` creates a separate credential namespace for that folder,
|
|
102
|
+
even when it targets the same API URL. A custom-folder profile does not read or
|
|
103
|
+
move credentials stored under the old unscoped account name; log in separately
|
|
104
|
+
in that profile if it previously relied on those credentials.
|
|
105
|
+
Log out of a custom-folder profile before deleting its folder; deleting the
|
|
106
|
+
folder alone does not remove its operating-system credential entry.
|
|
107
|
+
|
|
108
|
+
The CLI targets `https://allsquare.app/api/v1` by default. Set
|
|
109
|
+
`ALLSQUARE_API_URL` to use another deployment. Plain HTTP is accepted only for
|
|
110
|
+
loopback URLs such as `http://localhost:3000/api/v1`.
|
|
111
|
+
|
|
112
|
+
Successful JSON output uses `{ "ok": true, "data": ... }`. When a write
|
|
113
|
+
reuses an idempotency key the server has already completed, the output adds
|
|
114
|
+
`"replayed": true` (human output prints a note on stderr): nothing new was
|
|
115
|
+
written, and `data` is the original result, which may describe a resource that
|
|
116
|
+
has since changed or been deleted. Expected failures
|
|
117
|
+
use `{ "ok": false, "error": { "code": ..., "message": ..., "retryable": ... } }` on stderr
|
|
118
|
+
and exit nonzero, including argument-parser failures. Every error includes
|
|
119
|
+
`retryable`: true only when repeating the same command can succeed unchanged
|
|
120
|
+
(rate limits, service or network failures, temporary login-service errors, a
|
|
121
|
+
busy session refresh, or `IN_PROGRESS` while the same idempotent write is still
|
|
122
|
+
running); retry writes with the same `--idempotency-key`. Error codes use one style
|
|
123
|
+
(`INVALID_ARGUMENT`, `AUTH_REQUIRED`, `FORBIDDEN`, `NOT_FOUND`, `CONFLICT`,
|
|
124
|
+
`RATE_LIMITED`, `SERVICE_UNAVAILABLE`, `NETWORK_ERROR`, ...); the server's own
|
|
125
|
+
code is kept in `details.apiCode`. A group that does not exist returns
|
|
126
|
+
`FORBIDDEN`, the same as one you cannot access. Exit codes are 0 for success,
|
|
127
|
+
2 for invalid input or configuration, 3 for authentication or permission, 4 for
|
|
128
|
+
conflicts, 5 for temporary or service failures, and 6 for not found.
|
|
129
|
+
Unexpected failures may exit 1. `allsquare agent-guide --json` lists every
|
|
130
|
+
code.
|
|
131
|
+
|
|
132
|
+
`auth logout` always tries to remove local credentials even if the OAuth
|
|
133
|
+
server cannot be reached. Its JSON result includes `revocation` (`confirmed`,
|
|
134
|
+
`unconfirmed`, or `not_needed`). An unconfirmed result means the token may
|
|
135
|
+
remain valid on the server; revoke the CLI connection in AllSquare settings if
|
|
136
|
+
you need immediate server-side invalidation.
|
|
137
|
+
|
|
138
|
+
## Agent use
|
|
139
|
+
|
|
140
|
+
Agents should begin with `allsquare agent-guide --json`. The guide is a
|
|
141
|
+
versioned, machine-readable operating contract covering resource discovery,
|
|
142
|
+
pagination, least-privilege authorization, write confirmation, idempotent
|
|
143
|
+
retries, deletion, settlement, Venmo handoff, and the CLI output contract. The
|
|
144
|
+
same content is available for humans with `allsquare agent-guide`.
|
|
145
|
+
|
|
146
|
+
The guide supplements safeguards enforced by the CLI. In particular, `--yes`
|
|
147
|
+
means the calling agent has already obtained the required user confirmation; it
|
|
148
|
+
does not grant permission on its own. Agents must preview settlements, render
|
|
149
|
+
any returned Venmo links, avoid claiming that AllSquare completed or verified
|
|
150
|
+
an external Venmo transaction, and record only the expense IDs the user
|
|
151
|
+
selected.
|
|
152
|
+
|
|
153
|
+
`allsquare update --check` compares the installed version with the npm
|
|
154
|
+
`latest` release without changing the installation. `allsquare update` can
|
|
155
|
+
update recognized npm, pnpm, Yarn, or Bun global installations after an
|
|
156
|
+
interactive confirmation; agents and other non-interactive callers must pass
|
|
157
|
+
`--yes`. Source checkouts, local project dependencies, ephemeral invocations,
|
|
158
|
+
and ambiguous installations are never modified automatically. The command
|
|
159
|
+
returns installation-specific guidance instead. JSON execution captures
|
|
160
|
+
package-manager output so the final success or error envelope remains
|
|
161
|
+
machine-readable.
|
|
162
|
+
|
|
163
|
+
Group creation requires an explicit default currency. Repeat `--member-email`
|
|
164
|
+
to add existing accounts or invite up to 20 initial members. `groups add-member`
|
|
165
|
+
uses the same add-or-invite behavior for one address. These operations can send
|
|
166
|
+
email; JSON output reports whether the membership or invitation was created and
|
|
167
|
+
whether its notification was delivered. Both writes are idempotent, and
|
|
168
|
+
`--idempotency-key` is available when retrying the same logical request.
|
|
169
|
+
|
|
170
|
+
Settlement preview is non-mutating and works with read-only authorization. It
|
|
171
|
+
accepts up to 100 repeated `--expense-id` flags and shows the payments that a
|
|
172
|
+
settlement would create without recording the settlement.
|
|
173
|
+
|
|
174
|
+
Settlement execution requires write authorization and always previews the exact
|
|
175
|
+
expense IDs before recording anything. Interactive confirmation defaults to
|
|
176
|
+
“no”; agents, JSON callers, and other non-interactive sessions must pass
|
|
177
|
+
`--yes`. The confirmed request is idempotent, and `--idempotency-key` can be
|
|
178
|
+
reused when retrying the same logical settlement: if the first attempt
|
|
179
|
+
succeeded but its response was lost, the retry returns the original settlement
|
|
180
|
+
with `"replayed": true` even though the preview now reports the expenses as
|
|
181
|
+
settled. `expenses delete` and `recurring-expenses delete` behave the same way:
|
|
182
|
+
a retry with the original `--idempotency-key` returns the original deletion even
|
|
183
|
+
though the item is already gone. Recording a settlement marks
|
|
184
|
+
the selected expenses settled; it does not transfer money.
|
|
185
|
+
|
|
186
|
+
Expense creation requires an explicit currency: USD, CAD, EUR, or GBP. Write commands need a session
|
|
187
|
+
with write access; see the login notes above for when the CLI can prompt for
|
|
188
|
+
one. `--split-type amount` means split equally between the payer and each
|
|
189
|
+
`--split-with` member; the payer is always included. In a two-person
|
|
190
|
+
group, omitting `--split-with` splits with the other member (as the web app
|
|
191
|
+
does); larger groups must name at least one member besides the payer. Percentage and exact modes
|
|
192
|
+
accept repeated `MEMBER_ID=VALUE` allocation flags, validate their totals
|
|
193
|
+
locally, and include every named member even if it is not repeated in
|
|
194
|
+
`--split-with`. `--date` defaults to today in your local time zone. A retry with
|
|
195
|
+
`--idempotency-key` must be the identical command: the same flags and values,
|
|
196
|
+
including `--date` (it is in the first attempt's result) and `--currency`;
|
|
197
|
+
otherwise the server rejects it as a different request. The order of repeated
|
|
198
|
+
flags such as `--split-with` or `--expense-id` does not matter. The CLI generates an idempotency
|
|
199
|
+
key automatically; pass `--idempotency-key` only when retrying the same logical
|
|
200
|
+
creation across separate invocations.
|
|
201
|
+
|
|
202
|
+
Expense updates are partial, except that changing a split requires
|
|
203
|
+
`--split-type` plus the complete replacement participant list. Interactive
|
|
204
|
+
deletion displays the current expense and defaults to “no.” Agents and JSON
|
|
205
|
+
callers must pass `--yes`; deletion still reads the exact target before sending
|
|
206
|
+
the confirmed, idempotent delete request.
|
|
207
|
+
|
|
208
|
+
Recurring-expense writes use the same participant, split, amount, and
|
|
209
|
+
idempotency safeguards. Frequencies are `weekly`, `biweekly`, `monthly`, or
|
|
210
|
+
`yearly`. Pausing keeps generated history and stops future generation; resuming
|
|
211
|
+
continues from the next scheduled date without backfilling the paused period.
|
|
212
|
+
Recurring deletion is permanent, displays the exact template first for
|
|
213
|
+
interactive users, and requires `--yes` for agents and JSON callers. Deleting a
|
|
214
|
+
template stops future expenses but keeps the ones it already created. A
|
|
215
|
+
template whose start date is today or earlier creates its first expense
|
|
216
|
+
immediately and returns its ID as `immediateExpenseId` (`null` when only
|
|
217
|
+
scheduled). Updates cannot change the next date or the currency; delete and
|
|
218
|
+
recreate the template instead.
|
|
219
|
+
|
|
220
|
+
## Development
|
|
221
|
+
|
|
222
|
+
The CLI is a standalone package inside the AllSquare repository. It does not
|
|
223
|
+
import application or server modules from the repository root.
|
|
224
|
+
|
|
225
|
+
```sh
|
|
226
|
+
cd cli
|
|
227
|
+
pnpm install
|
|
228
|
+
pnpm check
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Before packaging from this repository, install both the root and `cli/`
|
|
232
|
+
dependencies, then run `pnpm --dir cli run prepack`. That gate runs the CLI
|
|
233
|
+
checks and compares its schemas with the generated v1 API contract. The CI
|
|
234
|
+
workflow runs both test suites on each push and pull request.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Context, Crypto, Effect, Layer, Schema } from "effect";
|
|
2
|
+
import { HttpClient } from "effect/unstable/http";
|
|
3
|
+
import { AuthService } from "../auth/auth-service.js";
|
|
4
|
+
import type { AuthError } from "../auth/auth-service.js";
|
|
5
|
+
import { API_READ_SCOPE, API_WRITE_SCOPE } from "../auth/model.js";
|
|
6
|
+
import { CliConfig } from "../config.js";
|
|
7
|
+
import { ApiRequestError } from "../errors.js";
|
|
8
|
+
import { Interaction } from "../interaction.js";
|
|
9
|
+
export type ApiClientError = AuthError | ApiRequestError;
|
|
10
|
+
export interface ApiResponse<A> {
|
|
11
|
+
readonly data: A;
|
|
12
|
+
readonly replayed: boolean;
|
|
13
|
+
}
|
|
14
|
+
type ResponseSchema<A> = Schema.ConstraintCodec<A, unknown, never, unknown>;
|
|
15
|
+
type BodySchema<B> = Schema.ConstraintCodec<B, unknown, never, never>;
|
|
16
|
+
type Query = Readonly<Record<string, string | undefined>>;
|
|
17
|
+
interface BaseRequestOptions<A> {
|
|
18
|
+
readonly path: string;
|
|
19
|
+
readonly query?: Query;
|
|
20
|
+
readonly schema: ResponseSchema<A>;
|
|
21
|
+
}
|
|
22
|
+
interface BodyRequestOptions<A, B> extends BaseRequestOptions<A> {
|
|
23
|
+
readonly body: B;
|
|
24
|
+
readonly bodySchema: BodySchema<B>;
|
|
25
|
+
}
|
|
26
|
+
type PostRequestOptions<A, B> = BodyRequestOptions<A, B> & ({
|
|
27
|
+
readonly scope: typeof API_READ_SCOPE;
|
|
28
|
+
readonly idempotencyKey?: never;
|
|
29
|
+
} | {
|
|
30
|
+
readonly scope: typeof API_WRITE_SCOPE;
|
|
31
|
+
readonly idempotencyKey?: string;
|
|
32
|
+
});
|
|
33
|
+
interface WriteRequestOptions<A, B> extends BodyRequestOptions<A, B> {
|
|
34
|
+
readonly idempotencyKey?: string;
|
|
35
|
+
}
|
|
36
|
+
declare const ApiClient_base: Context.ServiceClass<ApiClient, "@allsquare/cli/ApiClient", {
|
|
37
|
+
readonly get: <A>(options: BaseRequestOptions<A>) => Effect.Effect<A, ApiClientError>;
|
|
38
|
+
readonly post: <A, B>(options: PostRequestOptions<A, B>) => Effect.Effect<ApiResponse<A>, ApiClientError>;
|
|
39
|
+
readonly patch: <A, B>(options: WriteRequestOptions<A, B>) => Effect.Effect<ApiResponse<A>, ApiClientError>;
|
|
40
|
+
readonly delete: <A, B>(options: WriteRequestOptions<A, B>) => Effect.Effect<ApiResponse<A>, ApiClientError>;
|
|
41
|
+
}>;
|
|
42
|
+
export declare class ApiClient extends ApiClient_base {
|
|
43
|
+
static readonly layer: Layer.Layer<ApiClient, never, CliConfig | Crypto.Crypto | HttpClient.HttpClient | AuthService | Interaction>;
|
|
44
|
+
}
|
|
45
|
+
export {};
|
|
46
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { Console, Context, Crypto, Effect, Layer, Option, Schema } from "effect";
|
|
2
|
+
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
|
|
3
|
+
import { AuthService } from "../auth/auth-service.js";
|
|
4
|
+
import { API_READ_SCOPE, API_WRITE_SCOPE } from "../auth/model.js";
|
|
5
|
+
import { CliConfig } from "../config.js";
|
|
6
|
+
import { ApiRequestError } from "../errors.js";
|
|
7
|
+
import { Interaction } from "../interaction.js";
|
|
8
|
+
import { VERSION } from "../version.js";
|
|
9
|
+
const ApiErrorBody = Schema.Struct({
|
|
10
|
+
error: Schema.String,
|
|
11
|
+
code: Schema.optionalKey(Schema.String),
|
|
12
|
+
fields: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)),
|
|
13
|
+
route: Schema.optionalKey(Schema.String),
|
|
14
|
+
retryAfterSeconds: Schema.optionalKey(Schema.Number)
|
|
15
|
+
});
|
|
16
|
+
const apiErrorFromResponse = (status, body) => {
|
|
17
|
+
let decoded;
|
|
18
|
+
try {
|
|
19
|
+
const result = Schema.decodeUnknownOption(ApiErrorBody)(JSON.parse(body));
|
|
20
|
+
decoded = Option.getOrUndefined(result);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
decoded = undefined;
|
|
24
|
+
}
|
|
25
|
+
return new ApiRequestError({
|
|
26
|
+
message: decoded?.error ?? `API request failed with status ${status}`,
|
|
27
|
+
status,
|
|
28
|
+
...(decoded?.code === undefined ? {} : { apiCode: decoded.code }),
|
|
29
|
+
...(decoded?.fields === undefined ? {} : { fields: decoded.fields }),
|
|
30
|
+
...(decoded?.route === undefined ? {} : { route: decoded.route }),
|
|
31
|
+
...(decoded?.retryAfterSeconds === undefined
|
|
32
|
+
? {}
|
|
33
|
+
: { retryAfterSeconds: decoded.retryAfterSeconds })
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
const requestUrl = (apiUrl, path, query) => {
|
|
37
|
+
const url = new URL(`${apiUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`);
|
|
38
|
+
for (const [key, value] of Object.entries(query)) {
|
|
39
|
+
if (value !== undefined)
|
|
40
|
+
url.searchParams.set(key, value);
|
|
41
|
+
}
|
|
42
|
+
return url.toString();
|
|
43
|
+
};
|
|
44
|
+
export class ApiClient extends Context.Service()("@allsquare/cli/ApiClient") {
|
|
45
|
+
static layer = Layer.effect(ApiClient, Effect.gen(function* () {
|
|
46
|
+
const config = yield* CliConfig;
|
|
47
|
+
const auth = yield* AuthService;
|
|
48
|
+
const interaction = yield* Interaction;
|
|
49
|
+
const client = yield* HttpClient.HttpClient;
|
|
50
|
+
const crypto = yield* Crypto.Crypto;
|
|
51
|
+
const execute = (request, token, schema) => client.execute(request.pipe(HttpClientRequest.setHeader("accept", "application/json"), HttpClientRequest.setHeader("user-agent", `allsquare-cli/${VERSION}`), HttpClientRequest.bearerToken(token))).pipe(Effect.flatMap((response) => {
|
|
52
|
+
if (response.status < 200 || response.status >= 300) {
|
|
53
|
+
return response.text.pipe(Effect.orElseSucceed(() => ""), Effect.flatMap((body) => Effect.fail(apiErrorFromResponse(response.status, body))));
|
|
54
|
+
}
|
|
55
|
+
return HttpClientResponse.schemaBodyJson(schema)(response).pipe(Effect.map((data) => ({
|
|
56
|
+
data,
|
|
57
|
+
replayed: response.headers["idempotent-replayed"] === "true"
|
|
58
|
+
})), Effect.mapError((cause) => new ApiRequestError({
|
|
59
|
+
message: `API returned an invalid response: ${cause.message}`,
|
|
60
|
+
status: response.status
|
|
61
|
+
})));
|
|
62
|
+
}), Effect.mapError((cause) => cause instanceof ApiRequestError
|
|
63
|
+
? cause
|
|
64
|
+
: new ApiRequestError({
|
|
65
|
+
message: `API request failed: ${cause.message}`
|
|
66
|
+
})));
|
|
67
|
+
const requestWithRefresh = (options) => Effect.gen(function* () {
|
|
68
|
+
const accessToken = (scope) => auth.accessToken(scope).pipe(Effect.catchTag("AuthRequiredError", (error) =>
|
|
69
|
+
// Only a missing session starts consent, and only when someone
|
|
70
|
+
// can answer it. A read-only session was chosen deliberately.
|
|
71
|
+
scope === API_READ_SCOPE ||
|
|
72
|
+
error.reason === "insufficient_scope" ||
|
|
73
|
+
!interaction.canPromptForLogin
|
|
74
|
+
? Effect.fail(error)
|
|
75
|
+
: auth.login({
|
|
76
|
+
write: true,
|
|
77
|
+
noBrowser: false,
|
|
78
|
+
onAuthorizationUrl: (url) => Console.error(`Authorize AllSquare CLI to make changes:\n${url}`),
|
|
79
|
+
onBrowserOpenError: (cause) => Console.error(`Could not open your browser automatically: ${cause.message}`)
|
|
80
|
+
}).pipe(Effect.andThen(auth.accessToken(scope)))));
|
|
81
|
+
// Authorization happens before the request is sent, so an
|
|
82
|
+
// interactive login keeps the write's original idempotency key.
|
|
83
|
+
const token = yield* accessToken(options.scope);
|
|
84
|
+
const first = yield* Effect.result(execute(options.request, token, options.schema));
|
|
85
|
+
if (first._tag === "Success")
|
|
86
|
+
return first.success;
|
|
87
|
+
if (first.failure.status !== 401)
|
|
88
|
+
return yield* first.failure;
|
|
89
|
+
yield* auth.refresh;
|
|
90
|
+
const refreshedToken = yield* accessToken(options.scope);
|
|
91
|
+
return yield* execute(options.request, refreshedToken, options.schema);
|
|
92
|
+
});
|
|
93
|
+
const idempotencyKey = (supplied) => supplied === undefined
|
|
94
|
+
? crypto.randomUUIDv4.pipe(Effect.mapError((cause) => new ApiRequestError({
|
|
95
|
+
message: `Could not generate an idempotency key: ${cause.message}`
|
|
96
|
+
})))
|
|
97
|
+
: Effect.succeed(supplied);
|
|
98
|
+
const bodyRequest = (options) => Effect.gen(function* () {
|
|
99
|
+
const apiUrl = yield* config.apiUrl;
|
|
100
|
+
const url = requestUrl(apiUrl, options.path, options.query);
|
|
101
|
+
const request = options.method === "POST"
|
|
102
|
+
? HttpClientRequest.post(url)
|
|
103
|
+
: options.method === "PATCH"
|
|
104
|
+
? HttpClientRequest.patch(url)
|
|
105
|
+
: HttpClientRequest.delete(url);
|
|
106
|
+
const encoded = yield* request.pipe(HttpClientRequest.schemaBodyJson(options.bodySchema)(options.body), Effect.mapError((cause) => new ApiRequestError({
|
|
107
|
+
message: `Could not encode API request body: ${cause.message}`
|
|
108
|
+
})));
|
|
109
|
+
const authenticatedRequest = options.scope === API_WRITE_SCOPE
|
|
110
|
+
? encoded.pipe(HttpClientRequest.setHeader("idempotency-key", yield* idempotencyKey(options.idempotencyKey)))
|
|
111
|
+
: encoded;
|
|
112
|
+
return yield* requestWithRefresh({
|
|
113
|
+
request: authenticatedRequest,
|
|
114
|
+
scope: options.scope,
|
|
115
|
+
schema: options.schema
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
return ApiClient.of({
|
|
119
|
+
get: ({ path, query = {}, schema }) => Effect.gen(function* () {
|
|
120
|
+
const apiUrl = yield* config.apiUrl;
|
|
121
|
+
const url = requestUrl(apiUrl, path, query);
|
|
122
|
+
const response = yield* requestWithRefresh({
|
|
123
|
+
request: HttpClientRequest.get(url),
|
|
124
|
+
scope: API_READ_SCOPE,
|
|
125
|
+
schema
|
|
126
|
+
});
|
|
127
|
+
return response.data;
|
|
128
|
+
}),
|
|
129
|
+
post: ({ path, query = {}, body, bodySchema, schema, scope, idempotencyKey }) => bodyRequest({
|
|
130
|
+
method: "POST",
|
|
131
|
+
path,
|
|
132
|
+
query,
|
|
133
|
+
body,
|
|
134
|
+
bodySchema,
|
|
135
|
+
schema,
|
|
136
|
+
scope,
|
|
137
|
+
...(idempotencyKey === undefined ? {} : { idempotencyKey })
|
|
138
|
+
}),
|
|
139
|
+
patch: ({ path, query = {}, body, bodySchema, schema, idempotencyKey }) => bodyRequest({
|
|
140
|
+
method: "PATCH",
|
|
141
|
+
path,
|
|
142
|
+
query,
|
|
143
|
+
body,
|
|
144
|
+
bodySchema,
|
|
145
|
+
schema,
|
|
146
|
+
scope: API_WRITE_SCOPE,
|
|
147
|
+
...(idempotencyKey === undefined ? {} : { idempotencyKey })
|
|
148
|
+
}),
|
|
149
|
+
delete: ({ path, query = {}, body, bodySchema, schema, idempotencyKey }) => bodyRequest({
|
|
150
|
+
method: "DELETE",
|
|
151
|
+
path,
|
|
152
|
+
query,
|
|
153
|
+
body,
|
|
154
|
+
bodySchema,
|
|
155
|
+
schema,
|
|
156
|
+
scope: API_WRITE_SCOPE,
|
|
157
|
+
...(idempotencyKey === undefined ? {} : { idempotencyKey })
|
|
158
|
+
})
|
|
159
|
+
});
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
|