@nordsym/apiclaw 2.2.0 → 2.3.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 (176) hide show
  1. package/README.md +15 -2
  2. package/dist/bin-http.js +0 -0
  3. package/dist/bin.bundled.js +79288 -0
  4. package/dist/gateway-client.d.ts.map +1 -1
  5. package/dist/gateway-client.js +24 -2
  6. package/dist/gateway-client.js.map +1 -1
  7. package/dist/index.bundled.js +61263 -0
  8. package/dist/index.js +2 -2
  9. package/dist/index.js.map +1 -1
  10. package/package.json +7 -2
  11. package/.claude/settings.local.json +0 -13
  12. package/.env.prod +0 -1
  13. package/apiclaw-README.md +0 -494
  14. package/convex/_generated/api.d.ts +0 -145
  15. package/convex/_generated/api.js +0 -23
  16. package/convex/_generated/dataModel.d.ts +0 -60
  17. package/convex/_generated/server.d.ts +0 -143
  18. package/convex/_generated/server.js +0 -93
  19. package/convex/_listWorkspaces.ts +0 -13
  20. package/convex/adminActivate.ts +0 -53
  21. package/convex/adminStats.ts +0 -306
  22. package/convex/agents.ts +0 -939
  23. package/convex/analytics.ts +0 -187
  24. package/convex/apiKeys.ts +0 -220
  25. package/convex/backfillAnalytics.ts +0 -272
  26. package/convex/backfillSearchLogs.ts +0 -35
  27. package/convex/billing.ts +0 -834
  28. package/convex/capabilities.ts +0 -157
  29. package/convex/chains.ts +0 -1318
  30. package/convex/credits.ts +0 -211
  31. package/convex/crons.ts +0 -65
  32. package/convex/debugFilestackLogs.ts +0 -16
  33. package/convex/debugGetToken.ts +0 -18
  34. package/convex/directCall.ts +0 -713
  35. package/convex/earnProgress.ts +0 -753
  36. package/convex/email.ts +0 -329
  37. package/convex/feedback.ts +0 -265
  38. package/convex/funnel.ts +0 -431
  39. package/convex/guards.ts +0 -174
  40. package/convex/http.ts +0 -3756
  41. package/convex/inbound.ts +0 -32
  42. package/convex/logs.ts +0 -701
  43. package/convex/migrateFilestack.ts +0 -81
  44. package/convex/migratePartnersProd.ts +0 -174
  45. package/convex/migratePratham.ts +0 -126
  46. package/convex/migrateProviderWorkspaces.ts +0 -175
  47. package/convex/mou.ts +0 -91
  48. package/convex/nurture.ts +0 -355
  49. package/convex/providerKeys.ts +0 -289
  50. package/convex/providers.ts +0 -1135
  51. package/convex/purchases.ts +0 -183
  52. package/convex/ratelimit.ts +0 -104
  53. package/convex/schema.ts +0 -926
  54. package/convex/searchLogs.ts +0 -265
  55. package/convex/seedAPILayerAPIs.ts +0 -191
  56. package/convex/seedDirectCallConfigs.ts +0 -336
  57. package/convex/seedPratham.ts +0 -149
  58. package/convex/spendAlerts.ts +0 -442
  59. package/convex/stripeActions.ts +0 -607
  60. package/convex/teams.ts +0 -243
  61. package/convex/telemetry.ts +0 -81
  62. package/convex/tsconfig.json +0 -25
  63. package/convex/updateAPIStatus.ts +0 -44
  64. package/convex/usage.ts +0 -260
  65. package/convex/usageReports.ts +0 -357
  66. package/convex/waitlist.ts +0 -55
  67. package/convex/webhooks.ts +0 -494
  68. package/convex/workspaceSettings.ts +0 -143
  69. package/convex/workspaces.ts +0 -1331
  70. package/convex.json +0 -3
  71. package/direct-test.mjs +0 -51
  72. package/email-templates/filestack-provider-outreach.html +0 -162
  73. package/email-templates/partnership-template.html +0 -116
  74. package/email-templates/pratham-draft-preview.txt +0 -57
  75. package/email-templates/pratham-partnership-draft.html +0 -141
  76. package/reports/APIClaw-Session-Report-2026-04-05.pdf +0 -0
  77. package/reports/pipeline/PIPELINE-REPORT.json +0 -153
  78. package/reports/pipeline/acquire_apisguru.json +0 -17
  79. package/reports/pipeline/capabilities.json +0 -38
  80. package/reports/pipeline/discover_azure_recursive.json +0 -1551
  81. package/reports/pipeline/discover_github.json +0 -25
  82. package/reports/pipeline/discover_github_repos.json +0 -49
  83. package/reports/pipeline/discover_swaggerhub.json +0 -24
  84. package/reports/pipeline/discover_well_known.json +0 -23
  85. package/reports/pipeline/fetch_specs.json +0 -19
  86. package/reports/pipeline/generate_providers.json +0 -14
  87. package/reports/pipeline/match_registry.json +0 -11
  88. package/reports/pipeline/parse_specs.json +0 -17
  89. package/reports/pipeline/promote_candidates.json +0 -34
  90. package/reports/pipeline/validate.json +0 -30
  91. package/reports/pipeline/validate_smoke_details.json +0 -3835
  92. package/reports/session-report-2026-04-05.html +0 -433
  93. package/seed-apis-direct.mjs +0 -106
  94. package/src/access-control.ts +0 -174
  95. package/src/adapters/base.ts +0 -364
  96. package/src/adapters/claude-desktop.ts +0 -41
  97. package/src/adapters/cline.ts +0 -88
  98. package/src/adapters/continue.ts +0 -91
  99. package/src/adapters/cursor.ts +0 -43
  100. package/src/adapters/custom.ts +0 -188
  101. package/src/adapters/detect.ts +0 -202
  102. package/src/adapters/index.ts +0 -47
  103. package/src/adapters/windsurf.ts +0 -44
  104. package/src/bin-http.ts +0 -45
  105. package/src/bin.ts +0 -34
  106. package/src/capability-router.ts +0 -331
  107. package/src/chainExecutor.ts +0 -730
  108. package/src/chainResolver.test.ts +0 -246
  109. package/src/chainResolver.ts +0 -658
  110. package/src/cli/commands/demo.ts +0 -109
  111. package/src/cli/commands/doctor.ts +0 -435
  112. package/src/cli/commands/index.ts +0 -9
  113. package/src/cli/commands/login.ts +0 -203
  114. package/src/cli/commands/mcp-install.ts +0 -373
  115. package/src/cli/commands/restore.ts +0 -333
  116. package/src/cli/commands/setup.ts +0 -297
  117. package/src/cli/commands/uninstall.ts +0 -240
  118. package/src/cli/index.ts +0 -148
  119. package/src/cli.ts +0 -370
  120. package/src/confirmation.ts +0 -296
  121. package/src/credentials.ts +0 -455
  122. package/src/credits.ts +0 -329
  123. package/src/crypto.ts +0 -75
  124. package/src/discovery.ts +0 -568
  125. package/src/enterprise/env.ts +0 -156
  126. package/src/enterprise/index.ts +0 -7
  127. package/src/enterprise/script-generator.ts +0 -481
  128. package/src/execute-dynamic.ts +0 -617
  129. package/src/execute.ts +0 -2386
  130. package/src/funnel-client.ts +0 -168
  131. package/src/funnel.test.ts +0 -187
  132. package/src/gateway-client.ts +0 -192
  133. package/src/hivr-whitelist.ts +0 -110
  134. package/src/http-api.ts +0 -286
  135. package/src/http-server-minimal.ts +0 -154
  136. package/src/index.ts +0 -2702
  137. package/src/intelligent-gateway.ts +0 -339
  138. package/src/mcp-analytics.ts +0 -156
  139. package/src/metered.ts +0 -149
  140. package/src/open-apis-generated.ts +0 -157
  141. package/src/open-apis.ts +0 -558
  142. package/src/postinstall.ts +0 -40
  143. package/src/product-whitelist.ts +0 -246
  144. package/src/proxy.ts +0 -36
  145. package/src/registration-guard.ts +0 -117
  146. package/src/session.ts +0 -129
  147. package/src/stripe.ts +0 -497
  148. package/src/telemetry.ts +0 -71
  149. package/src/test.ts +0 -135
  150. package/src/types/convex-api.d.ts +0 -20
  151. package/src/types/convex-api.ts +0 -21
  152. package/src/types.ts +0 -109
  153. package/src/ui/colors.ts +0 -219
  154. package/src/ui/errors.ts +0 -394
  155. package/src/ui/index.ts +0 -17
  156. package/src/ui/prompts.ts +0 -390
  157. package/src/ui/spinner.ts +0 -325
  158. package/src/utils/backup.ts +0 -224
  159. package/src/utils/config.ts +0 -318
  160. package/src/utils/os.ts +0 -124
  161. package/src/utils/paths.ts +0 -203
  162. package/src/webhook.ts +0 -107
  163. package/test-10-working.cjs +0 -97
  164. package/test-14-final.cjs +0 -96
  165. package/test-actual-handlers.ts +0 -92
  166. package/test-apilayer-all-14.ts +0 -249
  167. package/test-apilayer-fixed.ts +0 -248
  168. package/test-direct-endpoints.ts +0 -174
  169. package/test-exact-endpoints.ts +0 -144
  170. package/test-final.ts +0 -83
  171. package/test-full-routing.ts +0 -100
  172. package/test-handlers-correct.ts +0 -217
  173. package/test-numverify-key.ts +0 -41
  174. package/test-via-handlers.ts +0 -92
  175. package/test-worldnews.mjs +0 -26
  176. package/tsconfig.json +0 -20
@@ -1,40 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * APIClaw Postinstall Hook
4
- * Prints a welcome message with links and fires ONE anonymous funnel
5
- * event (install) so we can measure top-of-funnel truthfully.
6
- *
7
- * Disable telemetry: APICLAW_TELEMETRY=false
8
- */
9
- import { emitFunnelEvent, hasLocalMarker, setLocalMarker } from './funnel-client.js';
10
- import { getMachineFingerprint, detectMCPClient } from './session.js';
11
-
12
- const CYAN = '\x1b[36m';
13
- const RESET = '\x1b[0m';
14
- const DIM = '\x1b[2m';
15
-
16
- try {
17
- const fp = getMachineFingerprint();
18
- const dedupeKey = `install:${fp}`;
19
- if (!hasLocalMarker(dedupeKey)) {
20
- emitFunnelEvent({
21
- event: 'install',
22
- fingerprint: fp,
23
- mcpClient: detectMCPClient(),
24
- platform: process.platform,
25
- version: process.env.npm_package_version || 'unknown',
26
- dedupeKey,
27
- });
28
- setLocalMarker(dedupeKey);
29
- }
30
- } catch {
31
- /* never block install */
32
- }
33
-
34
- console.log('');
35
- console.log(` 🦞 APIClaw installed successfully!`);
36
- console.log('');
37
- console.log(` → Sign in: ${CYAN}npx @nordsym/apiclaw login${RESET}`);
38
- console.log(` → Full setup: ${CYAN}npx @nordsym/apiclaw setup${RESET} ${DIM}(auto-detects Claude, Cursor, Windsurf)${RESET}`);
39
- console.log(` ⭐ Star us: ${CYAN}https://github.com/nordsym/apiclaw${RESET} ${DIM}(helps more devs find us)${RESET}`);
40
- console.log('');
@@ -1,246 +0,0 @@
1
- /**
2
- * Multi-Product Whitelist System
3
- * Supports multiple products (Hivr, NordSym, partners) with namespaced agentIds
4
- *
5
- * Format: product:agentId
6
- * Examples: hivr:bytebee, nordsym:mollebot, partner_x:agent1
7
- */
8
-
9
- interface ProductSource {
10
- name: string;
11
- convexUrl: string;
12
- queryPath: string;
13
- agentIdField: string;
14
- authToken?: string;
15
- }
16
-
17
- // Product sources configuration
18
- const PRODUCT_SOURCES: ProductSource[] = [
19
- {
20
- name: 'hivr',
21
- convexUrl: 'https://brilliant-puffin-712.eu-west-1.convex.cloud',
22
- queryPath: 'agents:list',
23
- agentIdField: 'handle', // ✅ Fixed: Hivr agents use 'handle', not 'agentId'
24
- },
25
- // Add more products here as needed
26
- // {
27
- // name: 'nordsym',
28
- // convexUrl: 'https://nordsym-deployment.convex.cloud',
29
- // queryPath: 'team:listAgents',
30
- // agentIdField: 'memberId',
31
- // },
32
- ];
33
-
34
- // Fallback static whitelist (emergency only)
35
- const STATIC_WHITELIST = [
36
- 'hivr:bytebee',
37
- 'hivr:analyzerbee',
38
- 'hivr:buildbee',
39
- 'hivr:buzzwriter',
40
- 'hivr:hivemind',
41
- 'hivr:hivesage',
42
- 'hivr:symbot',
43
- 'hivr:hivrqueen',
44
- 'hivr:marketmaven',
45
- 'hivr:reconbee',
46
- 'hivr:sprintbee',
47
- 'hivr:quillbee',
48
- ];
49
-
50
- // Cache per product (5 minutes TTL)
51
- interface ProductCache {
52
- agents: string[];
53
- expiresAt: number;
54
- }
55
-
56
- const cache = new Map<string, ProductCache>();
57
- const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
58
-
59
- /**
60
- * Fetch agents from a single product source
61
- */
62
- async function fetchFromProduct(source: ProductSource): Promise<string[]> {
63
- try {
64
- const headers: Record<string, string> = {
65
- 'Content-Type': 'application/json',
66
- };
67
-
68
- if (source.authToken) {
69
- headers['Authorization'] = `Bearer ${source.authToken}`;
70
- }
71
-
72
- const response = await fetch(`${source.convexUrl}/api/query`, {
73
- method: 'POST',
74
- headers,
75
- body: JSON.stringify({
76
- path: source.queryPath,
77
- args: {},
78
- }),
79
- });
80
-
81
- if (!response.ok) {
82
- console.warn(`[Whitelist] ${source.name}: HTTP ${response.status}`);
83
- return [];
84
- }
85
-
86
- const result = await response.json() as any;
87
-
88
- // Convex HTTP API returns { status: "success", value: [...] }
89
- const data = result.value || result;
90
-
91
- if (!Array.isArray(data)) {
92
- console.warn(`[Whitelist] ${source.name}: Invalid response format`, typeof data);
93
- return [];
94
- }
95
-
96
- // Extract agentIds and add namespace
97
- const agents = data
98
- .map((item: any) => {
99
- const agentId = item[source.agentIdField];
100
- if (!agentId) return null;
101
- return `${source.name}:${String(agentId).toLowerCase().trim()}`;
102
- })
103
- .filter((id): id is string => id !== null && id.length > 0);
104
-
105
- console.log(`[Whitelist] ${source.name}: Fetched ${agents.length} agents`);
106
- return agents;
107
-
108
- } catch (error) {
109
- console.error(`[Whitelist] ${source.name}: Fetch failed`, error);
110
- return [];
111
- }
112
- }
113
-
114
- /**
115
- * Fetch and merge agents from all product sources
116
- */
117
- async function fetchAllProducts(): Promise<string[]> {
118
- const results = await Promise.allSettled(
119
- PRODUCT_SOURCES.map(source => fetchFromProduct(source))
120
- );
121
-
122
- const allAgents: string[] = [];
123
-
124
- for (const result of results) {
125
- if (result.status === 'fulfilled') {
126
- allAgents.push(...result.value);
127
- }
128
- }
129
-
130
- // If no products returned data, use static fallback
131
- if (allAgents.length === 0) {
132
- console.warn('[Whitelist] All sources failed, using static fallback');
133
- return STATIC_WHITELIST;
134
- }
135
-
136
- return allAgents;
137
- }
138
-
139
- /**
140
- * Get current whitelist (cached or fresh)
141
- */
142
- export async function getWhitelist(): Promise<string[]> {
143
- const now = Date.now();
144
-
145
- // Check if any cache entry is still valid
146
- const validCaches: string[] = [];
147
- for (const [product, cached] of cache.entries()) {
148
- if (now < cached.expiresAt) {
149
- validCaches.push(...cached.agents);
150
- }
151
- }
152
-
153
- // If all caches valid, return merged
154
- if (validCaches.length > 0 && cache.size === PRODUCT_SOURCES.length) {
155
- return validCaches;
156
- }
157
-
158
- // Fetch fresh data
159
- const agents = await fetchAllProducts();
160
-
161
- // Update cache per product
162
- const agentsByProduct = new Map<string, string[]>();
163
- for (const agent of agents) {
164
- const [product] = agent.split(':');
165
- if (!agentsByProduct.has(product)) {
166
- agentsByProduct.set(product, []);
167
- }
168
- agentsByProduct.get(product)!.push(agent);
169
- }
170
-
171
- for (const [product, productAgents] of agentsByProduct.entries()) {
172
- cache.set(product, {
173
- agents: productAgents,
174
- expiresAt: now + CACHE_TTL,
175
- });
176
- }
177
-
178
- return agents;
179
- }
180
-
181
- /**
182
- * Check if agentId is authorized
183
- * Supports both namespaced (product:agent) and legacy (agent) formats
184
- */
185
- export async function isAuthorized(agentId: string | undefined): Promise<boolean> {
186
- if (!agentId) return false;
187
-
188
- const normalized = agentId.toLowerCase().trim();
189
- const whitelist = await getWhitelist();
190
-
191
- // Check exact match (namespaced)
192
- if (whitelist.includes(normalized)) {
193
- return true;
194
- }
195
-
196
- // Legacy support: check if agentId matches any product's agent (without namespace)
197
- // e.g., "bytebee" matches "hivr:bytebee"
198
- if (!normalized.includes(':')) {
199
- const legacyMatch = whitelist.some(entry => {
200
- const [, agent] = entry.split(':');
201
- return agent === normalized;
202
- });
203
- if (legacyMatch) {
204
- console.log(`[Whitelist] Legacy match for ${normalized}`);
205
- return true;
206
- }
207
- }
208
-
209
- return false;
210
- }
211
-
212
- /**
213
- * Extract product name from agentId
214
- */
215
- export function getProduct(agentId: string): string | null {
216
- const [product] = agentId.split(':');
217
- return product || null;
218
- }
219
-
220
- /**
221
- * Force refresh whitelist (call after adding new agent)
222
- */
223
- export function invalidateCache(product?: string): void {
224
- if (product) {
225
- cache.delete(product);
226
- console.log(`[Whitelist] Cache invalidated for ${product}`);
227
- } else {
228
- cache.clear();
229
- console.log('[Whitelist] All caches invalidated');
230
- }
231
- }
232
-
233
- /**
234
- * Add new product source dynamically
235
- */
236
- export function addProductSource(source: ProductSource): void {
237
- const existing = PRODUCT_SOURCES.find(s => s.name === source.name);
238
- if (existing) {
239
- console.warn(`[Whitelist] Product ${source.name} already exists, updating`);
240
- Object.assign(existing, source);
241
- } else {
242
- PRODUCT_SOURCES.push(source);
243
- console.log(`[Whitelist] Added product source: ${source.name}`);
244
- }
245
- invalidateCache(source.name);
246
- }
package/src/proxy.ts DELETED
@@ -1,36 +0,0 @@
1
- /**
2
- * APIClaw Proxy - Fallback to hosted API when no local credentials
3
- */
4
-
5
- import { readSession, getMachineFingerprint } from './session.js';
6
-
7
- const PROXY_BASE = "https://brilliant-puffin-712.eu-west-1.convex.site/proxy";
8
-
9
- export async function callProxy(provider: string, params: any): Promise<any> {
10
- const url = `${PROXY_BASE}/${provider}`;
11
-
12
- // Get session and fingerprint for tracking
13
- const session = readSession();
14
- const fingerprint = getMachineFingerprint();
15
- const identifier = session?.workspaceId || `anon:${fingerprint}`;
16
-
17
- const response = await fetch(url, {
18
- method: "POST",
19
- headers: {
20
- "Content-Type": "application/json",
21
- "X-APIClaw-Identifier": identifier,
22
- "X-APIClaw-Provider": provider,
23
- "X-APIClaw-Action": params.action || "call",
24
- },
25
- body: JSON.stringify(params),
26
- });
27
-
28
- if (!response.ok) {
29
- const errorData = await response.json().catch(() => ({ error: "Proxy request failed" })) as { error?: string };
30
- throw new Error(errorData.error || `Proxy error: ${response.status}`);
31
- }
32
-
33
- return response.json();
34
- }
35
-
36
- export const PROXY_PROVIDERS = ["openrouter", "brave_search", "resend", "elevenlabs", "46elks", "twilio", "replicate", "firecrawl", "e2b", "groq", "deepgram", "serper", "mistral", "cohere", "together", "stability", "assemblyai", "github", "apilayer"];
@@ -1,117 +0,0 @@
1
- /**
2
- * Client-side registration guard — single source of truth for "does this
3
- * workspaceContext pass the verified-owner check before an API call".
4
- *
5
- * Free paths (discover_apis, list_* , *_help) do NOT call this.
6
- * Paying paths (call_api single + chain, capability, resume_chain) DO.
7
- */
8
-
9
- export interface WorkspaceContextLike {
10
- sessionToken: string;
11
- workspaceId: string;
12
- email: string;
13
- tier: string;
14
- status: string;
15
- usageRemaining: number;
16
- usageCount: number;
17
- }
18
-
19
- export type GuardResult =
20
- | { ok: true; ctx: WorkspaceContextLike }
21
- | { ok: false; reason: GuardReason; payload: Record<string, unknown> };
22
-
23
- export type GuardReason =
24
- | "no_session"
25
- | "not_verified"
26
- | "quota_exceeded"
27
- | "pending_verification";
28
-
29
- // Paths that are allowed without a verified owner.
30
- export const FREE_CALL_PATHS = new Set<string>([
31
- "discover_apis",
32
- "list_categories",
33
- "list_connected",
34
- "list_capabilities",
35
- "apiclaw_help",
36
- "register_owner",
37
- "verify_code",
38
- "check_workspace_status",
39
- "remind_owner",
40
- "get_chain_status",
41
- "setup_metered_billing",
42
- "get_usage_summary",
43
- "estimate_cost",
44
- "check_balance",
45
- "add_credits",
46
- "get_api_details",
47
- "purchase_access",
48
- ]);
49
-
50
- // Paths that MUST go through requireVerifiedOwner.
51
- export const ENFORCED_CALL_PATHS = new Set<string>([
52
- "call_api",
53
- "capability",
54
- "resume_chain",
55
- ]);
56
-
57
- export function requireVerifiedOwner(
58
- workspaceContext: WorkspaceContextLike | null
59
- ): GuardResult {
60
- if (!workspaceContext) {
61
- return {
62
- ok: false,
63
- reason: "no_session",
64
- payload: {
65
- status: "registration_required",
66
- error: "Registration required to call APIs.",
67
- message:
68
- "Ask the user for their email, then call register_owner({ email }). A 6-digit code will be sent. Then call verify_code with the code.",
69
- action: "register_owner",
70
- free_tier: "50 API calls/month -- completely free.",
71
- },
72
- };
73
- }
74
-
75
- if (!workspaceContext.email) {
76
- return {
77
- ok: false,
78
- reason: "pending_verification",
79
- payload: {
80
- status: "registration_required",
81
- error: "Workspace is not linked to a verified email yet.",
82
- message: "Run register_owner({ email }) and verify_code to activate.",
83
- action: "register_owner",
84
- },
85
- };
86
- }
87
-
88
- if (workspaceContext.status !== "active") {
89
- return {
90
- ok: false,
91
- reason: "not_verified",
92
- payload: {
93
- status: "pending_verification",
94
- error: `Workspace status: ${workspaceContext.status}. Please verify your email.`,
95
- action: "verify_code",
96
- },
97
- };
98
- }
99
-
100
- if (workspaceContext.usageRemaining === 0) {
101
- return {
102
- ok: false,
103
- reason: "quota_exceeded",
104
- payload: {
105
- status: "quota_exceeded",
106
- error:
107
- workspaceContext.tier === "free"
108
- ? "You've hit the free tier limit. Upgrade at https://apiclaw.cloud/upgrade."
109
- : "Quota exceeded.",
110
- upgrade_url: "https://apiclaw.cloud/upgrade",
111
- action: "upgrade",
112
- },
113
- };
114
- }
115
-
116
- return { ok: true, ctx: workspaceContext };
117
- }
package/src/session.ts DELETED
@@ -1,129 +0,0 @@
1
- /**
2
- * Session management for APIClaw MCP server
3
- * Stores session token locally at ~/.apiclaw/session
4
- */
5
-
6
- import * as fs from 'fs';
7
- import * as path from 'path';
8
- import * as os from 'os';
9
-
10
- export interface SessionData {
11
- sessionToken: string;
12
- workspaceId: string;
13
- email: string;
14
- createdAt: number;
15
- }
16
-
17
- const SESSION_DIR = path.join(os.homedir(), '.apiclaw');
18
- const SESSION_FILE = path.join(SESSION_DIR, 'session');
19
- const SESSION_FILE_LEGACY = path.join(SESSION_DIR, 'session.json');
20
-
21
- /**
22
- * Ensure the ~/.apiclaw directory exists
23
- */
24
- function ensureSessionDir(): void {
25
- if (!fs.existsSync(SESSION_DIR)) {
26
- fs.mkdirSync(SESSION_DIR, { mode: 0o700 });
27
- }
28
- }
29
-
30
- /**
31
- * Read session from ~/.apiclaw/session
32
- * Returns null if no session file exists or if it's invalid
33
- */
34
- export function readSession(): SessionData | null {
35
- try {
36
- // Try primary session file first
37
- if (fs.existsSync(SESSION_FILE)) {
38
- const content = fs.readFileSync(SESSION_FILE, 'utf8');
39
- const data = JSON.parse(content) as SessionData;
40
- if (data.sessionToken && data.workspaceId && data.email) {
41
- return data;
42
- }
43
- // Invalid (e.g. empty email from anonymous write) — clear and fall through
44
- console.error('[APIClaw] Invalid session file, checking legacy...');
45
- fs.unlinkSync(SESSION_FILE);
46
- }
47
-
48
- // Fall back to session.json (written by CLI login in older versions)
49
- if (fs.existsSync(SESSION_FILE_LEGACY)) {
50
- const content = fs.readFileSync(SESSION_FILE_LEGACY, 'utf8');
51
- const data = JSON.parse(content) as SessionData;
52
- if (data.sessionToken && data.workspaceId && data.email) {
53
- console.error(`[APIClaw] Migrating session.json → session for ${data.email}`);
54
- // Migrate to canonical location
55
- fs.writeFileSync(SESSION_FILE, JSON.stringify({ ...data, createdAt: data.createdAt || Date.now() }, null, 2), { mode: 0o600 });
56
- return data;
57
- }
58
- }
59
-
60
- return null;
61
- } catch (error) {
62
- console.error('[APIClaw] Error reading session:', error);
63
- return null;
64
- }
65
- }
66
-
67
- /**
68
- * Write session to ~/.apiclaw/session
69
- */
70
- export function writeSession(sessionToken: string, workspaceId: string, email: string): void {
71
- try {
72
- ensureSessionDir();
73
-
74
- const data: SessionData = {
75
- sessionToken,
76
- workspaceId,
77
- email,
78
- createdAt: Date.now(),
79
- };
80
-
81
- fs.writeFileSync(SESSION_FILE, JSON.stringify(data, null, 2), {
82
- mode: 0o600, // Read/write for owner only
83
- });
84
-
85
- console.error(`[APIClaw] Session saved for ${email}`);
86
- } catch (error) {
87
- console.error('[APIClaw] Error writing session:', error);
88
- throw error;
89
- }
90
- }
91
-
92
- /**
93
- * Clear session file
94
- */
95
- export function clearSession(): void {
96
- try {
97
- if (fs.existsSync(SESSION_FILE)) {
98
- fs.unlinkSync(SESSION_FILE);
99
- console.error('[APIClaw] Session cleared');
100
- }
101
- } catch (error) {
102
- console.error('[APIClaw] Error clearing session:', error);
103
- }
104
- }
105
-
106
- /**
107
- * Get machine fingerprint (for session binding)
108
- * Uses hostname + username as a simple fingerprint
109
- */
110
- export function getMachineFingerprint(): string {
111
- const hostname = os.hostname();
112
- const username = os.userInfo().username;
113
- return `${hostname}:${username}`;
114
- }
115
-
116
- /**
117
- * Detect which MCP client is running the server
118
- * Priority: explicit env (set by mcp-install) → known env hints → fallback
119
- */
120
- export function detectMCPClient(): string {
121
- // 1. Explicit env (injected by mcp-install adapters)
122
- if (process.env.APICLAW_MCP_CLIENT) return process.env.APICLAW_MCP_CLIENT;
123
- // 2. Known environment variable hints
124
- if (process.env.CURSOR_TRACE_DIR) return 'cursor';
125
- if (process.env.CLAUDE_CODE) return 'claude-code';
126
- if (process.env.WINDSURF_SESSION) return 'windsurf';
127
- // 3. Fallback
128
- return 'unknown';
129
- }