olympay 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 (3) hide show
  1. package/README.md +407 -0
  2. package/dist/cli.js +322 -0
  3. package/package.json +33 -0
package/README.md ADDED
@@ -0,0 +1,407 @@
1
+ # olympay
2
+
3
+ > CLI for Olympay — financial control for autonomous AI agents.
4
+
5
+ ```
6
+ ██████╗ ██╗ ██╗ ██╗███╗ ███╗██████╗ █████╗ ██╗ ██╗
7
+ ██╔═══██╗██║ ╚██╗ ██╔╝████╗ ████║██╔══██╗██╔══██╗╚██╗ ██╔╝
8
+ ██║ ██║██║ ╚████╔╝ ██╔████╔██║██████╔╝███████║ ╚████╔╝
9
+ ██║ ██║██║ ╚██╔╝ ██║╚██╔╝██║██╔═══╝ ██╔══██║ ╚██╔╝
10
+ ╚██████╔╝███████╗ ██║ ██║ ╚═╝ ██║██║ ██║ ██║ ██║
11
+ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝
12
+ Financial control for autonomous AI agents • olympay.tech
13
+ ```
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install -g olympay
19
+ ```
20
+
21
+ Requires Node.js 18 or later.
22
+
23
+ ## Getting Started
24
+
25
+ ### 1. Create an account
26
+
27
+ Sign up at [olympay.tech](https://olympay.tech), go to **API** in the sidebar, and click **Generate Key** to create a workspace API key.
28
+
29
+ ### 2. Authenticate
30
+
31
+ ```bash
32
+ olympay login --key olympay_ws_YOUR_KEY
33
+ ```
34
+
35
+ ### 3. Spawn an agent
36
+
37
+ ```bash
38
+ olympay agent create --name "my-agent"
39
+ ```
40
+
41
+ This returns your agent's ID and its dedicated API key. Store both securely.
42
+
43
+ ---
44
+
45
+ ## Command Reference
46
+
47
+ ### `olympay login`
48
+
49
+ Authenticate with a workspace API key.
50
+
51
+ ```bash
52
+ olympay login --key <workspace_key>
53
+ ```
54
+
55
+ | Flag | Required | Description |
56
+ |---|---|---|
57
+ | `--key` | Yes | Workspace API key starting with `olympay_ws_` |
58
+ | `--api` | No | Override the API base URL (default: `https://api.olympay.tech/v1`) |
59
+
60
+ Credentials are saved to `~/.olympay/config.json`.
61
+
62
+ ---
63
+
64
+ ### `olympay logout`
65
+
66
+ Remove stored credentials.
67
+
68
+ ```bash
69
+ olympay logout
70
+ ```
71
+
72
+ ---
73
+
74
+ ### `olympay whoami`
75
+
76
+ Show the currently authenticated workspace API key (masked).
77
+
78
+ ```bash
79
+ olympay whoami
80
+ ```
81
+
82
+ ---
83
+
84
+ ### `olympay agent create`
85
+
86
+ Spawn a new AI agent. Returns the agent's ID and its dedicated API key.
87
+
88
+ ```bash
89
+ olympay agent create --name "billing-bot" --description "Handles recurring billing"
90
+ ```
91
+
92
+ | Flag | Required | Description |
93
+ |---|---|---|
94
+ | `--name` | Yes | Display name for the agent |
95
+ | `--description` | No | Optional description |
96
+
97
+ **Output:**
98
+ ```
99
+ Agent spawned successfully!
100
+ ────────────────────────────────────────────────
101
+ ID: agt_01j9k2...
102
+ Name: billing-bot
103
+ API Key: olympay_agt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
104
+ Status: active
105
+ ────────────────────────────────────────────────
106
+ Keep this API key safe - use it to authenticate transactions.
107
+ ```
108
+
109
+ ---
110
+
111
+ ### `olympay agent list`
112
+
113
+ List all agents in your workspace.
114
+
115
+ ```bash
116
+ olympay agent list
117
+ ```
118
+
119
+ ---
120
+
121
+ ### `olympay agent suspend`
122
+
123
+ Suspend an agent. All transactions from a suspended agent are immediately blocked.
124
+
125
+ ```bash
126
+ olympay agent suspend AGENT_ID
127
+ ```
128
+
129
+ ---
130
+
131
+ ### `olympay agent activate`
132
+
133
+ Re-activate a previously suspended agent.
134
+
135
+ ```bash
136
+ olympay agent activate AGENT_ID
137
+ ```
138
+
139
+ ---
140
+
141
+ ### `olympay account create`
142
+
143
+ Open a ledger account for an agent.
144
+
145
+ ```bash
146
+ olympay account create --agent AGENT_ID --name "main" --currency USD
147
+ ```
148
+
149
+ | Flag | Required | Description |
150
+ |---|---|---|
151
+ | `--agent` | Yes | Agent ID to associate the account with |
152
+ | `--name` | Yes | Account name |
153
+ | `--currency` | No | Currency code (default: `USD`) |
154
+
155
+ ---
156
+
157
+ ### `olympay account list`
158
+
159
+ List all accounts in your workspace, including balances.
160
+
161
+ ```bash
162
+ olympay account list
163
+ ```
164
+
165
+ ---
166
+
167
+ ### `olympay card issue`
168
+
169
+ Issue a virtual card linked to an agent account.
170
+
171
+ ```bash
172
+ olympay card issue --agent AGENT_ID --account ACCOUNT_ID --brand VISA
173
+ ```
174
+
175
+ | Flag | Required | Description |
176
+ |---|---|---|
177
+ | `--agent` | Yes | Agent ID |
178
+ | `--account` | Yes | Account ID to link the card to |
179
+ | `--brand` | No | Card brand (default: `VISA`) |
180
+ | `--last4` | No | Optional last 4 digits |
181
+
182
+ ---
183
+
184
+ ### `olympay card list`
185
+
186
+ List all virtual cards in your workspace.
187
+
188
+ ```bash
189
+ olympay card list
190
+ ```
191
+
192
+ ---
193
+
194
+ ### `olympay policy list`
195
+
196
+ List all spending policies in your workspace.
197
+
198
+ ```bash
199
+ olympay policy list
200
+ ```
201
+
202
+ ---
203
+
204
+ ### `olympay policy create`
205
+
206
+ Create a new spending policy.
207
+
208
+ ```bash
209
+ olympay policy create --name "daily-cap" --type SPEND_LIMIT --config '{"maxAmountMinor":10000}'
210
+ ```
211
+
212
+ | Flag | Required | Description |
213
+ |---|---|---|
214
+ | `--name` | Yes | Policy name |
215
+ | `--type` | Yes | Policy type (see [Policy Types](#policy-types)) |
216
+ | `--config` | No | JSON config object (depends on policy type) |
217
+ | `--description` | No | Optional description |
218
+
219
+ **Config by type:**
220
+
221
+ | Type | Config keys |
222
+ |---|---|
223
+ | `SPEND_LIMIT` | `maxAmountMinor` (integer, minor currency units) |
224
+ | `MERCHANT_ALLOWLIST` | `merchantIds` (array of strings) |
225
+ | `MERCHANT_BLOCKLIST` | `merchantIds` (array of strings) |
226
+ | `APPROVAL_REQUIRED` | No config needed |
227
+ | `TIME_WINDOW` | `startHour`, `endHour` (0-23, UTC) |
228
+
229
+ ---
230
+
231
+ ### `olympay policy assign`
232
+
233
+ Assign a policy to an agent, account, or card.
234
+
235
+ ```bash
236
+ olympay policy assign --policy POLICY_ID --target-type AGENT --target AGENT_ID
237
+ ```
238
+
239
+ | Flag | Required | Description |
240
+ |---|---|---|
241
+ | `--policy` | Yes | Policy ID |
242
+ | `--target-type` | Yes | `AGENT`, `ACCOUNT`, or `CARD` |
243
+ | `--target` | Yes | ID of the target entity |
244
+ | `--priority` | No | Evaluation priority (lower = higher, default: `100`) |
245
+
246
+ ---
247
+
248
+ ### `olympay tx eval`
249
+
250
+ Submit a transaction attempt for real-time policy evaluation. Returns the decision (`ALLOW`, `DENY`, or `REVIEW`) and the transaction record.
251
+
252
+ ```bash
253
+ olympay tx eval --agent AGENT_ID --account ACCOUNT_ID --amount 5000
254
+ ```
255
+
256
+ | Flag | Required | Description |
257
+ |---|---|---|
258
+ | `--agent` | Yes | Agent ID |
259
+ | `--account` | Yes | Account ID |
260
+ | `--amount` | Yes | Amount in minor units (e.g. `1000` = $10.00) |
261
+ | `--card` | No | Card ID |
262
+ | `--merchant` | No | Merchant identifier |
263
+ | `--currency` | No | Currency code (default: `USD`) |
264
+ | `--direction` | No | `DEBIT` or `CREDIT` (default: `DEBIT`) |
265
+
266
+ **Output:**
267
+ ```
268
+ Transaction DENY
269
+ ────────────────────────────────────────────────────────
270
+ Transaction ID: 3cafac02-d2b9-4b1f-a210-ce38a64a01d6
271
+ Amount: 200.00 USD
272
+ Decision: DENY
273
+ Reason: Denied by policy: SPEND_LIMIT
274
+ Policies: SPEND_LIMIT=DENY
275
+ ────────────────────────────────────────────────────────
276
+ ```
277
+
278
+ If a policy of type `APPROVAL_REQUIRED` matches, the decision is `REVIEW` and an approval request is created — visible in the dashboard.
279
+
280
+ ---
281
+
282
+ ### `olympay workspace generate-key`
283
+
284
+ Generate a new workspace API key. The full key value is shown only once.
285
+
286
+ ```bash
287
+ olympay workspace generate-key --name "CI pipeline"
288
+ ```
289
+
290
+ | Flag | Required | Description |
291
+ |---|---|---|
292
+ | `--name` | No | Label for the key (default: `CLI Key`) |
293
+
294
+ ---
295
+
296
+ ### `olympay workspace keys`
297
+
298
+ List all active (non-revoked) workspace API keys. Keys are shown in masked form.
299
+
300
+ ```bash
301
+ olympay workspace keys
302
+ ```
303
+
304
+ ---
305
+
306
+ ### `olympay workspace revoke`
307
+
308
+ Revoke a workspace API key by its ID. Revoked keys are immediately rejected by the API.
309
+
310
+ ```bash
311
+ olympay workspace revoke KEY_ID
312
+ ```
313
+
314
+ Use `olympay workspace keys` to find the key ID first.
315
+
316
+ ---
317
+
318
+ ## Full Workflow Example
319
+
320
+ ```bash
321
+ # 1. Authenticate
322
+ olympay login --key olympay_ws_...
323
+
324
+ # 2. Spawn an agent for handling SaaS payments
325
+ olympay agent create --name "saas-bot" --description "Manages SaaS subscriptions"
326
+ # Output: ID: <AGENT_ID>, API Key: olympay_agt_...
327
+
328
+ # 3. Open a USD account for the agent
329
+ olympay account create --agent <AGENT_ID> --name "saas-budget" --currency USD
330
+ # Output: ID: <ACCOUNT_ID>
331
+
332
+ # 4. Issue a virtual card
333
+ olympay card issue --agent <AGENT_ID> --account <ACCOUNT_ID> --brand VISA
334
+ # Output: ID: <CARD_ID>
335
+
336
+ # 5. Create a spending limit policy (blocks transactions above $100)
337
+ olympay policy create --name "100-cap" --type SPEND_LIMIT --config '{"maxAmountMinor":10000}'
338
+ # Output: ID: <POLICY_ID>
339
+
340
+ # 6. Assign the policy to the agent
341
+ olympay policy assign --policy <POLICY_ID> --target-type AGENT --target <AGENT_ID>
342
+
343
+ # 7. Evaluate a transaction attempt ($50 - should ALLOW)
344
+ olympay tx eval --agent <AGENT_ID> --account <ACCOUNT_ID> --amount 5000
345
+
346
+ # 8. Evaluate a transaction attempt ($200 - should DENY)
347
+ olympay tx eval --agent <AGENT_ID> --account <ACCOUNT_ID> --amount 20000
348
+
349
+ # 9. Emergency: suspend the agent
350
+ olympay agent suspend <AGENT_ID>
351
+
352
+ # 10. Resume after review
353
+ olympay agent activate <AGENT_ID>
354
+
355
+ # 11. List all resources
356
+ olympay agent list
357
+ olympay account list
358
+ olympay card list
359
+ olympay policy list
360
+ ```
361
+
362
+ ---
363
+
364
+ ## Configuration
365
+
366
+ Credentials are stored at `~/.olympay/config.json`:
367
+
368
+ ```json
369
+ {
370
+ "apiKey": "olympay_ws_...",
371
+ "apiBase": "https://api.olympay.tech/v1"
372
+ }
373
+ ```
374
+
375
+ To use a self-hosted instance, pass `--api` during login:
376
+
377
+ ```bash
378
+ olympay login --key olympay_ws_... --api https://your-api-domain.com/v1
379
+ ```
380
+
381
+ ---
382
+
383
+ ## Policy Types
384
+
385
+ Policies are managed in the dashboard. The following types are available:
386
+
387
+ | Type | Description |
388
+ |---|---|
389
+ | `SPEND_LIMIT` | Block transactions above a configured amount |
390
+ | `MERCHANT_ALLOWLIST` | Restrict spending to approved merchant IDs only |
391
+ | `MERCHANT_BLOCKLIST` | Block spending at specific merchant IDs |
392
+ | `APPROVAL_REQUIRED` | Route all transactions to human approval queue |
393
+ | `TIME_WINDOW` | Restrict spending to defined time ranges |
394
+
395
+ ---
396
+
397
+ ## Links
398
+
399
+ - **Dashboard**: [olympay.tech](https://olympay.tech)
400
+ - **API Base**: `https://api.olympay.tech/v1`
401
+ - **npm**: [npmjs.com/package/olympay](https://www.npmjs.com/package/olympay)
402
+
403
+ ---
404
+
405
+ ## License
406
+
407
+ MIT
package/dist/cli.js ADDED
@@ -0,0 +1,322 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { Command } from "commander";
5
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
6
+ import { homedir } from "os";
7
+ import { join } from "path";
8
+ var CONFIG_DIR = join(homedir(), ".olympay");
9
+ var CONFIG_FILE = join(CONFIG_DIR, "config.json");
10
+ var DEFAULT_API = "https://api.olympay.tech/v1";
11
+ var RESET = "\x1B[0m";
12
+ var GOLD = "\x1B[38;2;196;146;58m";
13
+ var DIM = "\x1B[2m";
14
+ var BOLD = "\x1B[1m";
15
+ var GREEN = "\x1B[32m";
16
+ var RED = "\x1B[31m";
17
+ var CYAN = "\x1B[36m";
18
+ var ASCII_BANNER = `
19
+ ${GOLD} \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557${RESET}
20
+ ${GOLD}\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D${RESET}
21
+ ${GOLD}\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2554\u255D ${RESET}
22
+ ${GOLD}\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2554\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u255A\u2588\u2588\u2554\u255D ${RESET}
23
+ ${GOLD}\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ${RESET}
24
+ ${GOLD} \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ${RESET}
25
+ ${DIM} Financial control for autonomous AI agents \u2022 olympay.tech${RESET}
26
+ `;
27
+ function loadConfig() {
28
+ if (!existsSync(CONFIG_FILE)) return null;
29
+ try {
30
+ return JSON.parse(readFileSync(CONFIG_FILE, "utf8"));
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+ function saveConfig(cfg) {
36
+ if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
37
+ writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2));
38
+ }
39
+ function requireConfig() {
40
+ const cfg = loadConfig();
41
+ if (!cfg?.apiKey) {
42
+ console.error(`${RED}Not logged in.${RESET} Run: ${GOLD}olympay login --key olympay_ws_...${RESET}`);
43
+ process.exit(1);
44
+ }
45
+ return cfg;
46
+ }
47
+ async function apiCall(method, path, body) {
48
+ const cfg = requireConfig();
49
+ const url = `${cfg.apiBase}${path}`;
50
+ const res = await fetch(url, {
51
+ method,
52
+ headers: {
53
+ "Content-Type": "application/json",
54
+ "Authorization": `Bearer ${cfg.apiKey}`
55
+ },
56
+ body: body ? JSON.stringify(body) : void 0
57
+ });
58
+ const json = await res.json();
59
+ if (!res.ok) {
60
+ const msg = json?.error?.message ?? `HTTP ${res.status}`;
61
+ console.error(`${RED}Error:${RESET} ${msg}`);
62
+ process.exit(1);
63
+ }
64
+ return json?.data ?? json;
65
+ }
66
+ var program = new Command();
67
+ program.name("olympay").description("Olympay CLI - spawn and manage AI agents with financial controls").version("0.1.0").addHelpText("before", ASCII_BANNER);
68
+ program.command("login").description("Authenticate with a workspace API key").requiredOption("--key <key>", "Workspace API key (olympay_ws_...)").option("--api <url>", "API base URL", DEFAULT_API).action((opts) => {
69
+ if (!opts.key.startsWith("olympay_ws_")) {
70
+ console.error(`${RED}Invalid key format.${RESET} Expected: ${GOLD}olympay_ws_...${RESET}`);
71
+ process.exit(1);
72
+ }
73
+ saveConfig({ apiKey: opts.key, apiBase: opts.api });
74
+ console.log(ASCII_BANNER);
75
+ console.log(`${GREEN}Logged in successfully.${RESET}`);
76
+ console.log(`${DIM}API base:${RESET} ${opts.api}`);
77
+ console.log(`${DIM}Config: ${RESET} ${CONFIG_FILE}`);
78
+ console.log(`
79
+ ${GOLD}Next steps:${RESET}`);
80
+ console.log(` ${CYAN}olympay agent create --name "my-bot"${RESET} Spawn your first agent`);
81
+ console.log(` ${CYAN}olympay agent list${RESET} List all agents`);
82
+ console.log(` ${CYAN}olympay account create --agent <id> --name "main"${RESET}`);
83
+ });
84
+ program.command("logout").description("Remove stored credentials").action(() => {
85
+ if (existsSync(CONFIG_FILE)) {
86
+ writeFileSync(CONFIG_FILE, "{}");
87
+ }
88
+ console.log(`${GREEN}Logged out.${RESET} Credentials removed from ${CONFIG_FILE}`);
89
+ });
90
+ program.command("whoami").description("Show current credentials").action(() => {
91
+ const cfg = loadConfig();
92
+ if (!cfg?.apiKey) {
93
+ console.log(`${DIM}Not logged in.${RESET}`);
94
+ } else {
95
+ const masked = cfg.apiKey.slice(0, 18) + "..." + cfg.apiKey.slice(-4);
96
+ console.log(`${GOLD}API key:${RESET} ${masked}`);
97
+ console.log(`${GOLD}API base:${RESET} ${cfg.apiBase}`);
98
+ }
99
+ });
100
+ var agentCmd = program.command("agent").description("Manage AI agents");
101
+ agentCmd.command("create").description("Spawn a new AI agent and receive its API key").requiredOption("--name <name>", "Agent name").option("--description <desc>", "Agent description").action(async (opts) => {
102
+ const agent = await apiCall("POST", "/agents", {
103
+ name: opts.name,
104
+ description: opts.description,
105
+ status: "active"
106
+ });
107
+ console.log(`
108
+ ${GREEN}Agent spawned successfully!${RESET}`);
109
+ console.log(`${DIM}${"\u2500".repeat(48)}${RESET}`);
110
+ console.log(`${GOLD}ID: ${RESET} ${agent.id}`);
111
+ console.log(`${GOLD}Name: ${RESET} ${agent.name}`);
112
+ console.log(`${GOLD}API Key: ${RESET} ${BOLD}${agent.apiKey}${RESET}`);
113
+ console.log(`${GOLD}Status: ${RESET} ${agent.status}`);
114
+ console.log(`${DIM}${"\u2500".repeat(48)}${RESET}`);
115
+ console.log(`${DIM}Keep this API key safe - use it to authenticate transactions.${RESET}`);
116
+ });
117
+ agentCmd.command("list").description("List all agents in your workspace").action(async () => {
118
+ const agents = await apiCall("GET", "/agents");
119
+ if (!agents.length) {
120
+ console.log(`${DIM}No agents found.${RESET} Create one with: ${GOLD}olympay agent create --name "..."${RESET}`);
121
+ return;
122
+ }
123
+ console.log(`
124
+ ${GOLD}${"ID".padEnd(38)} ${"NAME".padEnd(25)} ${"STATUS".padEnd(12)} API KEY${RESET}`);
125
+ console.log(`${DIM}${"\u2500".repeat(100)}${RESET}`);
126
+ for (const a of agents) {
127
+ const key = a.apiKey ? a.apiKey.slice(0, 20) + "..." : DIM + "-" + RESET;
128
+ const status = a.status === "active" ? `${GREEN}${a.status}${RESET}` : `${DIM}${a.status}${RESET}`;
129
+ console.log(`${a.id.padEnd(38)} ${a.name.padEnd(25)} ${a.status.padEnd(12)} ${key}`);
130
+ }
131
+ });
132
+ agentCmd.command("suspend <id>").description("Suspend an agent to block all transactions").action(async (id) => {
133
+ await apiCall("PATCH", `/agents/${id}/status`, { status: "suspended" });
134
+ console.log(`${GREEN}Agent ${id} suspended.${RESET}`);
135
+ });
136
+ agentCmd.command("activate <id>").description("Re-activate a suspended agent").action(async (id) => {
137
+ await apiCall("PATCH", `/agents/${id}/status`, { status: "active" });
138
+ console.log(`${GREEN}Agent ${id} activated.${RESET}`);
139
+ });
140
+ var accountCmd = program.command("account").description("Manage agent ledger accounts");
141
+ accountCmd.command("create").description("Open a ledger account for an agent").requiredOption("--agent <agentId>", "Agent ID").requiredOption("--name <name>", "Account name").option("--currency <currency>", "Currency code", "USD").action(async (opts) => {
142
+ const account = await apiCall("POST", "/accounts", {
143
+ agentId: opts.agent,
144
+ name: opts.name,
145
+ currency: opts.currency
146
+ });
147
+ console.log(`
148
+ ${GREEN}Account opened!${RESET}`);
149
+ console.log(`${GOLD}ID: ${RESET} ${account.id}`);
150
+ console.log(`${GOLD}Name: ${RESET} ${account.name}`);
151
+ console.log(`${GOLD}Currency: ${RESET} ${account.currency}`);
152
+ console.log(`${GOLD}Status: ${RESET} ${account.status}`);
153
+ });
154
+ accountCmd.command("list").description("List all accounts in your workspace").action(async () => {
155
+ const accounts = await apiCall("GET", "/accounts");
156
+ if (!accounts.length) {
157
+ console.log(`${DIM}No accounts found.${RESET}`);
158
+ return;
159
+ }
160
+ console.log(`
161
+ ${GOLD}${"ID".padEnd(38)} ${"NAME".padEnd(25)} ${"AGENT".padEnd(38)} BALANCE${RESET}`);
162
+ console.log(`${DIM}${"\u2500".repeat(110)}${RESET}`);
163
+ for (const a of accounts) {
164
+ const bal = `$${((a.balanceMinor ?? 0) / 100).toFixed(2)} ${a.currency}`;
165
+ console.log(`${a.id.padEnd(38)} ${a.name.padEnd(25)} ${a.agentId.padEnd(38)} ${bal}`);
166
+ }
167
+ });
168
+ var cardCmd = program.command("card").description("Manage virtual cards");
169
+ cardCmd.command("issue").description("Issue a virtual card linked to an agent account").requiredOption("--agent <agentId>", "Agent ID").requiredOption("--account <accountId>", "Account ID").option("--brand <brand>", "Card brand (e.g. VISA)", "VISA").option("--last4 <last4>", "Last 4 digits (optional)").action(async (opts) => {
170
+ const card = await apiCall("POST", "/cards", {
171
+ agentId: opts.agent,
172
+ accountId: opts.account,
173
+ brand: opts.brand,
174
+ last4: opts.last4
175
+ });
176
+ console.log(`
177
+ ${GREEN}Card issued!${RESET}`);
178
+ console.log(`${GOLD}ID: ${RESET} ${card.id}`);
179
+ console.log(`${GOLD}Brand: ${RESET} ${card.brand ?? "VISA"}`);
180
+ console.log(`${GOLD}Last4: ${RESET} ${card.last4 ?? DIM + "-" + RESET}`);
181
+ console.log(`${GOLD}Status: ${RESET} ${card.status}`);
182
+ });
183
+ cardCmd.command("list").description("List all virtual cards in your workspace").action(async () => {
184
+ const cards = await apiCall("GET", "/cards");
185
+ if (!cards.length) {
186
+ console.log(`${DIM}No cards found.${RESET}`);
187
+ return;
188
+ }
189
+ console.log(`
190
+ ${GOLD}${"ID".padEnd(38)} ${"BRAND".padEnd(8)} ${"LAST4".padEnd(6)} ${"STATUS".padEnd(12)} AGENT${RESET}`);
191
+ console.log(`${DIM}${"\u2500".repeat(90)}${RESET}`);
192
+ for (const c of cards) {
193
+ console.log(`${c.id.padEnd(38)} ${(c.brand ?? "-").padEnd(8)} ${(c.last4 ?? "-").padEnd(6)} ${c.status.padEnd(12)} ${c.agentId}`);
194
+ }
195
+ });
196
+ var policyCmd = program.command("policy").description("Manage spending policies");
197
+ policyCmd.command("list").description("List all spending policies in your workspace").action(async () => {
198
+ const policies = await apiCall("GET", "/policies");
199
+ if (!policies.length) {
200
+ console.log(`${DIM}No policies found.${RESET} Create one with: ${GOLD}olympay policy create --name "..." --type SPEND_LIMIT${RESET}`);
201
+ return;
202
+ }
203
+ console.log(`
204
+ ${GOLD}${"ID".padEnd(38)} ${"NAME".padEnd(30)} ${"TYPE".padEnd(25)} STATUS${RESET}`);
205
+ console.log(`${DIM}${"\u2500".repeat(100)}${RESET}`);
206
+ for (const p of policies) {
207
+ const status = p.status === "active" ? `${GREEN}${p.status}${RESET}` : `${DIM}${p.status}${RESET}`;
208
+ console.log(`${p.id.padEnd(38)} ${p.name.padEnd(30)} ${p.type.padEnd(25)} ${p.status}`);
209
+ }
210
+ });
211
+ policyCmd.command("create").description("Create a spending policy").requiredOption("--name <name>", "Policy name").requiredOption("--type <type>", "Policy type: SPEND_LIMIT | MERCHANT_ALLOWLIST | MERCHANT_BLOCKLIST | APPROVAL_REQUIRED | TIME_WINDOW").option("--config <json>", "Policy config as JSON string", "{}").option("--description <desc>", "Policy description").action(async (opts) => {
212
+ let configJson;
213
+ try {
214
+ configJson = JSON.parse(opts.config);
215
+ } catch {
216
+ console.error(`${RED}Error:${RESET} --config must be valid JSON (e.g. '{"maxAmountMinor":10000}')`);
217
+ process.exit(1);
218
+ }
219
+ const validTypes = ["SPEND_LIMIT", "MERCHANT_ALLOWLIST", "MERCHANT_BLOCKLIST", "APPROVAL_REQUIRED", "TIME_WINDOW"];
220
+ if (!validTypes.includes(opts.type)) {
221
+ console.error(`${RED}Error:${RESET} Invalid type. Must be one of: ${validTypes.join(", ")}`);
222
+ process.exit(1);
223
+ }
224
+ const policy = await apiCall("POST", "/policies", {
225
+ name: opts.name,
226
+ type: opts.type,
227
+ configJson,
228
+ description: opts.description
229
+ });
230
+ console.log(`
231
+ ${GREEN}Policy created!${RESET}`);
232
+ console.log(`${DIM}${"\u2500".repeat(56)}${RESET}`);
233
+ console.log(`${GOLD}ID: ${RESET} ${policy.id}`);
234
+ console.log(`${GOLD}Name: ${RESET} ${policy.name}`);
235
+ console.log(`${GOLD}Type: ${RESET} ${policy.type}`);
236
+ console.log(`${GOLD}Status: ${RESET} ${policy.status}`);
237
+ console.log(`${GOLD}Config: ${RESET} ${JSON.stringify(policy.configJson)}`);
238
+ console.log(`${DIM}${"\u2500".repeat(56)}${RESET}`);
239
+ });
240
+ policyCmd.command("assign").description("Assign a policy to an agent, account, or card").requiredOption("--policy <policyId>", "Policy ID").requiredOption("--target-type <type>", "Target type: AGENT | ACCOUNT | CARD").requiredOption("--target <targetId>", "Target entity ID").option("--priority <n>", "Priority (lower = higher priority)", "100").action(async (opts) => {
241
+ const validTargets = ["AGENT", "ACCOUNT", "CARD"];
242
+ if (!validTargets.includes(opts.targetType)) {
243
+ console.error(`${RED}Error:${RESET} --target-type must be one of: ${validTargets.join(", ")}`);
244
+ process.exit(1);
245
+ }
246
+ const assignment = await apiCall("POST", "/policy-assignments", {
247
+ policyId: opts.policy,
248
+ targetType: opts.targetType,
249
+ targetId: opts.target,
250
+ priority: parseInt(opts.priority, 10)
251
+ });
252
+ console.log(`
253
+ ${GREEN}Policy assigned!${RESET}`);
254
+ console.log(`${GOLD}Assignment ID: ${RESET} ${assignment.id}`);
255
+ console.log(`${GOLD}Policy: ${RESET} ${assignment.policyId}`);
256
+ console.log(`${GOLD}Target: ${RESET} ${assignment.targetType} ${assignment.targetId}`);
257
+ console.log(`${GOLD}Priority: ${RESET} ${assignment.priority}`);
258
+ });
259
+ var txCmd = program.command("tx").description("Evaluate and inspect transactions");
260
+ txCmd.command("eval").description("Submit a transaction attempt for policy evaluation").requiredOption("--agent <agentId>", "Agent ID").requiredOption("--account <accountId>", "Account ID").requiredOption("--amount <minor>", "Amount in minor currency units (e.g. 1000 = $10.00)").option("--card <cardId>", "Card ID (optional)").option("--merchant <merchantId>", "Merchant identifier (optional)").option("--currency <currency>", "Currency code", "USD").option("--direction <direction>", "DEBIT or CREDIT", "DEBIT").action(async (opts) => {
261
+ const amountMinor = parseInt(opts.amount, 10);
262
+ if (isNaN(amountMinor) || amountMinor <= 0) {
263
+ console.error(`${RED}Error:${RESET} --amount must be a positive integer (minor units)`);
264
+ process.exit(1);
265
+ }
266
+ const result = await apiCall("POST", "/transactions/attempt", {
267
+ agentId: opts.agent,
268
+ accountId: opts.account,
269
+ cardId: opts.card,
270
+ merchantId: opts.merchant,
271
+ amountMinor,
272
+ currency: opts.currency,
273
+ direction: opts.direction
274
+ });
275
+ const { transaction, decision, approvalRequest } = result;
276
+ const decColor = decision.result === "ALLOW" ? GREEN : decision.result === "DENY" ? RED : GOLD;
277
+ console.log(`
278
+ ${decColor}Transaction ${decision.result}${RESET}`);
279
+ console.log(`${DIM}${"\u2500".repeat(56)}${RESET}`);
280
+ console.log(`${GOLD}Transaction ID: ${RESET} ${transaction.id}`);
281
+ console.log(`${GOLD}Amount: ${RESET} ${(amountMinor / 100).toFixed(2)} ${opts.currency}`);
282
+ console.log(`${GOLD}Decision: ${RESET} ${decColor}${decision.result}${RESET}`);
283
+ console.log(`${GOLD}Reason: ${RESET} ${decision.reason}`);
284
+ if (decision.matchedPolicies?.length) {
285
+ console.log(`${GOLD}Policies: ${RESET} ${decision.matchedPolicies.map((p) => `${p.policyType}=${p.outcome}`).join(", ")}`);
286
+ }
287
+ if (approvalRequest) {
288
+ console.log(`${GOLD}Approval ID: ${RESET} ${approvalRequest.id}`);
289
+ console.log(`${DIM}Human approval required - review in dashboard${RESET}`);
290
+ }
291
+ console.log(`${DIM}${"\u2500".repeat(56)}${RESET}`);
292
+ });
293
+ var wsCmd = program.command("workspace").description("Manage workspace settings and API keys");
294
+ wsCmd.command("keys").description("List active workspace API keys").action(async () => {
295
+ const keys = await apiCall("GET", "/workspace/keys");
296
+ if (!keys.length) {
297
+ console.log(`${DIM}No keys found.${RESET} Generate one with: ${GOLD}olympay workspace generate-key${RESET}`);
298
+ return;
299
+ }
300
+ console.log(`
301
+ ${GOLD}${"ID".padEnd(38)} ${"NAME".padEnd(20)} KEY${RESET}`);
302
+ console.log(`${DIM}${"\u2500".repeat(100)}${RESET}`);
303
+ for (const k of keys) {
304
+ console.log(`${k.id.padEnd(38)} ${k.name.padEnd(20)} ${k.key}`);
305
+ }
306
+ });
307
+ wsCmd.command("generate-key").description("Generate a new workspace API key").option("--name <name>", "Key label", "CLI Key").action(async (opts) => {
308
+ const key = await apiCall("POST", "/workspace/keys", { name: opts.name });
309
+ console.log(`
310
+ ${GREEN}New workspace API key generated!${RESET}`);
311
+ console.log(`${DIM}${"\u2500".repeat(56)}${RESET}`);
312
+ console.log(`${GOLD}ID: ${RESET} ${key.id}`);
313
+ console.log(`${GOLD}Name: ${RESET} ${key.name}`);
314
+ console.log(`${GOLD}Key: ${RESET} ${BOLD}${key.key}${RESET}`);
315
+ console.log(`${DIM}${"\u2500".repeat(56)}${RESET}`);
316
+ console.log(`${DIM}Use with:${RESET} ${GOLD}olympay login --key ${key.key}${RESET}`);
317
+ });
318
+ wsCmd.command("revoke <id>").description("Revoke a workspace API key by ID").action(async (id) => {
319
+ await apiCall("DELETE", `/workspace/keys/${id}`);
320
+ console.log(`${GREEN}Key ${id} revoked successfully.${RESET}`);
321
+ });
322
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "olympay",
3
+ "version": "0.1.0",
4
+ "description": "CLI for Olympay - financial control for autonomous AI agents",
5
+ "keywords": [
6
+ "olympay",
7
+ "ai",
8
+ "agents",
9
+ "fintech",
10
+ "cli"
11
+ ],
12
+ "license": "MIT",
13
+ "type": "module",
14
+ "bin": {
15
+ "olympay": "dist/cli.js"
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "scripts": {
21
+ "build": "node build.mjs",
22
+ "olympay": "tsx ./src/cli.ts",
23
+ "typecheck": "tsc -p tsconfig.json --noEmit"
24
+ },
25
+ "dependencies": {
26
+ "commander": "^14.0.3"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "catalog:",
30
+ "esbuild": "^0.25.5",
31
+ "tsx": "catalog:"
32
+ }
33
+ }