0agent 1.0.8 → 1.0.10
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/dist/daemon.mjs +61 -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
|
|
package/dist/daemon.mjs
CHANGED
|
@@ -1736,6 +1736,20 @@ var AGENT_TOOLS = [
|
|
|
1736
1736
|
path: { type: "string", description: 'Directory path relative to working directory (default: ".")' }
|
|
1737
1737
|
}
|
|
1738
1738
|
}
|
|
1739
|
+
},
|
|
1740
|
+
{
|
|
1741
|
+
name: "scrape_url",
|
|
1742
|
+
description: "Scrape a URL and return clean structured content. Handles JavaScript-rendered pages, auto-adapts to page structure, returns text/links/metadata. Better than shell curl for web pages.",
|
|
1743
|
+
input_schema: {
|
|
1744
|
+
type: "object",
|
|
1745
|
+
properties: {
|
|
1746
|
+
url: { type: "string", description: "URL to scrape" },
|
|
1747
|
+
mode: { type: "string", description: 'What to extract: "text" (default), "links", "tables", "full", "markdown"' },
|
|
1748
|
+
selector: { type: "string", description: "Optional CSS selector to target specific element" },
|
|
1749
|
+
wait_ms: { type: "number", description: "Wait N ms after page load (for JS-heavy pages, default 0)" }
|
|
1750
|
+
},
|
|
1751
|
+
required: ["url"]
|
|
1752
|
+
}
|
|
1739
1753
|
}
|
|
1740
1754
|
];
|
|
1741
1755
|
var LLMExecutor = class {
|
|
@@ -2135,6 +2149,13 @@ var AgentExecutor = class {
|
|
|
2135
2149
|
return this.readFile(String(input.path ?? ""));
|
|
2136
2150
|
case "list_dir":
|
|
2137
2151
|
return this.listDir(input.path ? String(input.path) : void 0);
|
|
2152
|
+
case "scrape_url":
|
|
2153
|
+
return this.scrapeUrl(
|
|
2154
|
+
String(input.url ?? ""),
|
|
2155
|
+
String(input.mode ?? "text"),
|
|
2156
|
+
input.selector ? String(input.selector) : void 0,
|
|
2157
|
+
Number(input.wait_ms ?? 0)
|
|
2158
|
+
);
|
|
2138
2159
|
default:
|
|
2139
2160
|
return `Unknown tool: ${name}`;
|
|
2140
2161
|
}
|
|
@@ -2174,6 +2195,45 @@ var AgentExecutor = class {
|
|
|
2174
2195
|
return content.length > 8e3 ? content.slice(0, 8e3) + `
|
|
2175
2196
|
\u2026[truncated, ${content.length} total bytes]` : content;
|
|
2176
2197
|
}
|
|
2198
|
+
async scrapeUrl(url, mode, selector, waitMs) {
|
|
2199
|
+
if (!url.startsWith("http")) return "Error: URL must start with http:// or https://";
|
|
2200
|
+
const selectorLine = selector ? `element = page.find('${selector}')
|
|
2201
|
+
content = element.text if element else page.get_all_text()` : `content = page.get_all_text()`;
|
|
2202
|
+
const modeLine = mode === "links" ? `result = [a.attrib.get('href','') for a in page.find_all('a') if a.attrib.get('href','').startswith('http')]` : mode === "tables" ? `result = [str(t) for t in page.find_all('table')]` : mode === "markdown" ? `result = page.get_all_text()` : `result = page.get_all_text()`;
|
|
2203
|
+
const script = [
|
|
2204
|
+
`import sys`,
|
|
2205
|
+
`try:`,
|
|
2206
|
+
` from scrapling import Fetcher`,
|
|
2207
|
+
`except ImportError:`,
|
|
2208
|
+
` import subprocess, sys`,
|
|
2209
|
+
` subprocess.run([sys.executable, '-m', 'pip', 'install', 'scrapling', '-q'], check=True)`,
|
|
2210
|
+
` from scrapling import Fetcher`,
|
|
2211
|
+
`try:`,
|
|
2212
|
+
` fetcher = Fetcher(auto_match=False)`,
|
|
2213
|
+
` page = fetcher.get('${url}', timeout=20)`,
|
|
2214
|
+
` ${modeLine}`,
|
|
2215
|
+
` if isinstance(result, list):`,
|
|
2216
|
+
` print('\\n'.join(str(r) for r in result[:50]))`,
|
|
2217
|
+
` else:`,
|
|
2218
|
+
` text = str(result).strip()`,
|
|
2219
|
+
` print(text[:6000] + ('...[truncated]' if len(text)>6000 else ''))`,
|
|
2220
|
+
`except Exception as e:`,
|
|
2221
|
+
` # Fallback to simple fetch if scrapling fails`,
|
|
2222
|
+
` import urllib.request`,
|
|
2223
|
+
` try:`,
|
|
2224
|
+
` req = urllib.request.Request('${url}', headers={'User-Agent': 'Mozilla/5.0'})`,
|
|
2225
|
+
` with urllib.request.urlopen(req, timeout=15) as resp:`,
|
|
2226
|
+
` body = resp.read().decode('utf-8', errors='ignore')`,
|
|
2227
|
+
` # Strip tags simply`,
|
|
2228
|
+
` import re`,
|
|
2229
|
+
` text = re.sub(r'<[^>]+>', ' ', body)`,
|
|
2230
|
+
` text = re.sub(r'\\s+', ' ', text).strip()`,
|
|
2231
|
+
` print(text[:5000])`,
|
|
2232
|
+
` except Exception as e2:`,
|
|
2233
|
+
` print(f'Scrape failed: {e} / {e2}', file=sys.stderr)`
|
|
2234
|
+
].join("\n");
|
|
2235
|
+
return this.shellExec(`python3 -c "${script.replace(/"/g, '\\"').replace(/\n/g, ";")}"`, 3e4);
|
|
2236
|
+
}
|
|
2177
2237
|
listDir(dirPath) {
|
|
2178
2238
|
const safe = this.safePath(dirPath ?? ".");
|
|
2179
2239
|
if (!safe) return "Error: path outside working directory";
|
|
@@ -2212,6 +2272,7 @@ var AgentExecutor = class {
|
|
|
2212
2272
|
if (toolName === "write_file") return `"${input.path}"`;
|
|
2213
2273
|
if (toolName === "read_file") return `"${input.path}"`;
|
|
2214
2274
|
if (toolName === "list_dir") return `"${input.path ?? "."}"`;
|
|
2275
|
+
if (toolName === "scrape_url") return `"${String(input.url ?? "").slice(0, 60)}" mode=${input.mode ?? "text"}`;
|
|
2215
2276
|
return JSON.stringify(input).slice(0, 60);
|
|
2216
2277
|
}
|
|
2217
2278
|
};
|