@nordsym/apiclaw 1.3.2 → 1.3.4

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/convex/http.ts CHANGED
@@ -608,3 +608,157 @@ http.route({
608
608
  method: "OPTIONS",
609
609
  handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
610
610
  });
611
+
612
+ // ==============================================
613
+ // WORKSPACE / MAGIC LINK ENDPOINTS
614
+ // ==============================================
615
+
616
+ // Create magic link and send email
617
+ http.route({
618
+ path: "/workspace/magic-link",
619
+ method: "POST",
620
+ handler: httpAction(async (ctx, request) => {
621
+ try {
622
+ const body = await request.json();
623
+ const { email, fingerprint } = body;
624
+
625
+ if (!email || !email.includes("@")) {
626
+ return jsonResponse({ error: "Valid email required" }, 400);
627
+ }
628
+
629
+ // Create magic link
630
+ const result = await ctx.runMutation(api.workspaces.createMagicLink, {
631
+ email: email.toLowerCase(),
632
+ fingerprint,
633
+ });
634
+
635
+ // Send email
636
+ await ctx.runAction(api.email.sendMagicLinkEmail, {
637
+ email: email.toLowerCase(),
638
+ token: result.token,
639
+ });
640
+
641
+ return jsonResponse({
642
+ success: true,
643
+ token: result.token,
644
+ expiresAt: result.expiresAt,
645
+ message: "Magic link sent! Check your email.",
646
+ });
647
+ } catch (e: any) {
648
+ console.error("Magic link error:", e);
649
+ return jsonResponse({ error: e.message || "Failed to create magic link" }, 500);
650
+ }
651
+ }),
652
+ });
653
+
654
+ http.route({
655
+ path: "/workspace/magic-link",
656
+ method: "OPTIONS",
657
+ handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
658
+ });
659
+
660
+ // Poll magic link status (for agents to check if user clicked)
661
+ http.route({
662
+ path: "/workspace/poll",
663
+ method: "GET",
664
+ handler: httpAction(async (ctx, request) => {
665
+ const url = new URL(request.url);
666
+ const token = url.searchParams.get("token");
667
+
668
+ if (!token) {
669
+ return jsonResponse({ error: "token required" }, 400);
670
+ }
671
+
672
+ const result = await ctx.runQuery(api.workspaces.pollMagicLink, { token });
673
+ return jsonResponse(result);
674
+ }),
675
+ });
676
+
677
+ http.route({
678
+ path: "/workspace/poll",
679
+ method: "OPTIONS",
680
+ handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
681
+ });
682
+
683
+ // Verify session token
684
+ http.route({
685
+ path: "/workspace/verify-session",
686
+ method: "GET",
687
+ handler: httpAction(async (ctx, request) => {
688
+ const url = new URL(request.url);
689
+ const sessionToken = url.searchParams.get("sessionToken");
690
+
691
+ if (!sessionToken) {
692
+ return jsonResponse({ error: "sessionToken required" }, 400);
693
+ }
694
+
695
+ const result = await ctx.runQuery(api.workspaces.verifySession, { sessionToken });
696
+
697
+ if (!result) {
698
+ return jsonResponse({ error: "Invalid or expired session" }, 401);
699
+ }
700
+
701
+ return jsonResponse(result);
702
+ }),
703
+ });
704
+
705
+ http.route({
706
+ path: "/workspace/verify-session",
707
+ method: "OPTIONS",
708
+ handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
709
+ });
710
+
711
+ // Get workspace by email
712
+ http.route({
713
+ path: "/workspace/by-email",
714
+ method: "GET",
715
+ handler: httpAction(async (ctx, request) => {
716
+ const url = new URL(request.url);
717
+ const email = url.searchParams.get("email");
718
+
719
+ if (!email) {
720
+ return jsonResponse({ error: "email required" }, 400);
721
+ }
722
+
723
+ const result = await ctx.runQuery(api.workspaces.getByEmail, { email });
724
+
725
+ if (!result) {
726
+ return jsonResponse({ exists: false });
727
+ }
728
+
729
+ return jsonResponse({ exists: true, workspace: result });
730
+ }),
731
+ });
732
+
733
+ http.route({
734
+ path: "/workspace/by-email",
735
+ method: "OPTIONS",
736
+ handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
737
+ });
738
+
739
+ // Send reminder email
740
+ http.route({
741
+ path: "/workspace/send-reminder",
742
+ method: "POST",
743
+ handler: httpAction(async (ctx, request) => {
744
+ try {
745
+ const body = await request.json();
746
+ const { email, token } = body;
747
+
748
+ if (!email || !token) {
749
+ return jsonResponse({ error: "email and token required" }, 400);
750
+ }
751
+
752
+ await ctx.runAction(api.email.sendReminderEmail, { email, token });
753
+ return jsonResponse({ success: true });
754
+ } catch (e: any) {
755
+ return jsonResponse({ error: e.message }, 500);
756
+ }
757
+ }),
758
+ });
759
+
760
+ http.route({
761
+ path: "/workspace/send-reminder",
762
+ method: "OPTIONS",
763
+ handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
764
+ });
package/convex/schema.ts CHANGED
@@ -37,6 +37,49 @@ export default defineSchema({
37
37
  .index("by_purchaseId", ["purchaseId"])
38
38
  .index("by_providerId", ["providerId"]),
39
39
 
40
+ // ============================================
41
+ // WORKSPACE TABLES (MCP Agent Authentication)
42
+ // ============================================
43
+
44
+ // Workspaces (agent owner accounts)
45
+ workspaces: defineTable({
46
+ email: v.string(),
47
+ passwordHash: v.optional(v.string()),
48
+ status: v.string(), // "pending" | "active" | "suspended"
49
+ tier: v.string(), // "free" | "pro" | "enterprise"
50
+ usageCount: v.number(), // total API calls made
51
+ usageLimit: v.number(), // max API calls for tier
52
+ stripeCustomerId: v.optional(v.string()),
53
+ createdAt: v.number(),
54
+ updatedAt: v.number(),
55
+ })
56
+ .index("by_email", ["email"])
57
+ .index("by_stripeCustomerId", ["stripeCustomerId"])
58
+ .index("by_status", ["status"]),
59
+
60
+ // Agent sessions (for MCP server authentication)
61
+ agentSessions: defineTable({
62
+ workspaceId: v.id("workspaces"),
63
+ sessionToken: v.string(),
64
+ fingerprint: v.optional(v.string()), // machine fingerprint
65
+ lastUsedAt: v.number(),
66
+ createdAt: v.number(),
67
+ })
68
+ .index("by_sessionToken", ["sessionToken"])
69
+ .index("by_workspaceId", ["workspaceId"]),
70
+
71
+ // Magic links for workspace email verification
72
+ workspaceMagicLinks: defineTable({
73
+ email: v.string(),
74
+ token: v.string(),
75
+ sessionFingerprint: v.optional(v.string()),
76
+ expiresAt: v.number(),
77
+ usedAt: v.optional(v.number()),
78
+ createdAt: v.number(),
79
+ })
80
+ .index("by_token", ["token"])
81
+ .index("by_email", ["email"]),
82
+
40
83
  // Credit top-ups (from Stripe payments)
41
84
  creditTopups: defineTable({
42
85
  agentId: v.string(),