0agent 1.0.8 → 1.0.9
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/0agent.js +193 -0
- package/package.json +1 -1
package/bin/0agent.js
CHANGED
|
@@ -86,6 +86,14 @@ switch (cmd) {
|
|
|
86
86
|
showLogs(args.slice(1));
|
|
87
87
|
break;
|
|
88
88
|
|
|
89
|
+
case 'team':
|
|
90
|
+
await runTeamCommand(args.slice(1));
|
|
91
|
+
break;
|
|
92
|
+
|
|
93
|
+
case 'serve':
|
|
94
|
+
await runServe(args.slice(1));
|
|
95
|
+
break;
|
|
96
|
+
|
|
89
97
|
default:
|
|
90
98
|
showHelp();
|
|
91
99
|
break;
|
|
@@ -614,6 +622,183 @@ function showLogs(logArgs) {
|
|
|
614
622
|
|
|
615
623
|
// ─── Help ─────────────────────────────────────────────────────────────────
|
|
616
624
|
|
|
625
|
+
// ─── Team commands ────────────────────────────────────────────────────────────
|
|
626
|
+
|
|
627
|
+
async function runTeamCommand(teamArgs) {
|
|
628
|
+
const sub = teamArgs[0];
|
|
629
|
+
const SYNC_URL = process.env['ZEROAGENT_SYNC'] ?? 'http://localhost:4201';
|
|
630
|
+
|
|
631
|
+
switch (sub) {
|
|
632
|
+
case 'create': {
|
|
633
|
+
const name = teamArgs.slice(1).join(' ');
|
|
634
|
+
if (!name) { console.log(' Usage: 0agent team create "<name>"'); break; }
|
|
635
|
+
const res = await fetch(`${SYNC_URL}/api/teams`, {
|
|
636
|
+
method: 'POST',
|
|
637
|
+
headers: { 'Content-Type': 'application/json' },
|
|
638
|
+
body: JSON.stringify({
|
|
639
|
+
name,
|
|
640
|
+
creator_entity_id: crypto.randomUUID(),
|
|
641
|
+
creator_name: process.env['USER'] ?? 'User',
|
|
642
|
+
}),
|
|
643
|
+
}).catch(() => null);
|
|
644
|
+
if (!res?.ok) { console.log(` Sync server not running. Start it with: 0agent serve`); break; }
|
|
645
|
+
const team = await res.json();
|
|
646
|
+
console.log(`\n ✓ Team created: ${team.name}`);
|
|
647
|
+
console.log(` Invite code: \x1b[1m${team.invite_code}\x1b[0m`);
|
|
648
|
+
console.log(`\n Share with teammates:`);
|
|
649
|
+
console.log(` 0agent team join ${team.invite_code} --server ${SYNC_URL}\n`);
|
|
650
|
+
break;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
case 'join': {
|
|
654
|
+
const code = teamArgs[1]?.toUpperCase();
|
|
655
|
+
const serverIdx = teamArgs.indexOf('--server');
|
|
656
|
+
const serverUrl = serverIdx >= 0 ? teamArgs[serverIdx + 1] : SYNC_URL;
|
|
657
|
+
if (!code) { console.log(' Usage: 0agent team join <CODE> [--server <url>]'); break; }
|
|
658
|
+
const res = await fetch(`${serverUrl}/api/teams/by-code/${code}`).catch(() => null);
|
|
659
|
+
if (!res?.ok) { console.log(` Invalid code or sync server unreachable: ${serverUrl}`); break; }
|
|
660
|
+
const team = await res.json();
|
|
661
|
+
const joinRes = await fetch(`${serverUrl}/api/teams/${team.id}/join`, {
|
|
662
|
+
method: 'POST',
|
|
663
|
+
headers: { 'Content-Type': 'application/json' },
|
|
664
|
+
body: JSON.stringify({
|
|
665
|
+
entity_node_id: crypto.randomUUID(),
|
|
666
|
+
name: process.env['USER'] ?? 'User',
|
|
667
|
+
}),
|
|
668
|
+
});
|
|
669
|
+
if (!joinRes.ok) { console.log(' Failed to join team.'); break; }
|
|
670
|
+
console.log(`\n ✓ Joined: ${team.name}`);
|
|
671
|
+
console.log(` Members: ${team.members?.length ?? '?'}`);
|
|
672
|
+
console.log(` Sync server: ${serverUrl}\n`);
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
case 'list': {
|
|
677
|
+
// Show teams from local teams.yaml
|
|
678
|
+
const { readFileSync, existsSync } = await import('node:fs');
|
|
679
|
+
const { resolve } = await import('node:path');
|
|
680
|
+
const { homedir } = await import('node:os');
|
|
681
|
+
const teamsPath = resolve(homedir(), '.0agent', 'teams.yaml');
|
|
682
|
+
if (!existsSync(teamsPath)) { console.log('\n No teams joined yet. Use: 0agent team join <CODE>\n'); break; }
|
|
683
|
+
const YAML = await import('yaml');
|
|
684
|
+
const config = YAML.parse(readFileSync(teamsPath, 'utf8'));
|
|
685
|
+
console.log('\n Your teams:\n');
|
|
686
|
+
for (const m of (config.memberships ?? [])) {
|
|
687
|
+
const ago = m.last_synced_at ? `synced ${Math.round((Date.now() - m.last_synced_at) / 60000)}m ago` : 'never synced';
|
|
688
|
+
console.log(` ${m.team_name.padEnd(24)} ${m.invite_code} ${ago}`);
|
|
689
|
+
console.log(` ${' '.repeat(24)} ${m.server_url}`);
|
|
690
|
+
}
|
|
691
|
+
console.log();
|
|
692
|
+
break;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
default:
|
|
696
|
+
console.log(' Usage: 0agent team create "<name>" | join <CODE> [--server <url>] | list');
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// ─── Serve command (sync server + optional tunnel) ────────────────────────────
|
|
701
|
+
|
|
702
|
+
async function runServe(serveArgs) {
|
|
703
|
+
const hasTunnel = serveArgs.includes('--tunnel');
|
|
704
|
+
const port = parseInt(serveArgs.find(a => a.match(/^\d+$/)) ?? '4201', 10);
|
|
705
|
+
|
|
706
|
+
console.log(`\n Starting 0agent sync server on port ${port}...\n`);
|
|
707
|
+
|
|
708
|
+
// Find sync server entry point
|
|
709
|
+
const { resolve, dirname } = await import('node:path');
|
|
710
|
+
const { existsSync } = await import('node:fs');
|
|
711
|
+
const { spawn } = await import('node:child_process');
|
|
712
|
+
const { networkInterfaces } = await import('node:os');
|
|
713
|
+
|
|
714
|
+
const pkgRoot = resolve(dirname(new URL(import.meta.url).pathname), '..');
|
|
715
|
+
const serverScript = resolve(pkgRoot, 'packages', 'sync-server', 'src', 'index.ts');
|
|
716
|
+
|
|
717
|
+
if (!existsSync(serverScript)) {
|
|
718
|
+
console.log(' Sync server not found in package. Install with: npm install -g 0agent');
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// Start sync server
|
|
723
|
+
const proc = spawn(process.execPath, ['--experimental-specifier-resolution=node', serverScript], {
|
|
724
|
+
env: { ...process.env, SYNC_PORT: String(port), SYNC_HOST: '0.0.0.0' },
|
|
725
|
+
stdio: 'inherit',
|
|
726
|
+
detached: false,
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
// Get LAN IP
|
|
730
|
+
const nets = networkInterfaces();
|
|
731
|
+
let lanIp = '127.0.0.1';
|
|
732
|
+
for (const iface of Object.values(nets)) {
|
|
733
|
+
if (!iface) continue;
|
|
734
|
+
for (const net of iface) {
|
|
735
|
+
if (net.family === 'IPv4' && !net.internal) { lanIp = net.address; break; }
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
await sleep(1500);
|
|
740
|
+
|
|
741
|
+
const localUrl = `http://localhost:${port}`;
|
|
742
|
+
const lanUrl = `http://${lanIp}:${port}`;
|
|
743
|
+
|
|
744
|
+
console.log(`\n ✓ Sync server running`);
|
|
745
|
+
console.log(` Local: ${localUrl}`);
|
|
746
|
+
console.log(` LAN: ${lanUrl} ← share with teammates on same WiFi`);
|
|
747
|
+
|
|
748
|
+
if (hasTunnel) {
|
|
749
|
+
console.log('\n Opening public tunnel...');
|
|
750
|
+
let tunnelUrl = null;
|
|
751
|
+
|
|
752
|
+
// Try cloudflared
|
|
753
|
+
try {
|
|
754
|
+
const { execSync: es } = await import('node:child_process');
|
|
755
|
+
es('which cloudflared', { stdio: 'ignore' });
|
|
756
|
+
const cf = spawn('cloudflared', ['tunnel', '--url', localUrl], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
757
|
+
cf.unref();
|
|
758
|
+
tunnelUrl = await waitForTunnelUrl(cf, /https:\/\/[a-z0-9\-]+\.trycloudflare\.com/i, 12000);
|
|
759
|
+
} catch {}
|
|
760
|
+
|
|
761
|
+
// Try ngrok
|
|
762
|
+
if (!tunnelUrl) {
|
|
763
|
+
try {
|
|
764
|
+
const { execSync: es } = await import('node:child_process');
|
|
765
|
+
es('which ngrok', { stdio: 'ignore' });
|
|
766
|
+
const ng = spawn('ngrok', ['http', String(port), '--log=stdout'], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
767
|
+
ng.unref();
|
|
768
|
+
tunnelUrl = await waitForTunnelUrl(ng, /https:\/\/[a-z0-9\-]+\.ngrok/i, 8000);
|
|
769
|
+
} catch {}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
if (tunnelUrl) {
|
|
773
|
+
console.log(` Public: \x1b[1m${tunnelUrl}\x1b[0m ← share with anyone`);
|
|
774
|
+
const code = Math.random().toString(36).slice(2,5).toUpperCase() + '-' + Math.floor(1000+Math.random()*9000);
|
|
775
|
+
console.log(`\n Share this with teammates:`);
|
|
776
|
+
console.log(` 0agent team join <CODE> --server ${tunnelUrl}\n`);
|
|
777
|
+
} else {
|
|
778
|
+
console.log(' No tunnel tool found. Install cloudflared: brew install cloudflared');
|
|
779
|
+
console.log(' Using LAN only.');
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
console.log('\n Press Ctrl+C to stop.\n');
|
|
784
|
+
proc.on('close', () => process.exit(0));
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
async function waitForTunnelUrl(proc, pattern, timeout) {
|
|
788
|
+
return new Promise(resolve => {
|
|
789
|
+
const chunks = [];
|
|
790
|
+
const onData = d => {
|
|
791
|
+
const s = d.toString(); chunks.push(s);
|
|
792
|
+
const match = chunks.join('').match(pattern);
|
|
793
|
+
if (match) { cleanup(); resolve(match[0]); }
|
|
794
|
+
};
|
|
795
|
+
proc.stdout?.on('data', onData);
|
|
796
|
+
proc.stderr?.on('data', onData);
|
|
797
|
+
const timer = setTimeout(() => { cleanup(); resolve(null); }, timeout);
|
|
798
|
+
const cleanup = () => { clearTimeout(timer); proc.stdout?.removeListener('data', onData); proc.stderr?.removeListener('data', onData); };
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
|
|
617
802
|
function showHelp() {
|
|
618
803
|
console.log(`
|
|
619
804
|
0agent — An agent that learns.
|
|
@@ -632,6 +817,13 @@ function showHelp() {
|
|
|
632
817
|
0agent improve Self-improvement analysis
|
|
633
818
|
0agent logs Tail daemon logs
|
|
634
819
|
|
|
820
|
+
Team collaboration:
|
|
821
|
+
0agent serve Start sync server (LAN)
|
|
822
|
+
0agent serve --tunnel Start sync server + public tunnel
|
|
823
|
+
0agent team create "<name>" Create a team, get invite code
|
|
824
|
+
0agent team join <CODE> Join a team by invite code
|
|
825
|
+
0agent team list List your teams
|
|
826
|
+
|
|
635
827
|
Dashboard:
|
|
636
828
|
http://localhost:4200 Web UI (after starting daemon)
|
|
637
829
|
|
|
@@ -640,6 +832,7 @@ function showHelp() {
|
|
|
640
832
|
0agent /research "Acme Corp funding"
|
|
641
833
|
0agent /build --task next
|
|
642
834
|
0agent /qa --url https://staging.myapp.com
|
|
835
|
+
0agent serve --tunnel # then share the URL + 0agent team join <CODE>
|
|
643
836
|
`);
|
|
644
837
|
}
|
|
645
838
|
|