@arcteninc/core 0.0.139 → 0.0.140
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/dist/core.css +1 -1
- package/dist/index.cjs +7 -16
- package/dist/index.mjs +3419 -3315
- package/dist/lib/useAgent.d.ts.map +1 -1
- package/dist/types/use-agent.d.ts +1 -0
- package/dist/types/use-agent.d.ts.map +1 -1
- package/dist/utils/extract-tool-metadata.d.ts +2 -3
- package/dist/utils/extract-tool-metadata.d.ts.map +1 -1
- package/package.json +14 -5
- package/scripts/arcten-cli.cjs +39 -29
- package/scripts/cli-create-project.ts +608 -0
- package/scripts/cli-create-wrapper.cjs +60 -0
- package/scripts/cli-extract-types-auto.ts +153 -1
- package/scripts/cli-init-wizard-wrapper.cjs +4 -3
- package/scripts/cli-init-wizard.ts +503 -823
- package/scripts/cli-sync-wrapper.cjs +69 -0
- package/scripts/cli-sync.ts +596 -0
- package/scripts/config-parser.ts +432 -0
- package/scripts/dashboard-sync.ts +454 -0
- package/scripts/tree-sitter-discover.ts +542 -0
- package/scripts/wasm/tree-sitter-tsx.wasm +0 -0
- package/scripts/wasm/tree-sitter-typescript.wasm +0 -0
|
@@ -1,968 +1,648 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Arcten Init
|
|
3
|
+
* Arcten Init - Complete End-to-End Setup
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* Creates a FULLY WORKING setup with zero manual steps:
|
|
6
|
+
* 1. Deterministic discovery (tree-sitter scan)
|
|
7
|
+
* 2. Deterministic classification (AI API + pattern fallback)
|
|
8
|
+
* 3. Placement menu (user choice)
|
|
9
|
+
* 4. Deterministic file creation (templates)
|
|
10
|
+
* 5. AI review (checks generated files)
|
|
11
|
+
* 6. Run sync (generates arcten.tools.ts)
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
import * as fs from 'fs';
|
|
14
15
|
import * as path from 'path';
|
|
15
|
-
import { glob } from 'glob';
|
|
16
16
|
import * as readline from 'readline';
|
|
17
|
-
import {
|
|
17
|
+
import { execSync } from 'child_process';
|
|
18
|
+
import { discoverFunctionsInDirectory, filterLikelyTools, type DiscoveredFunction } from './tree-sitter-discover.js';
|
|
18
19
|
|
|
19
20
|
// Types
|
|
20
|
-
interface
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
interface InitContext {
|
|
22
|
+
projectRoot: string;
|
|
23
|
+
framework: 'nextjs-app' | 'nextjs-pages' | 'vite' | 'unknown';
|
|
24
|
+
toolsFile: string;
|
|
25
|
+
toolsFileRelative: string;
|
|
26
|
+
functions: DiscoveredFunction[];
|
|
27
|
+
apiKey: string;
|
|
28
|
+
projectId: string;
|
|
29
|
+
serverUrl: string;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
interface
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
version?: string;
|
|
34
|
-
type: 'nextjs-app' | 'nextjs-pages' | 'vite' | 'cra' | 'unknown';
|
|
35
|
-
};
|
|
36
|
-
dependencies: Record<string, string>;
|
|
37
|
-
functions: FunctionMetadata[];
|
|
38
|
-
stats: {
|
|
39
|
-
totalFunctions: number;
|
|
40
|
-
skippedPaths: string[];
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface AnalysisEstimate {
|
|
45
|
-
success: boolean;
|
|
46
|
-
estimatedTokens: number;
|
|
47
|
-
estimatedCost: number;
|
|
48
|
-
functionCount: number;
|
|
49
|
-
chunkCount: number;
|
|
50
|
-
willBeChunked: boolean;
|
|
51
|
-
currency: string;
|
|
52
|
-
message: string;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
interface AnalysisResult {
|
|
56
|
-
success: boolean;
|
|
57
|
-
tokensUsed: number;
|
|
58
|
-
inputTokens: number;
|
|
59
|
-
outputTokens: number;
|
|
60
|
-
recommendations: ToolRecommendation[];
|
|
61
|
-
suggestedAgents: AgentConfig[];
|
|
62
|
-
suggestedPlacements: PlacementSuggestion[];
|
|
63
|
-
stats: any;
|
|
32
|
+
interface Classification {
|
|
33
|
+
safe: string[];
|
|
34
|
+
sensitive: string[];
|
|
35
|
+
source: 'ai' | 'pattern-fallback';
|
|
64
36
|
}
|
|
65
37
|
|
|
66
|
-
|
|
67
|
-
name: string;
|
|
68
|
-
category: 'data-read' | 'data-write' | 'action' | 'query';
|
|
69
|
-
description: string;
|
|
70
|
-
shouldEnable: boolean;
|
|
71
|
-
requiresApproval: boolean;
|
|
72
|
-
sensitiveParams?: string[];
|
|
73
|
-
suggestedAgents?: string[];
|
|
74
|
-
}
|
|
38
|
+
type Placement = 'everywhere' | 'specific' | 'developer';
|
|
75
39
|
|
|
76
|
-
interface
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
40
|
+
interface InitResult {
|
|
41
|
+
context: InitContext;
|
|
42
|
+
classification: Classification;
|
|
43
|
+
placement: Placement;
|
|
44
|
+
specificPages?: string[];
|
|
81
45
|
}
|
|
82
46
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
location: string;
|
|
86
|
-
agentName: string;
|
|
87
|
-
}
|
|
47
|
+
// State
|
|
48
|
+
let rl: readline.Interface;
|
|
88
49
|
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
'.git',
|
|
93
|
-
'dist',
|
|
94
|
-
'build',
|
|
95
|
-
'.next',
|
|
96
|
-
'out',
|
|
97
|
-
'__tests__',
|
|
98
|
-
'*.test.*',
|
|
99
|
-
'*.spec.*',
|
|
100
|
-
];
|
|
101
|
-
|
|
102
|
-
// Helper: Create readline interface
|
|
103
|
-
function createPrompt() {
|
|
104
|
-
return readline.createInterface({
|
|
105
|
-
input: process.stdin,
|
|
106
|
-
output: process.stdout,
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Helper: Ask a question
|
|
111
|
-
function ask(rl: readline.Interface, question: string): Promise<string> {
|
|
112
|
-
return new Promise((resolve) => {
|
|
113
|
-
rl.question(question, (answer) => {
|
|
114
|
-
resolve(answer.trim());
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
}
|
|
50
|
+
// ============================================================================
|
|
51
|
+
// PHASE 1: Deterministic Discovery
|
|
52
|
+
// ============================================================================
|
|
118
53
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const answer = await ask(rl, `${question} (y/n): `);
|
|
122
|
-
return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Step 1: Check for API key
|
|
126
|
-
async function checkApiKey(rl: readline.Interface): Promise<{ apiKey: string; projectId: string }> {
|
|
127
|
-
console.log('\n📋 Step 1: Authentication\n');
|
|
128
|
-
|
|
129
|
-
let apiKey: string | undefined;
|
|
130
|
-
let foundInFile: string | undefined;
|
|
54
|
+
async function discoverContext(): Promise<InitContext> {
|
|
55
|
+
const projectRoot = process.cwd();
|
|
131
56
|
|
|
132
|
-
|
|
133
|
-
const envFiles = ['.env.local', '.env'];
|
|
57
|
+
console.log('Scanning your project...\n');
|
|
134
58
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
59
|
+
// Detect framework
|
|
60
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
61
|
+
let framework: InitContext['framework'] = 'unknown';
|
|
62
|
+
|
|
63
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
64
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
65
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
66
|
+
if (deps.next) {
|
|
67
|
+
const hasAppDir = fs.existsSync(path.join(projectRoot, 'app'));
|
|
68
|
+
framework = hasAppDir ? 'nextjs-app' : 'nextjs-pages';
|
|
69
|
+
} else if (deps.vite) {
|
|
70
|
+
framework = 'vite';
|
|
146
71
|
}
|
|
147
72
|
}
|
|
148
73
|
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
74
|
+
// Discover functions
|
|
75
|
+
let functions = await discoverFunctionsInDirectory(projectRoot, {
|
|
76
|
+
functionsOnly: true,
|
|
77
|
+
exportedOnly: true,
|
|
78
|
+
});
|
|
154
79
|
|
|
155
|
-
|
|
156
|
-
console.error('❌ API key is required');
|
|
157
|
-
process.exit(1);
|
|
158
|
-
}
|
|
80
|
+
functions = filterLikelyTools(functions);
|
|
159
81
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
console.log('
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const projectIdMatch = apiKey.match(/sk_(proj_[a-zA-Z0-9]+)_/);
|
|
169
|
-
if (!projectIdMatch) {
|
|
170
|
-
console.error('❌ Invalid API key format');
|
|
82
|
+
if (functions.length === 0) {
|
|
83
|
+
console.log('No exported functions found in your project.');
|
|
84
|
+
console.log('');
|
|
85
|
+
console.log('Create a tools file (e.g., app/tools.ts) with exported functions:');
|
|
86
|
+
console.log('');
|
|
87
|
+
console.log(' export function getOrders(status: string) { ... }');
|
|
88
|
+
console.log(' export function createOrder(data: OrderData) { ... }');
|
|
89
|
+
console.log('');
|
|
171
90
|
process.exit(1);
|
|
172
91
|
}
|
|
173
92
|
|
|
174
|
-
|
|
175
|
-
|
|
93
|
+
// Find the primary tools file (most functions)
|
|
94
|
+
const fileGroups = new Map<string, DiscoveredFunction[]>();
|
|
95
|
+
for (const fn of functions) {
|
|
96
|
+
const file = fn.file;
|
|
97
|
+
if (!fileGroups.has(file)) {
|
|
98
|
+
fileGroups.set(file, []);
|
|
99
|
+
}
|
|
100
|
+
fileGroups.get(file)!.push(fn);
|
|
101
|
+
}
|
|
176
102
|
|
|
177
|
-
|
|
178
|
-
|
|
103
|
+
// Sort by function count and pick the best
|
|
104
|
+
const sortedFiles = [...fileGroups.entries()].sort((a, b) => b[1].length - a[1].length);
|
|
105
|
+
const [toolsFile, toolFunctions] = sortedFiles[0];
|
|
179
106
|
|
|
180
|
-
|
|
181
|
-
async function scanCodebase(): Promise<CodebaseContext> {
|
|
182
|
-
console.log('\n📂 Step 2: Scanning codebase...\n');
|
|
107
|
+
const toolsFileRelative = path.relative(projectRoot, toolsFile);
|
|
183
108
|
|
|
184
|
-
|
|
109
|
+
// Check for API key
|
|
110
|
+
const auth = checkApiKey(projectRoot);
|
|
111
|
+
if (!auth) {
|
|
112
|
+
console.log('No API key found.');
|
|
113
|
+
console.log('');
|
|
114
|
+
console.log('Get your API key from: https://arcten.com/dashboard');
|
|
115
|
+
console.log('');
|
|
185
116
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
console.log(`✓ Detected framework: ${framework.name} (${framework.type})`);
|
|
117
|
+
const apiKeyInput = await askQuestion('Paste your API key: ');
|
|
118
|
+
const projectIdMatch = apiKeyInput.match(/sk_(proj_[a-zA-Z0-9]+)_/);
|
|
189
119
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
120
|
+
if (!projectIdMatch) {
|
|
121
|
+
console.log('Invalid API key format. Expected: sk_proj_xxxxx_yyyyy');
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
193
124
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
125
|
+
// Save to .env.local
|
|
126
|
+
const envPath = path.join(projectRoot, '.env.local');
|
|
127
|
+
let envContent = '';
|
|
128
|
+
if (fs.existsSync(envPath)) {
|
|
129
|
+
envContent = fs.readFileSync(envPath, 'utf-8');
|
|
130
|
+
}
|
|
131
|
+
if (!envContent.includes('ARCTEN_API_KEY')) {
|
|
132
|
+
envContent += `\nARCTEN_API_KEY=${apiKeyInput}\n`;
|
|
133
|
+
envContent += `NEXT_PUBLIC_ARCTEN_API_URL=https://api.arcten.com\n`;
|
|
134
|
+
fs.writeFileSync(envPath, envContent.trim() + '\n');
|
|
135
|
+
console.log(' Saved API key to .env.local');
|
|
136
|
+
}
|
|
197
137
|
|
|
198
|
-
|
|
199
|
-
|
|
138
|
+
return {
|
|
139
|
+
projectRoot,
|
|
140
|
+
framework,
|
|
141
|
+
toolsFile,
|
|
142
|
+
toolsFileRelative,
|
|
143
|
+
functions: toolFunctions,
|
|
144
|
+
apiKey: apiKeyInput,
|
|
145
|
+
projectId: projectIdMatch[1],
|
|
146
|
+
serverUrl: process.env.ARCTEN_SERVER_URL || 'https://api.arcten.com',
|
|
147
|
+
};
|
|
148
|
+
}
|
|
200
149
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
framework,
|
|
205
|
-
dependencies,
|
|
206
|
-
functions,
|
|
207
|
-
stats: {
|
|
208
|
-
totalFunctions: functions.length,
|
|
209
|
-
skippedPaths: EXCLUDE_PATHS,
|
|
210
|
-
},
|
|
211
|
-
});
|
|
212
|
-
console.log(`📊 Context size: ${(contextStr.length / 1024).toFixed(2)} KB (~${Math.round(contextStr.length / 4)} tokens)`);
|
|
150
|
+
console.log(`Found ${framework === 'nextjs-app' ? 'Next.js App Router' : framework} project`);
|
|
151
|
+
console.log(`Found ${toolFunctions.length} functions in ${toolsFileRelative}`);
|
|
152
|
+
console.log('');
|
|
213
153
|
|
|
214
154
|
return {
|
|
215
|
-
|
|
155
|
+
projectRoot,
|
|
216
156
|
framework,
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
157
|
+
toolsFile,
|
|
158
|
+
toolsFileRelative,
|
|
159
|
+
functions: toolFunctions,
|
|
160
|
+
apiKey: auth.apiKey,
|
|
161
|
+
projectId: auth.projectId,
|
|
162
|
+
serverUrl: process.env.ARCTEN_SERVER_URL || 'https://api.arcten.com',
|
|
223
163
|
};
|
|
224
164
|
}
|
|
225
165
|
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
|
|
166
|
+
// ============================================================================
|
|
167
|
+
// PHASE 2: Deterministic Classification
|
|
168
|
+
// ============================================================================
|
|
229
169
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
235
|
-
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
236
|
-
|
|
237
|
-
if (deps.next) {
|
|
238
|
-
const hasAppDir = fs.existsSync(path.join(projectRoot, 'app'));
|
|
239
|
-
return {
|
|
240
|
-
name: hasAppDir ? 'Next.js App Router' : 'Next.js Pages Router',
|
|
241
|
-
version: deps.next,
|
|
242
|
-
type: hasAppDir ? 'nextjs-app' : 'nextjs-pages',
|
|
243
|
-
};
|
|
244
|
-
}
|
|
170
|
+
async function classifyTools(context: InitContext): Promise<Classification> {
|
|
171
|
+
console.log('Classifying tools...');
|
|
245
172
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
173
|
+
const toolsToClassify = context.functions.map(f => ({
|
|
174
|
+
name: f.name,
|
|
175
|
+
jsDoc: f.jsDocComment,
|
|
176
|
+
}));
|
|
249
177
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
178
|
+
// Try AI classification first
|
|
179
|
+
try {
|
|
180
|
+
const response = await fetch(`${context.serverUrl}/ai-init/classify`, {
|
|
181
|
+
method: 'POST',
|
|
182
|
+
headers: {
|
|
183
|
+
'Content-Type': 'application/json',
|
|
184
|
+
'x-arcten-api-key': context.apiKey,
|
|
185
|
+
},
|
|
186
|
+
body: JSON.stringify({ tools: toolsToClassify }),
|
|
187
|
+
});
|
|
253
188
|
|
|
254
|
-
|
|
255
|
-
|
|
189
|
+
if (response.ok) {
|
|
190
|
+
const result = await response.json() as {
|
|
191
|
+
classifications: Record<string, 'safe' | 'sensitive'>;
|
|
192
|
+
safe: string[];
|
|
193
|
+
sensitive: string[];
|
|
194
|
+
};
|
|
256
195
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
196
|
+
console.log(` ${result.safe.length} safe (auto-execute)`);
|
|
197
|
+
console.log(` ${result.sensitive.length} sensitive (needs approval)`);
|
|
198
|
+
console.log('');
|
|
260
199
|
|
|
261
|
-
|
|
262
|
-
|
|
200
|
+
return {
|
|
201
|
+
safe: result.safe,
|
|
202
|
+
sensitive: result.sensitive,
|
|
203
|
+
source: 'ai',
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
} catch {
|
|
207
|
+
// Fall through to pattern-based
|
|
263
208
|
}
|
|
264
209
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
210
|
+
// Pattern-based fallback
|
|
211
|
+
const safePatterns = /^(get|list|search|find|fetch|load|calculate|count)/i;
|
|
212
|
+
const sensitivePatterns = /^(create|update|delete|set|add|remove|regenerate)/i;
|
|
213
|
+
const sensitiveReads = /^(get|fetch).*(api.?key|credential|secret|password|token)/i;
|
|
214
|
+
|
|
215
|
+
const safe: string[] = [];
|
|
216
|
+
const sensitive: string[] = [];
|
|
217
|
+
|
|
218
|
+
for (const tool of toolsToClassify) {
|
|
219
|
+
if (sensitiveReads.test(tool.name)) {
|
|
220
|
+
sensitive.push(tool.name);
|
|
221
|
+
} else if (sensitivePatterns.test(tool.name)) {
|
|
222
|
+
sensitive.push(tool.name);
|
|
223
|
+
} else if (safePatterns.test(tool.name)) {
|
|
224
|
+
safe.push(tool.name);
|
|
225
|
+
} else {
|
|
226
|
+
sensitive.push(tool.name); // Default to sensitive
|
|
227
|
+
}
|
|
280
228
|
}
|
|
281
229
|
|
|
282
|
-
|
|
283
|
-
|
|
230
|
+
console.log(` ${safe.length} safe (auto-execute)`);
|
|
231
|
+
console.log(` ${sensitive.length} sensitive (needs approval)`);
|
|
232
|
+
console.log('');
|
|
284
233
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const metadataMatch = content.match(/export const toolMetadata = ([\s\S]+) as const;/);
|
|
234
|
+
return { safe, sensitive, source: 'pattern-fallback' };
|
|
235
|
+
}
|
|
288
236
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
237
|
+
// ============================================================================
|
|
238
|
+
// PHASE 3: Placement Menu
|
|
239
|
+
// ============================================================================
|
|
292
240
|
|
|
293
|
-
|
|
294
|
-
|
|
241
|
+
async function showPlacementMenu(): Promise<Placement> {
|
|
242
|
+
console.log('Where should your agent appear?\n');
|
|
243
|
+
console.log(' [1] 🌍 Everywhere - sidebar on every page');
|
|
244
|
+
console.log(' [2] 📄 Specific pages - only where you choose');
|
|
245
|
+
console.log(' [3] 🔧 Developer mode - just the hook, build your own UI');
|
|
246
|
+
console.log('');
|
|
295
247
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
// Find which file this function was discovered from
|
|
299
|
-
const fileIndex = metadata.discoveredFrom.findIndex((f: string) =>
|
|
300
|
-
content.includes(name)
|
|
301
|
-
);
|
|
248
|
+
const answer = await askQuestion('Choose (1/2/3): ');
|
|
249
|
+
const choice = answer.trim();
|
|
302
250
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
name: paramName,
|
|
308
|
-
type: prop.type || 'any',
|
|
309
|
-
})),
|
|
310
|
-
returnType: fn.returnType,
|
|
311
|
-
jsDoc: fn.description,
|
|
312
|
-
sourceCode: `${name}(${Object.keys(fn.parameters?.properties || {}).join(', ')})`,
|
|
313
|
-
});
|
|
251
|
+
if (choice === '2' || choice.toLowerCase().includes('specific')) {
|
|
252
|
+
return 'specific';
|
|
253
|
+
} else if (choice === '3' || choice.toLowerCase().includes('dev')) {
|
|
254
|
+
return 'developer';
|
|
314
255
|
}
|
|
315
256
|
|
|
316
|
-
return
|
|
257
|
+
return 'everywhere';
|
|
317
258
|
}
|
|
318
259
|
|
|
319
|
-
//
|
|
320
|
-
|
|
321
|
-
|
|
260
|
+
// ============================================================================
|
|
261
|
+
// PHASE 4: Deterministic File Creation
|
|
262
|
+
// ============================================================================
|
|
322
263
|
|
|
323
|
-
|
|
324
|
-
|
|
264
|
+
async function createAllFiles(result: InitResult): Promise<string[]> {
|
|
265
|
+
const { context, classification, placement } = result;
|
|
266
|
+
const { projectRoot, toolsFileRelative, projectId, framework } = context;
|
|
267
|
+
const createdFiles: string[] = [];
|
|
325
268
|
|
|
326
|
-
|
|
327
|
-
if (EXCLUDE_PATHS.some(p => entry.name.includes(p.replace('*', '')))) {
|
|
328
|
-
continue;
|
|
329
|
-
}
|
|
269
|
+
console.log('Creating files...\n');
|
|
330
270
|
|
|
331
|
-
|
|
332
|
-
|
|
271
|
+
// Determine app directory based on framework
|
|
272
|
+
const appDir = framework === 'nextjs-app' ? 'app' : 'src';
|
|
273
|
+
const appDirPath = path.join(projectRoot, appDir);
|
|
333
274
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
275
|
+
// 1. Create .arcten/config.ts
|
|
276
|
+
const arctenDir = path.join(projectRoot, '.arcten');
|
|
277
|
+
if (!fs.existsSync(arctenDir)) {
|
|
278
|
+
fs.mkdirSync(arctenDir, { recursive: true });
|
|
337
279
|
}
|
|
338
280
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
);
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
281
|
+
const configContent = `// Arcten configuration
|
|
282
|
+
export const arctenConfig = {
|
|
283
|
+
projectId: "${projectId}",
|
|
284
|
+
toolFile: "./${toolsFileRelative.replace(/\\/g, '/')}",
|
|
285
|
+
routes: [
|
|
286
|
+
{ pattern: "/*", agentName: "default" },
|
|
287
|
+
],
|
|
288
|
+
} as const;
|
|
289
|
+
`;
|
|
290
|
+
fs.writeFileSync(path.join(arctenDir, 'config.ts'), configContent);
|
|
291
|
+
createdFiles.push('.arcten/config.ts');
|
|
292
|
+
console.log(' Created .arcten/config.ts');
|
|
293
|
+
|
|
294
|
+
// 2. Create app/api/arcten/token/route.ts (Next.js App Router only)
|
|
295
|
+
if (framework === 'nextjs-app') {
|
|
296
|
+
const tokenDir = path.join(appDirPath, 'api', 'arcten', 'token');
|
|
297
|
+
if (!fs.existsSync(tokenDir)) {
|
|
298
|
+
fs.mkdirSync(tokenDir, { recursive: true });
|
|
299
|
+
}
|
|
355
300
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
console.log(' 1. Edit descriptions now (one by one)');
|
|
359
|
-
console.log(' 2. Skip - I\'ll add JSDoc comments manually');
|
|
360
|
-
console.log(' 3. Skip - I\'ll edit in dashboard later');
|
|
301
|
+
const tokenRouteContent = `import { verifyToken } from "@arcteninc/core/server";
|
|
302
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
361
303
|
|
|
362
|
-
|
|
304
|
+
export async function POST(req: NextRequest) {
|
|
305
|
+
try {
|
|
306
|
+
const { user } = await req.json();
|
|
307
|
+
const apiKey = process.env.ARCTEN_API_KEY;
|
|
363
308
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
309
|
+
if (!apiKey) {
|
|
310
|
+
return NextResponse.json(
|
|
311
|
+
{ error: "ARCTEN_API_KEY not configured" },
|
|
312
|
+
{ status: 500 }
|
|
313
|
+
);
|
|
314
|
+
}
|
|
370
315
|
|
|
371
|
-
|
|
316
|
+
const tokenResponse = await verifyToken({
|
|
317
|
+
apiKey,
|
|
318
|
+
user: user || { id: "anonymous", name: "User" },
|
|
319
|
+
apiUrl: process.env.NEXT_PUBLIC_ARCTEN_API_URL || "https://api.arcten.com",
|
|
320
|
+
});
|
|
372
321
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
console.log('✅ Description editing complete\n');
|
|
381
|
-
} else {
|
|
382
|
-
console.log('\n⏭️ Skipping description editing\n');
|
|
383
|
-
console.log('💡 You can add JSDoc comments to your code or edit descriptions in the dashboard.');
|
|
322
|
+
return NextResponse.json(tokenResponse);
|
|
323
|
+
} catch (error: any) {
|
|
324
|
+
console.error("Token generation error:", error);
|
|
325
|
+
return NextResponse.json(
|
|
326
|
+
{ error: error.message || "Failed to generate token" },
|
|
327
|
+
{ status: 500 }
|
|
328
|
+
);
|
|
384
329
|
}
|
|
385
|
-
|
|
386
|
-
return functions;
|
|
387
330
|
}
|
|
331
|
+
`;
|
|
332
|
+
fs.writeFileSync(path.join(tokenDir, 'route.ts'), tokenRouteContent);
|
|
333
|
+
createdFiles.push(`${appDir}/api/arcten/token/route.ts`);
|
|
334
|
+
console.log(` Created ${appDir}/api/arcten/token/route.ts`);
|
|
335
|
+
}
|
|
388
336
|
|
|
389
|
-
//
|
|
390
|
-
|
|
391
|
-
console.log('\n💭 Step 3: Define your agent\'s purpose\n');
|
|
392
|
-
console.log('Examples:');
|
|
393
|
-
console.log(' - "Help customers place orders and track shipments"');
|
|
394
|
-
console.log(' - "Answer questions about our product documentation"');
|
|
395
|
-
console.log(' - "Assist with user account management"');
|
|
337
|
+
// 3. Create wrapper component based on placement
|
|
338
|
+
const toolsImportPath = path.relative(appDirPath, context.toolsFile).replace(/\.ts$/, '').replace(/\\/g, '/');
|
|
396
339
|
|
|
397
|
-
|
|
340
|
+
if (placement === 'everywhere') {
|
|
341
|
+
const wrapperContent = `"use client";
|
|
398
342
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
}
|
|
343
|
+
import { ArctenAgent } from "@arcteninc/core";
|
|
344
|
+
import { toolMetadata } from "../.arcten/tool-metadata";
|
|
345
|
+
import { allTools, safeToolNames } from "../.arcten/arcten.tools";
|
|
403
346
|
|
|
404
|
-
|
|
347
|
+
interface ArctenWrapperProps {
|
|
348
|
+
children: React.ReactNode;
|
|
405
349
|
}
|
|
406
350
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
351
|
+
export function ArctenWrapper({ children }: ArctenWrapperProps) {
|
|
352
|
+
return (
|
|
353
|
+
<div className="flex h-screen">
|
|
354
|
+
<div className="flex-1 overflow-auto">
|
|
355
|
+
{children}
|
|
356
|
+
</div>
|
|
357
|
+
<ArctenAgent
|
|
358
|
+
apiBaseUrl={process.env.NEXT_PUBLIC_ARCTEN_API_URL || "https://api.arcten.com"}
|
|
359
|
+
user={{ id: "user-1", name: "User" }}
|
|
360
|
+
tools={allTools}
|
|
361
|
+
safeToolNames={safeToolNames}
|
|
362
|
+
toolMetadata={toolMetadata.functions}
|
|
363
|
+
/>
|
|
364
|
+
</div>
|
|
365
|
+
);
|
|
366
|
+
}
|
|
421
367
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
368
|
+
export default ArctenWrapper;
|
|
369
|
+
`;
|
|
370
|
+
fs.writeFileSync(path.join(appDirPath, 'ArctenWrapper.tsx'), wrapperContent);
|
|
371
|
+
createdFiles.push(`${appDir}/ArctenWrapper.tsx`);
|
|
372
|
+
console.log(` Created ${appDir}/ArctenWrapper.tsx`);
|
|
373
|
+
|
|
374
|
+
// 4. Modify layout.tsx to wrap with ArctenWrapper
|
|
375
|
+
const layoutPath = path.join(appDirPath, 'layout.tsx');
|
|
376
|
+
if (fs.existsSync(layoutPath)) {
|
|
377
|
+
let layoutContent = fs.readFileSync(layoutPath, 'utf-8');
|
|
378
|
+
|
|
379
|
+
// Add import if not present
|
|
380
|
+
if (!layoutContent.includes('ArctenWrapper')) {
|
|
381
|
+
// Add import at the top (after existing imports)
|
|
382
|
+
const importStatement = `import { ArctenWrapper } from "./ArctenWrapper";\n`;
|
|
383
|
+
const lastImportIndex = layoutContent.lastIndexOf('import ');
|
|
384
|
+
if (lastImportIndex !== -1) {
|
|
385
|
+
const endOfImport = layoutContent.indexOf('\n', lastImportIndex);
|
|
386
|
+
layoutContent = layoutContent.slice(0, endOfImport + 1) + importStatement + layoutContent.slice(endOfImport + 1);
|
|
387
|
+
} else {
|
|
388
|
+
layoutContent = importStatement + layoutContent;
|
|
389
|
+
}
|
|
427
390
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
391
|
+
// Wrap {children} with <ArctenWrapper>
|
|
392
|
+
layoutContent = layoutContent.replace(
|
|
393
|
+
/(\{children\})/,
|
|
394
|
+
'<ArctenWrapper>{children}</ArctenWrapper>'
|
|
395
|
+
);
|
|
431
396
|
|
|
432
|
-
|
|
433
|
-
|
|
397
|
+
fs.writeFileSync(layoutPath, layoutContent);
|
|
398
|
+
createdFiles.push(`${appDir}/layout.tsx (modified)`);
|
|
399
|
+
console.log(` Modified ${appDir}/layout.tsx`);
|
|
400
|
+
}
|
|
434
401
|
}
|
|
402
|
+
} else if (placement === 'specific') {
|
|
403
|
+
// Create standalone component for specific pages
|
|
404
|
+
const componentContent = `"use client";
|
|
405
|
+
|
|
406
|
+
import { ArctenAgent } from "@arcteninc/core";
|
|
407
|
+
import { toolMetadata } from "../.arcten/tool-metadata";
|
|
408
|
+
import { allTools, safeToolNames } from "../.arcten/arcten.tools";
|
|
409
|
+
|
|
410
|
+
export function ArctenChat() {
|
|
411
|
+
return (
|
|
412
|
+
<ArctenAgent
|
|
413
|
+
apiBaseUrl={process.env.NEXT_PUBLIC_ARCTEN_API_URL || "https://api.arcten.com"}
|
|
414
|
+
user={{ id: "user-1", name: "User" }}
|
|
415
|
+
tools={allTools}
|
|
416
|
+
safeToolNames={safeToolNames}
|
|
417
|
+
toolMetadata={toolMetadata.functions}
|
|
418
|
+
/>
|
|
419
|
+
);
|
|
420
|
+
}
|
|
435
421
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
console.error(' • No internet connection');
|
|
442
|
-
console.error(' • API server is down');
|
|
443
|
-
console.error(' • Firewall blocking the request');
|
|
444
|
-
throw new Error('Connection failed');
|
|
422
|
+
export default ArctenChat;
|
|
423
|
+
`;
|
|
424
|
+
const componentsDir = path.join(projectRoot, 'components');
|
|
425
|
+
if (!fs.existsSync(componentsDir)) {
|
|
426
|
+
fs.mkdirSync(componentsDir, { recursive: true });
|
|
445
427
|
}
|
|
446
|
-
|
|
428
|
+
fs.writeFileSync(path.join(componentsDir, 'ArctenChat.tsx'), componentContent);
|
|
429
|
+
createdFiles.push('components/ArctenChat.tsx');
|
|
430
|
+
console.log(' Created components/ArctenChat.tsx');
|
|
431
|
+
console.log('');
|
|
432
|
+
console.log(' Import it in your pages:');
|
|
433
|
+
console.log(' import { ArctenChat } from "@/components/ArctenChat";');
|
|
434
|
+
console.log('');
|
|
435
|
+
} else {
|
|
436
|
+
// Developer mode - just show hook usage
|
|
437
|
+
console.log('');
|
|
438
|
+
console.log(' Developer mode - use the hook directly:');
|
|
439
|
+
console.log('');
|
|
440
|
+
console.log(' import { useAgent } from "@arcteninc/core";');
|
|
441
|
+
console.log(' import { allTools, safeToolNames } from "./.arcten/arcten.tools";');
|
|
442
|
+
console.log(' import { toolMetadata } from "./.arcten/tool-metadata";');
|
|
443
|
+
console.log('');
|
|
444
|
+
console.log(' const { messages, sendMessage } = useAgent({');
|
|
445
|
+
console.log(' tools: allTools,');
|
|
446
|
+
console.log(' safeToolNames,');
|
|
447
|
+
console.log(' toolMetadata: toolMetadata.functions,');
|
|
448
|
+
console.log(' });');
|
|
449
|
+
console.log('');
|
|
447
450
|
}
|
|
448
|
-
}
|
|
449
451
|
|
|
450
|
-
//
|
|
451
|
-
|
|
452
|
-
|
|
452
|
+
// 5. Update package.json scripts
|
|
453
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
454
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
455
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
453
456
|
|
|
454
|
-
|
|
455
|
-
const url = `${serverUrl}/tools/analyze/estimate`;
|
|
456
|
-
const response = await fetch(url, {
|
|
457
|
-
method: 'POST',
|
|
458
|
-
headers: {
|
|
459
|
-
'Content-Type': 'application/json',
|
|
460
|
-
},
|
|
461
|
-
body: JSON.stringify({ context }),
|
|
462
|
-
});
|
|
457
|
+
let modified = false;
|
|
463
458
|
|
|
464
|
-
if
|
|
465
|
-
|
|
466
|
-
|
|
459
|
+
// Add arcten sync to dev and build (runs silently if no changes)
|
|
460
|
+
if (pkg.scripts?.dev && !pkg.scripts.dev.includes('arcten sync')) {
|
|
461
|
+
pkg.scripts.dev = `arcten sync --yes && ${pkg.scripts.dev}`;
|
|
462
|
+
modified = true;
|
|
467
463
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
if (error.cause?.code === 'ENOTFOUND' || error.cause?.code === 'ECONNREFUSED' || error.message.includes('fetch')) {
|
|
472
|
-
console.error(`\n❌ Unable to connect to: ${serverUrl}`);
|
|
473
|
-
console.error('\nPossible issues:');
|
|
474
|
-
console.error(' • No internet connection');
|
|
475
|
-
console.error(' • API server is down');
|
|
476
|
-
console.error(' • Firewall blocking the request');
|
|
477
|
-
console.error('\n💡 Tip: Use --allow-all flag to skip analysis and enable all tools');
|
|
478
|
-
throw new Error('Connection failed');
|
|
464
|
+
if (pkg.scripts?.build && !pkg.scripts.build.includes('arcten sync')) {
|
|
465
|
+
pkg.scripts.build = `arcten sync --yes && ${pkg.scripts.build}`;
|
|
466
|
+
modified = true;
|
|
479
467
|
}
|
|
480
|
-
throw error;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
468
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
console.log(`│ Estimated cost: $${estimate.estimatedCost.toFixed(2)}`.padEnd(50) + '│');
|
|
490
|
-
console.log(`│ ${estimate.functionCount} functions across ${estimate.chunkCount} chunk${estimate.chunkCount > 1 ? 's' : ''}`.padEnd(50) + '│');
|
|
491
|
-
console.log('│ │');
|
|
492
|
-
|
|
493
|
-
if (estimate.willBeChunked) {
|
|
494
|
-
console.log('│ ⚠️ Large codebase - will process in chunks │');
|
|
495
|
-
console.log('│ │');
|
|
469
|
+
if (modified) {
|
|
470
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
471
|
+
createdFiles.push('package.json (modified)');
|
|
472
|
+
console.log(' Updated package.json scripts');
|
|
473
|
+
}
|
|
496
474
|
}
|
|
497
475
|
|
|
498
|
-
console.log('│ This will be deducted from your subscription. │');
|
|
499
|
-
console.log('│ │');
|
|
500
|
-
console.log('│ The AI will: │');
|
|
501
|
-
console.log(`│ • Categorize your ${estimate.functionCount} functions`.padEnd(50) + '│');
|
|
502
|
-
console.log('│ • Suggest which to enable for your use case │');
|
|
503
|
-
console.log('│ • Recommend agent configurations │');
|
|
504
|
-
console.log('│ • Suggest where to place the agent │');
|
|
505
|
-
console.log('└─────────────────────────────────────────────────┘');
|
|
506
476
|
console.log('');
|
|
507
|
-
|
|
508
|
-
return await confirm(rl, `? Continue with analysis ($${estimate.estimatedCost.toFixed(2)})?`);
|
|
477
|
+
return createdFiles;
|
|
509
478
|
}
|
|
510
479
|
|
|
511
|
-
//
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
serverUrl: string,
|
|
515
|
-
context: CodebaseContext,
|
|
516
|
-
userIntent: string
|
|
517
|
-
): Promise<AnalysisResult> {
|
|
518
|
-
console.log('\n🤖 Analyzing with AI...\n');
|
|
519
|
-
|
|
520
|
-
// Get JWT token from API key
|
|
521
|
-
const token = await getAuthToken(apiKey, serverUrl);
|
|
522
|
-
console.log(`📡 Using token for analysis: ${token ? token.substring(0, 20) + '...' : 'NO TOKEN'}`);
|
|
523
|
-
console.log(`📡 Calling: ${serverUrl}/tools/analyze`);
|
|
524
|
-
|
|
525
|
-
const response = await fetch(`${serverUrl}/tools/analyze`, {
|
|
526
|
-
method: 'POST',
|
|
527
|
-
headers: {
|
|
528
|
-
'Content-Type': 'application/json',
|
|
529
|
-
'Authorization': `Bearer ${token}`,
|
|
530
|
-
},
|
|
531
|
-
body: JSON.stringify({ context, userIntent }),
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
console.log(`📡 Analysis response status: ${response.status}`);
|
|
535
|
-
|
|
536
|
-
if (!response.ok) {
|
|
537
|
-
const error = await response.json();
|
|
538
|
-
console.error(`📡 Analysis error response:`, error);
|
|
539
|
-
throw new Error(error.error || 'Analysis failed');
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
const result = await response.json();
|
|
543
|
-
console.log(`📡 Analysis result:`, JSON.stringify(result).substring(0, 200));
|
|
544
|
-
|
|
545
|
-
if (!result || !result.success) {
|
|
546
|
-
throw new Error(result?.error || 'Analysis failed - invalid response');
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
const tokensUsed = result.tokensUsed || 0;
|
|
550
|
-
const actualCost = result.stats?.actualCost || 0;
|
|
551
|
-
|
|
552
|
-
console.log(`✓ Analysis complete - ${tokensUsed.toLocaleString()} tokens used`);
|
|
553
|
-
console.log(` Actual cost: $${actualCost.toFixed(2)}`);
|
|
480
|
+
// ============================================================================
|
|
481
|
+
// PHASE 5: AI Review (disabled - endpoint not implemented)
|
|
482
|
+
// ============================================================================
|
|
554
483
|
|
|
555
|
-
|
|
484
|
+
async function aiReviewFiles(_result: InitResult, _createdFiles: string[]): Promise<void> {
|
|
485
|
+
// AI review is optional and requires server endpoint /ai-init/review
|
|
486
|
+
// Skipping for now since the generated files are deterministic
|
|
556
487
|
}
|
|
557
488
|
|
|
558
|
-
//
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
serverUrl: string,
|
|
562
|
-
projectId: string,
|
|
563
|
-
tools: ToolRecommendation[],
|
|
564
|
-
agents?: AgentConfig[]
|
|
565
|
-
): Promise<void> {
|
|
566
|
-
console.log('\n☁️ Step 7: Syncing to dashboard...\n');
|
|
567
|
-
|
|
568
|
-
// Convert tools to the format expected by bulkUpsert
|
|
569
|
-
const toolsData = tools.map(tool => ({
|
|
570
|
-
name: tool.name,
|
|
571
|
-
description: tool.description,
|
|
572
|
-
signature: JSON.stringify({ /* simplified */ }),
|
|
573
|
-
sourceFile: undefined, // Not tracked in wizard yet
|
|
574
|
-
category: tool.category,
|
|
575
|
-
isEnabled: tool.shouldEnable,
|
|
576
|
-
isOverridable: true,
|
|
577
|
-
requiresApproval: tool.requiresApproval,
|
|
578
|
-
sensitiveParams: tool.sensitiveParams || [],
|
|
579
|
-
}));
|
|
489
|
+
// ============================================================================
|
|
490
|
+
// PHASE 6: Run Sync
|
|
491
|
+
// ============================================================================
|
|
580
492
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
name: agent.name,
|
|
584
|
-
systemPrompt: agent.systemPrompt,
|
|
585
|
-
enabledTools: agent.enabledTools,
|
|
586
|
-
description: agent.description || `Agent: ${agent.name}`,
|
|
587
|
-
})) || [];
|
|
493
|
+
async function runSync(): Promise<void> {
|
|
494
|
+
console.log('Running sync...\n');
|
|
588
495
|
|
|
589
496
|
try {
|
|
590
|
-
// Get
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
//
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
'
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
console.log(`✅ Synced ${tools.length} tools${agents && agents.length > 0 ? ` and ${agents.length} agents` : ''} to dashboard`);
|
|
619
|
-
|
|
620
|
-
if (result.upserted) {
|
|
621
|
-
const created = result.upserted.filter((t: any) => t.created).length;
|
|
622
|
-
const updated = result.upserted.filter((t: any) => !t.created).length;
|
|
623
|
-
console.log(` 📝 Tools - Created: ${created} | Updated: ${updated}`);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
if (result.agents && agents && agents.length > 0) {
|
|
627
|
-
const agentCreated = result.agents.filter((a: any) => a.created).length;
|
|
628
|
-
const agentUpdated = result.agents.filter((a: any) => !a.created).length;
|
|
629
|
-
console.log(` 📝 Agents - Created: ${agentCreated} | Updated: ${agentUpdated}`);
|
|
497
|
+
// Get script directory (ESM compatible)
|
|
498
|
+
const scriptDir = path.dirname(new URL(import.meta.url).pathname).replace(/^\/([A-Z]:)/, '$1');
|
|
499
|
+
|
|
500
|
+
// Run arcten sync which handles extract-types + tool array generation
|
|
501
|
+
const syncScript = path.join(scriptDir, 'cli-sync-wrapper.cjs');
|
|
502
|
+
if (fs.existsSync(syncScript)) {
|
|
503
|
+
execSync(`node "${syncScript}" --yes`, {
|
|
504
|
+
cwd: process.cwd(),
|
|
505
|
+
stdio: 'inherit',
|
|
506
|
+
});
|
|
507
|
+
} else {
|
|
508
|
+
// Fallback: run extract-types directly
|
|
509
|
+
console.log(' Running arcten-extract-types...');
|
|
510
|
+
const extractScript = path.join(scriptDir, 'cli-extract-types-auto-wrapper.js');
|
|
511
|
+
if (fs.existsSync(extractScript)) {
|
|
512
|
+
execSync(`node "${extractScript}"`, {
|
|
513
|
+
cwd: process.cwd(),
|
|
514
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
515
|
+
});
|
|
516
|
+
} else {
|
|
517
|
+
execSync('npx arcten-extract-types', {
|
|
518
|
+
cwd: process.cwd(),
|
|
519
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
console.log(' Done');
|
|
630
523
|
}
|
|
631
524
|
} catch (error: any) {
|
|
632
|
-
console.
|
|
633
|
-
|
|
525
|
+
console.log(' Warning: Sync encountered an error');
|
|
526
|
+
console.log(' You can run `arcten sync` manually after setup');
|
|
634
527
|
}
|
|
635
528
|
}
|
|
636
529
|
|
|
637
|
-
//
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
): Promise<void> {
|
|
641
|
-
if (!placements || placements.length === 0) {
|
|
642
|
-
return;
|
|
643
|
-
}
|
|
530
|
+
// ============================================================================
|
|
531
|
+
// Helper Functions
|
|
532
|
+
// ============================================================================
|
|
644
533
|
|
|
645
|
-
|
|
534
|
+
function checkApiKey(projectRoot: string): { apiKey: string; projectId: string } | null {
|
|
535
|
+
const envFiles = ['.env.local', '.env'];
|
|
646
536
|
|
|
647
|
-
for (const
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
537
|
+
for (const envFile of envFiles) {
|
|
538
|
+
const envPath = path.join(projectRoot, envFile);
|
|
539
|
+
if (fs.existsSync(envPath)) {
|
|
540
|
+
const envContent = fs.readFileSync(envPath, 'utf-8');
|
|
541
|
+
const match = envContent.match(/ARCTEN_API_KEY=([^\s\n]+)/);
|
|
542
|
+
if (match) {
|
|
543
|
+
const key = match[1];
|
|
544
|
+
const projectIdMatch = key.match(/sk_(proj_[a-zA-Z0-9]+)_/);
|
|
545
|
+
if (projectIdMatch) {
|
|
546
|
+
return { apiKey: key, projectId: projectIdMatch[1] };
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
652
550
|
}
|
|
653
551
|
|
|
654
|
-
|
|
655
|
-
console.log(' 1. Install @arcteninc/core: bun add @arcteninc/core');
|
|
656
|
-
console.log(' 2. Import and use the agent in your components:');
|
|
657
|
-
console.log('');
|
|
658
|
-
console.log(' import { useAgent } from "@arcteninc/core";');
|
|
659
|
-
console.log(' import { arctenConfig } from "./.arcten/config";');
|
|
660
|
-
console.log('');
|
|
661
|
-
console.log(' function MyComponent() {');
|
|
662
|
-
console.log(' const agent = useAgent({');
|
|
663
|
-
console.log(' projectId: arctenConfig.projectId,');
|
|
664
|
-
console.log(' agentName: "default",');
|
|
665
|
-
console.log(' tools: myTools,');
|
|
666
|
-
console.log(' });');
|
|
667
|
-
console.log('');
|
|
668
|
-
console.log(' return (');
|
|
669
|
-
console.log(' <div>');
|
|
670
|
-
console.log(' {agent.messages.map(m => <div key={m.id}>{m.content}</div>)}');
|
|
671
|
-
console.log(' </div>');
|
|
672
|
-
console.log(' );');
|
|
673
|
-
console.log(' }');
|
|
674
|
-
console.log('');
|
|
675
|
-
console.log(' 3. For pre-built UI, use <ArctenAgent /> component');
|
|
676
|
-
console.log('');
|
|
677
|
-
console.log(' For more info: https://arcten.com/docs/integration');
|
|
678
|
-
console.log('');
|
|
552
|
+
return null;
|
|
679
553
|
}
|
|
680
554
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
functions: FunctionMetadata[]
|
|
687
|
-
): Promise<AgentConfig[]> {
|
|
688
|
-
const token = await getAuthToken(apiKey, serverUrl);
|
|
689
|
-
|
|
690
|
-
const tools = functions.map(fn => ({
|
|
691
|
-
name: fn.name,
|
|
692
|
-
description: fn.jsDoc || `Function: ${fn.name}`,
|
|
693
|
-
}));
|
|
694
|
-
|
|
695
|
-
const response = await fetch(`${serverUrl}/tools/generate-agents`, {
|
|
696
|
-
method: 'POST',
|
|
697
|
-
headers: {
|
|
698
|
-
'Content-Type': 'application/json',
|
|
699
|
-
'Authorization': `Bearer ${token}`,
|
|
700
|
-
},
|
|
701
|
-
body: JSON.stringify({
|
|
702
|
-
description,
|
|
703
|
-
tools,
|
|
704
|
-
}),
|
|
555
|
+
function askQuestion(question: string): Promise<string> {
|
|
556
|
+
return new Promise((resolve) => {
|
|
557
|
+
rl.question(question, (answer) => {
|
|
558
|
+
resolve(answer.trim());
|
|
559
|
+
});
|
|
705
560
|
});
|
|
706
|
-
|
|
707
|
-
if (!response.ok) {
|
|
708
|
-
const errorText = await response.text().catch(() => 'Unknown error');
|
|
709
|
-
throw new Error(`Failed to generate agents: ${response.status} ${errorText}`);
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
const result = await response.json();
|
|
713
|
-
|
|
714
|
-
if (!result.success || !result.agents) {
|
|
715
|
-
throw new Error(result.error || 'Failed to generate agent profiles');
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
return result.agents;
|
|
719
561
|
}
|
|
720
562
|
|
|
721
|
-
//
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
agentName: string = 'default'
|
|
725
|
-
): Promise<void> {
|
|
726
|
-
console.log('\n📁 Step 8: Generating local configuration...\n');
|
|
727
|
-
|
|
728
|
-
try {
|
|
729
|
-
// Create .arcten directory if it doesn't exist
|
|
730
|
-
const arctenDir = path.join(process.cwd(), '.arcten');
|
|
731
|
-
if (!fs.existsSync(arctenDir)) {
|
|
732
|
-
fs.mkdirSync(arctenDir, { recursive: true });
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
// Generate .arcten/config.ts
|
|
736
|
-
const configContent = `/**
|
|
737
|
-
* Arcten Configuration
|
|
738
|
-
* Generated by: arcten init
|
|
739
|
-
*
|
|
740
|
-
* This file contains your project ID and route configurations.
|
|
741
|
-
* Modify this file to change which agents are used on different routes.
|
|
742
|
-
*/
|
|
743
|
-
|
|
744
|
-
export const arctenConfig = {
|
|
745
|
-
projectId: "${projectId}",
|
|
746
|
-
routes: [
|
|
747
|
-
// Default route - matches all pages
|
|
748
|
-
{ pattern: "/*", agentName: "${agentName}" },
|
|
749
|
-
|
|
750
|
-
// Add more route-specific agents here
|
|
751
|
-
// Example:
|
|
752
|
-
// { pattern: "/products/*", agentName: "sales-assistant" },
|
|
753
|
-
// { pattern: "/support/*", agentName: "support-bot" },
|
|
754
|
-
],
|
|
755
|
-
} as const;
|
|
756
|
-
`;
|
|
757
|
-
|
|
758
|
-
const configPath = path.join(arctenDir, 'config.ts');
|
|
759
|
-
fs.writeFileSync(configPath, configContent, 'utf-8');
|
|
760
|
-
console.log(`✅ Created ${path.relative(process.cwd(), configPath)}`);
|
|
563
|
+
// ============================================================================
|
|
564
|
+
// Main
|
|
565
|
+
// ============================================================================
|
|
761
566
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
Import the config in your application:
|
|
775
|
-
|
|
776
|
-
\`\`\`typescript
|
|
777
|
-
import { arctenConfig } from './.arcten/config';
|
|
778
|
-
import { useAgent } from '@arcteninc/core';
|
|
567
|
+
async function main() {
|
|
568
|
+
console.log('');
|
|
569
|
+
console.log(' ___ ____________ _____ _____ _ _ ');
|
|
570
|
+
console.log(' / _ \\ | ___ \\ ___ \\_ _| ___| \\ | |');
|
|
571
|
+
console.log('/ /_\\ \\| |_/ / | / | | | |__ | \\| |');
|
|
572
|
+
console.log('| _ || /| | | | | | __|| . ` |');
|
|
573
|
+
console.log('| | | || |\\ \\| |__/\\ | | | |___| |\\ |');
|
|
574
|
+
console.log('\\_| |_/\\_| \\_\\____/ \\_/ \\____/\\_| \\_/');
|
|
575
|
+
console.log('');
|
|
576
|
+
console.log(' Setup Wizard');
|
|
577
|
+
console.log('');
|
|
779
578
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
tools: myTools,
|
|
579
|
+
// Create readline interface
|
|
580
|
+
rl = readline.createInterface({
|
|
581
|
+
input: process.stdin,
|
|
582
|
+
output: process.stdout,
|
|
785
583
|
});
|
|
786
584
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
For more information, visit https://arcten.com/docs
|
|
792
|
-
`;
|
|
793
|
-
|
|
794
|
-
const readmePath = path.join(arctenDir, 'README.md');
|
|
795
|
-
fs.writeFileSync(readmePath, readmeContent, 'utf-8');
|
|
796
|
-
console.log(`✅ Created ${path.relative(process.cwd(), readmePath)}`);
|
|
797
|
-
|
|
798
|
-
} catch (error: any) {
|
|
799
|
-
console.error(`❌ Failed to generate config: ${error.message}`);
|
|
800
|
-
throw error;
|
|
801
|
-
}
|
|
802
|
-
}
|
|
585
|
+
try {
|
|
586
|
+
// PHASE 1: Deterministic Discovery
|
|
587
|
+
const context = await discoverContext();
|
|
803
588
|
|
|
804
|
-
//
|
|
805
|
-
|
|
806
|
-
// Check for flags
|
|
807
|
-
const args = process.argv.slice(2);
|
|
808
|
-
const skipAnalysis = args.includes('--skip-analysis');
|
|
809
|
-
const allowAll = args.includes('--allow-all');
|
|
589
|
+
// PHASE 2: Deterministic Classification
|
|
590
|
+
const classification = await classifyTools(context);
|
|
810
591
|
|
|
811
|
-
|
|
812
|
-
console.log('');
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
} else if (skipAnalysis) {
|
|
820
|
-
console.log('');
|
|
821
|
-
console.log('╔═══════════════════════════════════════════════╗');
|
|
822
|
-
console.log('║ ║');
|
|
823
|
-
console.log('║ 🔄 Arcten Sync Tools 🔄 ║');
|
|
824
|
-
console.log('║ ║');
|
|
825
|
-
console.log('╚═══════════════════════════════════════════════╝');
|
|
826
|
-
console.log('');
|
|
827
|
-
} else {
|
|
828
|
-
console.log('');
|
|
829
|
-
console.log('╔═══════════════════════════════════════════════╗');
|
|
830
|
-
console.log('║ ║');
|
|
831
|
-
console.log('║ 🚀 Arcten Setup Wizard 🚀 ║');
|
|
832
|
-
console.log('║ ║');
|
|
833
|
-
console.log('╚═══════════════════════════════════════════════╝');
|
|
592
|
+
// Show classification summary
|
|
593
|
+
console.log('Classification:');
|
|
594
|
+
if (classification.safe.length > 0) {
|
|
595
|
+
console.log(` Safe: ${classification.safe.slice(0, 5).join(', ')}${classification.safe.length > 5 ? '...' : ''}`);
|
|
596
|
+
}
|
|
597
|
+
if (classification.sensitive.length > 0) {
|
|
598
|
+
console.log(` Sensitive: ${classification.sensitive.slice(0, 5).join(', ')}${classification.sensitive.length > 5 ? '...' : ''}`);
|
|
599
|
+
}
|
|
834
600
|
console.log('');
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
const rl = createPrompt();
|
|
838
|
-
const serverUrl = process.env.ARCTEN_SERVER_URL || 'https://api.arcten.com';
|
|
839
601
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
if (hasExistingSetup && !skipAnalysis && !allowAll) {
|
|
846
|
-
console.log('⚠️ Arcten is already configured in this project!\n');
|
|
847
|
-
console.log('📁 Found existing .arcten/ directory\n');
|
|
848
|
-
console.log('Running init again will:');
|
|
849
|
-
console.log(' • Overwrite your .arcten/config.ts file');
|
|
850
|
-
console.log(' • Run AI analysis again (costs money)');
|
|
851
|
-
console.log(' • Resync all tools to dashboard\n');
|
|
852
|
-
|
|
853
|
-
const shouldContinue = await confirm(rl, 'Do you want to continue and reconfigure?');
|
|
854
|
-
|
|
855
|
-
if (!shouldContinue) {
|
|
856
|
-
console.log('\n💡 Tip: To re-sync tools without analysis, use:');
|
|
857
|
-
console.log(' arcten sync\n');
|
|
858
|
-
rl.close();
|
|
859
|
-
process.exit(0);
|
|
860
|
-
}
|
|
602
|
+
const classificationOk = await askQuestion('Does this look right? (y/n): ');
|
|
603
|
+
if (classificationOk.toLowerCase() === 'n') {
|
|
604
|
+
console.log('');
|
|
605
|
+
console.log('You can adjust classifications later by editing .arcten/sync-state.json');
|
|
606
|
+
console.log('and running `arcten sync`.');
|
|
861
607
|
console.log('');
|
|
862
608
|
}
|
|
863
609
|
|
|
864
|
-
//
|
|
865
|
-
const
|
|
866
|
-
|
|
867
|
-
// Step 2: Scan codebase
|
|
868
|
-
const context = await scanCodebase();
|
|
869
|
-
|
|
870
|
-
// Step 2.5: Handle missing descriptions (only in full wizard mode, not sync/allow-all)
|
|
871
|
-
if (!skipAnalysis && !allowAll) {
|
|
872
|
-
context.functions = await handleMissingDescriptions(rl, context.functions);
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
// Step 3: Sync all tools to dashboard
|
|
876
|
-
console.log('\n☁️ Syncing tools to dashboard...\n');
|
|
877
|
-
const tools: ToolRecommendation[] = context.functions.map(fn => ({
|
|
878
|
-
name: fn.name,
|
|
879
|
-
category: 'query' as const,
|
|
880
|
-
description: fn.jsDoc || `Function: ${fn.name}`,
|
|
881
|
-
shouldEnable: allowAll, // Enable all in allow-all mode
|
|
882
|
-
requiresApproval: false,
|
|
883
|
-
sensitiveParams: [],
|
|
884
|
-
}));
|
|
885
|
-
|
|
886
|
-
await syncToDashboard(apiKey, serverUrl, projectId, tools);
|
|
887
|
-
console.log(`✅ Synced ${tools.length} tools to dashboard\n`);
|
|
888
|
-
|
|
889
|
-
// Step 4: Create agents
|
|
890
|
-
let agents: AgentConfig[] = [];
|
|
891
|
-
|
|
892
|
-
if (skipAnalysis) {
|
|
893
|
-
// Just create default agent
|
|
894
|
-
agents = [{
|
|
895
|
-
name: 'default',
|
|
896
|
-
systemPrompt: 'You are a helpful assistant.',
|
|
897
|
-
enabledTools: context.functions.map(f => f.name),
|
|
898
|
-
description: 'General assistant',
|
|
899
|
-
}];
|
|
900
|
-
} else {
|
|
901
|
-
// Ask user to describe agents
|
|
902
|
-
console.log('📋 Tools synced. Now let\'s create your agent(s).\n');
|
|
903
|
-
const agentDescription = await ask(rl,
|
|
904
|
-
'Describe the agent(s) you want (or press Enter for a default agent):\n> '
|
|
905
|
-
);
|
|
610
|
+
// PHASE 3: Placement Menu
|
|
611
|
+
const placement = await showPlacementMenu();
|
|
612
|
+
console.log('');
|
|
906
613
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
enabledTools: context.functions.map(f => f.name),
|
|
913
|
-
description: 'General assistant',
|
|
914
|
-
}];
|
|
915
|
-
} else {
|
|
916
|
-
// Generate agents via LLM
|
|
917
|
-
console.log('\n🤖 Generating agent profiles...\n');
|
|
918
|
-
agents = await generateAgents(apiKey, serverUrl, agentDescription, context.functions);
|
|
919
|
-
|
|
920
|
-
// Show what will be created
|
|
921
|
-
console.log('🤖 Agents to create:\n');
|
|
922
|
-
for (const agent of agents) {
|
|
923
|
-
console.log(` 📌 ${agent.name}: ${agent.description}`);
|
|
924
|
-
console.log(` Tools: ${agent.enabledTools.length} of ${context.functions.length}\n`);
|
|
925
|
-
}
|
|
614
|
+
const result: InitResult = {
|
|
615
|
+
context,
|
|
616
|
+
classification,
|
|
617
|
+
placement,
|
|
618
|
+
};
|
|
926
619
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
// Fallback to default
|
|
930
|
-
agents = [{
|
|
931
|
-
name: 'default',
|
|
932
|
-
systemPrompt: 'You are a helpful assistant.',
|
|
933
|
-
enabledTools: context.functions.map(f => f.name),
|
|
934
|
-
description: 'General assistant',
|
|
935
|
-
}];
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
}
|
|
620
|
+
// PHASE 4: Deterministic File Creation
|
|
621
|
+
const createdFiles = await createAllFiles(result);
|
|
939
622
|
|
|
940
|
-
//
|
|
941
|
-
|
|
942
|
-
await syncToDashboard(apiKey, serverUrl, projectId, tools, agents);
|
|
943
|
-
console.log(`✅ Synced ${agents.length} agent(s) to dashboard\n`);
|
|
623
|
+
// PHASE 5: AI Review
|
|
624
|
+
await aiReviewFiles(result, createdFiles);
|
|
944
625
|
|
|
945
|
-
//
|
|
946
|
-
await
|
|
626
|
+
// PHASE 6: Run Sync
|
|
627
|
+
await runSync();
|
|
947
628
|
|
|
948
|
-
console.log('\n✅ Setup complete!\n');
|
|
949
|
-
console.log(`📍 Dashboard: https://arcten.com/dashboard/projects/${projectId}`);
|
|
950
629
|
console.log('');
|
|
951
|
-
|
|
630
|
+
console.log('Setup complete!');
|
|
631
|
+
console.log('');
|
|
632
|
+
console.log('Run `bun dev` (or `npm run dev`) to start your app.');
|
|
633
|
+
console.log('');
|
|
952
634
|
} catch (error: any) {
|
|
953
|
-
console.error('
|
|
635
|
+
console.error('Error:', error.message);
|
|
954
636
|
process.exit(1);
|
|
955
637
|
} finally {
|
|
956
638
|
rl.close();
|
|
957
639
|
}
|
|
958
640
|
}
|
|
959
641
|
|
|
960
|
-
// Run
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
});
|
|
966
|
-
}
|
|
642
|
+
// Run
|
|
643
|
+
main().catch((error) => {
|
|
644
|
+
console.error('Fatal error:', error);
|
|
645
|
+
process.exit(1);
|
|
646
|
+
});
|
|
967
647
|
|
|
968
648
|
export { main };
|