@slothmoney/agent-cli 0.1.0 → 0.2.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.
- package/CHANGELOG.md +9 -0
- package/README.md +53 -9
- package/dist/args.js +43 -0
- package/dist/cli.js +160 -15
- package/dist/credential-store.js +43 -0
- package/dist/errors.js +3 -1
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0 - 2026-07-23
|
|
4
|
+
|
|
5
|
+
- Add `auth login`, `auth status`, and `auth logout`.
|
|
6
|
+
- Store PATs in native OS credential storage, separated by API origin.
|
|
7
|
+
- Keep `SLOTH_AGENT_TOKEN` as the highest-precedence option for CI and
|
|
8
|
+
headless systems.
|
|
9
|
+
- Validate imported PATs before saving and report live authentication status
|
|
10
|
+
without exposing credentials.
|
|
11
|
+
|
|
3
12
|
## 0.1.0 - 2026-07-18
|
|
4
13
|
|
|
5
14
|
- Publish the first installable Sloth Agent CLI.
|
package/README.md
CHANGED
|
@@ -15,22 +15,62 @@ sloth-agent --version
|
|
|
15
15
|
For a one-off pinned run:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npm exec --yes --package=@slothmoney/agent-cli@0.
|
|
18
|
+
npm exec --yes --package=@slothmoney/agent-cli@0.2.0 -- sloth-agent --help
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
## Authenticate
|
|
22
22
|
|
|
23
23
|
Create a personal access token in Sloth Money under
|
|
24
|
-
**Settings > Developer access
|
|
25
|
-
|
|
24
|
+
**Settings > Developer access**, then save it in your operating system's
|
|
25
|
+
secure credential store:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
sloth-agent auth login
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The prompt hides the token. The CLI validates it before replacing any
|
|
32
|
+
credential already stored for the selected API origin.
|
|
33
|
+
|
|
34
|
+
For a non-interactive import, pass the token through stdin or import it from
|
|
35
|
+
the environment:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
printf '%s' "$SLOTH_AGENT_TOKEN" | sloth-agent auth login --token-stdin
|
|
39
|
+
sloth-agent auth login --from-env
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Never put a token in a command argument. For CI and headless systems, keep
|
|
43
|
+
using an environment secret:
|
|
26
44
|
|
|
27
45
|
```bash
|
|
28
46
|
export SLOTH_AGENT_TOKEN="sloth_pat_v1_..."
|
|
29
47
|
```
|
|
30
48
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
49
|
+
`SLOTH_AGENT_TOKEN` always overrides a stored credential.
|
|
50
|
+
|
|
51
|
+
To migrate from an existing environment-only setup:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
sloth-agent auth login --from-env
|
|
55
|
+
unset SLOTH_AGENT_TOKEN
|
|
56
|
+
sloth-agent auth status
|
|
57
|
+
sloth-agent categories
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Check the active credential with a live API request:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
sloth-agent auth status
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
This updates the PAT's `lastUsedAt` value. To remove the local credential:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
sloth-agent auth logout
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Logout does not unset `SLOTH_AGENT_TOKEN` or revoke the PAT. Revoke a PAT
|
|
73
|
+
remotely in **Sloth Money Settings > Developer access**.
|
|
34
74
|
|
|
35
75
|
## Commands
|
|
36
76
|
|
|
@@ -94,14 +134,18 @@ set `SLOTH_AGENT_API_BASE_URL=http://localhost:4000` or pass
|
|
|
94
134
|
`--base-url http://localhost:4000`. Non-local HTTP origins are rejected so a
|
|
95
135
|
token cannot be sent over an unencrypted connection.
|
|
96
136
|
|
|
137
|
+
Stored credentials are separated by normalized API origin. One credential is
|
|
138
|
+
stored per origin; log out and log in again to switch accounts on the same
|
|
139
|
+
origin.
|
|
140
|
+
|
|
97
141
|
Command results are JSON on stdout. Diagnostics are written to stderr.
|
|
98
142
|
|
|
99
143
|
| Exit code | Meaning |
|
|
100
144
|
| --- | --- |
|
|
101
145
|
| `0` | Success |
|
|
102
|
-
| `1` | API, network, response-validation, or partial assignment failure |
|
|
103
|
-
| `2` | Invalid command, option, URL, date, or assignment input |
|
|
104
|
-
| `3` |
|
|
146
|
+
| `1` | API, network, credential-store, response-validation, or partial assignment failure |
|
|
147
|
+
| `2` | Invalid command, option, URL, date, auth input, or assignment input |
|
|
148
|
+
| `3` | No credential or native secure storage is unavailable |
|
|
105
149
|
|
|
106
150
|
Assignment writes are best-effort. A response containing any failed assignment
|
|
107
151
|
returns exit code `1` while preserving the complete API response on stdout.
|
package/dist/args.js
CHANGED
|
@@ -122,6 +122,46 @@ function parseTransactions(args) {
|
|
|
122
122
|
function withBaseUrl(value, baseUrl) {
|
|
123
123
|
return baseUrl === undefined ? value : { ...value, baseUrl };
|
|
124
124
|
}
|
|
125
|
+
function parseAuth(args, baseUrl) {
|
|
126
|
+
const authCommand = args.shift();
|
|
127
|
+
if (!authCommand) {
|
|
128
|
+
throw new UsageError('auth requires login, status, or logout');
|
|
129
|
+
}
|
|
130
|
+
if (authCommand === 'login') {
|
|
131
|
+
let input;
|
|
132
|
+
for (const argument of args) {
|
|
133
|
+
if (argument === '--token-stdin') {
|
|
134
|
+
if (input === 'stdin') {
|
|
135
|
+
throw new UsageError('--token-stdin may only be provided once');
|
|
136
|
+
}
|
|
137
|
+
if (input === 'environment') {
|
|
138
|
+
throw new UsageError('--token-stdin and --from-env are mutually exclusive');
|
|
139
|
+
}
|
|
140
|
+
input = 'stdin';
|
|
141
|
+
}
|
|
142
|
+
else if (argument === '--from-env') {
|
|
143
|
+
if (input === 'environment') {
|
|
144
|
+
throw new UsageError('--from-env may only be provided once');
|
|
145
|
+
}
|
|
146
|
+
if (input === 'stdin') {
|
|
147
|
+
throw new UsageError('--token-stdin and --from-env are mutually exclusive');
|
|
148
|
+
}
|
|
149
|
+
input = 'environment';
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
throw new UsageError(`Unknown auth login option: ${argument}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return withBaseUrl({ command: 'auth-login', input: input ?? 'prompt' }, baseUrl);
|
|
156
|
+
}
|
|
157
|
+
if (authCommand === 'status' || authCommand === 'logout') {
|
|
158
|
+
if (args.length > 0) {
|
|
159
|
+
throw new UsageError(`Unknown auth ${authCommand} option: ${args[0]}`);
|
|
160
|
+
}
|
|
161
|
+
return withBaseUrl({ command: `auth-${authCommand}` }, baseUrl);
|
|
162
|
+
}
|
|
163
|
+
throw new UsageError(`Unknown auth command: ${authCommand}`);
|
|
164
|
+
}
|
|
125
165
|
export function parseArgs(argv) {
|
|
126
166
|
if (argv.includes('--help') || argv.includes('-h'))
|
|
127
167
|
return { command: 'help' };
|
|
@@ -131,6 +171,9 @@ export function parseArgs(argv) {
|
|
|
131
171
|
const command = args.shift();
|
|
132
172
|
if (!command)
|
|
133
173
|
return { command: 'help' };
|
|
174
|
+
if (command === 'auth') {
|
|
175
|
+
return parseAuth(args, baseUrl);
|
|
176
|
+
}
|
|
134
177
|
if (command === 'categories') {
|
|
135
178
|
if (args.length > 0) {
|
|
136
179
|
throw new UsageError(`Unknown categories option: ${args[0]}`);
|
package/dist/cli.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import { parseArgs, resolveBaseUrl, } from './args.js';
|
|
3
3
|
import { parseApiResponse, validateAssignmentPayload, } from './contracts.js';
|
|
4
|
+
import { createSystemCredentialStore, secureStorageUnavailableError, } from './credential-store.js';
|
|
4
5
|
import { ApiError, CliError, ConfigError, UsageError, } from './errors.js';
|
|
5
|
-
export const CLI_VERSION = '0.
|
|
6
|
+
export const CLI_VERSION = '0.2.0';
|
|
6
7
|
const REQUEST_TIMEOUT_MS = 30_000;
|
|
7
8
|
export function usageText() {
|
|
8
9
|
return [
|
|
9
10
|
'Sloth Agent CLI',
|
|
10
11
|
'',
|
|
11
12
|
'Usage:',
|
|
13
|
+
' sloth-agent auth login [--token-stdin | --from-env] [--base-url URL]',
|
|
14
|
+
' sloth-agent auth status [--base-url URL]',
|
|
15
|
+
' sloth-agent auth logout [--base-url URL]',
|
|
12
16
|
' sloth-agent categories [--base-url URL]',
|
|
13
17
|
' sloth-agent transactions [--uncategorized[=true|false]] [--limit N]',
|
|
14
18
|
' [--start-date YYYY-MM-DD] [--end-date YYYY-MM-DD] [--q TEXT]',
|
|
@@ -26,15 +30,63 @@ export function usageText() {
|
|
|
26
30
|
function writeJson(write, data) {
|
|
27
31
|
write(`${JSON.stringify(data, null, 2)}\n`);
|
|
28
32
|
}
|
|
29
|
-
function requireToken(environment) {
|
|
30
|
-
const token = environment.SLOTH_AGENT_TOKEN;
|
|
31
|
-
if (!token?.trim())
|
|
32
|
-
throw new ConfigError('SLOTH_AGENT_TOKEN is required');
|
|
33
|
-
return token;
|
|
34
|
-
}
|
|
35
33
|
function redact(value, token) {
|
|
36
34
|
return token ? value.split(token).join('[REDACTED]') : value;
|
|
37
35
|
}
|
|
36
|
+
function environmentToken(environment) {
|
|
37
|
+
const token = environment.SLOTH_AGENT_TOKEN;
|
|
38
|
+
return token?.trim() ? token : undefined;
|
|
39
|
+
}
|
|
40
|
+
async function defaultReadSecret() {
|
|
41
|
+
const { default: password } = await import('@inquirer/password');
|
|
42
|
+
return password({
|
|
43
|
+
message: 'Personal access token:',
|
|
44
|
+
mask: '*',
|
|
45
|
+
}, {
|
|
46
|
+
output: process.stderr,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async function defaultReadStdin() {
|
|
50
|
+
const chunks = [];
|
|
51
|
+
for await (const chunk of process.stdin) {
|
|
52
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
53
|
+
}
|
|
54
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
55
|
+
}
|
|
56
|
+
function validateLoginToken(value) {
|
|
57
|
+
if (!value.startsWith('sloth_pat_v1_') || /\s/.test(value)) {
|
|
58
|
+
throw new UsageError('Personal access token must use the sloth_pat_v1_ prefix and contain no whitespace');
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
function stripStdinLineEnding(value) {
|
|
63
|
+
if (value.endsWith('\r\n'))
|
|
64
|
+
return value.slice(0, -2);
|
|
65
|
+
if (value.endsWith('\n'))
|
|
66
|
+
return value.slice(0, -1);
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
async function loadCredentialStore(getCredentialStore) {
|
|
70
|
+
try {
|
|
71
|
+
return await getCredentialStore();
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (error instanceof CliError)
|
|
75
|
+
throw error;
|
|
76
|
+
throw secureStorageUnavailableError();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function resolveCredential(environment, origin, getCredentialStore) {
|
|
80
|
+
const token = environmentToken(environment);
|
|
81
|
+
if (token)
|
|
82
|
+
return { source: 'environment', token };
|
|
83
|
+
const credentialStore = await loadCredentialStore(getCredentialStore);
|
|
84
|
+
const storedToken = await credentialStore.get(origin);
|
|
85
|
+
if (!storedToken) {
|
|
86
|
+
throw new ConfigError(`No credential found for ${origin}. Run "sloth-agent auth login" or set SLOTH_AGENT_TOKEN.`);
|
|
87
|
+
}
|
|
88
|
+
return { source: 'keychain', token: storedToken };
|
|
89
|
+
}
|
|
38
90
|
function readAssignmentFile(filePath) {
|
|
39
91
|
try {
|
|
40
92
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
@@ -74,7 +126,7 @@ async function parseHttpResponse(response, token) {
|
|
|
74
126
|
}
|
|
75
127
|
catch {
|
|
76
128
|
if (response.ok)
|
|
77
|
-
throw new ApiError('Agent API returned invalid JSON');
|
|
129
|
+
throw new ApiError('Agent API returned invalid JSON', response.status);
|
|
78
130
|
}
|
|
79
131
|
}
|
|
80
132
|
if (!response.ok) {
|
|
@@ -84,10 +136,39 @@ async function parseHttpResponse(response, token) {
|
|
|
84
136
|
&& typeof data.error === 'string')
|
|
85
137
|
? data.error
|
|
86
138
|
: `Agent API request failed with status ${response.status}`;
|
|
87
|
-
throw new ApiError(redact(message, token));
|
|
139
|
+
throw new ApiError(redact(message, token), response.status);
|
|
88
140
|
}
|
|
89
141
|
return data;
|
|
90
142
|
}
|
|
143
|
+
function requestHeaders(token) {
|
|
144
|
+
return {
|
|
145
|
+
Accept: 'application/json',
|
|
146
|
+
Authorization: `Bearer ${token}`,
|
|
147
|
+
'User-Agent': `sloth-agent/${CLI_VERSION}`,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
async function validateCredentialRemotely(fetchImplementation, origin, token) {
|
|
151
|
+
const response = await fetchImplementation(`${origin}/api/agent/v1/categories`, {
|
|
152
|
+
method: 'GET',
|
|
153
|
+
headers: requestHeaders(token),
|
|
154
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
155
|
+
});
|
|
156
|
+
parseApiResponse('categories', await parseHttpResponse(response, token));
|
|
157
|
+
}
|
|
158
|
+
function maskedTokenSuffix(token) {
|
|
159
|
+
return token.length > 4 ? `…${token.slice(-4)}` : '…';
|
|
160
|
+
}
|
|
161
|
+
function classifyRemoteStatus(error) {
|
|
162
|
+
if (error instanceof ApiError) {
|
|
163
|
+
if (error.status === 401)
|
|
164
|
+
return 'invalid_or_expired';
|
|
165
|
+
if (error.status === 402)
|
|
166
|
+
return 'payment_required';
|
|
167
|
+
if (error.status === 403)
|
|
168
|
+
return 'insufficient_scope';
|
|
169
|
+
}
|
|
170
|
+
return 'unreachable';
|
|
171
|
+
}
|
|
91
172
|
function hasFailures(value) {
|
|
92
173
|
if (!value || typeof value !== 'object' || !('failed' in value))
|
|
93
174
|
return false;
|
|
@@ -96,6 +177,11 @@ function hasFailures(value) {
|
|
|
96
177
|
export async function runCli(argv = process.argv.slice(2), options = {}) {
|
|
97
178
|
const environment = options.env ?? process.env;
|
|
98
179
|
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
180
|
+
const getCredentialStore = options.getCredentialStore ?? createSystemCredentialStore;
|
|
181
|
+
const isInteractive = options.isInteractive
|
|
182
|
+
?? Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
183
|
+
const readSecret = options.readSecret ?? defaultReadSecret;
|
|
184
|
+
const readStdin = options.readStdin ?? defaultReadStdin;
|
|
99
185
|
const writeStdout = options.writeStdout ?? ((value) => process.stdout.write(value));
|
|
100
186
|
const writeStderr = options.writeStderr ?? ((value) => process.stderr.write(value));
|
|
101
187
|
let token;
|
|
@@ -109,13 +195,72 @@ export async function runCli(argv = process.argv.slice(2), options = {}) {
|
|
|
109
195
|
writeStdout(`${CLI_VERSION}\n`);
|
|
110
196
|
return 0;
|
|
111
197
|
}
|
|
112
|
-
token = requireToken(environment);
|
|
113
198
|
const baseUrl = resolveBaseUrl(environment, parsed.baseUrl);
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
199
|
+
if (parsed.command === 'auth-login') {
|
|
200
|
+
if (parsed.input === 'prompt' && !isInteractive) {
|
|
201
|
+
throw new UsageError('Interactive login requires a TTY. Use --token-stdin or --from-env.');
|
|
202
|
+
}
|
|
203
|
+
let rawToken;
|
|
204
|
+
if (parsed.input === 'environment') {
|
|
205
|
+
const tokenFromEnvironment = environmentToken(environment);
|
|
206
|
+
if (!tokenFromEnvironment) {
|
|
207
|
+
throw new ConfigError('SLOTH_AGENT_TOKEN is required with --from-env');
|
|
208
|
+
}
|
|
209
|
+
rawToken = tokenFromEnvironment;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
rawToken = await (parsed.input === 'stdin' ? readStdin() : readSecret());
|
|
213
|
+
if (parsed.input === 'stdin')
|
|
214
|
+
rawToken = stripStdinLineEnding(rawToken);
|
|
215
|
+
}
|
|
216
|
+
token = validateLoginToken(rawToken);
|
|
217
|
+
await validateCredentialRemotely(fetchImplementation, baseUrl, token);
|
|
218
|
+
const credentialStore = await loadCredentialStore(getCredentialStore);
|
|
219
|
+
await credentialStore.set(baseUrl, token);
|
|
220
|
+
const environmentOverrideActive = environmentToken(environment) !== undefined;
|
|
221
|
+
writeJson(writeStdout, {
|
|
222
|
+
activeSource: environmentOverrideActive ? 'environment' : 'keychain',
|
|
223
|
+
environmentOverrideActive,
|
|
224
|
+
origin: baseUrl,
|
|
225
|
+
stored: true,
|
|
226
|
+
});
|
|
227
|
+
return 0;
|
|
228
|
+
}
|
|
229
|
+
if (parsed.command === 'auth-status') {
|
|
230
|
+
const credential = await resolveCredential(environment, baseUrl, getCredentialStore);
|
|
231
|
+
token = credential.token;
|
|
232
|
+
let remoteStatus = 'valid';
|
|
233
|
+
let exitCode = 0;
|
|
234
|
+
try {
|
|
235
|
+
await validateCredentialRemotely(fetchImplementation, baseUrl, token);
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
remoteStatus = classifyRemoteStatus(error);
|
|
239
|
+
exitCode = 1;
|
|
240
|
+
}
|
|
241
|
+
writeJson(writeStdout, {
|
|
242
|
+
origin: baseUrl,
|
|
243
|
+
remoteStatus,
|
|
244
|
+
source: credential.source,
|
|
245
|
+
tokenSuffix: maskedTokenSuffix(token),
|
|
246
|
+
});
|
|
247
|
+
return exitCode;
|
|
248
|
+
}
|
|
249
|
+
if (parsed.command === 'auth-logout') {
|
|
250
|
+
const credentialStore = await loadCredentialStore(getCredentialStore);
|
|
251
|
+
const localCredentialRemoved = await credentialStore.delete(baseUrl);
|
|
252
|
+
writeJson(writeStdout, {
|
|
253
|
+
environmentOverrideActive: environmentToken(environment) !== undefined,
|
|
254
|
+
localCredentialRemoved,
|
|
255
|
+
origin: baseUrl,
|
|
256
|
+
remoteRevoked: false,
|
|
257
|
+
revocationInstructions: 'Revoke the token in Sloth Money Settings > Developer access.',
|
|
258
|
+
});
|
|
259
|
+
return 0;
|
|
260
|
+
}
|
|
261
|
+
const credential = await resolveCredential(environment, baseUrl, getCredentialStore);
|
|
262
|
+
token = credential.token;
|
|
263
|
+
const headers = requestHeaders(token);
|
|
119
264
|
if (parsed.command === 'assign') {
|
|
120
265
|
const payload = validateAssignmentPayload(readAssignmentFile(parsed.input));
|
|
121
266
|
const endpoint = `${baseUrl}/api/agent/v1/transaction-assignments`;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ApiError, ConfigError, } from './errors.js';
|
|
2
|
+
const KEYCHAIN_SERVICE = 'app.slothmoney.agent-cli';
|
|
3
|
+
export function secureStorageUnavailableError() {
|
|
4
|
+
return new ConfigError('Native secure credential storage is unavailable. Set SLOTH_AGENT_TOKEN instead.');
|
|
5
|
+
}
|
|
6
|
+
export async function createSystemCredentialStore() {
|
|
7
|
+
let keytar;
|
|
8
|
+
try {
|
|
9
|
+
const module = await import('@github/keytar');
|
|
10
|
+
keytar = ('default' in module
|
|
11
|
+
? module.default
|
|
12
|
+
: module);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
throw secureStorageUnavailableError();
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
delete: async (origin) => {
|
|
19
|
+
try {
|
|
20
|
+
return await keytar.deletePassword(KEYCHAIN_SERVICE, origin);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw new ApiError('Failed to delete the credential from native secure storage');
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
get: async (origin) => {
|
|
27
|
+
try {
|
|
28
|
+
return await keytar.getPassword(KEYCHAIN_SERVICE, origin);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
throw secureStorageUnavailableError();
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
set: async (origin, token) => {
|
|
35
|
+
try {
|
|
36
|
+
await keytar.setPassword(KEYCHAIN_SERVICE, origin, token);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
throw new ApiError('Failed to save the credential to native secure storage');
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
package/dist/errors.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slothmoney/agent-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Command-line access to the Sloth Money Agent API.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -50,5 +50,9 @@
|
|
|
50
50
|
"typescript": "^5.8.3",
|
|
51
51
|
"typescript-eslint": "^8.35.1",
|
|
52
52
|
"vitest": "^3.2.4"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@github/keytar": "7.10.6",
|
|
56
|
+
"@inquirer/password": "4.0.18"
|
|
53
57
|
}
|
|
54
58
|
}
|