@nordsym/apiclaw 1.3.6 → 1.3.8

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 (214) hide show
  1. package/README.md +422 -169
  2. package/convex/_generated/api.d.ts +16 -0
  3. package/convex/agents.ts +403 -0
  4. package/convex/billing.ts +651 -216
  5. package/convex/crons.ts +17 -0
  6. package/convex/directCall.ts +80 -0
  7. package/convex/earnProgress.ts +753 -0
  8. package/convex/email.ts +135 -82
  9. package/convex/feedback.ts +265 -0
  10. package/convex/http.ts +80 -4
  11. package/convex/logs.ts +304 -0
  12. package/convex/providerKeys.ts +289 -0
  13. package/convex/providers.ts +18 -0
  14. package/convex/schema.ts +185 -1
  15. package/convex/stripeActions.ts +512 -0
  16. package/convex/webhooks.ts +494 -0
  17. package/convex/workspaces.ts +158 -3
  18. package/dist/adapters/base.d.ts +112 -0
  19. package/dist/adapters/base.d.ts.map +1 -0
  20. package/dist/adapters/base.js +247 -0
  21. package/dist/adapters/base.js.map +1 -0
  22. package/dist/adapters/claude-desktop.d.ts +12 -0
  23. package/dist/adapters/claude-desktop.d.ts.map +1 -0
  24. package/dist/adapters/claude-desktop.js +36 -0
  25. package/dist/adapters/claude-desktop.js.map +1 -0
  26. package/dist/adapters/cline.d.ts +20 -0
  27. package/dist/adapters/cline.d.ts.map +1 -0
  28. package/dist/adapters/cline.js +77 -0
  29. package/dist/adapters/cline.js.map +1 -0
  30. package/dist/adapters/continue.d.ts +26 -0
  31. package/dist/adapters/continue.d.ts.map +1 -0
  32. package/dist/adapters/continue.js +68 -0
  33. package/dist/adapters/continue.js.map +1 -0
  34. package/dist/adapters/cursor.d.ts +12 -0
  35. package/dist/adapters/cursor.d.ts.map +1 -0
  36. package/dist/adapters/cursor.js +38 -0
  37. package/dist/adapters/cursor.js.map +1 -0
  38. package/dist/adapters/custom.d.ts +47 -0
  39. package/dist/adapters/custom.d.ts.map +1 -0
  40. package/dist/adapters/custom.js +146 -0
  41. package/dist/adapters/custom.js.map +1 -0
  42. package/dist/adapters/detect.d.ts +69 -0
  43. package/dist/adapters/detect.d.ts.map +1 -0
  44. package/dist/adapters/detect.js +158 -0
  45. package/dist/adapters/detect.js.map +1 -0
  46. package/dist/adapters/index.d.ts +21 -0
  47. package/dist/adapters/index.d.ts.map +1 -0
  48. package/dist/adapters/index.js +23 -0
  49. package/dist/adapters/index.js.map +1 -0
  50. package/dist/adapters/windsurf.d.ts +12 -0
  51. package/dist/adapters/windsurf.d.ts.map +1 -0
  52. package/dist/adapters/windsurf.js +39 -0
  53. package/dist/adapters/windsurf.js.map +1 -0
  54. package/dist/bin.d.ts +9 -0
  55. package/dist/bin.d.ts.map +1 -0
  56. package/dist/bin.js +19 -0
  57. package/dist/bin.js.map +1 -0
  58. package/dist/cli/commands/doctor.d.ts +34 -0
  59. package/dist/cli/commands/doctor.d.ts.map +1 -0
  60. package/dist/cli/commands/doctor.js +312 -0
  61. package/dist/cli/commands/doctor.js.map +1 -0
  62. package/dist/cli/commands/index.d.ts +9 -0
  63. package/dist/cli/commands/index.d.ts.map +1 -0
  64. package/dist/cli/commands/index.js +9 -0
  65. package/dist/cli/commands/index.js.map +1 -0
  66. package/dist/cli/commands/restore.d.ts +50 -0
  67. package/dist/cli/commands/restore.d.ts.map +1 -0
  68. package/dist/cli/commands/restore.js +260 -0
  69. package/dist/cli/commands/restore.js.map +1 -0
  70. package/dist/cli/commands/setup.d.ts +19 -0
  71. package/dist/cli/commands/setup.d.ts.map +1 -0
  72. package/dist/cli/commands/setup.js +206 -0
  73. package/dist/cli/commands/setup.js.map +1 -0
  74. package/dist/cli/commands/uninstall.d.ts +37 -0
  75. package/dist/cli/commands/uninstall.d.ts.map +1 -0
  76. package/dist/cli/commands/uninstall.js +189 -0
  77. package/dist/cli/commands/uninstall.js.map +1 -0
  78. package/dist/cli/index.d.ts +7 -0
  79. package/dist/cli/index.d.ts.map +1 -0
  80. package/dist/cli/index.js +97 -0
  81. package/dist/cli/index.js.map +1 -0
  82. package/dist/discovery.d.ts +6 -2
  83. package/dist/discovery.d.ts.map +1 -1
  84. package/dist/discovery.js +296 -2
  85. package/dist/discovery.js.map +1 -1
  86. package/dist/enterprise/env.d.ts +56 -0
  87. package/dist/enterprise/env.d.ts.map +1 -0
  88. package/dist/enterprise/env.js +124 -0
  89. package/dist/enterprise/env.js.map +1 -0
  90. package/dist/enterprise/index.d.ts +7 -0
  91. package/dist/enterprise/index.d.ts.map +1 -0
  92. package/dist/enterprise/index.js +7 -0
  93. package/dist/enterprise/index.js.map +1 -0
  94. package/dist/enterprise/script-generator.d.ts +32 -0
  95. package/dist/enterprise/script-generator.d.ts.map +1 -0
  96. package/dist/enterprise/script-generator.js +461 -0
  97. package/dist/enterprise/script-generator.js.map +1 -0
  98. package/dist/execute.d.ts +21 -0
  99. package/dist/execute.d.ts.map +1 -1
  100. package/dist/execute.js +231 -0
  101. package/dist/execute.js.map +1 -1
  102. package/dist/index.js +257 -7
  103. package/dist/index.js.map +1 -1
  104. package/dist/metered.d.ts +62 -0
  105. package/dist/metered.d.ts.map +1 -0
  106. package/dist/metered.js +81 -0
  107. package/dist/metered.js.map +1 -0
  108. package/dist/stripe.d.ts +62 -0
  109. package/dist/stripe.d.ts.map +1 -1
  110. package/dist/stripe.js +212 -0
  111. package/dist/stripe.js.map +1 -1
  112. package/dist/types.d.ts +29 -0
  113. package/dist/types.d.ts.map +1 -1
  114. package/dist/ui/colors.d.ts +111 -0
  115. package/dist/ui/colors.d.ts.map +1 -0
  116. package/dist/ui/colors.js +185 -0
  117. package/dist/ui/colors.js.map +1 -0
  118. package/dist/ui/errors.d.ts +69 -0
  119. package/dist/ui/errors.d.ts.map +1 -0
  120. package/dist/ui/errors.js +334 -0
  121. package/dist/ui/errors.js.map +1 -0
  122. package/dist/ui/index.d.ts +10 -0
  123. package/dist/ui/index.d.ts.map +1 -0
  124. package/dist/ui/index.js +14 -0
  125. package/dist/ui/index.js.map +1 -0
  126. package/dist/ui/prompts.d.ts +88 -0
  127. package/dist/ui/prompts.d.ts.map +1 -0
  128. package/dist/ui/prompts.js +295 -0
  129. package/dist/ui/prompts.js.map +1 -0
  130. package/dist/ui/spinner.d.ts +112 -0
  131. package/dist/ui/spinner.d.ts.map +1 -0
  132. package/dist/ui/spinner.js +229 -0
  133. package/dist/ui/spinner.js.map +1 -0
  134. package/dist/utils/backup.d.ts +48 -0
  135. package/dist/utils/backup.d.ts.map +1 -0
  136. package/dist/utils/backup.js +182 -0
  137. package/dist/utils/backup.js.map +1 -0
  138. package/dist/utils/config.d.ts +80 -0
  139. package/dist/utils/config.d.ts.map +1 -0
  140. package/dist/utils/config.js +221 -0
  141. package/dist/utils/config.js.map +1 -0
  142. package/dist/utils/os.d.ts +45 -0
  143. package/dist/utils/os.d.ts.map +1 -0
  144. package/dist/utils/os.js +106 -0
  145. package/dist/utils/os.js.map +1 -0
  146. package/dist/utils/paths.d.ts +38 -0
  147. package/dist/utils/paths.d.ts.map +1 -0
  148. package/dist/utils/paths.js +160 -0
  149. package/dist/utils/paths.js.map +1 -0
  150. package/docs/PRD-BILLING.md +226 -0
  151. package/docs/PRD-EARN-SYSTEM.md +261 -0
  152. package/docs/PRD-MCP-AUTO-SETUP.md +623 -0
  153. package/docs/PRD-final-polish.md +117 -0
  154. package/docs/PRD-mobile-responsive.md +56 -0
  155. package/docs/PRD-navigation-expansion.md +295 -0
  156. package/docs/PRD-stripe-billing.md +312 -0
  157. package/docs/PRD-workspace-cleanup.md +200 -0
  158. package/docs/enterprise-deployment.md +728 -0
  159. package/landing/next.config.mjs +14 -0
  160. package/landing/public/stats.json +4 -2
  161. package/landing/scripts/generate-stats.js +12 -0
  162. package/landing/src/app/api/billing/checkout/route.ts +109 -0
  163. package/landing/src/app/api/billing/payment-method/route.ts +118 -0
  164. package/landing/src/app/api/billing/portal/route.ts +64 -0
  165. package/landing/src/app/api/workspace-auth/magic-link/route.ts +6 -3
  166. package/landing/src/app/auth/verify/page.tsx +31 -9
  167. package/landing/src/app/docs/page.tsx +1 -1
  168. package/landing/src/app/earn/page.tsx +6 -6
  169. package/landing/src/app/join/page.tsx +49 -0
  170. package/landing/src/app/login/page.tsx +8 -2
  171. package/landing/src/app/page.tsx +81 -96
  172. package/landing/src/app/providers/dashboard/page.tsx +1 -1
  173. package/landing/src/app/providers/register/page.tsx +1 -1
  174. package/landing/src/app/workspace/page.tsx +3269 -534
  175. package/landing/src/components/CheckoutButton.tsx +188 -0
  176. package/landing/src/components/EarnCreditsTab.tsx +842 -0
  177. package/landing/src/components/Toast.tsx +84 -0
  178. package/landing/src/lib/stats.json +3 -1
  179. package/package.json +9 -2
  180. package/src/adapters/base.ts +363 -0
  181. package/src/adapters/claude-desktop.ts +41 -0
  182. package/src/adapters/cline.ts +88 -0
  183. package/src/adapters/continue.ts +91 -0
  184. package/src/adapters/cursor.ts +43 -0
  185. package/src/adapters/custom.ts +188 -0
  186. package/src/adapters/detect.ts +202 -0
  187. package/src/adapters/index.ts +47 -0
  188. package/src/adapters/windsurf.ts +44 -0
  189. package/src/bin.ts +19 -0
  190. package/src/cli/commands/doctor.ts +367 -0
  191. package/src/cli/commands/index.ts +9 -0
  192. package/src/cli/commands/restore.ts +333 -0
  193. package/src/cli/commands/setup.ts +276 -0
  194. package/src/cli/commands/uninstall.ts +240 -0
  195. package/src/cli/index.ts +107 -0
  196. package/src/discovery.ts +328 -3
  197. package/src/enterprise/env.ts +156 -0
  198. package/src/enterprise/index.ts +7 -0
  199. package/src/enterprise/script-generator.ts +481 -0
  200. package/src/execute.ts +256 -0
  201. package/src/index.ts +290 -7
  202. package/src/metered.ts +149 -0
  203. package/src/stripe.ts +253 -0
  204. package/src/types.ts +32 -0
  205. package/src/ui/colors.ts +219 -0
  206. package/src/ui/errors.ts +394 -0
  207. package/src/ui/index.ts +17 -0
  208. package/src/ui/prompts.ts +390 -0
  209. package/src/ui/spinner.ts +325 -0
  210. package/src/utils/backup.ts +224 -0
  211. package/src/utils/config.ts +315 -0
  212. package/src/utils/os.ts +124 -0
  213. package/src/utils/paths.ts +203 -0
  214. package/landing/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Backup System
3
+ * Handles timestamped backups of config files with automatic cleanup
4
+ */
5
+
6
+ import { existsSync, mkdirSync, readdirSync, unlinkSync, copyFileSync, statSync } from 'fs';
7
+ import { dirname, basename, join, extname } from 'path';
8
+
9
+ const MAX_BACKUPS = 5;
10
+ const BACKUP_PATTERN = /\.backup\.(\d+)\.json$/;
11
+
12
+ export interface BackupResult {
13
+ success: boolean;
14
+ backupPath: string | null;
15
+ error?: string;
16
+ }
17
+
18
+ export interface BackupInfo {
19
+ path: string;
20
+ timestamp: number;
21
+ date: Date;
22
+ filename: string;
23
+ }
24
+
25
+ /**
26
+ * Generate backup filename with timestamp
27
+ */
28
+ function generateBackupFilename(originalPath: string): string {
29
+ const dir = dirname(originalPath);
30
+ const base = basename(originalPath, '.json');
31
+ const timestamp = Date.now();
32
+
33
+ return join(dir, `${base}.backup.${timestamp}.json`);
34
+ }
35
+
36
+ /**
37
+ * Create a backup of a config file
38
+ */
39
+ export function createBackup(configPath: string): BackupResult {
40
+ try {
41
+ // Check if original file exists
42
+ if (!existsSync(configPath)) {
43
+ return {
44
+ success: true,
45
+ backupPath: null,
46
+ // No backup needed if file doesn't exist
47
+ };
48
+ }
49
+
50
+ // Ensure backup directory exists
51
+ const dir = dirname(configPath);
52
+ if (!existsSync(dir)) {
53
+ mkdirSync(dir, { recursive: true });
54
+ }
55
+
56
+ // Generate backup path
57
+ const backupPath = generateBackupFilename(configPath);
58
+
59
+ // Copy file
60
+ copyFileSync(configPath, backupPath);
61
+
62
+ // Cleanup old backups
63
+ cleanupOldBackups(configPath);
64
+
65
+ return {
66
+ success: true,
67
+ backupPath,
68
+ };
69
+ } catch (error) {
70
+ return {
71
+ success: false,
72
+ backupPath: null,
73
+ error: error instanceof Error ? error.message : 'Unknown error creating backup',
74
+ };
75
+ }
76
+ }
77
+
78
+ /**
79
+ * List all backups for a config file
80
+ */
81
+ export function listBackups(configPath: string): BackupInfo[] {
82
+ const dir = dirname(configPath);
83
+ const baseName = basename(configPath, '.json');
84
+
85
+ if (!existsSync(dir)) {
86
+ return [];
87
+ }
88
+
89
+ const files = readdirSync(dir);
90
+ const backups: BackupInfo[] = [];
91
+
92
+ for (const file of files) {
93
+ // Match backup pattern: {basename}.backup.{timestamp}.json
94
+ const match = file.match(new RegExp(`^${escapeRegex(baseName)}\\.backup\\.(\\d+)\\.json$`));
95
+
96
+ if (match) {
97
+ const timestamp = parseInt(match[1], 10);
98
+ const fullPath = join(dir, file);
99
+
100
+ backups.push({
101
+ path: fullPath,
102
+ timestamp,
103
+ date: new Date(timestamp),
104
+ filename: file,
105
+ });
106
+ }
107
+ }
108
+
109
+ // Sort by timestamp, newest first
110
+ return backups.sort((a, b) => b.timestamp - a.timestamp);
111
+ }
112
+
113
+ /**
114
+ * Get the most recent backup
115
+ */
116
+ export function getLatestBackup(configPath: string): BackupInfo | null {
117
+ const backups = listBackups(configPath);
118
+ return backups[0] || null;
119
+ }
120
+
121
+ /**
122
+ * Cleanup old backups, keeping only MAX_BACKUPS most recent
123
+ */
124
+ export function cleanupOldBackups(configPath: string): number {
125
+ const backups = listBackups(configPath);
126
+ let deleted = 0;
127
+
128
+ // Keep only the first MAX_BACKUPS
129
+ const toDelete = backups.slice(MAX_BACKUPS);
130
+
131
+ for (const backup of toDelete) {
132
+ try {
133
+ unlinkSync(backup.path);
134
+ deleted++;
135
+ } catch {
136
+ // Ignore deletion errors
137
+ }
138
+ }
139
+
140
+ return deleted;
141
+ }
142
+
143
+ /**
144
+ * Restore from a backup
145
+ */
146
+ export function restoreBackup(backupPath: string, targetPath: string): BackupResult {
147
+ try {
148
+ if (!existsSync(backupPath)) {
149
+ return {
150
+ success: false,
151
+ backupPath: null,
152
+ error: `Backup file not found: ${backupPath}`,
153
+ };
154
+ }
155
+
156
+ // Backup current file before restoring
157
+ const preRestoreBackup = createBackup(targetPath);
158
+
159
+ // Copy backup to target
160
+ copyFileSync(backupPath, targetPath);
161
+
162
+ return {
163
+ success: true,
164
+ backupPath: preRestoreBackup.backupPath,
165
+ };
166
+ } catch (error) {
167
+ return {
168
+ success: false,
169
+ backupPath: null,
170
+ error: error instanceof Error ? error.message : 'Unknown error restoring backup',
171
+ };
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Restore from the most recent backup
177
+ */
178
+ export function restoreLatestBackup(configPath: string): BackupResult {
179
+ const latest = getLatestBackup(configPath);
180
+
181
+ if (!latest) {
182
+ return {
183
+ success: false,
184
+ backupPath: null,
185
+ error: 'No backups found',
186
+ };
187
+ }
188
+
189
+ return restoreBackup(latest.path, configPath);
190
+ }
191
+
192
+ /**
193
+ * Format backup info for display
194
+ */
195
+ export function formatBackupInfo(backup: BackupInfo): string {
196
+ const date = backup.date.toLocaleString();
197
+ return `${backup.filename} (${date})`;
198
+ }
199
+
200
+ /**
201
+ * Escape regex special characters
202
+ */
203
+ function escapeRegex(str: string): string {
204
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
205
+ }
206
+
207
+ /**
208
+ * Delete all backups for a config file
209
+ */
210
+ export function deleteAllBackups(configPath: string): number {
211
+ const backups = listBackups(configPath);
212
+ let deleted = 0;
213
+
214
+ for (const backup of backups) {
215
+ try {
216
+ unlinkSync(backup.path);
217
+ deleted++;
218
+ } catch {
219
+ // Ignore deletion errors
220
+ }
221
+ }
222
+
223
+ return deleted;
224
+ }
@@ -0,0 +1,315 @@
1
+ /**
2
+ * Config Management
3
+ * Handles JSON read/write/merge operations for MCP config files
4
+ */
5
+
6
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
7
+ import { dirname } from 'path';
8
+ import { createBackup } from './backup.js';
9
+
10
+ export interface MCPServerConfig {
11
+ command: string;
12
+ args?: string[];
13
+ env?: Record<string, string>;
14
+ }
15
+
16
+ export interface MCPConfig {
17
+ mcpServers?: Record<string, MCPServerConfig>;
18
+ [key: string]: unknown;
19
+ }
20
+
21
+ // Continue uses array format for mcpServers
22
+ export interface ContinueServerConfig {
23
+ name: string;
24
+ command: string;
25
+ args?: string[];
26
+ env?: Record<string, string>;
27
+ }
28
+
29
+ export interface ContinueConfig {
30
+ mcpServers?: ContinueServerConfig[];
31
+ [key: string]: unknown;
32
+ }
33
+
34
+ export interface ConfigReadResult {
35
+ success: boolean;
36
+ config: MCPConfig | ContinueConfig | null;
37
+ error?: string;
38
+ isNew?: boolean;
39
+ }
40
+
41
+ export interface ConfigWriteResult {
42
+ success: boolean;
43
+ error?: string;
44
+ backupPath?: string | null;
45
+ }
46
+
47
+ export interface MergeOptions {
48
+ force?: boolean;
49
+ workspace?: string;
50
+ serverName?: string;
51
+ }
52
+
53
+ /**
54
+ * Read and parse a JSON config file
55
+ */
56
+ export function readConfig(configPath: string): ConfigReadResult {
57
+ try {
58
+ if (!existsSync(configPath)) {
59
+ return {
60
+ success: true,
61
+ config: {},
62
+ isNew: true,
63
+ };
64
+ }
65
+
66
+ const content = readFileSync(configPath, 'utf-8');
67
+
68
+ // Handle empty files
69
+ if (!content.trim()) {
70
+ return {
71
+ success: true,
72
+ config: {},
73
+ isNew: true,
74
+ };
75
+ }
76
+
77
+ const config = JSON.parse(content);
78
+
79
+ return {
80
+ success: true,
81
+ config,
82
+ isNew: false,
83
+ };
84
+ } catch (error) {
85
+ if (error instanceof SyntaxError) {
86
+ return {
87
+ success: false,
88
+ config: null,
89
+ error: `Invalid JSON in config file: ${error.message}`,
90
+ };
91
+ }
92
+
93
+ return {
94
+ success: false,
95
+ config: null,
96
+ error: error instanceof Error ? error.message : 'Unknown error reading config',
97
+ };
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Write config to file with backup
103
+ */
104
+ export function writeConfig(
105
+ configPath: string,
106
+ config: MCPConfig | ContinueConfig,
107
+ createBackupFirst = true
108
+ ): ConfigWriteResult {
109
+ try {
110
+ // Ensure directory exists
111
+ const dir = dirname(configPath);
112
+ if (!existsSync(dir)) {
113
+ mkdirSync(dir, { recursive: true });
114
+ }
115
+
116
+ // Create backup if file exists and backup requested
117
+ let backupPath: string | null = null;
118
+ if (createBackupFirst && existsSync(configPath)) {
119
+ const backupResult = createBackup(configPath);
120
+ if (!backupResult.success) {
121
+ return {
122
+ success: false,
123
+ error: `Failed to create backup: ${backupResult.error}`,
124
+ };
125
+ }
126
+ backupPath = backupResult.backupPath;
127
+ }
128
+
129
+ // Validate JSON before writing
130
+ const content = JSON.stringify(config, null, 2);
131
+ JSON.parse(content); // Validation parse
132
+
133
+ // Write file
134
+ writeFileSync(configPath, content, 'utf-8');
135
+
136
+ return {
137
+ success: true,
138
+ backupPath,
139
+ };
140
+ } catch (error) {
141
+ return {
142
+ success: false,
143
+ error: error instanceof Error ? error.message : 'Unknown error writing config',
144
+ };
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Deep merge two objects
150
+ */
151
+ export function deepMerge<T extends Record<string, unknown>>(
152
+ target: T,
153
+ source: Partial<T>
154
+ ): T {
155
+ const result = { ...target };
156
+
157
+ for (const key in source) {
158
+ const sourceValue = source[key];
159
+ const targetValue = result[key];
160
+
161
+ if (
162
+ sourceValue !== null &&
163
+ typeof sourceValue === 'object' &&
164
+ !Array.isArray(sourceValue) &&
165
+ targetValue !== null &&
166
+ typeof targetValue === 'object' &&
167
+ !Array.isArray(targetValue)
168
+ ) {
169
+ // Recursively merge objects
170
+ result[key] = deepMerge(
171
+ targetValue as Record<string, unknown>,
172
+ sourceValue as Record<string, unknown>
173
+ ) as T[Extract<keyof T, string>];
174
+ } else {
175
+ // Overwrite value
176
+ result[key] = sourceValue as T[Extract<keyof T, string>];
177
+ }
178
+ }
179
+
180
+ return result;
181
+ }
182
+
183
+ /**
184
+ * Generate APIClaw MCP server configuration
185
+ */
186
+ export function generateApiclawConfig(options: MergeOptions = {}): MCPServerConfig {
187
+ const config: MCPServerConfig = {
188
+ command: 'npx',
189
+ args: ['-y', '@nordsym/apiclaw'],
190
+ };
191
+
192
+ if (options.workspace) {
193
+ config.env = {
194
+ APICLAW_WORKSPACE: options.workspace,
195
+ };
196
+ }
197
+
198
+ return config;
199
+ }
200
+
201
+ /**
202
+ * Generate APIClaw config for Continue (array format)
203
+ */
204
+ export function generateApiclawContinueConfig(options: MergeOptions = {}): ContinueServerConfig {
205
+ const config: ContinueServerConfig = {
206
+ name: options.serverName || 'apiclaw',
207
+ command: 'npx',
208
+ args: ['-y', '@nordsym/apiclaw'],
209
+ };
210
+
211
+ if (options.workspace) {
212
+ config.env = {
213
+ APICLAW_WORKSPACE: options.workspace,
214
+ };
215
+ }
216
+
217
+ return config;
218
+ }
219
+
220
+ /**
221
+ * Check if APIClaw is already configured
222
+ */
223
+ export function hasApiclawConfig(config: MCPConfig | ContinueConfig, serverName = 'apiclaw'): boolean {
224
+ // Handle Continue's array format
225
+ if (Array.isArray((config as ContinueConfig).mcpServers)) {
226
+ const continueConfig = config as ContinueConfig;
227
+ return continueConfig.mcpServers?.some(s => s.name === serverName) || false;
228
+ }
229
+
230
+ // Handle standard object format
231
+ const mcpConfig = config as MCPConfig;
232
+ return mcpConfig.mcpServers?.[serverName] !== undefined;
233
+ }
234
+
235
+ /**
236
+ * Merge APIClaw config into existing config (standard format)
237
+ */
238
+ export function mergeApiclawConfig(
239
+ existingConfig: MCPConfig,
240
+ options: MergeOptions = {}
241
+ ): MCPConfig {
242
+ const serverName = options.serverName || 'apiclaw';
243
+ const apiclawConfig = generateApiclawConfig(options);
244
+
245
+ return deepMerge(existingConfig, {
246
+ mcpServers: {
247
+ ...existingConfig.mcpServers,
248
+ [serverName]: apiclawConfig,
249
+ },
250
+ });
251
+ }
252
+
253
+ /**
254
+ * Merge APIClaw config into Continue config (array format)
255
+ */
256
+ export function mergeApiclawContinueConfig(
257
+ existingConfig: ContinueConfig,
258
+ options: MergeOptions = {}
259
+ ): ContinueConfig {
260
+ const serverName = options.serverName || 'apiclaw';
261
+ const apiclawConfig = generateApiclawContinueConfig(options);
262
+
263
+ const existingServers = existingConfig.mcpServers || [];
264
+
265
+ // Check if already exists
266
+ const existingIndex = existingServers.findIndex(s => s.name === serverName);
267
+
268
+ let newServers: ContinueServerConfig[];
269
+ if (existingIndex >= 0) {
270
+ // Update existing
271
+ newServers = [...existingServers];
272
+ newServers[existingIndex] = apiclawConfig;
273
+ } else {
274
+ // Add new
275
+ newServers = [...existingServers, apiclawConfig];
276
+ }
277
+
278
+ return {
279
+ ...existingConfig,
280
+ mcpServers: newServers,
281
+ };
282
+ }
283
+
284
+ /**
285
+ * Remove APIClaw from config
286
+ */
287
+ export function removeApiclawConfig(
288
+ config: MCPConfig | ContinueConfig,
289
+ serverName = 'apiclaw'
290
+ ): MCPConfig | ContinueConfig {
291
+ // Handle Continue's array format
292
+ if (Array.isArray((config as ContinueConfig).mcpServers)) {
293
+ const continueConfig = config as ContinueConfig;
294
+ return {
295
+ ...continueConfig,
296
+ mcpServers: continueConfig.mcpServers?.filter(s => s.name !== serverName),
297
+ };
298
+ }
299
+
300
+ // Handle standard object format
301
+ const mcpConfig = config as MCPConfig;
302
+ const { [serverName]: _, ...remainingServers } = mcpConfig.mcpServers || {};
303
+
304
+ return {
305
+ ...mcpConfig,
306
+ mcpServers: remainingServers,
307
+ };
308
+ }
309
+
310
+ /**
311
+ * Detect if config uses Continue's array format
312
+ */
313
+ export function isContinueFormat(config: MCPConfig | ContinueConfig): config is ContinueConfig {
314
+ return Array.isArray((config as ContinueConfig).mcpServers);
315
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * OS Detection Utility
3
+ * Detects the operating system and provides platform-specific helpers
4
+ */
5
+
6
+ import { platform, homedir } from 'os';
7
+ import { join } from 'path';
8
+
9
+ export type Platform = 'mac' | 'win' | 'linux';
10
+
11
+ /**
12
+ * Detect the current operating system
13
+ */
14
+ export function detectOS(): Platform {
15
+ const os = platform();
16
+
17
+ switch (os) {
18
+ case 'darwin':
19
+ return 'mac';
20
+ case 'win32':
21
+ return 'win';
22
+ case 'linux':
23
+ return 'linux';
24
+ default:
25
+ // Default to linux for other Unix-like systems
26
+ return 'linux';
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Get the home directory for the current user
32
+ */
33
+ export function getHomeDir(): string {
34
+ return homedir();
35
+ }
36
+
37
+ /**
38
+ * Get the app data directory based on OS
39
+ * - macOS: ~/Library/Application Support
40
+ * - Windows: %APPDATA%
41
+ * - Linux: ~/.config
42
+ */
43
+ export function getAppDataDir(): string {
44
+ const home = getHomeDir();
45
+ const os = detectOS();
46
+
47
+ switch (os) {
48
+ case 'mac':
49
+ return join(home, 'Library', 'Application Support');
50
+ case 'win':
51
+ return process.env.APPDATA || join(home, 'AppData', 'Roaming');
52
+ case 'linux':
53
+ return process.env.XDG_CONFIG_HOME || join(home, '.config');
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Get the user profile directory (Windows-specific, falls back to home)
59
+ */
60
+ export function getUserProfileDir(): string {
61
+ return process.env.USERPROFILE || getHomeDir();
62
+ }
63
+
64
+ /**
65
+ * Check if running as root/admin
66
+ */
67
+ export function isElevated(): boolean {
68
+ const os = detectOS();
69
+
70
+ if (os === 'win') {
71
+ // Windows admin check is more complex, skip for now
72
+ return false;
73
+ }
74
+
75
+ // Unix-like: check if UID is 0
76
+ return process.getuid?.() === 0;
77
+ }
78
+
79
+ /**
80
+ * Get OS-specific path separator
81
+ */
82
+ export function getPathSeparator(): string {
83
+ return detectOS() === 'win' ? '\\' : '/';
84
+ }
85
+
86
+ /**
87
+ * Normalize path for current OS
88
+ */
89
+ export function normalizePath(path: string): string {
90
+ const os = detectOS();
91
+
92
+ if (os === 'win') {
93
+ // Convert forward slashes to backslashes on Windows
94
+ return path.replace(/\//g, '\\');
95
+ }
96
+
97
+ return path;
98
+ }
99
+
100
+ /**
101
+ * Expand ~ to home directory
102
+ */
103
+ export function expandHome(path: string): string {
104
+ if (path.startsWith('~')) {
105
+ return join(getHomeDir(), path.slice(1));
106
+ }
107
+ return path;
108
+ }
109
+
110
+ /**
111
+ * Get OS display name
112
+ */
113
+ export function getOSDisplayName(): string {
114
+ const os = detectOS();
115
+
116
+ switch (os) {
117
+ case 'mac':
118
+ return 'macOS';
119
+ case 'win':
120
+ return 'Windows';
121
+ case 'linux':
122
+ return 'Linux';
123
+ }
124
+ }