@nordsym/apiclaw 1.3.3 → 1.3.5

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 (42) hide show
  1. package/convex/_generated/api.d.ts +6 -0
  2. package/convex/billing.ts +341 -0
  3. package/convex/email.ts +276 -0
  4. package/convex/http.ts +154 -0
  5. package/convex/schema.ts +43 -0
  6. package/convex/workspaces.ts +663 -0
  7. package/dist/cli.d.ts +7 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +272 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/index.js +396 -4
  12. package/dist/index.js.map +1 -1
  13. package/dist/session.d.ts +29 -0
  14. package/dist/session.d.ts.map +1 -0
  15. package/dist/session.js +87 -0
  16. package/dist/session.js.map +1 -0
  17. package/docs/PRD-agent-first-billing.md +525 -0
  18. package/docs/PRD-workspace-fixes.md +178 -0
  19. package/landing/package-lock.json +21 -3
  20. package/landing/package.json +2 -1
  21. package/landing/src/app/api/stripe/webhook/route.ts +178 -0
  22. package/landing/src/app/api/workspace-auth/magic-link/route.ts +84 -0
  23. package/landing/src/app/api/workspace-auth/session/route.ts +73 -0
  24. package/landing/src/app/api/workspace-auth/verify/route.ts +57 -0
  25. package/landing/src/app/auth/verify/page.tsx +292 -0
  26. package/landing/src/app/dashboard/layout.tsx +22 -0
  27. package/landing/src/app/dashboard/page.tsx +22 -0
  28. package/landing/src/app/dashboard/verify/page.tsx +108 -0
  29. package/landing/src/app/login/page.tsx +204 -0
  30. package/landing/src/app/page.tsx +23 -7
  31. package/landing/src/app/providers/dashboard/layout.tsx +5 -4
  32. package/landing/src/app/providers/dashboard/page.tsx +11 -641
  33. package/landing/src/app/upgrade/page.tsx +288 -0
  34. package/landing/src/app/workspace/layout.tsx +30 -0
  35. package/landing/src/app/workspace/page.tsx +1637 -0
  36. package/landing/src/lib/stats.json +14 -15
  37. package/landing/src/middleware.ts +50 -0
  38. package/landing/tsconfig.tsbuildinfo +1 -1
  39. package/package.json +1 -1
  40. package/src/cli.ts +320 -0
  41. package/src/index.ts +444 -4
  42. package/src/session.ts +103 -0
@@ -0,0 +1,663 @@
1
+ import { mutation, query } from "./_generated/server";
2
+ import { v } from "convex/values";
3
+
4
+ // ============================================
5
+ // MAGIC LINK AUTH FOR WORKSPACES
6
+ // ============================================
7
+
8
+ // Create magic link for workspace email auth
9
+ export const createMagicLink = mutation({
10
+ args: {
11
+ email: v.string(),
12
+ fingerprint: v.optional(v.string()),
13
+ },
14
+ handler: async (ctx, { email, fingerprint }) => {
15
+ const token = generateToken();
16
+ const expiresAt = Date.now() + 15 * 60 * 1000; // 15 minutes
17
+
18
+ await ctx.db.insert("workspaceMagicLinks", {
19
+ email: email.toLowerCase(),
20
+ token,
21
+ sessionFingerprint: fingerprint,
22
+ expiresAt,
23
+ createdAt: Date.now(),
24
+ });
25
+
26
+ return { token, expiresAt };
27
+ },
28
+ });
29
+
30
+ // Verify magic link and create workspace + session
31
+ export const verifyMagicLink = mutation({
32
+ args: {
33
+ token: v.string(),
34
+ fingerprint: v.optional(v.string()),
35
+ },
36
+ handler: async (ctx, { token, fingerprint }) => {
37
+ const magicLink = await ctx.db
38
+ .query("workspaceMagicLinks")
39
+ .withIndex("by_token", (q) => q.eq("token", token))
40
+ .first();
41
+
42
+ if (!magicLink) {
43
+ return { success: false, error: "Invalid token" };
44
+ }
45
+
46
+ if (magicLink.expiresAt < Date.now()) {
47
+ return { success: false, error: "Token expired" };
48
+ }
49
+
50
+ if (magicLink.usedAt) {
51
+ return { success: false, error: "Token already used" };
52
+ }
53
+
54
+ // Mark as used
55
+ await ctx.db.patch(magicLink._id, { usedAt: Date.now() });
56
+
57
+ // Find or create workspace
58
+ let workspace = await ctx.db
59
+ .query("workspaces")
60
+ .withIndex("by_email", (q) => q.eq("email", magicLink.email))
61
+ .first();
62
+
63
+ if (!workspace) {
64
+ // Create new workspace with free tier
65
+ const workspaceId = await ctx.db.insert("workspaces", {
66
+ email: magicLink.email,
67
+ status: "active",
68
+ tier: "free",
69
+ usageCount: 0,
70
+ usageLimit: 1000, // 1000 free API calls
71
+ createdAt: Date.now(),
72
+ updatedAt: Date.now(),
73
+ });
74
+ workspace = await ctx.db.get(workspaceId);
75
+ }
76
+
77
+ // Create agent session
78
+ const sessionToken = generateToken();
79
+
80
+ await ctx.db.insert("agentSessions", {
81
+ workspaceId: workspace!._id,
82
+ sessionToken,
83
+ fingerprint: fingerprint || magicLink.sessionFingerprint,
84
+ lastUsedAt: Date.now(),
85
+ createdAt: Date.now(),
86
+ });
87
+
88
+ return {
89
+ success: true,
90
+ sessionToken,
91
+ workspace: {
92
+ id: workspace!._id,
93
+ email: workspace!.email,
94
+ tier: workspace!.tier,
95
+ },
96
+ };
97
+ },
98
+ });
99
+
100
+ // Get session from token
101
+ export const getSession = query({
102
+ args: { token: v.string() },
103
+ handler: async (ctx, { token }) => {
104
+ const session = await ctx.db
105
+ .query("agentSessions")
106
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
107
+ .first();
108
+
109
+ if (!session) {
110
+ return null;
111
+ }
112
+
113
+ const workspace = await ctx.db.get(session.workspaceId);
114
+ if (!workspace) return null;
115
+
116
+ return {
117
+ workspaceId: workspace._id,
118
+ email: workspace.email,
119
+ tier: workspace.tier,
120
+ status: workspace.status,
121
+ usageCount: workspace.usageCount,
122
+ usageLimit: workspace.usageLimit,
123
+ };
124
+ },
125
+ });
126
+
127
+ // ============================================
128
+ // DASHBOARD QUERIES
129
+ // ============================================
130
+
131
+ // Get full workspace dashboard data
132
+ export const getWorkspaceDashboard = query({
133
+ args: { token: v.string() },
134
+ handler: async (ctx, { token }) => {
135
+ // Verify session
136
+ const session = await ctx.db
137
+ .query("agentSessions")
138
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
139
+ .first();
140
+
141
+ if (!session) {
142
+ return null;
143
+ }
144
+
145
+ // Note: lastUsedAt is updated via touchSession mutation separately
146
+
147
+ const workspace = await ctx.db.get(session.workspaceId);
148
+ if (!workspace) return null;
149
+
150
+ // Get all agent sessions for this workspace
151
+ const agentSessions = await ctx.db
152
+ .query("agentSessions")
153
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
154
+ .collect();
155
+
156
+ // Get usage logs for this workspace (via agent credits or purchases)
157
+ const credits = await ctx.db
158
+ .query("agentCredits")
159
+ .collect();
160
+
161
+ // Filter credits that belong to this workspace's agents
162
+ const workspaceCredits = credits.filter(c =>
163
+ agentSessions.some(s => c.agentId === s.sessionToken)
164
+ );
165
+
166
+ // Get purchases for workspace agents
167
+ const purchases = await ctx.db
168
+ .query("purchases")
169
+ .collect();
170
+
171
+ const workspacePurchases = purchases.filter(p =>
172
+ agentSessions.some(s => p.agentId === s.sessionToken)
173
+ );
174
+
175
+ // Calculate usage remaining
176
+ const usageRemaining = workspace.usageLimit - workspace.usageCount;
177
+ const usagePercentage = (workspace.usageCount / workspace.usageLimit) * 100;
178
+
179
+ return {
180
+ workspace: {
181
+ id: workspace._id,
182
+ email: workspace.email,
183
+ tier: workspace.tier,
184
+ status: workspace.status,
185
+ usageCount: workspace.usageCount,
186
+ usageLimit: workspace.usageLimit,
187
+ usageRemaining,
188
+ usagePercentage,
189
+ stripeCustomerId: workspace.stripeCustomerId,
190
+ createdAt: workspace.createdAt,
191
+ },
192
+ stats: {
193
+ totalAgents: agentSessions.length,
194
+ totalCredits: workspaceCredits.reduce((sum, c) => sum + c.balanceUsd, 0),
195
+ totalPurchases: workspacePurchases.length,
196
+ },
197
+ };
198
+ },
199
+ });
200
+
201
+ // Get connected agents for workspace
202
+ export const getConnectedAgents = query({
203
+ args: { token: v.string() },
204
+ handler: async (ctx, { token }) => {
205
+ const session = await ctx.db
206
+ .query("agentSessions")
207
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
208
+ .first();
209
+
210
+ if (!session) {
211
+ return [];
212
+ }
213
+
214
+ const agentSessions = await ctx.db
215
+ .query("agentSessions")
216
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
217
+ .collect();
218
+
219
+ return agentSessions.map((s) => ({
220
+ id: s._id,
221
+ fingerprint: s.fingerprint || "Unknown",
222
+ lastUsedAt: s.lastUsedAt,
223
+ createdAt: s.createdAt,
224
+ isCurrent: s.sessionToken === token,
225
+ }));
226
+ },
227
+ });
228
+
229
+ // Get usage breakdown by provider
230
+ export const getUsageBreakdown = query({
231
+ args: { token: v.string() },
232
+ handler: async (ctx, { token }) => {
233
+ const session = await ctx.db
234
+ .query("agentSessions")
235
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
236
+ .first();
237
+
238
+ if (!session) {
239
+ return { byProvider: [], byDay: [], total: 0 };
240
+ }
241
+
242
+ // Get all sessions for this workspace
243
+ const agentSessions = await ctx.db
244
+ .query("agentSessions")
245
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
246
+ .collect();
247
+
248
+ const sessionTokens = agentSessions.map(s => s.sessionToken);
249
+
250
+ // Get purchases for these agents
251
+ const allPurchases = await ctx.db.query("purchases").collect();
252
+ const workspacePurchases = allPurchases.filter(p => sessionTokens.includes(p.agentId));
253
+
254
+ // Get usage for purchases
255
+ const allUsage = await ctx.db.query("usage").collect();
256
+ const purchaseIds = workspacePurchases.map(p => p._id);
257
+ const workspaceUsage = allUsage.filter(u => purchaseIds.includes(u.purchaseId));
258
+
259
+ // Aggregate by provider
260
+ const byProvider: Record<string, { calls: number; cost: number }> = {};
261
+ for (const usage of workspaceUsage) {
262
+ if (!byProvider[usage.providerId]) {
263
+ byProvider[usage.providerId] = { calls: 0, cost: 0 };
264
+ }
265
+ byProvider[usage.providerId].calls += usage.unitsUsed;
266
+ byProvider[usage.providerId].cost += usage.costIncurredUsd;
267
+ }
268
+
269
+ // Aggregate by day (last 14 days)
270
+ const now = Date.now();
271
+ const fourteenDaysAgo = now - 14 * 24 * 60 * 60 * 1000;
272
+ const byDay: Record<string, number> = {};
273
+
274
+ for (const usage of workspaceUsage) {
275
+ if (usage.lastUsedAt >= fourteenDaysAgo) {
276
+ const day = new Date(usage.lastUsedAt).toISOString().split("T")[0];
277
+ byDay[day] = (byDay[day] || 0) + usage.unitsUsed;
278
+ }
279
+ }
280
+
281
+ return {
282
+ byProvider: Object.entries(byProvider).map(([provider, data]) => ({
283
+ provider,
284
+ calls: data.calls,
285
+ cost: data.cost,
286
+ })),
287
+ byDay: Object.entries(byDay)
288
+ .map(([date, calls]) => ({ date, calls }))
289
+ .sort((a, b) => a.date.localeCompare(b.date)),
290
+ total: workspaceUsage.reduce((sum, u) => sum + u.unitsUsed, 0),
291
+ };
292
+ },
293
+ });
294
+
295
+ // ============================================
296
+ // AGENT MANAGEMENT
297
+ // ============================================
298
+
299
+ // Revoke an agent session
300
+ export const revokeAgentSession = mutation({
301
+ args: {
302
+ token: v.string(),
303
+ sessionId: v.id("agentSessions"),
304
+ },
305
+ handler: async (ctx, { token, sessionId }) => {
306
+ // Verify the requesting session
307
+ const session = await ctx.db
308
+ .query("agentSessions")
309
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
310
+ .first();
311
+
312
+ if (!session) {
313
+ throw new Error("Unauthorized");
314
+ }
315
+
316
+ // Get the session to revoke
317
+ const targetSession = await ctx.db.get(sessionId);
318
+ if (!targetSession) {
319
+ throw new Error("Session not found");
320
+ }
321
+
322
+ // Verify same workspace
323
+ if (targetSession.workspaceId !== session.workspaceId) {
324
+ throw new Error("Unauthorized");
325
+ }
326
+
327
+ // Prevent revoking current session
328
+ if (targetSession.sessionToken === token) {
329
+ throw new Error("Cannot revoke current session");
330
+ }
331
+
332
+ // Delete the session
333
+ await ctx.db.delete(sessionId);
334
+
335
+ return { success: true };
336
+ },
337
+ });
338
+
339
+ // Logout (delete current session)
340
+ export const logout = mutation({
341
+ args: { token: v.string() },
342
+ handler: async (ctx, { token }) => {
343
+ const session = await ctx.db
344
+ .query("agentSessions")
345
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
346
+ .first();
347
+
348
+ if (session) {
349
+ await ctx.db.delete(session._id);
350
+ }
351
+
352
+ return { success: true };
353
+ },
354
+ });
355
+
356
+ // ============================================
357
+ // WORKSPACE MANAGEMENT
358
+ // ============================================
359
+
360
+ // Update workspace tier (for Stripe webhooks)
361
+ export const updateTier = mutation({
362
+ args: {
363
+ workspaceId: v.id("workspaces"),
364
+ tier: v.string(),
365
+ usageLimit: v.number(),
366
+ stripeCustomerId: v.optional(v.string()),
367
+ },
368
+ handler: async (ctx, { workspaceId, tier, usageLimit, stripeCustomerId }) => {
369
+ const updates: Record<string, unknown> = {
370
+ tier,
371
+ usageLimit,
372
+ updatedAt: Date.now(),
373
+ };
374
+
375
+ if (stripeCustomerId) {
376
+ updates.stripeCustomerId = stripeCustomerId;
377
+ }
378
+
379
+ await ctx.db.patch(workspaceId, updates);
380
+ return { success: true };
381
+ },
382
+ });
383
+
384
+ // Increment usage count
385
+ export const incrementUsage = mutation({
386
+ args: {
387
+ workspaceId: v.id("workspaces"),
388
+ amount: v.optional(v.number()),
389
+ },
390
+ handler: async (ctx, { workspaceId, amount = 1 }) => {
391
+ const workspace = await ctx.db.get(workspaceId);
392
+ if (!workspace) {
393
+ throw new Error("Workspace not found");
394
+ }
395
+
396
+ const newCount = workspace.usageCount + amount;
397
+
398
+ // Check if over limit
399
+ if (newCount > workspace.usageLimit) {
400
+ throw new Error("Usage limit exceeded");
401
+ }
402
+
403
+ await ctx.db.patch(workspaceId, {
404
+ usageCount: newCount,
405
+ updatedAt: Date.now(),
406
+ });
407
+
408
+ return {
409
+ success: true,
410
+ usageCount: newCount,
411
+ usageRemaining: workspace.usageLimit - newCount,
412
+ };
413
+ },
414
+ });
415
+
416
+ // ============================================
417
+ // POLLING & VERIFICATION ENDPOINTS (for HTTP API)
418
+ // ============================================
419
+
420
+ // Poll magic link status (for agents to check if user clicked)
421
+ export const pollMagicLink = query({
422
+ args: { token: v.string() },
423
+ handler: async (ctx, { token }) => {
424
+ const magicLink = await ctx.db
425
+ .query("workspaceMagicLinks")
426
+ .withIndex("by_token", (q) => q.eq("token", token))
427
+ .first();
428
+
429
+ if (!magicLink) {
430
+ return { status: "not_found" };
431
+ }
432
+
433
+ const now = Date.now();
434
+
435
+ if (magicLink.usedAt) {
436
+ // Get the workspace and session
437
+ const workspace = await ctx.db
438
+ .query("workspaces")
439
+ .withIndex("by_email", (q) => q.eq("email", magicLink.email))
440
+ .first();
441
+
442
+ // Get the latest session for this workspace
443
+ const session = workspace
444
+ ? await ctx.db
445
+ .query("agentSessions")
446
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", workspace._id))
447
+ .order("desc")
448
+ .first()
449
+ : null;
450
+
451
+ return {
452
+ status: "verified",
453
+ workspace: workspace
454
+ ? {
455
+ id: workspace._id,
456
+ email: workspace.email,
457
+ tier: workspace.tier,
458
+ usageCount: workspace.usageCount,
459
+ usageLimit: workspace.usageLimit,
460
+ }
461
+ : null,
462
+ sessionToken: session?.sessionToken,
463
+ };
464
+ }
465
+
466
+ if (magicLink.expiresAt < now) {
467
+ return { status: "expired" };
468
+ }
469
+
470
+ return {
471
+ status: "pending",
472
+ expiresAt: magicLink.expiresAt,
473
+ };
474
+ },
475
+ });
476
+
477
+ // Verify session token (for HTTP API)
478
+ export const verifySession = query({
479
+ args: { sessionToken: v.string() },
480
+ handler: async (ctx, { sessionToken }) => {
481
+ const session = await ctx.db
482
+ .query("agentSessions")
483
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", sessionToken))
484
+ .first();
485
+
486
+ if (!session) {
487
+ return null;
488
+ }
489
+
490
+ const workspace = await ctx.db.get(session.workspaceId);
491
+ if (!workspace || workspace.status !== "active") {
492
+ return null;
493
+ }
494
+
495
+ return {
496
+ workspaceId: workspace._id,
497
+ email: workspace.email,
498
+ tier: workspace.tier,
499
+ usageCount: workspace.usageCount,
500
+ usageLimit: workspace.usageLimit,
501
+ };
502
+ },
503
+ });
504
+
505
+ // Get workspace by email (for HTTP API)
506
+ export const getByEmail = query({
507
+ args: { email: v.string() },
508
+ handler: async (ctx, { email }) => {
509
+ const workspace = await ctx.db
510
+ .query("workspaces")
511
+ .withIndex("by_email", (q) => q.eq("email", email.toLowerCase()))
512
+ .first();
513
+
514
+ if (!workspace) {
515
+ return null;
516
+ }
517
+
518
+ return {
519
+ id: workspace._id,
520
+ email: workspace.email,
521
+ status: workspace.status,
522
+ tier: workspace.tier,
523
+ usageCount: workspace.usageCount,
524
+ usageLimit: workspace.usageLimit,
525
+ };
526
+ },
527
+ });
528
+
529
+ // Touch session (update lastUsedAt)
530
+ export const touchSession = mutation({
531
+ args: { sessionToken: v.string() },
532
+ handler: async (ctx, { sessionToken }) => {
533
+ const session = await ctx.db
534
+ .query("agentSessions")
535
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", sessionToken))
536
+ .first();
537
+
538
+ if (session) {
539
+ await ctx.db.patch(session._id, { lastUsedAt: Date.now() });
540
+ }
541
+ },
542
+ });
543
+
544
+ // ============================================
545
+ // MCP WORKSPACE FUNCTIONS
546
+ // ============================================
547
+
548
+ // Create a new workspace (called from MCP register_owner)
549
+ export const createWorkspace = mutation({
550
+ args: { email: v.string() },
551
+ handler: async (ctx, { email }) => {
552
+ const normalizedEmail = email.toLowerCase().trim();
553
+
554
+ // Check if workspace exists
555
+ const existing = await ctx.db
556
+ .query("workspaces")
557
+ .withIndex("by_email", (q) => q.eq("email", normalizedEmail))
558
+ .first();
559
+
560
+ if (existing) {
561
+ return {
562
+ success: false,
563
+ error: "workspace_exists",
564
+ workspaceId: existing._id,
565
+ status: existing.status,
566
+ };
567
+ }
568
+
569
+ // Create new workspace
570
+ const workspaceId = await ctx.db.insert("workspaces", {
571
+ email: normalizedEmail,
572
+ status: "pending",
573
+ tier: "free",
574
+ usageCount: 0,
575
+ usageLimit: 100, // Free tier limit
576
+ createdAt: Date.now(),
577
+ updatedAt: Date.now(),
578
+ });
579
+
580
+ return { success: true, workspaceId };
581
+ },
582
+ });
583
+
584
+ // Create agent session for workspace (called from MCP after verification)
585
+ export const createAgentSession = mutation({
586
+ args: {
587
+ workspaceId: v.id("workspaces"),
588
+ fingerprint: v.optional(v.string()),
589
+ },
590
+ handler: async (ctx, { workspaceId, fingerprint }) => {
591
+ const workspace = await ctx.db.get(workspaceId);
592
+ if (!workspace) {
593
+ return { success: false, error: "workspace_not_found" };
594
+ }
595
+
596
+ if (workspace.status !== "active") {
597
+ return { success: false, error: "workspace_not_active" };
598
+ }
599
+
600
+ const sessionToken = "apiclaw_" + generateToken();
601
+
602
+ await ctx.db.insert("agentSessions", {
603
+ workspaceId,
604
+ sessionToken,
605
+ fingerprint,
606
+ lastUsedAt: Date.now(),
607
+ createdAt: Date.now(),
608
+ });
609
+
610
+ return { success: true, sessionToken };
611
+ },
612
+ });
613
+
614
+ // ============================================
615
+ // HELPER FUNCTIONS
616
+ // ============================================
617
+
618
+ function generateToken(): string {
619
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
620
+ let result = "";
621
+ for (let i = 0; i < 48; i++) {
622
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
623
+ }
624
+ return result;
625
+ }
626
+
627
+ // Get workspace status (for MCP check_workspace_status tool)
628
+ export const getWorkspaceStatus = query({
629
+ args: {
630
+ sessionToken: v.string(),
631
+ },
632
+ handler: async (ctx, args) => {
633
+ const session = await ctx.db
634
+ .query("agentSessions")
635
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.sessionToken))
636
+ .first();
637
+
638
+ if (!session) {
639
+ return { authenticated: false };
640
+ }
641
+
642
+ const workspace = await ctx.db.get(session.workspaceId);
643
+ if (!workspace) {
644
+ return { authenticated: false };
645
+ }
646
+
647
+ const usageRemaining = workspace.usageLimit > 0
648
+ ? workspace.usageLimit - workspace.usageCount
649
+ : -1; // -1 = unlimited
650
+
651
+ return {
652
+ authenticated: true,
653
+ email: workspace.email,
654
+ status: workspace.status,
655
+ tier: workspace.tier,
656
+ usageCount: workspace.usageCount,
657
+ usageLimit: workspace.usageLimit,
658
+ usageRemaining,
659
+ hasStripe: !!workspace.stripeCustomerId,
660
+ createdAt: workspace.createdAt,
661
+ };
662
+ },
663
+ });
package/dist/cli.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * APIClaw Interactive CLI
4
+ * Run with: npx @nordsym/apiclaw --cli
5
+ */
6
+ export declare function startCLI(): Promise<void>;
7
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;GAGG;AA6NH,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CA8F9C"}