@arcteninc/core 0.0.176 → 0.0.177

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 (103) hide show
  1. package/README.md +73 -78
  2. package/dist/index.cjs +3 -16
  3. package/dist/index.d.ts +1 -6
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.mjs +851 -8410
  6. package/dist/lib/useAgent.d.ts +1 -2
  7. package/dist/lib/useAgent.d.ts.map +1 -1
  8. package/dist/types/use-agent.d.ts +3 -44
  9. package/dist/types/use-agent.d.ts.map +1 -1
  10. package/dist/utils/extract-tool-metadata.d.ts.map +1 -1
  11. package/package.json +7 -46
  12. package/scripts/arcten-cli.cjs +14 -108
  13. package/scripts/cli-extract-types-auto.ts +22 -4
  14. package/scripts/update-core.cjs +124 -0
  15. package/dist/components/ArctenAgent.d.ts +0 -52
  16. package/dist/components/ArctenAgent.d.ts.map +0 -1
  17. package/dist/components/ai-elements/prompt-input.d.ts +0 -187
  18. package/dist/components/ai-elements/prompt-input.d.ts.map +0 -1
  19. package/dist/components/ai-elements/reasoning.d.ts +0 -17
  20. package/dist/components/ai-elements/reasoning.d.ts.map +0 -1
  21. package/dist/components/ai-elements/response.d.ts +0 -8
  22. package/dist/components/ai-elements/response.d.ts.map +0 -1
  23. package/dist/components/ai-elements/shimmer.d.ts +0 -10
  24. package/dist/components/ai-elements/shimmer.d.ts.map +0 -1
  25. package/dist/components/citation-button.d.ts +0 -14
  26. package/dist/components/citation-button.d.ts.map +0 -1
  27. package/dist/components/citation-text-renderer.d.ts +0 -31
  28. package/dist/components/citation-text-renderer.d.ts.map +0 -1
  29. package/dist/components/secure-modals/EmailModal.d.ts +0 -10
  30. package/dist/components/secure-modals/EmailModal.d.ts.map +0 -1
  31. package/dist/components/secure-modals/FormModal.d.ts +0 -20
  32. package/dist/components/secure-modals/FormModal.d.ts.map +0 -1
  33. package/dist/components/secure-modals/PasswordModal.d.ts +0 -10
  34. package/dist/components/secure-modals/PasswordModal.d.ts.map +0 -1
  35. package/dist/components/secure-modals/PhoneModal.d.ts +0 -10
  36. package/dist/components/secure-modals/PhoneModal.d.ts.map +0 -1
  37. package/dist/components/secure-modals/PinModal.d.ts +0 -11
  38. package/dist/components/secure-modals/PinModal.d.ts.map +0 -1
  39. package/dist/components/secure-modals/SecureModalProvider.d.ts +0 -13
  40. package/dist/components/secure-modals/SecureModalProvider.d.ts.map +0 -1
  41. package/dist/components/secure-modals/TextModal.d.ts +0 -11
  42. package/dist/components/secure-modals/TextModal.d.ts.map +0 -1
  43. package/dist/components/secure-modals/index.d.ts +0 -10
  44. package/dist/components/secure-modals/index.d.ts.map +0 -1
  45. package/dist/components/secure-modals/types.d.ts +0 -34
  46. package/dist/components/secure-modals/types.d.ts.map +0 -1
  47. package/dist/components/tool-call-approval.d.ts +0 -9
  48. package/dist/components/tool-call-approval.d.ts.map +0 -1
  49. package/dist/components/tool-call-result.d.ts +0 -8
  50. package/dist/components/tool-call-result.d.ts.map +0 -1
  51. package/dist/components/ui/autotextarea.d.ts +0 -19
  52. package/dist/components/ui/autotextarea.d.ts.map +0 -1
  53. package/dist/components/ui/badge.d.ts +0 -10
  54. package/dist/components/ui/badge.d.ts.map +0 -1
  55. package/dist/components/ui/button.d.ts +0 -14
  56. package/dist/components/ui/button.d.ts.map +0 -1
  57. package/dist/components/ui/collapsible.d.ts +0 -6
  58. package/dist/components/ui/collapsible.d.ts.map +0 -1
  59. package/dist/components/ui/command.d.ts +0 -19
  60. package/dist/components/ui/command.d.ts.map +0 -1
  61. package/dist/components/ui/dialog.d.ts +0 -16
  62. package/dist/components/ui/dialog.d.ts.map +0 -1
  63. package/dist/components/ui/dropdown-menu.d.ts +0 -26
  64. package/dist/components/ui/dropdown-menu.d.ts.map +0 -1
  65. package/dist/components/ui/hover-card.d.ts +0 -7
  66. package/dist/components/ui/hover-card.d.ts.map +0 -1
  67. package/dist/components/ui/input-group.d.ts +0 -17
  68. package/dist/components/ui/input-group.d.ts.map +0 -1
  69. package/dist/components/ui/input.d.ts +0 -4
  70. package/dist/components/ui/input.d.ts.map +0 -1
  71. package/dist/components/ui/kbd.d.ts +0 -4
  72. package/dist/components/ui/kbd.d.ts.map +0 -1
  73. package/dist/components/ui/select.d.ts +0 -16
  74. package/dist/components/ui/select.d.ts.map +0 -1
  75. package/dist/components/ui/textarea.d.ts +0 -4
  76. package/dist/components/ui/textarea.d.ts.map +0 -1
  77. package/dist/components/ui/tooltip.d.ts +0 -8
  78. package/dist/components/ui/tooltip.d.ts.map +0 -1
  79. package/dist/core.css +0 -1
  80. package/dist/utils/form-generator.d.ts +0 -29
  81. package/dist/utils/form-generator.d.ts.map +0 -1
  82. package/dist/utils/secure-param-detector.d.ts +0 -26
  83. package/dist/utils/secure-param-detector.d.ts.map +0 -1
  84. package/scripts/cli-agent-inject.ts +0 -205
  85. package/scripts/cli-agent-wrapper.cjs +0 -51
  86. package/scripts/cli-agent.ts +0 -483
  87. package/scripts/cli-create-project.ts +0 -608
  88. package/scripts/cli-create-wrapper.cjs +0 -60
  89. package/scripts/cli-init-wizard-wrapper.cjs +0 -58
  90. package/scripts/cli-init-wizard.ts +0 -646
  91. package/scripts/cli-prompt-wrapper.cjs +0 -51
  92. package/scripts/cli-prompt.ts +0 -306
  93. package/scripts/cli-sync-wrapper.cjs +0 -69
  94. package/scripts/cli-sync.ts +0 -915
  95. package/scripts/cli-tools-wrapper.cjs +0 -51
  96. package/scripts/cli-tools.ts +0 -320
  97. package/scripts/cli-uninstall-wrapper.cjs +0 -66
  98. package/scripts/cli-uninstall.ts +0 -173
  99. package/scripts/config-parser.ts +0 -432
  100. package/scripts/dashboard-sync.ts +0 -454
  101. package/scripts/tree-sitter-discover.ts +0 -526
  102. package/scripts/wasm/tree-sitter-tsx.wasm +0 -0
  103. package/scripts/wasm/tree-sitter-typescript.wasm +0 -0
@@ -1,915 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Arcten Sync - Sync tools from codebase to dashboard
4
- *
5
- * Usage:
6
- * arcten sync Sync tools only
7
- * arcten sync --prompt Also sync .arcten/prompt.md
8
- * arcten sync --agents Also sync .arcten/agents/*.yaml
9
- * arcten sync --all Sync everything (tools + prompt + agents)
10
- * arcten sync --classify Force AI re-classification of all tools
11
- * arcten sync --yes Auto-confirm (for CI/scripts)
12
- *
13
- * Runs:
14
- * 1. arcten-extract-types (generates .arcten/tool-metadata.ts)
15
- * 2. Generates .arcten/arcten.tools.ts with imports and arrays
16
- * 3. Updates .arcten/sync-state.json for diff tracking
17
- * 4. Syncs to dashboard (functions are READONLY)
18
- * 5. Optionally syncs prompt and/or agents
19
- */
20
-
21
- import * as fs from 'fs';
22
- import * as path from 'path';
23
- import * as readline from 'readline';
24
- import { execSync } from 'child_process';
25
-
26
- interface ToolMetadata {
27
- generated: string;
28
- discoveredFrom: string[];
29
- functions: Record<string, {
30
- name: string;
31
- description?: string;
32
- parameters: unknown;
33
- returnType: string;
34
- isAsync: boolean;
35
- }>;
36
- toolOrder: string[];
37
- }
38
-
39
- interface SyncState {
40
- lastSync: string;
41
- toolsFile: string;
42
- functions: Record<string, {
43
- classification: 'safe' | 'sensitive';
44
- }>;
45
- }
46
-
47
- interface ClassificationResult {
48
- source: string;
49
- classifications: Record<string, 'safe' | 'sensitive'>;
50
- safe: string[];
51
- sensitive: string[];
52
- }
53
-
54
- interface AgentConfig {
55
- name: string;
56
- description?: string;
57
- isDefault?: boolean;
58
- toolMode?: 'inherit' | 'custom' | 'additive';
59
- systemPrompt?: string;
60
- enabledTools?: string[];
61
- }
62
-
63
- interface SyncOptions {
64
- syncTools: boolean;
65
- syncPrompt: boolean;
66
- syncAgents: boolean;
67
- forceClassify: boolean;
68
- autoYes: boolean;
69
- quiet: boolean;
70
- }
71
-
72
- // State
73
- let rl: readline.Interface;
74
- let apiKey: string;
75
- let projectId: string;
76
- let serverUrl: string;
77
-
78
- /**
79
- * Read tool-metadata.ts to get current functions
80
- */
81
- function readToolMetadata(projectRoot: string): ToolMetadata | null {
82
- const metadataPath = path.join(projectRoot, '.arcten', 'tool-metadata.ts');
83
-
84
- if (!fs.existsSync(metadataPath)) {
85
- return null;
86
- }
87
-
88
- const content = fs.readFileSync(metadataPath, 'utf-8');
89
-
90
- // Extract the object from "export const toolMetadata = { ... } as const;"
91
- const match = content.match(/export const toolMetadata = ({[\s\S]*}) as const;/);
92
- if (!match) {
93
- return null;
94
- }
95
-
96
- try {
97
- // Use eval to parse the object (it's generated code, safe to eval)
98
- const metadata = eval(`(${match[1]})`);
99
- return metadata as ToolMetadata;
100
- } catch {
101
- return null;
102
- }
103
- }
104
-
105
- /**
106
- * Read sync-state.json to get previous state
107
- */
108
- function readSyncState(projectRoot: string): SyncState | null {
109
- const statePath = path.join(projectRoot, '.arcten', 'sync-state.json');
110
-
111
- if (!fs.existsSync(statePath)) {
112
- return null;
113
- }
114
-
115
- try {
116
- const content = fs.readFileSync(statePath, 'utf-8');
117
- const parsed = JSON.parse(content) as SyncState;
118
- // Validate the structure
119
- if (!parsed.functions || typeof parsed.functions !== 'object') {
120
- return null;
121
- }
122
- return parsed;
123
- } catch {
124
- return null;
125
- }
126
- }
127
-
128
- /**
129
- * Write sync-state.json
130
- */
131
- function writeSyncState(projectRoot: string, state: SyncState): void {
132
- const arctenDir = path.join(projectRoot, '.arcten');
133
- if (!fs.existsSync(arctenDir)) {
134
- fs.mkdirSync(arctenDir, { recursive: true });
135
- }
136
-
137
- const statePath = path.join(arctenDir, 'sync-state.json');
138
- fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
139
- }
140
-
141
- /**
142
- * Generate arcten.tools.ts with imports and arrays
143
- */
144
- function generateToolsFile(
145
- projectRoot: string,
146
- toolsFilePath: string,
147
- safeTools: string[],
148
- sensitiveTools: string[]
149
- ): void {
150
- const arctenDir = path.join(projectRoot, '.arcten');
151
- if (!fs.existsSync(arctenDir)) {
152
- fs.mkdirSync(arctenDir, { recursive: true });
153
- }
154
-
155
- // Calculate relative import path from .arcten to tools file
156
- const toolsFileRelative = path.relative(arctenDir, path.join(projectRoot, toolsFilePath));
157
- const importPath = toolsFileRelative.replace(/\.ts$/, '').replace(/\\/g, '/');
158
-
159
- const allTools = [...safeTools, ...sensitiveTools];
160
-
161
- const content = `// .arcten/arcten.tools.ts - AUTO-GENERATED by arcten sync
162
- // DO NOT EDIT - run \`arcten sync\` to regenerate
163
-
164
- import { preserveToolName } from '@arcteninc/core';
165
- import {
166
- ${allTools.map(t => ` ${t} as _${t},`).join('\n')}
167
- } from '${importPath}';
168
-
169
- // Wrap tools with preserveToolName to prevent minification from breaking tool name matching
170
- ${allTools.map(t => `const ${t} = preserveToolName(_${t}, '${t}');`).join('\n')}
171
-
172
- // Safe tools - auto-execute without user approval
173
- export const safeTools = [
174
- ${safeTools.map(t => ` ${t},`).join('\n')}
175
- ] as const;
176
-
177
- // Sensitive tools - require user approval before execution
178
- export const sensitiveTools = [
179
- ${sensitiveTools.map(t => ` ${t},`).join('\n')}
180
- ] as const;
181
-
182
- // All tools combined
183
- export const allTools = [...safeTools, ...sensitiveTools];
184
-
185
- // Tool names for the safeToolNames prop
186
- export const safeToolNames = [
187
- ${safeTools.map(t => ` '${t}',`).join('\n')}
188
- ];
189
-
190
- // Type exports
191
- export type SafeToolName = typeof safeTools[number]['name'];
192
- export type SensitiveToolName = typeof sensitiveTools[number]['name'];
193
- export type AllToolName = SafeToolName | SensitiveToolName;
194
- `;
195
-
196
- const outputPath = path.join(arctenDir, 'arcten.tools.ts');
197
- fs.writeFileSync(outputPath, content);
198
- }
199
-
200
- /**
201
- * Classify tools using AI or pattern fallback
202
- */
203
- async function classifyTools(
204
- tools: Array<{ name: string; description?: string }>,
205
- existingClassifications: Record<string, { classification: 'safe' | 'sensitive' }>
206
- ): Promise<ClassificationResult> {
207
- // Filter to only new tools (not already classified)
208
- const newTools = tools.filter(t => !existingClassifications[t.name]);
209
-
210
- if (newTools.length === 0) {
211
- // All tools already classified
212
- const classifications: Record<string, 'safe' | 'sensitive'> = {};
213
- for (const tool of tools) {
214
- classifications[tool.name] = existingClassifications[tool.name]?.classification || 'sensitive';
215
- }
216
- return {
217
- source: 'existing',
218
- classifications,
219
- safe: tools.filter(t => classifications[t.name] === 'safe').map(t => t.name),
220
- sensitive: tools.filter(t => classifications[t.name] === 'sensitive').map(t => t.name),
221
- };
222
- }
223
-
224
- // Try AI classification for new tools
225
- try {
226
- const response = await fetch(`${serverUrl}/ai-init/classify`, {
227
- method: 'POST',
228
- headers: {
229
- 'Content-Type': 'application/json',
230
- 'x-arcten-api-key': apiKey,
231
- },
232
- body: JSON.stringify({
233
- tools: newTools.map(t => ({ name: t.name, jsDoc: t.description })),
234
- }),
235
- });
236
-
237
- if (response.ok) {
238
- const aiResult = await response.json() as ClassificationResult;
239
-
240
- // Merge with existing classifications
241
- const mergedClassifications: Record<string, 'safe' | 'sensitive'> = {};
242
- for (const tool of tools) {
243
- if (existingClassifications[tool.name]) {
244
- mergedClassifications[tool.name] = existingClassifications[tool.name].classification;
245
- } else {
246
- mergedClassifications[tool.name] = aiResult.classifications[tool.name] || 'sensitive';
247
- }
248
- }
249
-
250
- return {
251
- source: 'ai',
252
- classifications: mergedClassifications,
253
- safe: tools.filter(t => mergedClassifications[t.name] === 'safe').map(t => t.name),
254
- sensitive: tools.filter(t => mergedClassifications[t.name] === 'sensitive').map(t => t.name),
255
- };
256
- }
257
- } catch {
258
- // Fall through to pattern fallback
259
- }
260
-
261
- // Pattern-based fallback
262
- const safePatterns = /^(get|list|search|find|fetch|load|calculate|count)/i;
263
- const sensitivePatterns = /^(create|update|delete|set|add|remove|regenerate)/i;
264
- const sensitiveReads = /^(get|fetch).*(api.?key|credential|secret|password|token)/i;
265
-
266
- const classifications: Record<string, 'safe' | 'sensitive'> = {};
267
-
268
- for (const tool of tools) {
269
- if (existingClassifications[tool.name]) {
270
- classifications[tool.name] = existingClassifications[tool.name].classification;
271
- } else if (sensitiveReads.test(tool.name)) {
272
- classifications[tool.name] = 'sensitive';
273
- } else if (sensitivePatterns.test(tool.name)) {
274
- classifications[tool.name] = 'sensitive';
275
- } else if (safePatterns.test(tool.name)) {
276
- classifications[tool.name] = 'safe';
277
- } else {
278
- classifications[tool.name] = 'sensitive'; // Default to sensitive
279
- }
280
- }
281
-
282
- return {
283
- source: 'pattern-fallback',
284
- classifications,
285
- safe: tools.filter(t => classifications[t.name] === 'safe').map(t => t.name),
286
- sensitive: tools.filter(t => classifications[t.name] === 'sensitive').map(t => t.name),
287
- };
288
- }
289
-
290
- /**
291
- * Sync to dashboard
292
- */
293
- async function syncToDashboard(
294
- tools: Array<{ name: string; description?: string; requiresApproval: boolean }>
295
- ): Promise<{ success: boolean; error?: string }> {
296
- try {
297
- // Get JWT token
298
- const tokenResponse = await fetch(`${serverUrl}/token`, {
299
- method: 'POST',
300
- headers: { 'Content-Type': 'application/json' },
301
- body: JSON.stringify({ apiKey }),
302
- });
303
-
304
- if (!tokenResponse.ok) {
305
- return { success: false, error: 'Failed to authenticate' };
306
- }
307
-
308
- const { clientToken: token } = await tokenResponse.json();
309
-
310
- // Format tools for sync
311
- const toolsData = tools.map(tool => ({
312
- name: tool.name,
313
- description: tool.description || `Function: ${tool.name}`,
314
- signature: JSON.stringify({}),
315
- isEnabled: true,
316
- isOverridable: true,
317
- requiresApproval: tool.requiresApproval,
318
- sensitiveParams: [],
319
- readonly: true, // Functions are readonly in dashboard
320
- syncedAt: Date.now(),
321
- }));
322
-
323
- // Sync to dashboard
324
- const syncResponse = await fetch(`${serverUrl}/tools/sync`, {
325
- method: 'POST',
326
- headers: {
327
- 'Content-Type': 'application/json',
328
- 'Authorization': `Bearer ${token}`,
329
- },
330
- body: JSON.stringify({
331
- projectId,
332
- tools: toolsData,
333
- }),
334
- });
335
-
336
- if (!syncResponse.ok) {
337
- const error = await syncResponse.text();
338
- return { success: false, error: `Sync failed: ${error}` };
339
- }
340
-
341
- return { success: true };
342
- } catch (error: any) {
343
- return { success: false, error: error.message };
344
- }
345
- }
346
-
347
- /**
348
- * Sync system prompt to dashboard
349
- */
350
- async function syncPromptToDashboard(
351
- projectRoot: string
352
- ): Promise<{ success: boolean; error?: string }> {
353
- const promptPath = path.join(projectRoot, '.arcten', 'prompt.md');
354
-
355
- if (!fs.existsSync(promptPath)) {
356
- return { success: false, error: 'No .arcten/prompt.md found. Create one with `arcten prompt`.' };
357
- }
358
-
359
- const systemPrompt = fs.readFileSync(promptPath, 'utf-8');
360
-
361
- try {
362
- // Get JWT token
363
- const tokenResponse = await fetch(`${serverUrl}/token`, {
364
- method: 'POST',
365
- headers: { 'Content-Type': 'application/json' },
366
- body: JSON.stringify({ apiKey }),
367
- });
368
-
369
- if (!tokenResponse.ok) {
370
- return { success: false, error: 'Failed to authenticate' };
371
- }
372
-
373
- const { clientToken: token } = await tokenResponse.json();
374
-
375
- // Sync prompt
376
- const syncResponse = await fetch(`${serverUrl}/prompt/sync`, {
377
- method: 'POST',
378
- headers: {
379
- 'Content-Type': 'application/json',
380
- Authorization: `Bearer ${token}`,
381
- },
382
- body: JSON.stringify({
383
- projectId,
384
- systemPrompt,
385
- }),
386
- });
387
-
388
- if (!syncResponse.ok) {
389
- const error = await syncResponse.text();
390
- return { success: false, error: `Prompt sync failed: ${error}` };
391
- }
392
-
393
- return { success: true };
394
- } catch (error: any) {
395
- return { success: false, error: error.message };
396
- }
397
- }
398
-
399
- /**
400
- * Parse simple YAML agent config
401
- */
402
- function parseAgentYaml(content: string): AgentConfig {
403
- const config: AgentConfig = { name: '' };
404
- const lines = content.split('\n');
405
-
406
- let inSystemPrompt = false;
407
- let inEnabledTools = false;
408
- let systemPromptLines: string[] = [];
409
- let enabledTools: string[] = [];
410
-
411
- for (const line of lines) {
412
- if (inSystemPrompt) {
413
- if (line.match(/^[a-zA-Z]/) || line.match(/^#/)) {
414
- inSystemPrompt = false;
415
- config.systemPrompt = systemPromptLines.join('\n').trim();
416
- } else {
417
- systemPromptLines.push(line.replace(/^ /, ''));
418
- continue;
419
- }
420
- }
421
-
422
- if (inEnabledTools) {
423
- if (line.match(/^\s*-\s+/)) {
424
- const tool = line.replace(/^\s*-\s+/, '').trim();
425
- if (tool && !tool.startsWith('#')) {
426
- enabledTools.push(tool);
427
- }
428
- continue;
429
- } else if (!line.match(/^\s*#/) && line.trim()) {
430
- inEnabledTools = false;
431
- config.enabledTools = enabledTools;
432
- }
433
- }
434
-
435
- if (line.trim().startsWith('#')) continue;
436
-
437
- const match = line.match(/^(\w+):\s*(.*)$/);
438
- if (match) {
439
- const [, key, value] = match;
440
-
441
- if (key === 'systemPrompt' && value.includes('|')) {
442
- inSystemPrompt = true;
443
- systemPromptLines = [];
444
- continue;
445
- }
446
-
447
- if (key === 'enabledTools' && !value.trim()) {
448
- inEnabledTools = true;
449
- enabledTools = [];
450
- continue;
451
- }
452
-
453
- switch (key) {
454
- case 'name':
455
- config.name = value.trim();
456
- break;
457
- case 'description':
458
- config.description = value.trim();
459
- break;
460
- case 'isDefault':
461
- config.isDefault = value.trim().toLowerCase() === 'true';
462
- break;
463
- case 'toolMode':
464
- config.toolMode = value.trim() as 'inherit' | 'custom' | 'additive';
465
- break;
466
- }
467
- }
468
- }
469
-
470
- if (inSystemPrompt) {
471
- config.systemPrompt = systemPromptLines.join('\n').trim();
472
- }
473
- if (inEnabledTools && enabledTools.length > 0) {
474
- config.enabledTools = enabledTools;
475
- }
476
-
477
- return config;
478
- }
479
-
480
- /**
481
- * Sync agents to dashboard
482
- */
483
- async function syncAgentsToDashboard(
484
- projectRoot: string
485
- ): Promise<{ success: boolean; agents: string[]; error?: string }> {
486
- const agentsDir = path.join(projectRoot, '.arcten', 'agents');
487
-
488
- if (!fs.existsSync(agentsDir)) {
489
- return { success: true, agents: [] };
490
- }
491
-
492
- const agentFiles = fs.readdirSync(agentsDir)
493
- .filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
494
-
495
- if (agentFiles.length === 0) {
496
- return { success: true, agents: [] };
497
- }
498
-
499
- const agents: AgentConfig[] = [];
500
- for (const file of agentFiles) {
501
- const content = fs.readFileSync(path.join(agentsDir, file), 'utf-8');
502
- const config = parseAgentYaml(content);
503
- if (config.name) {
504
- agents.push(config);
505
- }
506
- }
507
-
508
- try {
509
- // Get JWT token
510
- const tokenResponse = await fetch(`${serverUrl}/token`, {
511
- method: 'POST',
512
- headers: { 'Content-Type': 'application/json' },
513
- body: JSON.stringify({ apiKey }),
514
- });
515
-
516
- if (!tokenResponse.ok) {
517
- return { success: false, agents: [], error: 'Failed to authenticate' };
518
- }
519
-
520
- const { clientToken: token } = await tokenResponse.json();
521
-
522
- // Sync agents
523
- const syncResponse = await fetch(`${serverUrl}/agents/sync`, {
524
- method: 'POST',
525
- headers: {
526
- 'Content-Type': 'application/json',
527
- Authorization: `Bearer ${token}`,
528
- },
529
- body: JSON.stringify({
530
- projectId,
531
- agents: agents.map(a => ({
532
- name: a.name,
533
- description: a.description || '',
534
- isDefault: a.isDefault || false,
535
- toolMode: a.toolMode || 'inherit',
536
- systemPrompt: a.systemPrompt || '',
537
- enabledTools: a.enabledTools || [],
538
- })),
539
- }),
540
- });
541
-
542
- if (!syncResponse.ok) {
543
- const error = await syncResponse.text();
544
- return { success: false, agents: agents.map(a => a.name), error: `Agent sync failed: ${error}` };
545
- }
546
-
547
- return { success: true, agents: agents.map(a => a.name) };
548
- } catch (error: any) {
549
- return { success: false, agents: agents.map(a => a.name), error: error.message };
550
- }
551
- }
552
-
553
- /**
554
- * Check for API key
555
- */
556
- function checkApiKey(): { apiKey: string; projectId: string } | null {
557
- // First check environment variable (for CI/Vercel builds)
558
- const envKey = process.env.ARCTEN_API_KEY;
559
- if (envKey) {
560
- const projectIdMatch = envKey.match(/sk_(proj_[a-zA-Z0-9]+)_/);
561
- if (projectIdMatch) {
562
- return { apiKey: envKey, projectId: projectIdMatch[1] };
563
- }
564
- }
565
-
566
- // Fall back to .env files for local development
567
- const envFiles = ['.env.local', '.env'];
568
-
569
- for (const envFile of envFiles) {
570
- const envPath = path.join(process.cwd(), envFile);
571
- if (fs.existsSync(envPath)) {
572
- const envContent = fs.readFileSync(envPath, 'utf-8');
573
- const match = envContent.match(/ARCTEN_API_KEY=([^\s\n]+)/);
574
- if (match) {
575
- const key = match[1];
576
- const projectIdMatch = key.match(/sk_(proj_[a-zA-Z0-9]+)_/);
577
- if (projectIdMatch) {
578
- return { apiKey: key, projectId: projectIdMatch[1] };
579
- }
580
- }
581
- }
582
- }
583
-
584
- return null;
585
- }
586
-
587
- /**
588
- * Ask user a yes/no question
589
- */
590
- function askYesNo(question: string): Promise<boolean> {
591
- return new Promise((resolve) => {
592
- rl.question(`${question} (y/n) `, (answer) => {
593
- resolve(answer.trim().toLowerCase().startsWith('y'));
594
- });
595
- });
596
- }
597
-
598
- /**
599
- * Parse command line options
600
- */
601
- function parseOptions(args: string[]): SyncOptions {
602
- const autoYes = args.includes('--yes') || args.includes('-y');
603
- const syncAll = args.includes('--all');
604
- const syncPrompt = syncAll || args.includes('--prompt');
605
- const syncAgents = syncAll || args.includes('--agents');
606
- const forceClassify = args.includes('--classify');
607
-
608
- return {
609
- syncTools: true, // Always sync tools
610
- syncPrompt,
611
- syncAgents,
612
- forceClassify,
613
- autoYes,
614
- quiet: autoYes,
615
- };
616
- }
617
-
618
- /**
619
- * Main sync function
620
- */
621
- async function main() {
622
- const projectRoot = process.cwd();
623
- const args = process.argv.slice(2);
624
- const options = parseOptions(args);
625
-
626
- if (!options.quiet) {
627
- console.log('');
628
- console.log('Arcten Sync');
629
- if (options.syncPrompt || options.syncAgents) {
630
- const extras = [];
631
- if (options.syncPrompt) extras.push('prompt');
632
- if (options.syncAgents) extras.push('agents');
633
- console.log(` (including: ${extras.join(', ')})`);
634
- }
635
- console.log('');
636
- }
637
-
638
- // Check for API key
639
- const auth = checkApiKey();
640
- if (!auth) {
641
- if (!options.quiet) {
642
- console.log('No API key found. Run `arcten init` first.');
643
- }
644
- process.exit(1);
645
- }
646
-
647
- apiKey = auth.apiKey;
648
- projectId = auth.projectId;
649
- serverUrl = process.env.ARCTEN_SERVER_URL || 'https://api.arcten.com';
650
-
651
- // Create readline interface (only if interactive)
652
- if (!options.autoYes) {
653
- rl = readline.createInterface({
654
- input: process.stdin,
655
- output: process.stdout,
656
- });
657
- }
658
-
659
- try {
660
- // Step 1: Run arcten-extract-types to update tool-metadata.ts
661
- if (!options.quiet) console.log('Extracting tool types...');
662
- // Get script directory (ESM compatible)
663
- const scriptDir = path.dirname(new URL(import.meta.url).pathname).replace(/^\/([A-Z]:)/, '$1');
664
-
665
- try {
666
- // Read config to get tool file path
667
- const configPath = path.join(projectRoot, '.arcten', 'config.ts');
668
- let toolsArg = '';
669
- if (fs.existsSync(configPath)) {
670
- const configContent = fs.readFileSync(configPath, 'utf-8');
671
- const toolFileMatch = configContent.match(/toolFile:\s*["']([^"']+)["']/);
672
- if (toolFileMatch) {
673
- toolsArg = ` --tools "${toolFileMatch[1]}"`;
674
- }
675
- }
676
-
677
- // Try running the wrapper script directly first
678
- const extractScript = path.join(scriptDir, 'cli-extract-types-auto-wrapper.js');
679
- if (fs.existsSync(extractScript)) {
680
- execSync(`node "${extractScript}"${toolsArg}`, {
681
- cwd: projectRoot,
682
- stdio: options.quiet ? 'pipe' : ['inherit', 'pipe', 'pipe'],
683
- });
684
- if (!options.quiet) console.log(' Done');
685
- } else {
686
- // Fall back to npx
687
- execSync(`npx arcten-extract-types${toolsArg}`, {
688
- cwd: projectRoot,
689
- stdio: options.quiet ? 'pipe' : ['inherit', 'pipe', 'pipe'],
690
- });
691
- if (!options.quiet) console.log(' Done');
692
- }
693
- } catch (error: any) {
694
- if (!options.quiet) console.log(' Warning: Could not run extract-types, continuing with existing metadata...');
695
- }
696
-
697
- // Step 2: Read tool-metadata.ts
698
- if (!options.quiet) console.log('Reading tool metadata...');
699
- const metadata = readToolMetadata(projectRoot);
700
-
701
- if (!metadata) {
702
- if (!options.quiet) {
703
- console.log('');
704
- console.log('No tool-metadata.ts found.');
705
- console.log('Make sure you have a tools file (e.g., app/tools.ts) with exported functions.');
706
- console.log('');
707
- }
708
- if (rl) rl.close();
709
- process.exit(1);
710
- }
711
-
712
- const currentFunctions = Object.keys(metadata.functions);
713
- const toolsFile = metadata.discoveredFrom[0] || 'app/tools.ts';
714
-
715
- if (!options.quiet) console.log(`Found ${currentFunctions.length} functions from ${toolsFile}`);
716
-
717
- // Step 2: Read previous sync state
718
- const previousState = readSyncState(projectRoot);
719
- const previousFunctions = previousState
720
- ? Object.keys(previousState.functions)
721
- : [];
722
-
723
- // Step 3: Compute diff
724
- const newFunctions = currentFunctions.filter(f => !previousFunctions.includes(f));
725
- const removedFunctions = previousFunctions.filter(f => !currentFunctions.includes(f));
726
-
727
- // Always regenerate arcten.tools.ts to pick up any classification changes in sync-state.json
728
- // Only skip dashboard sync if truly no changes
729
- const hasChanges = newFunctions.length > 0 || removedFunctions.length > 0;
730
-
731
- if (!hasChanges && previousState && !options.forceClassify) {
732
- if (!options.autoYes) {
733
- console.log('');
734
- console.log('No function changes detected since last sync.');
735
- console.log('(Will still regenerate arcten.tools.ts to apply any classification changes)');
736
- }
737
- }
738
-
739
- // Show changes if any
740
- if (newFunctions.length > 0) {
741
- console.log('');
742
- console.log(`New functions since last sync:`);
743
- for (const fn of newFunctions) {
744
- console.log(` + ${fn}`);
745
- }
746
- }
747
-
748
- if (removedFunctions.length > 0) {
749
- console.log('');
750
- console.log(`Removed functions:`);
751
- for (const fn of removedFunctions) {
752
- console.log(` - ${fn}`);
753
- }
754
- }
755
-
756
- // Step 4: Classify tools (AI for new, preserve existing unless --classify)
757
- if (!options.quiet) {
758
- console.log('');
759
- console.log('Classifying tools...');
760
- }
761
-
762
- // If forceClassify, clear existing classifications to force re-classification
763
- const existingClassifications = options.forceClassify ? {} : (previousState?.functions || {});
764
- const toolsToClassify = currentFunctions.map(name => ({
765
- name,
766
- description: metadata.functions[name]?.description,
767
- }));
768
-
769
- const classification = await classifyTools(toolsToClassify, existingClassifications);
770
-
771
- if (!options.quiet) {
772
- console.log('');
773
- console.log(`Classification (${classification.source}${options.forceClassify ? ', forced' : ''}):`);
774
- console.log(` Safe (auto-execute): ${classification.safe.length} tools`);
775
- if (classification.safe.length > 0 && classification.safe.length <= 10) {
776
- console.log(` ${classification.safe.join(', ')}`);
777
- }
778
- console.log(` Sensitive (needs approval): ${classification.sensitive.length} tools`);
779
- if (classification.sensitive.length > 0 && classification.sensitive.length <= 10) {
780
- console.log(` ${classification.sensitive.join(', ')}`);
781
- }
782
- }
783
-
784
- // Step 5: Confirm with user (skip if auto-yes or no new classifications needed)
785
- const needsNewClassification = newFunctions.length > 0 || options.forceClassify;
786
- if (!options.autoYes && needsNewClassification) {
787
- console.log('');
788
- const proceed = await askYesNo('Does this look right?');
789
-
790
- if (!proceed) {
791
- console.log('');
792
- console.log('You can adjust classifications in .arcten/sync-state.json and re-run sync.');
793
- if (rl) rl.close();
794
- process.exit(0);
795
- }
796
- }
797
-
798
- // Step 6: Generate arcten.tools.ts
799
- if (!options.quiet) {
800
- console.log('');
801
- console.log('Generating .arcten/arcten.tools.ts...');
802
- }
803
- generateToolsFile(projectRoot, toolsFile.replace(/\\/g, '/'), classification.safe, classification.sensitive);
804
- if (!options.quiet) console.log(' Created .arcten/arcten.tools.ts');
805
-
806
- // Step 7: Update sync-state.json
807
- const newState: SyncState = {
808
- lastSync: new Date().toISOString(),
809
- toolsFile,
810
- functions: {},
811
- };
812
- for (const name of currentFunctions) {
813
- newState.functions[name] = {
814
- classification: classification.classifications[name],
815
- };
816
- }
817
- writeSyncState(projectRoot, newState);
818
- if (!options.quiet) console.log(' Updated .arcten/sync-state.json');
819
-
820
- // Step 8: Sync tools to dashboard (skip logging in quiet mode unless there's an error)
821
- if (!options.quiet) {
822
- console.log('');
823
- console.log('Syncing tools to dashboard...');
824
- }
825
-
826
- const toolsForDashboard = currentFunctions.map(name => ({
827
- name,
828
- description: metadata.functions[name]?.description,
829
- requiresApproval: classification.classifications[name] === 'sensitive',
830
- }));
831
-
832
- const syncResult = await syncToDashboard(toolsForDashboard);
833
-
834
- if (syncResult.success) {
835
- if (!options.quiet) console.log(` Synced ${toolsForDashboard.length} tools to dashboard`);
836
- } else {
837
- // Always show dashboard errors
838
- console.log(` Warning: Dashboard sync failed: ${syncResult.error}`);
839
- if (!options.quiet) console.log(' Local files were still updated.');
840
- }
841
-
842
- // Step 9: Sync prompt if requested
843
- if (options.syncPrompt) {
844
- if (!options.quiet) {
845
- console.log('');
846
- console.log('Syncing system prompt...');
847
- }
848
-
849
- const promptResult = await syncPromptToDashboard(projectRoot);
850
- if (promptResult.success) {
851
- if (!options.quiet) console.log(' Synced .arcten/prompt.md to dashboard');
852
- } else {
853
- console.log(` Warning: ${promptResult.error}`);
854
- }
855
- }
856
-
857
- // Step 10: Sync agents if requested
858
- if (options.syncAgents) {
859
- if (!options.quiet) {
860
- console.log('');
861
- console.log('Syncing agents...');
862
- }
863
-
864
- const agentsResult = await syncAgentsToDashboard(projectRoot);
865
- if (agentsResult.success) {
866
- if (agentsResult.agents.length > 0) {
867
- if (!options.quiet) console.log(` Synced ${agentsResult.agents.length} agents: ${agentsResult.agents.join(', ')}`);
868
- } else {
869
- if (!options.quiet) console.log(' No agents found in .arcten/agents/');
870
- }
871
- } else {
872
- console.log(` Warning: ${agentsResult.error}`);
873
- }
874
- }
875
-
876
- if (!options.quiet) {
877
- console.log('');
878
- console.log('Sync complete!');
879
- console.log('');
880
- console.log('Usage in your components:');
881
- console.log('');
882
- console.log(' import { allTools, safeToolNames } from "./.arcten/arcten.tools";');
883
- console.log('');
884
- console.log(' <ArctenAgent');
885
- console.log(' tools={allTools}');
886
- console.log(' safeToolNames={safeToolNames}');
887
- console.log(' />');
888
- console.log('');
889
- if (!options.syncPrompt && !options.syncAgents) {
890
- console.log('To sync prompt and agents, use:');
891
- console.log(' arcten sync --all');
892
- console.log('');
893
- }
894
- } else {
895
- // Brief message when there were changes in quiet mode
896
- if (newFunctions.length > 0 || removedFunctions.length > 0) {
897
- console.log(`Synced ${newFunctions.length} new, ${removedFunctions.length} removed tools`);
898
- }
899
- }
900
-
901
- } catch (error: any) {
902
- console.error('Error:', error.message);
903
- process.exit(1);
904
- } finally {
905
- if (rl) rl.close();
906
- }
907
- }
908
-
909
- // Run
910
- main().catch((error) => {
911
- console.error('Fatal error:', error);
912
- process.exit(1);
913
- });
914
-
915
- export { main };