arkna 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/commands/connect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6EpC,eAAO,MAAM,cAAc,SAmOvB,CAAC"}
1
+ {"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/commands/connect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2SpC,eAAO,MAAM,cAAc,SA+TvB,CAAC"}
@@ -39,21 +39,60 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.connectCommand = void 0;
40
40
  const commander_1 = require("commander");
41
41
  const inquirer_1 = __importDefault(require("inquirer"));
42
- const os = __importStar(require("os"));
42
+ const fs = __importStar(require("fs"));
43
43
  const path = __importStar(require("path"));
44
44
  const ui_1 = require("../lib/ui");
45
45
  const arkna_dir_1 = require("../lib/arkna-dir");
46
46
  const verify_1 = require("../lib/verify");
47
+ const connectivity_1 = require("../lib/connectivity");
47
48
  const platforms_1 = require("../lib/platforms");
48
49
  const VALID_PLATFORMS = ['openclaw', 'crewai', 'langchain', 'custom'];
50
+ // ── Admin session cache (login once, reuse for all agents) ──────────────────
51
+ let cachedJwt = null;
52
+ async function ensureAdminSession(url) {
53
+ if (cachedJwt)
54
+ return cachedJwt;
55
+ console.log(`\n ${(0, ui_1.dim)('Login to ARKNA to auto-create integration tokens.')}`);
56
+ const { email } = await inquirer_1.default.prompt([{
57
+ type: 'input',
58
+ name: 'email',
59
+ message: (0, ui_1.dim)(' Email:'),
60
+ prefix: '',
61
+ validate: (v) => v.includes('@') || 'Enter a valid email',
62
+ }]);
63
+ const { password } = await inquirer_1.default.prompt([{
64
+ type: 'password',
65
+ name: 'password',
66
+ message: (0, ui_1.dim)(' Password:'),
67
+ mask: '\u25CF',
68
+ prefix: '',
69
+ validate: (v) => v.length > 0 || 'Password is required',
70
+ }]);
71
+ const s = (0, ui_1.startSpinner)('Authenticating...');
72
+ const result = await (0, connectivity_1.adminLogin)(url, email, password);
73
+ s.stop();
74
+ if (!result.success || !result.session) {
75
+ console.log(` ${(0, ui_1.warn)('\u2718')} Login failed: ${(0, ui_1.dim)(result.error || 'unknown error')}`);
76
+ return null;
77
+ }
78
+ if (result.session.user.role !== 'Admin') {
79
+ console.log(` ${(0, ui_1.warn)('\u2718')} ${(0, ui_1.dim)('Account is not an Admin. Token creation requires Admin role.')}`);
80
+ return null;
81
+ }
82
+ console.log(` ${(0, ui_1.brand)('\u2714')} Logged in as ${result.session.user.email}`);
83
+ cachedJwt = result.session.jwt;
84
+ return cachedJwt;
85
+ }
86
+ // ── Token resolution ────────────────────────────────────────────────────────
49
87
  /**
50
- * Resolve token from (in priority order):
88
+ * Resolve token for an agent. Priority:
51
89
  * 1. ARKNA_TOKEN env var
52
- * 2. .arkna/.env file
53
- * 3. Interactive secure prompt
54
- * 4. -t flag (last resort leaks to shell history)
90
+ * 2. .arkna/.env file in the agent's directory
91
+ * 3. -t flag
92
+ * 4. Auto-create via admin API (login once, reuse session)
93
+ * 5. Manual paste fallback
55
94
  */
56
- async function resolveToken(flagToken, baseDir) {
95
+ async function resolveToken(url, agentName, flagToken, baseDir) {
57
96
  // 1. Env var
58
97
  if (process.env.ARKNA_TOKEN) {
59
98
  console.log(` ${(0, ui_1.dim)('Token:')} from ARKNA_TOKEN env var`);
@@ -70,8 +109,32 @@ async function resolveToken(flagToken, baseDir) {
70
109
  console.log(` ${(0, ui_1.warn)('\u26A0')} ${(0, ui_1.dim)('Token passed via -t flag (visible in shell history)')}`);
71
110
  return flagToken;
72
111
  }
73
- // 4. Interactive secure prompt
74
- console.log(` ${(0, ui_1.dim)('No token found. Run')} ${(0, ui_1.brand)('arkna login')} ${(0, ui_1.dim)('first, or paste token below:')}`);
112
+ // 4. Offer auto-create vs manual paste
113
+ const { method } = await inquirer_1.default.prompt([{
114
+ type: 'list',
115
+ name: 'method',
116
+ message: 'No token found. How to authenticate?',
117
+ choices: [
118
+ { name: `Login to ARKNA ${(0, ui_1.dim)('(auto-create token)')}`, value: 'login' },
119
+ { name: `Paste token manually`, value: 'paste' },
120
+ ],
121
+ }]);
122
+ if (method === 'login') {
123
+ const jwt = await ensureAdminSession(url);
124
+ if (jwt) {
125
+ const s = (0, ui_1.startSpinner)(`Creating token for ${agentName}...`);
126
+ const result = await (0, connectivity_1.provisionToken)(url, jwt, agentName);
127
+ s.stop();
128
+ if (result.success && result.token) {
129
+ console.log(` ${(0, ui_1.brand)('\u2714')} Token created ${(0, ui_1.dim)(`(${(0, ui_1.maskToken)(result.token)})`)}`);
130
+ return result.token;
131
+ }
132
+ console.log(` ${(0, ui_1.warn)('\u2718')} Failed to create token: ${(0, ui_1.dim)(result.error || 'unknown')}`);
133
+ console.log(` ${(0, ui_1.dim)('Falling back to manual paste...')}`);
134
+ }
135
+ }
136
+ // 5. Manual paste fallback
137
+ console.log(` ${(0, ui_1.dim)('Paste your token from Settings > External Agents:')}`);
75
138
  const { token } = await inquirer_1.default.prompt([{
76
139
  type: 'password',
77
140
  name: 'token',
@@ -104,98 +167,69 @@ function resolveUrl(flagUrl, baseDir) {
104
167
  return envCreds.url;
105
168
  return 'https://api.arkna.com.au';
106
169
  }
107
- exports.connectCommand = new commander_1.Command('connect')
108
- .description('Wire an agent to route through the ARKNA governance gateway')
109
- .argument('<platform>', `Agent platform (${VALID_PLATFORMS.join(', ')})`)
110
- .option('-t, --token <token>', 'Integration token (prefer ARKNA_TOKEN env var instead)')
111
- .option('-u, --url <url>', 'ARKNA gateway URL (or set ARKNA_URL env var)')
112
- .option('-n, --name <name>', 'Agent name')
113
- .option('-c, --config <path>', 'Path to agent config file (OpenClaw: YAML to modify)')
114
- .option('-d, --dir <dir>', 'Output dir for wrapper files (CrewAI/LangChain)')
115
- .action(async (platform, options) => {
116
- (0, ui_1.banner)();
117
- // ── Validate platform ────────────────────────────────────────────────
118
- if (!VALID_PLATFORMS.includes(platform)) {
119
- (0, ui_1.errorBox)('Invalid platform', [
120
- `Got: ${platform}`,
121
- (0, ui_1.dim)(`Valid platforms: ${VALID_PLATFORMS.join(', ')}`),
122
- ]);
123
- process.exit(1);
124
- }
125
- const outputDir = options.dir || process.cwd();
126
- const baseDir = options.config ? path.dirname(options.config) : outputDir;
127
- // ── Resolve URL ──────────────────────────────────────────────────────
128
- const url = resolveUrl(options.url, baseDir);
129
- if (!url.startsWith('http://') && !url.startsWith('https://')) {
130
- (0, ui_1.errorBox)('Invalid gateway URL', [
131
- `Got: ${url}`,
132
- (0, ui_1.dim)('URL must start with http:// or https://'),
133
- ]);
134
- process.exit(1);
135
- }
136
- // ── Resolve token (env > .arkna/.env > prompt > flag) ────────────────
137
- const token = await resolveToken(options.token, baseDir);
138
- if (!token.startsWith('intk_')) {
139
- (0, ui_1.errorBox)('Invalid token', [
140
- (0, ui_1.dim)('Token must start with intk_'),
141
- (0, ui_1.dim)('Create one in Settings > External Agents.'),
142
- ]);
143
- process.exit(1);
144
- }
145
- const agentName = options.name || os.hostname();
146
- // ── Step 1: Platform-specific wiring ─────────────────────────────────
147
- (0, ui_1.step)(1, 3, 'Wire Agent');
170
+ async function connectSingleAgent(platform, agentDir, configFile, agentName, url, token, stepOffset) {
171
+ // ── Wire Agent ──────────────────────────────────────────────────────────
172
+ (0, ui_1.step)(stepOffset.wire, stepOffset.total, `Wire Agent${stepOffset.total > 4 ? ` \u2014 ${agentName}` : ''}`);
148
173
  let patchText;
149
174
  switch (platform) {
150
175
  case 'openclaw': {
151
- if (options.config) {
176
+ if (configFile) {
152
177
  const spinner = (0, ui_1.startSpinner)('Modifying OpenClaw config...');
153
- const result = (0, platforms_1.wireOpenClaw)(options.config, url, agentName);
178
+ const result = (0, platforms_1.wireOpenClaw)(configFile, url, agentName);
154
179
  spinner.stop();
155
180
  if (!result.success) {
156
- (0, ui_1.errorBox)('Failed to modify config', [
157
- result.error || 'Unknown error',
158
- ]);
159
- process.exit(1);
181
+ (0, ui_1.errorBox)('Failed to modify config', [result.error || 'Unknown error']);
182
+ return { name: agentName, platform, ok: false };
160
183
  }
161
184
  if (result.alreadyConfigured) {
162
- console.log(` ${(0, ui_1.dim)('connectors.arkna already exists \u2014 skipping.')}`);
185
+ console.log(` ${(0, ui_1.brand)('\u2714')} connectors.arkna already exists ${(0, ui_1.dim)('\u2014 skipping.')}`);
163
186
  }
164
187
  else {
165
- console.log(` ${(0, ui_1.brand)('\u2714')} Modified ${options.config}`);
166
- console.log(` ${(0, ui_1.dim)('Backup saved to ' + options.config + '.bak')}`);
188
+ console.log(` ${(0, ui_1.brand)('\u2714')} Patched ${path.basename(configFile)} with ARKNA connector`);
189
+ console.log(` ${(0, ui_1.dim)('Backup saved to ' + configFile + '.bak')}`);
167
190
  }
168
191
  }
169
192
  else {
170
- // No config file provided print the YAML block
171
- console.log(` ${(0, ui_1.dim)('No --config provided. Add this to your OpenClaw YAML:')}\n`);
193
+ console.log(` ${(0, ui_1.dim)('No OpenClaw config YAML found. Add this to your config:')}\n`);
172
194
  console.log((0, ui_1.dim)((0, platforms_1.getOpenClawYamlBlock)(agentName)));
173
195
  patchText = 'Add the YAML block above to your OpenClaw config file.';
174
196
  }
175
197
  break;
176
198
  }
177
199
  case 'crewai': {
178
- const spinner = (0, ui_1.startSpinner)('Generating CrewAI wrapper...');
179
- const result = (0, platforms_1.wireCrewAI)(outputDir, agentName);
180
- spinner.stop();
181
- if (!result.success) {
182
- (0, ui_1.errorBox)('Failed to generate wrapper', [result.error || 'Unknown error']);
183
- process.exit(1);
200
+ const wrapperPath = path.join(agentDir, 'arkna_gateway.py');
201
+ if (existsAndNotEmpty(wrapperPath)) {
202
+ console.log(` ${(0, ui_1.brand)('\u2714')} arkna_gateway.py already exists ${(0, ui_1.dim)('\u2014 skipping.')}`);
203
+ }
204
+ else {
205
+ const spinner = (0, ui_1.startSpinner)('Generating CrewAI wrapper...');
206
+ const result = (0, platforms_1.wireCrewAI)(agentDir, agentName);
207
+ spinner.stop();
208
+ if (!result.success) {
209
+ (0, ui_1.errorBox)('Failed to generate wrapper', [result.error || 'Unknown error']);
210
+ return { name: agentName, platform, ok: false };
211
+ }
212
+ patchText = result.patch;
213
+ console.log(` ${(0, ui_1.brand)('\u2714')} Created ${result.wrapperPath}`);
184
214
  }
185
- patchText = result.patch;
186
- console.log(` ${(0, ui_1.brand)('\u2714')} Created ${result.wrapperPath}`);
187
215
  break;
188
216
  }
189
217
  case 'langchain': {
190
- const spinner = (0, ui_1.startSpinner)('Generating LangChain wrapper...');
191
- const result = (0, platforms_1.wireLangChain)(outputDir, agentName);
192
- spinner.stop();
193
- if (!result.success) {
194
- (0, ui_1.errorBox)('Failed to generate wrapper', [result.error || 'Unknown error']);
195
- process.exit(1);
218
+ const wrapperPath = path.join(agentDir, 'arkna_tool.py');
219
+ if (existsAndNotEmpty(wrapperPath)) {
220
+ console.log(` ${(0, ui_1.brand)('\u2714')} arkna_tool.py already exists ${(0, ui_1.dim)('\u2014 skipping.')}`);
221
+ }
222
+ else {
223
+ const spinner = (0, ui_1.startSpinner)('Generating LangChain wrapper...');
224
+ const result = (0, platforms_1.wireLangChain)(agentDir, agentName);
225
+ spinner.stop();
226
+ if (!result.success) {
227
+ (0, ui_1.errorBox)('Failed to generate wrapper', [result.error || 'Unknown error']);
228
+ return { name: agentName, platform, ok: false };
229
+ }
230
+ patchText = result.patch;
231
+ console.log(` ${(0, ui_1.brand)('\u2714')} Created ${result.wrapperPath}`);
196
232
  }
197
- patchText = result.patch;
198
- console.log(` ${(0, ui_1.brand)('\u2714')} Created ${result.wrapperPath}`);
199
233
  break;
200
234
  }
201
235
  case 'custom': {
@@ -205,13 +239,13 @@ exports.connectCommand = new commander_1.Command('connect')
205
239
  break;
206
240
  }
207
241
  }
208
- // ── Step 2: Verify gateway ───────────────────────────────────────────
209
- (0, ui_1.step)(2, 3, 'Verify Gateway');
242
+ // ── Verify ────────────────────────────────────────────────────────────────
243
+ (0, ui_1.step)(stepOffset.verify, stepOffset.total, `Verify${stepOffset.total > 4 ? ` \u2014 ${agentName}` : ''}`);
210
244
  let activeToken = token;
211
245
  let { gatewayOk, tokenOk } = await (0, verify_1.verifyGateway)(url, activeToken);
212
- // If env var token failed, try .arkna/.env token (may be newer from arkna login)
246
+ // If env var token failed, try .arkna/.env token
213
247
  if (gatewayOk && !tokenOk && process.env.ARKNA_TOKEN) {
214
- const fileEnv = (0, arkna_dir_1.readArknaEnv)(baseDir);
248
+ const fileEnv = (0, arkna_dir_1.readArknaEnv)(agentDir);
215
249
  if (fileEnv?.token && fileEnv.token !== activeToken) {
216
250
  console.log(`\n ${(0, ui_1.warn)('\u26A0')} ${(0, ui_1.dim)('Env var token rejected. Trying token from .arkna/.env...')}`);
217
251
  const retry = await (0, verify_1.verifyGateway)(url, fileEnv.token);
@@ -219,33 +253,252 @@ exports.connectCommand = new commander_1.Command('connect')
219
253
  activeToken = fileEnv.token;
220
254
  gatewayOk = retry.gatewayOk;
221
255
  tokenOk = retry.tokenOk;
222
- console.log(` ${(0, ui_1.dim)('Using newer token from .arkna/.env (run')} ${(0, ui_1.brand)('source .arkna/.env')} ${(0, ui_1.dim)('to fix your shell)')}`);
223
256
  }
224
257
  }
225
258
  }
226
- // ── Step 3: Write .arkna/.env ────────────────────────────────────────
227
- (0, ui_1.step)(3, 3, 'Save Credentials');
228
- const files = (0, arkna_dir_1.writeTokenEnv)(url, activeToken, baseDir);
229
- (0, ui_1.fileTree)('.arkna', files);
230
- // ── Summary ──────────────────────────────────────────────────────────
259
+ // Save credentials into the agent's own directory
260
+ const files = (0, arkna_dir_1.writeTokenEnv)(url, activeToken, agentDir);
261
+ (0, ui_1.fileTree)(`${path.basename(agentDir)}/.arkna`, files);
262
+ // Show patch text for single-agent flow
263
+ if (patchText && platform !== 'custom' && platform !== 'openclaw' && stepOffset.total <= 4) {
264
+ (0, ui_1.hint)([
265
+ `${(0, ui_1.brandBold)('NEXT:')} Add these lines to your agent:`,
266
+ '',
267
+ ...patchText.split('\n').map(l => ` ${(0, ui_1.dim)(l)}`),
268
+ ]);
269
+ }
270
+ return { name: agentName, platform, ok: gatewayOk && tokenOk };
271
+ }
272
+ // ── Command ─────────────────────────────────────────────────────────────────
273
+ exports.connectCommand = new commander_1.Command('connect')
274
+ .description('Wire an agent to route through the ARKNA governance gateway')
275
+ .argument('[platform]', `Agent platform (${VALID_PLATFORMS.join(', ')}) \u2014 auto-detected if omitted`)
276
+ .option('-t, --token <token>', 'Integration token (prefer ARKNA_TOKEN env var instead)')
277
+ .option('-u, --url <url>', 'ARKNA gateway URL (or set ARKNA_URL env var)')
278
+ .option('-n, --name <name>', 'Agent name (defaults to directory name)')
279
+ .option('-c, --config <path>', 'Path to agent config file (OpenClaw: YAML to modify)')
280
+ .option('-d, --dir <dir>', 'Output dir for wrapper files (CrewAI/LangChain)')
281
+ .action(async (platformArg, options) => {
282
+ (0, ui_1.banner)();
283
+ const workDir = options.dir || process.cwd();
284
+ // ── Step 1: Detect Agent(s) ─────────────────────────────────────────────
285
+ (0, ui_1.step)(1, 4, 'Detect Agent');
286
+ // Check for multi-agent project (only when no platform/config explicitly given)
287
+ if (!platformArg && !options.config) {
288
+ const agents = (0, platforms_1.detectAgents)(workDir);
289
+ if (agents.length > 1) {
290
+ // ── Multi-agent flow ──────────────────────────────────────────────
291
+ console.log(` Found ${(0, ui_1.brand)(String(agents.length))} agents:\n`);
292
+ for (const a of agents) {
293
+ console.log(` ${(0, ui_1.brand)('\u2022')} ${a.name} ${(0, ui_1.dim)(`(${a.platform} \u2014 ${a.evidence})`)}`);
294
+ }
295
+ console.log('');
296
+ const { selected } = await inquirer_1.default.prompt([{
297
+ type: 'checkbox',
298
+ name: 'selected',
299
+ message: 'Select agents to connect:',
300
+ choices: agents.map(a => ({
301
+ name: `${a.name} ${(0, ui_1.dim)(`(${a.platform})`)}`,
302
+ value: a.name,
303
+ checked: true,
304
+ })),
305
+ validate: (input) => input.length > 0 || 'Select at least one agent',
306
+ }]);
307
+ const selectedAgents = agents.filter(a => selected.includes(a.name));
308
+ console.log(`\n Connecting ${(0, ui_1.brand)(String(selectedAgents.length))} agent${selectedAgents.length > 1 ? 's' : ''}...`);
309
+ const url = resolveUrl(options.url, workDir);
310
+ if (!url.startsWith('http://') && !url.startsWith('https://')) {
311
+ (0, ui_1.errorBox)('Invalid gateway URL', [`Got: ${url}`, (0, ui_1.dim)('URL must start with http:// or https://')]);
312
+ process.exit(1);
313
+ }
314
+ // ── Resolve tokens ──────────────────────────────────────────────
315
+ // Check which agents already have valid tokens
316
+ (0, ui_1.step)(2, 4, 'Credentials');
317
+ const resolved = [];
318
+ const needsToken = [];
319
+ for (const agent of selectedAgents) {
320
+ const existing = (0, arkna_dir_1.readArknaEnv)(agent.path);
321
+ if (existing?.token && !process.env.ARKNA_TOKEN) {
322
+ const s = (0, ui_1.startSpinner)(`Checking ${agent.name}...`);
323
+ const { tokenOk } = await (0, verify_1.verifyGateway)(existing.url || url, existing.token);
324
+ s.stop();
325
+ if (tokenOk) {
326
+ console.log(` ${(0, ui_1.brand)('\u2714')} ${agent.name}: saved token valid`);
327
+ resolved.push({ agent, token: existing.token });
328
+ continue;
329
+ }
330
+ }
331
+ needsToken.push(agent);
332
+ }
333
+ // Auto-provision tokens for agents that need them
334
+ if (needsToken.length > 0) {
335
+ console.log(`\n ${needsToken.length} agent${needsToken.length > 1 ? 's' : ''} need${needsToken.length === 1 ? 's' : ''} a new token.`);
336
+ const jwt = await ensureAdminSession(url);
337
+ if (jwt) {
338
+ for (const agent of needsToken) {
339
+ const s = (0, ui_1.startSpinner)(`Creating token for ${agent.name}...`);
340
+ const result = await (0, connectivity_1.provisionToken)(url, jwt, agent.name);
341
+ s.stop();
342
+ if (result.success && result.token) {
343
+ console.log(` ${(0, ui_1.brand)('\u2714')} ${agent.name}: token created ${(0, ui_1.dim)(`(${(0, ui_1.maskToken)(result.token)})`)}`);
344
+ resolved.push({ agent, token: result.token });
345
+ }
346
+ else {
347
+ console.log(` ${(0, ui_1.warn)('\u2718')} ${agent.name}: ${(0, ui_1.dim)(result.error || 'failed')}`);
348
+ }
349
+ }
350
+ }
351
+ else {
352
+ // Login failed — fall back to manual paste per agent
353
+ console.log(` ${(0, ui_1.dim)('Falling back to manual token entry...')}\n`);
354
+ for (const agent of needsToken) {
355
+ console.log(` ${(0, ui_1.dim)(`Paste token for ${(0, ui_1.brand)(agent.name)}:`)}`);
356
+ const { token } = await inquirer_1.default.prompt([{
357
+ type: 'password',
358
+ name: 'token',
359
+ message: (0, ui_1.brand)(` ${agent.name}:`),
360
+ mask: '\u25CF',
361
+ prefix: '',
362
+ validate: (v) => v.startsWith('intk_') || 'Token must start with intk_',
363
+ }]);
364
+ resolved.push({ agent, token });
365
+ }
366
+ }
367
+ }
368
+ // ── Wire + Verify each agent ────────────────────────────────────
369
+ const results = [];
370
+ for (let i = 0; i < resolved.length; i++) {
371
+ const { agent, token } = resolved[i];
372
+ (0, ui_1.divider)();
373
+ console.log(`\n ${(0, ui_1.brandBold)(`\u25B6 ${agent.name}`)} ${(0, ui_1.dim)(`(${i + 1}/${resolved.length})`)}`);
374
+ let configFile = agent.configFile;
375
+ if (agent.platform === 'openclaw' && !configFile) {
376
+ configFile = (0, platforms_1.findOpenClawConfig)(agent.path) ?? undefined;
377
+ }
378
+ const result = await connectSingleAgent(agent.platform, agent.path, configFile, agent.name, url, token, { wire: 3, verify: 4, total: 4 });
379
+ results.push(result);
380
+ }
381
+ // ── Multi-agent summary ──────────────────────────────────────────
382
+ (0, ui_1.divider)();
383
+ const okCount = results.filter(r => r.ok).length;
384
+ const failCount = results.filter(r => !r.ok).length;
385
+ const skippedCount = selectedAgents.length - resolved.length;
386
+ if (failCount === 0 && skippedCount === 0) {
387
+ (0, ui_1.successBox)(`${okCount} of ${selectedAgents.length} agents connected`, results.map(r => ` ${(0, ui_1.brand)('\u2714')} ${r.name} ${(0, ui_1.dim)(`(${r.platform})`)}`));
388
+ }
389
+ else {
390
+ console.log('');
391
+ for (const r of results) {
392
+ const icon = r.ok ? (0, ui_1.brand)('\u2714') : (0, ui_1.warn)('\u2718');
393
+ console.log(` ${icon} ${r.name} ${(0, ui_1.dim)(`(${r.platform})`)}`);
394
+ }
395
+ if (skippedCount > 0) {
396
+ console.log(` ${(0, ui_1.warn)('\u2718')} ${skippedCount} agent${skippedCount > 1 ? 's' : ''} skipped (no token)`);
397
+ }
398
+ if (okCount > 0) {
399
+ console.log(`\n ${(0, ui_1.brand)(String(okCount))} connected, ${(0, ui_1.warn)(String(failCount + skippedCount))} failed`);
400
+ }
401
+ }
402
+ (0, ui_1.hint)([
403
+ `${(0, ui_1.brandBold)('WHAT HAPPENS NEXT:')}`,
404
+ ` Each agent registers itself when it starts.`,
405
+ ` ${(0, ui_1.dim)('1.')} Set env vars per agent: ${(0, ui_1.brand)('source <agent-dir>/.arkna/.env')}`,
406
+ ` ${(0, ui_1.dim)('2.')} Start your agents`,
407
+ ` ${(0, ui_1.dim)('Then:')} arkna verify --watch`,
408
+ ]);
409
+ return; // Done — multi-agent flow complete
410
+ }
411
+ }
412
+ // ── Single-agent flow ───────────────────────────────────────────────────
413
+ const baseDir = options.config ? path.dirname(options.config) : workDir;
414
+ let platform;
415
+ let configFile = options.config;
416
+ if (platformArg) {
417
+ if (!VALID_PLATFORMS.includes(platformArg)) {
418
+ (0, ui_1.errorBox)('Invalid platform', [
419
+ `Got: ${platformArg}`,
420
+ (0, ui_1.dim)(`Valid platforms: ${VALID_PLATFORMS.join(', ')}`),
421
+ ]);
422
+ process.exit(1);
423
+ }
424
+ platform = platformArg;
425
+ console.log(` ${(0, ui_1.brand)('\u2714')} ${platform} ${(0, ui_1.dim)('(specified)')}`);
426
+ }
427
+ else {
428
+ const detection = (0, platforms_1.detectPlatform)(workDir);
429
+ if (detection) {
430
+ platform = detection.platform;
431
+ console.log(` ${(0, ui_1.brand)('\u2714')} ${detection.platform} agent detected ${(0, ui_1.dim)(`(${detection.evidence})`)}`);
432
+ if (detection.configFile && !configFile) {
433
+ configFile = detection.configFile;
434
+ }
435
+ }
436
+ else {
437
+ const { selectedPlatform } = await inquirer_1.default.prompt([{
438
+ type: 'list',
439
+ name: 'selectedPlatform',
440
+ message: 'What agent platform are you using?',
441
+ choices: [
442
+ { name: 'OpenClaw', value: 'openclaw' },
443
+ { name: 'CrewAI', value: 'crewai' },
444
+ { name: 'LangChain', value: 'langchain' },
445
+ { name: 'Custom / Other', value: 'custom' },
446
+ ],
447
+ }]);
448
+ platform = selectedPlatform;
449
+ }
450
+ }
451
+ // For OpenClaw without a config file specified, try to auto-find one
452
+ if (platform === 'openclaw' && !configFile) {
453
+ configFile = (0, platforms_1.findOpenClawConfig)(workDir) ?? undefined;
454
+ }
455
+ // ── Step 2: Credentials ─────────────────────────────────────────────────
456
+ (0, ui_1.step)(2, 4, 'Credentials');
457
+ const url = resolveUrl(options.url, baseDir);
458
+ if (!url.startsWith('http://') && !url.startsWith('https://')) {
459
+ (0, ui_1.errorBox)('Invalid gateway URL', [
460
+ `Got: ${url}`,
461
+ (0, ui_1.dim)('URL must start with http:// or https://'),
462
+ ]);
463
+ process.exit(1);
464
+ }
465
+ const agentName = options.name || path.basename(process.cwd());
466
+ // Check if existing .arkna/.env has a valid token — skip prompt if so
467
+ const existingEnv = (0, arkna_dir_1.readArknaEnv)(baseDir);
468
+ let skipTokenPrompt = false;
469
+ if (existingEnv?.token && !options.token && !process.env.ARKNA_TOKEN) {
470
+ const s = (0, ui_1.startSpinner)('Checking saved token...');
471
+ const { tokenOk } = await (0, verify_1.verifyGateway)(existingEnv.url || url, existingEnv.token);
472
+ s.stop();
473
+ if (tokenOk) {
474
+ skipTokenPrompt = true;
475
+ console.log(` ${(0, ui_1.brand)('\u2714')} Saved token is valid ${(0, ui_1.dim)('(from .arkna/.env)')}`);
476
+ }
477
+ }
478
+ const token = skipTokenPrompt
479
+ ? existingEnv.token
480
+ : await resolveToken(url, agentName, options.token, baseDir);
481
+ if (!token.startsWith('intk_')) {
482
+ (0, ui_1.errorBox)('Invalid token', [
483
+ (0, ui_1.dim)('Token must start with intk_'),
484
+ (0, ui_1.dim)('Create one in Settings > External Agents.'),
485
+ ]);
486
+ process.exit(1);
487
+ }
488
+ console.log(` ${(0, ui_1.brand)('\u2714')} Gateway: ${(0, ui_1.dim)(url)}`);
489
+ // ── Steps 3-4: Wire + Verify ────────────────────────────────────────────
490
+ const result = await connectSingleAgent(platform, workDir, configFile, agentName, url, token, { wire: 3, verify: 4, total: 4 });
491
+ // ── Summary ─────────────────────────────────────────────────────────────
231
492
  (0, ui_1.divider)();
232
- if (gatewayOk && tokenOk) {
493
+ if (result.ok) {
233
494
  (0, ui_1.successBox)('CONFIGURED', [
234
495
  `${(0, ui_1.dim)('Agent:')} ${agentName}`,
235
496
  `${(0, ui_1.dim)('Platform:')} ${platform}`,
236
497
  `${(0, ui_1.dim)('Gateway:')} ${url}`,
237
498
  `${(0, ui_1.dim)('Token:')} valid`,
238
499
  ]);
239
- if (patchText && platform !== 'custom' && platform !== 'openclaw') {
240
- (0, ui_1.hint)([
241
- `${(0, ui_1.brandBold)('NEXT:')} Add these lines to your agent:`,
242
- '',
243
- ...patchText.split('\n').map(l => ` ${(0, ui_1.dim)(l)}`),
244
- ]);
245
- }
246
500
  }
247
- else if (gatewayOk && !tokenOk) {
248
- // Token failed — check if .arkna/.env has a different (newer) token
501
+ else {
249
502
  const fileEnv = (0, arkna_dir_1.readArknaEnv)(baseDir);
250
503
  const usingEnvVar = !!process.env.ARKNA_TOKEN;
251
504
  const fileHasDifferentToken = fileEnv?.token && fileEnv.token !== token;
@@ -259,49 +512,47 @@ exports.connectCommand = new commander_1.Command('connect')
259
512
  ]);
260
513
  }
261
514
  else {
262
- (0, ui_1.errorBox)('Token rejected by gateway', [
263
- (0, ui_1.dim)('The gateway returned 401 for this token.'),
264
- (0, ui_1.dim)('Run `arkna login` to get a fresh token, then retry.'),
515
+ (0, ui_1.errorBox)('Gateway verification failed', [
516
+ (0, ui_1.dim)('Check the errors above and run `arkna test` to retry.'),
265
517
  ]);
266
518
  }
267
519
  }
268
- else {
269
- (0, ui_1.errorBox)('Gateway verification failed', [
270
- (0, ui_1.dim)('The agent was wired but gateway checks did not pass.'),
271
- (0, ui_1.dim)('Check the errors above and run `arkna test` to retry.'),
272
- ]);
273
- }
274
- const platformHint = {
520
+ // Streamlined "WHAT HAPPENS NEXT"
521
+ const nextSteps = {
275
522
  openclaw: [
276
- `${(0, ui_1.dim)('1.')} Add the YAML block above to your OpenClaw agent config`,
277
- `${(0, ui_1.dim)('2.')} Set env vars: ${(0, ui_1.brand)('source .arkna/.env')}`,
278
- `${(0, ui_1.dim)('3.')} Start your OpenClaw agent (it will auto-register with ARKNA on startup)`,
523
+ `Your agent registers itself when it starts.`,
524
+ `${(0, ui_1.dim)('1.')} Set env vars: ${(0, ui_1.brand)('source .arkna/.env')}`,
525
+ `${(0, ui_1.dim)('2.')} Start your OpenClaw agent`,
279
526
  ],
280
527
  crewai: [
281
- `${(0, ui_1.dim)('1.')} Add the import lines above to your CrewAI entrypoint`,
282
- `${(0, ui_1.dim)('2.')} Set env vars: ${(0, ui_1.brand)('source .arkna/.env')}`,
283
- `${(0, ui_1.dim)('3.')} Run your CrewAI agent (it will auto-register with ARKNA on startup)`,
528
+ `Your agent registers itself when it starts.`,
529
+ `${(0, ui_1.dim)('1.')} Set env vars: ${(0, ui_1.brand)('source .arkna/.env')}`,
530
+ `${(0, ui_1.dim)('2.')} Run your CrewAI agent`,
284
531
  ],
285
532
  langchain: [
286
- `${(0, ui_1.dim)('1.')} Import ${(0, ui_1.brand)('get_arkna_tools()')} in your LangChain agent`,
287
- `${(0, ui_1.dim)('2.')} Set env vars: ${(0, ui_1.brand)('source .arkna/.env')}`,
288
- `${(0, ui_1.dim)('3.')} Run your LangChain agent (it will auto-register with ARKNA on startup)`,
533
+ `Your agent registers itself when it starts.`,
534
+ `${(0, ui_1.dim)('1.')} Set env vars: ${(0, ui_1.brand)('source .arkna/.env')}`,
535
+ `${(0, ui_1.dim)('2.')} Run your LangChain agent`,
289
536
  ],
290
537
  custom: [
291
- `${(0, ui_1.dim)('1.')} Add the API calls above to your agent code`,
292
- `${(0, ui_1.dim)('2.')} Set env vars: ${(0, ui_1.brand)('source .arkna/.env')}`,
293
- `${(0, ui_1.dim)('3.')} Start your agent \u2014 it must call ${(0, ui_1.brand)('POST /api/gateway/register')} on startup`,
538
+ `Your agent must call ${(0, ui_1.brand)('POST /api/gateway/register')} on startup.`,
539
+ `${(0, ui_1.dim)('1.')} Set env vars: ${(0, ui_1.brand)('source .arkna/.env')}`,
540
+ `${(0, ui_1.dim)('2.')} Start your agent`,
294
541
  ],
295
- }[platform] || [];
542
+ };
296
543
  (0, ui_1.hint)([
297
544
  `${(0, ui_1.brandBold)('WHAT HAPPENS NEXT:')}`,
298
- ` Your agent is not in ARKNA yet. It registers itself when it starts.`,
299
- ` Run these commands from your ${(0, ui_1.brandBold)('agent project directory')}:`,
300
- '',
301
- ...platformHint.map(l => ` ${l}`),
302
- '',
303
- ` ${(0, ui_1.dim)('Then verify:')}`,
304
- ` ${(0, ui_1.dim)('$')} arkna verify --watch ${(0, ui_1.dim)('# waits for your agent to register')}`,
545
+ ...nextSteps[platform].map(l => ` ${l}`),
546
+ ` ${(0, ui_1.dim)('Then:')} arkna verify --watch`,
305
547
  ]);
306
548
  });
549
+ function existsAndNotEmpty(filePath) {
550
+ try {
551
+ const stat = fs.statSync(filePath);
552
+ return stat.isFile() && stat.size > 0;
553
+ }
554
+ catch {
555
+ return false;
556
+ }
557
+ }
307
558
  //# sourceMappingURL=connect.js.map