codecard-cli 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.
- package/LICENSE +21 -0
- package/README.md +94 -0
- package/dist/commands/alerts.d.ts +2 -0
- package/dist/commands/alerts.js +44 -0
- package/dist/commands/auth/login.d.ts +2 -0
- package/dist/commands/auth/login.js +43 -0
- package/dist/commands/auth/logout.d.ts +2 -0
- package/dist/commands/auth/logout.js +20 -0
- package/dist/commands/auth/whoami.d.ts +2 -0
- package/dist/commands/auth/whoami.js +32 -0
- package/dist/commands/balance.d.ts +2 -0
- package/dist/commands/balance.js +35 -0
- package/dist/commands/card/freeze.d.ts +3 -0
- package/dist/commands/card/freeze.js +68 -0
- package/dist/commands/card/list.d.ts +2 -0
- package/dist/commands/card/list.js +44 -0
- package/dist/commands/pnl.d.ts +2 -0
- package/dist/commands/pnl.js +57 -0
- package/dist/commands/project/create.d.ts +2 -0
- package/dist/commands/project/create.js +36 -0
- package/dist/commands/project/list.d.ts +2 -0
- package/dist/commands/project/list.js +42 -0
- package/dist/commands/rewards.d.ts +2 -0
- package/dist/commands/rewards.js +61 -0
- package/dist/commands/spend.d.ts +2 -0
- package/dist/commands/spend.js +116 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +52 -0
- package/dist/lib/api-client.d.ts +23 -0
- package/dist/lib/api-client.js +113 -0
- package/dist/lib/config.d.ts +16 -0
- package/dist/lib/config.js +40 -0
- package/dist/lib/output.d.ts +20 -0
- package/dist/lib/output.js +69 -0
- package/dist/lib/update-check.d.ts +1 -0
- package/dist/lib/update-check.js +74 -0
- package/dist/mcp-server.d.ts +2 -0
- package/dist/mcp-server.js +133 -0
- package/dist/shared/alert.d.ts +24 -0
- package/dist/shared/alert.js +2 -0
- package/dist/shared/alerts.d.ts +3 -0
- package/dist/shared/alerts.js +103 -0
- package/dist/shared/card.d.ts +12 -0
- package/dist/shared/card.js +2 -0
- package/dist/shared/cards.d.ts +2 -0
- package/dist/shared/cards.js +60 -0
- package/dist/shared/common.d.ts +21 -0
- package/dist/shared/common.js +2 -0
- package/dist/shared/filters.d.ts +8 -0
- package/dist/shared/filters.js +59 -0
- package/dist/shared/formatters.d.ts +7 -0
- package/dist/shared/formatters.js +44 -0
- package/dist/shared/index.d.ts +15 -0
- package/dist/shared/index.js +43 -0
- package/dist/shared/project.d.ts +24 -0
- package/dist/shared/project.js +2 -0
- package/dist/shared/projects.d.ts +3 -0
- package/dist/shared/projects.js +104 -0
- package/dist/shared/reward.d.ts +26 -0
- package/dist/shared/reward.js +2 -0
- package/dist/shared/rewards.d.ts +4 -0
- package/dist/shared/rewards.js +25 -0
- package/dist/shared/transaction.d.ts +36 -0
- package/dist/shared/transaction.js +2 -0
- package/dist/shared/transactions.d.ts +2 -0
- package/dist/shared/transactions.js +34 -0
- package/dist/shared/user.d.ts +12 -0
- package/dist/shared/user.js +2 -0
- package/dist/shared/users.d.ts +3 -0
- package/dist/shared/users.js +15 -0
- package/package.json +41 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
5
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6
|
+
const zod_1 = require("zod");
|
|
7
|
+
const shared_1 = require("./shared");
|
|
8
|
+
// Mutable state
|
|
9
|
+
const projects = [...shared_1.mockProjects];
|
|
10
|
+
const cards = shared_1.mockCards.map((c) => ({ ...c }));
|
|
11
|
+
const server = new mcp_js_1.McpServer({
|
|
12
|
+
name: "codecard",
|
|
13
|
+
version: "0.1.0",
|
|
14
|
+
});
|
|
15
|
+
// ─── Tools ──────────────────────────────────────
|
|
16
|
+
server.tool("list_projects", "List all projects with their status and budget", {}, async () => {
|
|
17
|
+
const list = projects.map((p) => `${p.name} (${p.status}) — ${(0, shared_1.currency)(p.monthlyBudget)}/mo`).join("\n");
|
|
18
|
+
return { content: [{ type: "text", text: `Projects (${projects.length}):\n${list}` }] };
|
|
19
|
+
});
|
|
20
|
+
server.tool("create_project", "Create a new project with a virtual card", { name: zod_1.z.string().describe("Project name (kebab-case)"), budget: zod_1.z.number().optional().describe("Monthly budget in dollars (default: 2000)") }, async ({ name, budget }) => {
|
|
21
|
+
const id = `proj_${String(projects.length + 1).padStart(3, "0")}`;
|
|
22
|
+
const cardId = `card_${String(cards.length + 1).padStart(3, "0")}`;
|
|
23
|
+
const last4 = String(Math.floor(1000 + Math.random() * 9000));
|
|
24
|
+
const colors = ["#b8f036", "#5b8cff", "#a07cff", "#3ee8c8", "#ff5f8f"];
|
|
25
|
+
const b = budget || 2000;
|
|
26
|
+
const project = { id, name, description: "", status: "active", color: colors[projects.length % colors.length], cardId, monthlyBudget: b, createdAt: new Date().toISOString() };
|
|
27
|
+
projects.push(project);
|
|
28
|
+
cards.push({ id: cardId, projectId: id, last4, cardHolder: "AGENT", status: "active", dailyLimit: Math.round(b / 10), monthlyLimit: b, currentMonthSpend: 0, createdAt: new Date().toISOString() });
|
|
29
|
+
return { content: [{ type: "text", text: `✓ Project "${name}" created\n✓ Card •••• ${last4} assigned\n✓ Budget: ${(0, shared_1.currency)(b)}/mo` }] };
|
|
30
|
+
});
|
|
31
|
+
server.tool("get_project_pnl", "Get profit & loss for a specific project", { project: zod_1.z.string().describe("Project name") }, async ({ project: projectName }) => {
|
|
32
|
+
const proj = projects.find((p) => p.name === projectName);
|
|
33
|
+
if (!proj)
|
|
34
|
+
return { content: [{ type: "text", text: `Project "${projectName}" not found` }], isError: true };
|
|
35
|
+
const pnl = shared_1.mockProjectPnls.find((p) => p.projectId === proj.id);
|
|
36
|
+
if (!pnl)
|
|
37
|
+
return { content: [{ type: "text", text: `No P&L data for "${projectName}"` }] };
|
|
38
|
+
const providers = pnl.topProviders.map((p) => ` ${p.name}: ${(0, shared_1.currency)(p.amount)}`).join("\n");
|
|
39
|
+
return {
|
|
40
|
+
content: [{
|
|
41
|
+
type: "text",
|
|
42
|
+
text: `${pnl.projectName} — ${pnl.period}\n\nSpend: ${(0, shared_1.currency)(pnl.spend)}\nRevenue: ${(0, shared_1.currency)(pnl.revenue)}\nNet: ${pnl.net >= 0 ? "+" : ""}${(0, shared_1.currency)(pnl.net)}\nMargin: ${pnl.margin.toFixed(1)}%\n\nTop Providers:\n${providers}`,
|
|
43
|
+
}],
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
server.tool("get_spend", "Get spend breakdown by category, provider, and project", { project: zod_1.z.string().optional().describe("Filter by project name") }, async ({ project: projectName }) => {
|
|
47
|
+
let txns = shared_1.mockTransactions;
|
|
48
|
+
if (projectName) {
|
|
49
|
+
const proj = projects.find((p) => p.name === projectName);
|
|
50
|
+
if (!proj)
|
|
51
|
+
return { content: [{ type: "text", text: `Project "${projectName}" not found` }], isError: true };
|
|
52
|
+
txns = txns.filter((t) => t.projectId === proj.id);
|
|
53
|
+
}
|
|
54
|
+
const breakdown = (0, shared_1.buildSpendBreakdown)(txns, projects);
|
|
55
|
+
const cats = breakdown.byCategory.map((c) => ` ${c.category}: ${(0, shared_1.currency)(c.amount)} (${c.percentage.toFixed(1)}%)`).join("\n");
|
|
56
|
+
const provs = breakdown.byProvider.slice(0, 8).map((p) => ` ${p.provider}: ${(0, shared_1.currency)(p.amount)} (${p.percentage.toFixed(1)}%)`).join("\n");
|
|
57
|
+
return {
|
|
58
|
+
content: [{
|
|
59
|
+
type: "text",
|
|
60
|
+
text: `Spend ${projectName ? `— ${projectName}` : "Overview"}\n\nTotal: ${(0, shared_1.currency)(breakdown.totalSpend)}\n\nBy Category:\n${cats}\n\nBy Provider:\n${provs}`,
|
|
61
|
+
}],
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
server.tool("get_balance", "Check account balance and credit", {}, async () => {
|
|
65
|
+
return {
|
|
66
|
+
content: [{
|
|
67
|
+
type: "text",
|
|
68
|
+
text: `Account Balance\n\nAvailable: ${(0, shared_1.currency)(12400)}\nPending: ${(0, shared_1.currency)(340)}\nCredit Limit: ${(0, shared_1.currency)(25000)}\nCredit Used: ${(0, shared_1.currency)(7890)}\nCredit Free: ${(0, shared_1.currency)(17110)}`,
|
|
69
|
+
}],
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
server.tool("list_cards", "List all virtual cards with status and spend", {}, async () => {
|
|
73
|
+
const list = cards.map((c) => {
|
|
74
|
+
const proj = projects.find((p) => p.id === c.projectId);
|
|
75
|
+
return `•••• ${c.last4} | ${proj?.name || "personal"} | ${c.status} | ${(0, shared_1.currency)(c.currentMonthSpend)} / ${(0, shared_1.currency)(c.monthlyLimit)}`;
|
|
76
|
+
}).join("\n");
|
|
77
|
+
const active = cards.filter((c) => c.status === "active").length;
|
|
78
|
+
const frozen = cards.filter((c) => c.status === "frozen").length;
|
|
79
|
+
return { content: [{ type: "text", text: `Cards (${active} active, ${frozen} frozen):\n${list}` }] };
|
|
80
|
+
});
|
|
81
|
+
server.tool("freeze_card", "Freeze a virtual card to block all transactions", { card_id: zod_1.z.string().describe("Card ID (e.g. card_001)") }, async ({ card_id }) => {
|
|
82
|
+
const card = cards.find((c) => c.id === card_id);
|
|
83
|
+
if (!card)
|
|
84
|
+
return { content: [{ type: "text", text: `Card ${card_id} not found` }], isError: true };
|
|
85
|
+
card.status = "frozen";
|
|
86
|
+
return { content: [{ type: "text", text: `✓ Card •••• ${card.last4} frozen` }] };
|
|
87
|
+
});
|
|
88
|
+
server.tool("unfreeze_card", "Unfreeze a virtual card to re-enable transactions", { card_id: zod_1.z.string().describe("Card ID (e.g. card_001)") }, async ({ card_id }) => {
|
|
89
|
+
const card = cards.find((c) => c.id === card_id);
|
|
90
|
+
if (!card)
|
|
91
|
+
return { content: [{ type: "text", text: `Card ${card_id} not found` }], isError: true };
|
|
92
|
+
card.status = "active";
|
|
93
|
+
return { content: [{ type: "text", text: `✓ Card •••• ${card.last4} unfrozen` }] };
|
|
94
|
+
});
|
|
95
|
+
server.tool("get_rewards", "View Code Credits balance and reward tiers", {}, async () => {
|
|
96
|
+
const bal = shared_1.mockRewardBalance;
|
|
97
|
+
const tiers = shared_1.mockRewardTiers.map((t) => ` ${t.rate}% — ${t.category}: ${(0, shared_1.currency)(t.earned)} earned on ${(0, shared_1.currency)(t.spend)}`).join("\n");
|
|
98
|
+
return {
|
|
99
|
+
content: [{
|
|
100
|
+
type: "text",
|
|
101
|
+
text: `Code Credits\n\nAvailable: ${(0, shared_1.currency)(bal.totalCredits)}\nPending: ${(0, shared_1.currency)(bal.pendingCredits)}\nLifetime: ${(0, shared_1.currency)(bal.lifetimeEarned)} earned, ${(0, shared_1.currency)(bal.lifetimeRedeemed)} redeemed\n\nReward Tiers:\n${tiers}`,
|
|
102
|
+
}],
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
server.tool("get_alerts", "View spend alerts and anomalies", {}, async () => {
|
|
106
|
+
const unread = shared_1.mockAlertEvents.filter((e) => !e.acknowledged).length;
|
|
107
|
+
const list = shared_1.mockAlertEvents.map((e) => {
|
|
108
|
+
const badge = e.acknowledged ? "○" : "●";
|
|
109
|
+
return `${badge} [${e.severity.toUpperCase()}] ${e.title}\n ${e.projectName} — ${e.message}`;
|
|
110
|
+
}).join("\n\n");
|
|
111
|
+
return { content: [{ type: "text", text: `Alerts (${unread} unread):\n\n${list}` }] };
|
|
112
|
+
});
|
|
113
|
+
// ─── Resources ──────────────────────────────────
|
|
114
|
+
server.resource("projects", "codecard://projects", async () => ({
|
|
115
|
+
contents: [{
|
|
116
|
+
uri: "codecard://projects",
|
|
117
|
+
mimeType: "application/json",
|
|
118
|
+
text: JSON.stringify(projects, null, 2),
|
|
119
|
+
}],
|
|
120
|
+
}));
|
|
121
|
+
server.resource("cards", "codecard://cards", async () => ({
|
|
122
|
+
contents: [{
|
|
123
|
+
uri: "codecard://cards",
|
|
124
|
+
mimeType: "application/json",
|
|
125
|
+
text: JSON.stringify(cards, null, 2),
|
|
126
|
+
}],
|
|
127
|
+
}));
|
|
128
|
+
// ─── Start ──────────────────────────────────────
|
|
129
|
+
async function main() {
|
|
130
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
131
|
+
await server.connect(transport);
|
|
132
|
+
}
|
|
133
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type AlertType = "spend-spike" | "budget-threshold" | "projected-overrun" | "new-merchant";
|
|
2
|
+
export type AlertSeverity = "critical" | "warning" | "info";
|
|
3
|
+
export type AlertChannel = "cli" | "email" | "webhook";
|
|
4
|
+
export interface AlertRule {
|
|
5
|
+
id: string;
|
|
6
|
+
type: AlertType;
|
|
7
|
+
projectId: string | null;
|
|
8
|
+
threshold: number;
|
|
9
|
+
autoFreeze: boolean;
|
|
10
|
+
channels: AlertChannel[];
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface AlertEvent {
|
|
14
|
+
id: string;
|
|
15
|
+
ruleId: string;
|
|
16
|
+
type: AlertType;
|
|
17
|
+
severity: AlertSeverity;
|
|
18
|
+
projectId: string;
|
|
19
|
+
projectName: string;
|
|
20
|
+
title: string;
|
|
21
|
+
message: string;
|
|
22
|
+
triggeredAt: string;
|
|
23
|
+
acknowledged: boolean;
|
|
24
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mockAlertEvents = exports.mockAlertRules = void 0;
|
|
4
|
+
exports.mockAlertRules = [
|
|
5
|
+
{
|
|
6
|
+
id: "rule_001",
|
|
7
|
+
type: "spend-spike",
|
|
8
|
+
projectId: null,
|
|
9
|
+
threshold: 3,
|
|
10
|
+
autoFreeze: false,
|
|
11
|
+
channels: ["cli", "email"],
|
|
12
|
+
enabled: true,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: "rule_002",
|
|
16
|
+
type: "budget-threshold",
|
|
17
|
+
projectId: null,
|
|
18
|
+
threshold: 80,
|
|
19
|
+
autoFreeze: false,
|
|
20
|
+
channels: ["cli"],
|
|
21
|
+
enabled: true,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "rule_003",
|
|
25
|
+
type: "projected-overrun",
|
|
26
|
+
projectId: "proj_003",
|
|
27
|
+
threshold: 100,
|
|
28
|
+
autoFreeze: true,
|
|
29
|
+
channels: ["cli", "email", "webhook"],
|
|
30
|
+
enabled: true,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: "rule_004",
|
|
34
|
+
type: "new-merchant",
|
|
35
|
+
projectId: null,
|
|
36
|
+
threshold: 0,
|
|
37
|
+
autoFreeze: false,
|
|
38
|
+
channels: ["cli"],
|
|
39
|
+
enabled: true,
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
exports.mockAlertEvents = [
|
|
43
|
+
{
|
|
44
|
+
id: "alert_001",
|
|
45
|
+
ruleId: "rule_001",
|
|
46
|
+
type: "spend-spike",
|
|
47
|
+
severity: "critical",
|
|
48
|
+
projectId: "proj_003",
|
|
49
|
+
projectName: "client-dashco",
|
|
50
|
+
title: "Spend spike detected",
|
|
51
|
+
message: "OpenAI spend jumped 340% vs. 7-day average on client-dashco. Daily spend: $480 (avg: $109).",
|
|
52
|
+
triggeredAt: "2026-03-27T14:30:00Z",
|
|
53
|
+
acknowledged: false,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "alert_002",
|
|
57
|
+
ruleId: "rule_002",
|
|
58
|
+
type: "budget-threshold",
|
|
59
|
+
severity: "warning",
|
|
60
|
+
projectId: "proj_003",
|
|
61
|
+
projectName: "client-dashco",
|
|
62
|
+
title: "Budget 80% reached",
|
|
63
|
+
message: "client-dashco has spent $4,100 of $8,000 monthly budget (51.3%). At current rate, budget will be exhausted by March 28.",
|
|
64
|
+
triggeredAt: "2026-03-25T09:00:00Z",
|
|
65
|
+
acknowledged: true,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: "alert_003",
|
|
69
|
+
ruleId: "rule_003",
|
|
70
|
+
type: "projected-overrun",
|
|
71
|
+
severity: "warning",
|
|
72
|
+
projectId: "proj_003",
|
|
73
|
+
projectName: "client-dashco",
|
|
74
|
+
title: "Projected budget overrun",
|
|
75
|
+
message: "At current burn rate, client-dashco will exceed its $8,000 budget by ~$1,200 before month end.",
|
|
76
|
+
triggeredAt: "2026-03-24T08:00:00Z",
|
|
77
|
+
acknowledged: true,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: "alert_004",
|
|
81
|
+
ruleId: "rule_004",
|
|
82
|
+
type: "new-merchant",
|
|
83
|
+
severity: "info",
|
|
84
|
+
projectId: "proj_004",
|
|
85
|
+
projectName: "experiment-rag",
|
|
86
|
+
title: "New merchant detected",
|
|
87
|
+
message: "First charge from Replicate on experiment-rag card •••• 9012: $160.00.",
|
|
88
|
+
triggeredAt: "2026-03-22T09:15:00Z",
|
|
89
|
+
acknowledged: true,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "alert_005",
|
|
93
|
+
ruleId: "rule_001",
|
|
94
|
+
type: "spend-spike",
|
|
95
|
+
severity: "critical",
|
|
96
|
+
projectId: "proj_001",
|
|
97
|
+
projectName: "ai-tutor-app",
|
|
98
|
+
title: "Spend spike detected",
|
|
99
|
+
message: "Anthropic spend jumped 580% on ai-tutor-app. Possible prompt loop. Card •••• 4096 auto-paused.",
|
|
100
|
+
triggeredAt: "2026-03-15T06:45:00Z",
|
|
101
|
+
acknowledged: true,
|
|
102
|
+
},
|
|
103
|
+
];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type CardStatus = "active" | "frozen" | "deactivated";
|
|
2
|
+
export interface VirtualCard {
|
|
3
|
+
id: string;
|
|
4
|
+
projectId: string | null;
|
|
5
|
+
last4: string;
|
|
6
|
+
cardHolder: string;
|
|
7
|
+
status: CardStatus;
|
|
8
|
+
dailyLimit: number;
|
|
9
|
+
monthlyLimit: number;
|
|
10
|
+
currentMonthSpend: number;
|
|
11
|
+
createdAt: string;
|
|
12
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mockCards = void 0;
|
|
4
|
+
exports.mockCards = [
|
|
5
|
+
{
|
|
6
|
+
id: "card_001",
|
|
7
|
+
projectId: "proj_001",
|
|
8
|
+
last4: "4096",
|
|
9
|
+
cardHolder: "SARAH CHEN",
|
|
10
|
+
status: "active",
|
|
11
|
+
dailyLimit: 500,
|
|
12
|
+
monthlyLimit: 5000,
|
|
13
|
+
currentMonthSpend: 2340,
|
|
14
|
+
createdAt: "2026-01-10T08:00:00Z",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: "card_002",
|
|
18
|
+
projectId: "proj_002",
|
|
19
|
+
last4: "7281",
|
|
20
|
+
cardHolder: "SARAH CHEN",
|
|
21
|
+
status: "active",
|
|
22
|
+
dailyLimit: 200,
|
|
23
|
+
monthlyLimit: 2000,
|
|
24
|
+
currentMonthSpend: 890,
|
|
25
|
+
createdAt: "2026-02-01T12:00:00Z",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "card_003",
|
|
29
|
+
projectId: "proj_003",
|
|
30
|
+
last4: "3344",
|
|
31
|
+
cardHolder: "SARAH CHEN",
|
|
32
|
+
status: "active",
|
|
33
|
+
dailyLimit: 1000,
|
|
34
|
+
monthlyLimit: 8000,
|
|
35
|
+
currentMonthSpend: 4100,
|
|
36
|
+
createdAt: "2026-02-15T09:00:00Z",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "card_004",
|
|
40
|
+
projectId: "proj_004",
|
|
41
|
+
last4: "9012",
|
|
42
|
+
cardHolder: "SARAH CHEN",
|
|
43
|
+
status: "active",
|
|
44
|
+
dailyLimit: 100,
|
|
45
|
+
monthlyLimit: 1000,
|
|
46
|
+
currentMonthSpend: 560,
|
|
47
|
+
createdAt: "2026-03-01T14:00:00Z",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "card_005",
|
|
51
|
+
projectId: null,
|
|
52
|
+
last4: "5555",
|
|
53
|
+
cardHolder: "SARAH CHEN",
|
|
54
|
+
status: "frozen",
|
|
55
|
+
dailyLimit: 300,
|
|
56
|
+
monthlyLimit: 3000,
|
|
57
|
+
currentMonthSpend: 0,
|
|
58
|
+
createdAt: "2026-01-05T10:00:00Z",
|
|
59
|
+
},
|
|
60
|
+
];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface ApiResponse<T> {
|
|
2
|
+
success: boolean;
|
|
3
|
+
data: T;
|
|
4
|
+
error?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface Pagination {
|
|
7
|
+
page: number;
|
|
8
|
+
perPage: number;
|
|
9
|
+
total: number;
|
|
10
|
+
totalPages: number;
|
|
11
|
+
}
|
|
12
|
+
export interface DateRange {
|
|
13
|
+
from: string;
|
|
14
|
+
to: string;
|
|
15
|
+
}
|
|
16
|
+
export interface AccountBalance {
|
|
17
|
+
available: number;
|
|
18
|
+
pending: number;
|
|
19
|
+
creditLimit: number;
|
|
20
|
+
creditUsed: number;
|
|
21
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Transaction, SpendBreakdown } from "./transaction";
|
|
2
|
+
export declare function filterByProject(transactions: Transaction[], projectId: string): Transaction[];
|
|
3
|
+
export declare function filterByDateRange(transactions: Transaction[], from: string, to: string): Transaction[];
|
|
4
|
+
export declare function filterByProvider(transactions: Transaction[], provider: string): Transaction[];
|
|
5
|
+
export declare function buildSpendBreakdown(transactions: Transaction[], projects: {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
}[]): SpendBreakdown;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.filterByProject = filterByProject;
|
|
4
|
+
exports.filterByDateRange = filterByDateRange;
|
|
5
|
+
exports.filterByProvider = filterByProvider;
|
|
6
|
+
exports.buildSpendBreakdown = buildSpendBreakdown;
|
|
7
|
+
function filterByProject(transactions, projectId) {
|
|
8
|
+
return transactions.filter((t) => t.projectId === projectId);
|
|
9
|
+
}
|
|
10
|
+
function filterByDateRange(transactions, from, to) {
|
|
11
|
+
const fromDate = new Date(from).getTime();
|
|
12
|
+
const toDate = new Date(to).getTime();
|
|
13
|
+
return transactions.filter((t) => {
|
|
14
|
+
const d = new Date(t.date).getTime();
|
|
15
|
+
return d >= fromDate && d <= toDate;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function filterByProvider(transactions, provider) {
|
|
19
|
+
return transactions.filter((t) => t.provider === provider);
|
|
20
|
+
}
|
|
21
|
+
function buildSpendBreakdown(transactions, projects) {
|
|
22
|
+
const totalSpend = transactions.reduce((sum, t) => sum + t.amount, 0);
|
|
23
|
+
// By category
|
|
24
|
+
const catMap = new Map();
|
|
25
|
+
for (const t of transactions) {
|
|
26
|
+
catMap.set(t.category, (catMap.get(t.category) || 0) + t.amount);
|
|
27
|
+
}
|
|
28
|
+
const byCategory = Array.from(catMap.entries())
|
|
29
|
+
.map(([category, amount]) => ({ category, amount, percentage: totalSpend ? (amount / totalSpend) * 100 : 0 }))
|
|
30
|
+
.sort((a, b) => b.amount - a.amount);
|
|
31
|
+
// By provider
|
|
32
|
+
const provMap = new Map();
|
|
33
|
+
for (const t of transactions) {
|
|
34
|
+
provMap.set(t.provider, (provMap.get(t.provider) || 0) + t.amount);
|
|
35
|
+
}
|
|
36
|
+
const byProvider = Array.from(provMap.entries())
|
|
37
|
+
.map(([provider, amount]) => ({ provider, amount, percentage: totalSpend ? (amount / totalSpend) * 100 : 0 }))
|
|
38
|
+
.sort((a, b) => b.amount - a.amount);
|
|
39
|
+
// By project
|
|
40
|
+
const projMap = new Map();
|
|
41
|
+
for (const t of transactions) {
|
|
42
|
+
projMap.set(t.projectId, (projMap.get(t.projectId) || 0) + t.amount);
|
|
43
|
+
}
|
|
44
|
+
const byProject = Array.from(projMap.entries()).map(([projectId, amount]) => ({
|
|
45
|
+
projectId,
|
|
46
|
+
projectName: projects.find((p) => p.id === projectId)?.name || projectId,
|
|
47
|
+
amount,
|
|
48
|
+
})).sort((a, b) => b.amount - a.amount);
|
|
49
|
+
// Daily trend
|
|
50
|
+
const dayMap = new Map();
|
|
51
|
+
for (const t of transactions) {
|
|
52
|
+
const day = t.date.split("T")[0];
|
|
53
|
+
dayMap.set(day, (dayMap.get(day) || 0) + t.amount);
|
|
54
|
+
}
|
|
55
|
+
const dailyTrend = Array.from(dayMap.entries())
|
|
56
|
+
.map(([date, amount]) => ({ date, amount }))
|
|
57
|
+
.sort((a, b) => a.date.localeCompare(b.date));
|
|
58
|
+
return { totalSpend, byCategory, byProvider, byProject, dailyTrend };
|
|
59
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function currency(amount: number): string;
|
|
2
|
+
export declare function currencyCompact(amount: number): string;
|
|
3
|
+
export declare function percentage(value: number): string;
|
|
4
|
+
export declare function dateShort(dateStr: string): string;
|
|
5
|
+
export declare function dateFull(dateStr: string): string;
|
|
6
|
+
export declare function timeAgo(dateStr: string): string;
|
|
7
|
+
export declare function maskCard(last4: string): string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.currency = currency;
|
|
4
|
+
exports.currencyCompact = currencyCompact;
|
|
5
|
+
exports.percentage = percentage;
|
|
6
|
+
exports.dateShort = dateShort;
|
|
7
|
+
exports.dateFull = dateFull;
|
|
8
|
+
exports.timeAgo = timeAgo;
|
|
9
|
+
exports.maskCard = maskCard;
|
|
10
|
+
function currency(amount) {
|
|
11
|
+
const sign = amount < 0 ? "-" : "";
|
|
12
|
+
return `${sign}$${Math.abs(amount).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
13
|
+
}
|
|
14
|
+
function currencyCompact(amount) {
|
|
15
|
+
const sign = amount < 0 ? "-" : amount > 0 ? "+" : "";
|
|
16
|
+
return `${sign}$${Math.abs(amount).toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: 0 })}`;
|
|
17
|
+
}
|
|
18
|
+
function percentage(value) {
|
|
19
|
+
return `${value.toFixed(1)}%`;
|
|
20
|
+
}
|
|
21
|
+
function dateShort(dateStr) {
|
|
22
|
+
const d = new Date(dateStr);
|
|
23
|
+
return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
24
|
+
}
|
|
25
|
+
function dateFull(dateStr) {
|
|
26
|
+
const d = new Date(dateStr);
|
|
27
|
+
return d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
|
|
28
|
+
}
|
|
29
|
+
function timeAgo(dateStr) {
|
|
30
|
+
const now = new Date();
|
|
31
|
+
const d = new Date(dateStr);
|
|
32
|
+
const diffMs = now.getTime() - d.getTime();
|
|
33
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
34
|
+
if (diffMins < 60)
|
|
35
|
+
return `${diffMins}m ago`;
|
|
36
|
+
const diffHours = Math.floor(diffMins / 60);
|
|
37
|
+
if (diffHours < 24)
|
|
38
|
+
return `${diffHours}h ago`;
|
|
39
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
40
|
+
return `${diffDays}d ago`;
|
|
41
|
+
}
|
|
42
|
+
function maskCard(last4) {
|
|
43
|
+
return `•••• ${last4}`;
|
|
44
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from "./user";
|
|
2
|
+
export * from "./project";
|
|
3
|
+
export * from "./card";
|
|
4
|
+
export * from "./transaction";
|
|
5
|
+
export * from "./reward";
|
|
6
|
+
export * from "./alert";
|
|
7
|
+
export * from "./common";
|
|
8
|
+
export { mockUser, mockSession } from "./users";
|
|
9
|
+
export { mockProjects, mockProjectPnls } from "./projects";
|
|
10
|
+
export { mockCards } from "./cards";
|
|
11
|
+
export { mockTransactions } from "./transactions";
|
|
12
|
+
export { mockRewardBalance, mockRewardTiers, mockRewardHistory } from "./rewards";
|
|
13
|
+
export { mockAlertRules, mockAlertEvents } from "./alerts";
|
|
14
|
+
export * from "./formatters";
|
|
15
|
+
export * from "./filters";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.mockAlertEvents = exports.mockAlertRules = exports.mockRewardHistory = exports.mockRewardTiers = exports.mockRewardBalance = exports.mockTransactions = exports.mockCards = exports.mockProjectPnls = exports.mockProjects = exports.mockSession = exports.mockUser = void 0;
|
|
18
|
+
__exportStar(require("./user"), exports);
|
|
19
|
+
__exportStar(require("./project"), exports);
|
|
20
|
+
__exportStar(require("./card"), exports);
|
|
21
|
+
__exportStar(require("./transaction"), exports);
|
|
22
|
+
__exportStar(require("./reward"), exports);
|
|
23
|
+
__exportStar(require("./alert"), exports);
|
|
24
|
+
__exportStar(require("./common"), exports);
|
|
25
|
+
var users_1 = require("./users");
|
|
26
|
+
Object.defineProperty(exports, "mockUser", { enumerable: true, get: function () { return users_1.mockUser; } });
|
|
27
|
+
Object.defineProperty(exports, "mockSession", { enumerable: true, get: function () { return users_1.mockSession; } });
|
|
28
|
+
var projects_1 = require("./projects");
|
|
29
|
+
Object.defineProperty(exports, "mockProjects", { enumerable: true, get: function () { return projects_1.mockProjects; } });
|
|
30
|
+
Object.defineProperty(exports, "mockProjectPnls", { enumerable: true, get: function () { return projects_1.mockProjectPnls; } });
|
|
31
|
+
var cards_1 = require("./cards");
|
|
32
|
+
Object.defineProperty(exports, "mockCards", { enumerable: true, get: function () { return cards_1.mockCards; } });
|
|
33
|
+
var transactions_1 = require("./transactions");
|
|
34
|
+
Object.defineProperty(exports, "mockTransactions", { enumerable: true, get: function () { return transactions_1.mockTransactions; } });
|
|
35
|
+
var rewards_1 = require("./rewards");
|
|
36
|
+
Object.defineProperty(exports, "mockRewardBalance", { enumerable: true, get: function () { return rewards_1.mockRewardBalance; } });
|
|
37
|
+
Object.defineProperty(exports, "mockRewardTiers", { enumerable: true, get: function () { return rewards_1.mockRewardTiers; } });
|
|
38
|
+
Object.defineProperty(exports, "mockRewardHistory", { enumerable: true, get: function () { return rewards_1.mockRewardHistory; } });
|
|
39
|
+
var alerts_1 = require("./alerts");
|
|
40
|
+
Object.defineProperty(exports, "mockAlertRules", { enumerable: true, get: function () { return alerts_1.mockAlertRules; } });
|
|
41
|
+
Object.defineProperty(exports, "mockAlertEvents", { enumerable: true, get: function () { return alerts_1.mockAlertEvents; } });
|
|
42
|
+
__exportStar(require("./formatters"), exports);
|
|
43
|
+
__exportStar(require("./filters"), exports);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type ProjectStatus = "active" | "paused" | "archived";
|
|
2
|
+
export interface Project {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
status: ProjectStatus;
|
|
7
|
+
color: string;
|
|
8
|
+
cardId: string;
|
|
9
|
+
monthlyBudget: number;
|
|
10
|
+
createdAt: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ProjectPnl {
|
|
13
|
+
projectId: string;
|
|
14
|
+
projectName: string;
|
|
15
|
+
period: string;
|
|
16
|
+
spend: number;
|
|
17
|
+
revenue: number;
|
|
18
|
+
net: number;
|
|
19
|
+
margin: number;
|
|
20
|
+
topProviders: {
|
|
21
|
+
name: string;
|
|
22
|
+
amount: number;
|
|
23
|
+
}[];
|
|
24
|
+
}
|