@claw-link/gateway-host 0.3.11 → 0.3.13

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/bin/cli.js CHANGED
@@ -45,6 +45,13 @@ async function main() {
45
45
  case 'concurrency':
46
46
  return require('../scripts/install').setConcurrencyCmd();
47
47
 
48
+ case 'link':
49
+ return require('../scripts/install').linkCmd();
50
+
51
+ case 'set-workdir':
52
+ case 'workdir':
53
+ return require('../scripts/install').setWorkdirCmd();
54
+
48
55
  case 'agents':
49
56
  case 'list':
50
57
  return require('../scripts/install').listAgents();
@@ -107,6 +114,8 @@ async function main() {
107
114
  'Usage: clhost <command>',
108
115
  ' install Install the background service (one-time)',
109
116
  ' add-agent Add an agent by pasting its Host Token (pulls runtime from ClawLink)',
117
+ ' link Link MANY agents at once with a dashboard code (clhost link <clk_link_…>)',
118
+ ' set-workdir Move an agent\'s workspace directory (clhost set-workdir <id> <dir>)',
110
119
  ' rm-agent Remove a mapped agent (clhost rm-agent <id>)',
111
120
  ' retoken Re-enter a new Host Token for an existing agent (clhost retoken <id>)',
112
121
  ' set-model Set the model an agent’s runtime uses (clhost set-model <id> <model>)',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claw-link/gateway-host",
3
- "version": "0.3.11",
3
+ "version": "0.3.13",
4
4
  "description": "ClawLink Host Gateway — a secure, outbound-only worker that bridges a local agent CLI (OpenClaw, Hermes, Claude, Codex, Cursor) to your ClawLink agents. No inbound ports; authenticated per-agent by a Host Token.",
5
5
  "homepage": "https://claw-link.co",
6
6
  "bin": {
@@ -365,6 +365,77 @@ async function setConcurrencyCmd() {
365
365
  console.log(` ✓ ${agent.agent_id} concurrency → ${n}${n > 1 ? ' (per-job isolated working dirs)' : ''}.` + note);
366
366
  }
367
367
 
368
+ // `link <code>` — redeem a one-time Host Link code from the dashboard (Harness Studio →
369
+ // "Connect host"): the server rotates Host Tokens for every agent in the code and returns them
370
+ // once; we map them all into this host's config in one shot. No token pasting, no CSVs.
371
+ async function linkCmd() {
372
+ const code = (process.argv[3] || '').trim();
373
+ if (!code.startsWith('clk_link_')) {
374
+ console.log('Usage: clhost link <clk_link_…>');
375
+ console.log(' Generate a code in ClawLink → Harness Studio → "Connect host" (valid 10 min, one use).');
376
+ return;
377
+ }
378
+ const cfg = config.load() || config.defaultConfig();
379
+ ensureBridgeUrl(cfg);
380
+ const url = cfg.bridge_url.replace(/host-bridge\/?$/, 'host-link');
381
+ console.log(' Redeeming link code…');
382
+ let resp;
383
+ try {
384
+ const r = await fetch(url, {
385
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
386
+ body: JSON.stringify({ action: 'redeem', code }),
387
+ });
388
+ resp = await r.json();
389
+ if (!r.ok || resp.ok === false) throw new Error(resp.error || `HTTP ${r.status}`);
390
+ } catch (e) {
391
+ console.log(` ✗ Could not redeem: ${e.message}`);
392
+ return;
393
+ }
394
+
395
+ const detected = detectRuntimes();
396
+ let added = 0, updated = 0;
397
+ for (const a of resp.agents || []) {
398
+ const existing = (cfg.agents || []).find((x) => x.agent_id === a.agent_id);
399
+ const entry = existing || { agent_id: a.agent_id };
400
+ entry.host_token = a.host_token;
401
+ entry.runtime = a.runtime;
402
+ if (!entry.binary && a.runtime !== 'openclaw' && detected[a.runtime]) entry.binary = detected[a.runtime];
403
+ if (!entry.work_dir) entry.work_dir = config.defaultWorkDir(a.runtime, a.agent_id);
404
+ // Baseline skill files — silent, only-missing, never overwrites (safe for real projects).
405
+ try {
406
+ const wd = config.expandHome(entry.work_dir);
407
+ fs.mkdirSync(wd, { recursive: true });
408
+ scaffold.copyBaseline(a.runtime, wd);
409
+ } catch { /* best effort */ }
410
+ if (existing) updated++; else { cfg.agents = [...(cfg.agents || []), entry]; added++; }
411
+ console.log(` ✓ ${a.name} (${a.runtime}) → ${entry.work_dir}`);
412
+ }
413
+ config.save(cfg);
414
+ console.log(`\n Linked ${added} new, ${updated} re-tokened agent(s).`);
415
+ const r2 = await gracefulServiceRestartCLI();
416
+ console.log(r2.restarted ? ' ✓ Host restarted and registering.' : ' ⚠ Run `clhost restart` (or start) to register.');
417
+ console.log('\n Point coding agents at their project folders: clhost set-workdir <id> <dir>');
418
+ console.log(' Then check: clhost agents · clhost status\n');
419
+ }
420
+
421
+ // `set-workdir <id-or-prefix> <dir>` — move an agent's workspace (coding runtimes run inside it).
422
+ async function setWorkdirCmd() {
423
+ const cfg = config.load();
424
+ if (!cfg || !(cfg.agents || []).length) { console.log('No agents configured.'); return; }
425
+ const target = (process.argv[3] || '').trim();
426
+ const dir = (process.argv[4] || '').trim();
427
+ if (!target || !dir) { console.log('Usage: clhost set-workdir <agent-id|prefix> <directory>'); printAgents(cfg); return; }
428
+ const agent = cfg.agents.find((a) => a.agent_id && (a.agent_id === target || a.agent_id.startsWith(target)));
429
+ if (!agent) { console.log(` No agent matched '${target}'.`); return; }
430
+ const resolved = config.expandHome(dir);
431
+ if (!fs.existsSync(resolved)) { console.log(` ✗ ${dir} does not exist.`); return; }
432
+ agent.work_dir = resolved;
433
+ try { scaffold.copyBaseline(agent.runtime, resolved); } catch { /* best effort */ }
434
+ config.save(cfg);
435
+ const r = await gracefulServiceRestartCLI();
436
+ console.log(` ✓ ${agent.agent_id} workspace → ${resolved}.` + (r.restarted ? ' Service restarted.' : ' Run `clhost restart` to apply.'));
437
+ }
438
+
368
439
  // `start | stop | restart` — control the installed background service.
369
440
  function serviceControl(action) {
370
441
  const platform = process.platform;
@@ -794,7 +865,7 @@ async function update() {
794
865
  }
795
866
 
796
867
  module.exports = {
797
- run, installOnly, addAgentCmd, removeAgentCmd, retokenCmd, setModelCmd, setPermissionCmd, setConcurrencyCmd, listAgents, status,
868
+ run, installOnly, addAgentCmd, removeAgentCmd, retokenCmd, setModelCmd, setPermissionCmd, setConcurrencyCmd, linkCmd, setWorkdirCmd, listAgents, status,
798
869
  serviceControl, serviceControlCmd, installService, SERVICE_LABEL,
799
870
  versionCmd, doctor, logs, update,
800
871
  };