arkna 1.1.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.
- package/dist/commands/connect.d.ts.map +1 -1
- package/dist/commands/connect.js +377 -125
- package/dist/commands/connect.js.map +1 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/connectivity.d.ts +28 -0
- package/dist/lib/connectivity.d.ts.map +1 -1
- package/dist/lib/connectivity.js +66 -0
- package/dist/lib/connectivity.js.map +1 -1
- package/dist/lib/platforms.d.ts +32 -0
- package/dist/lib/platforms.d.ts.map +1 -1
- package/dist/lib/platforms.js +193 -0
- package/dist/lib/platforms.js.map +1 -1
- package/dist/lib/update-check.d.ts +7 -0
- package/dist/lib/update-check.d.ts.map +1 -0
- package/dist/lib/update-check.js +94 -0
- package/dist/lib/update-check.js.map +1 -0
- package/package.json +1 -1
|
@@ -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;
|
|
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"}
|
package/dist/commands/connect.js
CHANGED
|
@@ -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
|
|
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
|
|
88
|
+
* Resolve token for an agent. Priority:
|
|
51
89
|
* 1. ARKNA_TOKEN env var
|
|
52
|
-
* 2. .arkna/.env file
|
|
53
|
-
* 3.
|
|
54
|
-
* 4. -
|
|
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.
|
|
74
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
.
|
|
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, 2, '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 (
|
|
176
|
+
if (configFile) {
|
|
152
177
|
const spinner = (0, ui_1.startSpinner)('Modifying OpenClaw config...');
|
|
153
|
-
const result = (0, platforms_1.wireOpenClaw)(
|
|
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
|
-
|
|
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.
|
|
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')}
|
|
166
|
-
console.log(` ${(0, ui_1.dim)('Backup saved to ' +
|
|
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
|
-
|
|
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
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
// ──
|
|
209
|
-
(0, ui_1.step)(
|
|
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
|
|
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)(
|
|
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,32 +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
|
-
//
|
|
227
|
-
const files = (0, arkna_dir_1.writeTokenEnv)(url, activeToken,
|
|
228
|
-
(0, ui_1.fileTree)(
|
|
229
|
-
//
|
|
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 ─────────────────────────────────────────────────────────────
|
|
230
492
|
(0, ui_1.divider)();
|
|
231
|
-
if (
|
|
493
|
+
if (result.ok) {
|
|
232
494
|
(0, ui_1.successBox)('CONFIGURED', [
|
|
233
495
|
`${(0, ui_1.dim)('Agent:')} ${agentName}`,
|
|
234
496
|
`${(0, ui_1.dim)('Platform:')} ${platform}`,
|
|
235
497
|
`${(0, ui_1.dim)('Gateway:')} ${url}`,
|
|
236
498
|
`${(0, ui_1.dim)('Token:')} valid`,
|
|
237
499
|
]);
|
|
238
|
-
if (patchText && platform !== 'custom' && platform !== 'openclaw') {
|
|
239
|
-
(0, ui_1.hint)([
|
|
240
|
-
`${(0, ui_1.brandBold)('NEXT:')} Add these lines to your agent:`,
|
|
241
|
-
'',
|
|
242
|
-
...patchText.split('\n').map(l => ` ${(0, ui_1.dim)(l)}`),
|
|
243
|
-
]);
|
|
244
|
-
}
|
|
245
500
|
}
|
|
246
|
-
else
|
|
247
|
-
// Token failed — check if .arkna/.env has a different (newer) token
|
|
501
|
+
else {
|
|
248
502
|
const fileEnv = (0, arkna_dir_1.readArknaEnv)(baseDir);
|
|
249
503
|
const usingEnvVar = !!process.env.ARKNA_TOKEN;
|
|
250
504
|
const fileHasDifferentToken = fileEnv?.token && fileEnv.token !== token;
|
|
@@ -258,49 +512,47 @@ exports.connectCommand = new commander_1.Command('connect')
|
|
|
258
512
|
]);
|
|
259
513
|
}
|
|
260
514
|
else {
|
|
261
|
-
(0, ui_1.errorBox)('
|
|
262
|
-
(0, ui_1.dim)('
|
|
263
|
-
(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.'),
|
|
264
517
|
]);
|
|
265
518
|
}
|
|
266
519
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
(0, ui_1.dim)('The agent was wired but gateway checks did not pass.'),
|
|
270
|
-
(0, ui_1.dim)('Check the errors above and run `arkna test` to retry.'),
|
|
271
|
-
]);
|
|
272
|
-
}
|
|
273
|
-
const platformHint = {
|
|
520
|
+
// Streamlined "WHAT HAPPENS NEXT"
|
|
521
|
+
const nextSteps = {
|
|
274
522
|
openclaw: [
|
|
275
|
-
|
|
276
|
-
`${(0, ui_1.dim)('
|
|
277
|
-
`${(0, ui_1.dim)('
|
|
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`,
|
|
278
526
|
],
|
|
279
527
|
crewai: [
|
|
280
|
-
|
|
281
|
-
`${(0, ui_1.dim)('
|
|
282
|
-
`${(0, ui_1.dim)('
|
|
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`,
|
|
283
531
|
],
|
|
284
532
|
langchain: [
|
|
285
|
-
|
|
286
|
-
`${(0, ui_1.dim)('
|
|
287
|
-
`${(0, ui_1.dim)('
|
|
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`,
|
|
288
536
|
],
|
|
289
537
|
custom: [
|
|
290
|
-
|
|
291
|
-
`${(0, ui_1.dim)('
|
|
292
|
-
`${(0, ui_1.dim)('
|
|
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`,
|
|
293
541
|
],
|
|
294
|
-
}
|
|
542
|
+
};
|
|
295
543
|
(0, ui_1.hint)([
|
|
296
544
|
`${(0, ui_1.brandBold)('WHAT HAPPENS NEXT:')}`,
|
|
297
|
-
|
|
298
|
-
`
|
|
299
|
-
'',
|
|
300
|
-
...platformHint.map(l => ` ${l}`),
|
|
301
|
-
'',
|
|
302
|
-
` ${(0, ui_1.dim)('Then verify:')}`,
|
|
303
|
-
` ${(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`,
|
|
304
547
|
]);
|
|
305
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
|
+
}
|
|
306
558
|
//# sourceMappingURL=connect.js.map
|