playnex-cli 0.1.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/README.md +15 -0
- package/bin/playnex.js +229 -0
- package/package.json +20 -0
- package/playnex-bootstrap.ps1 +30 -0
- package/src/commands/agent/add-tool.js +40 -0
- package/src/commands/agent/chat.js +90 -0
- package/src/commands/agent/create.js +62 -0
- package/src/commands/agent/run.js +102 -0
- package/src/commands/capsules/mirror.js +58 -0
- package/src/commands/login.js +59 -0
- package/src/commands/scheduler/scheduler.js +154 -0
- package/src/commands/tools/registry.js +121 -0
- package/src/commands/workspace/sync.js +98 -0
- package/src/services/agents.js +2 -0
- package/src/services/config.js +32 -0
- package/src/tools/browser.js +59 -0
- package/src/tools/capsule-append.js +67 -0
- package/src/tools/capsule-search.js +74 -0
- package/src/tools/capsule-writer.js +38 -0
- package/src/tools/file-writer.js +49 -0
- package/src/tools/local-capsule-search.js +63 -0
- package/src/tools/local-embeddings.js +74 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import inquirer from "inquirer";
|
|
4
|
+
import { saveConfig } from "../services/config.js";
|
|
5
|
+
|
|
6
|
+
const API_BASE = "https://playnex.app";
|
|
7
|
+
|
|
8
|
+
export default async function login() {
|
|
9
|
+
console.log(chalk.cyan("\nPlaynex Login\n"));
|
|
10
|
+
|
|
11
|
+
const answers = await inquirer.prompt([
|
|
12
|
+
{
|
|
13
|
+
type: "input",
|
|
14
|
+
name: "email",
|
|
15
|
+
message: "Email:"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
type: "password",
|
|
19
|
+
name: "password",
|
|
20
|
+
message: "Password:"
|
|
21
|
+
}
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const res = await axios.post(`${API_BASE}/cli/login`, {
|
|
26
|
+
email: answers.email,
|
|
27
|
+
password: answers.password
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (!res.data || !res.data.token) {
|
|
31
|
+
console.log(chalk.red("Login failed: no token returned."));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
saveConfig({ token: res.data.token });
|
|
36
|
+
|
|
37
|
+
console.log(chalk.green("\n✔ Login successful!\n"));
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.log(chalk.red("\nLogin failed.\n"));
|
|
40
|
+
|
|
41
|
+
if (err.response) {
|
|
42
|
+
console.log(chalk.red(`Server responded with ${err.response.status}`));
|
|
43
|
+
|
|
44
|
+
if (err.response.data?.forcePasswordChange) {
|
|
45
|
+
console.log(
|
|
46
|
+
chalk.yellow(
|
|
47
|
+
"\nYour account requires a password change before continuing."
|
|
48
|
+
)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (err.response.data?.error) {
|
|
53
|
+
console.log(chalk.red(err.response.data.error));
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
console.log(err.message);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { spawn } from "child_process";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Agent Scheduler
|
|
9
|
+
* Allows agents to run automatically on intervals.
|
|
10
|
+
*
|
|
11
|
+
* Commands:
|
|
12
|
+
* playnex scheduler add <agent> <interval>
|
|
13
|
+
* playnex scheduler list
|
|
14
|
+
* playnex scheduler remove <id>
|
|
15
|
+
* playnex scheduler run (daemon)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const WORKSPACE_DIR = path.join(os.homedir(), ".playnex", "workspace");
|
|
19
|
+
const SCHEDULE_FILE = path.join(WORKSPACE_DIR, "schedule.json");
|
|
20
|
+
|
|
21
|
+
// Ensure schedule file exists
|
|
22
|
+
function ensureScheduleFile() {
|
|
23
|
+
if (!fs.existsSync(WORKSPACE_DIR)) {
|
|
24
|
+
fs.mkdirSync(WORKSPACE_DIR, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
if (!fs.existsSync(SCHEDULE_FILE)) {
|
|
27
|
+
fs.writeFileSync(SCHEDULE_FILE, JSON.stringify([], null, 2), "utf8");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function loadSchedule() {
|
|
32
|
+
ensureScheduleFile();
|
|
33
|
+
return JSON.parse(fs.readFileSync(SCHEDULE_FILE, "utf8"));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function saveSchedule(data) {
|
|
37
|
+
fs.writeFileSync(SCHEDULE_FILE, JSON.stringify(data, null, 2), "utf8");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------
|
|
41
|
+
// Add a scheduled agent
|
|
42
|
+
// ---------------------------------------------
|
|
43
|
+
export function schedulerAdd(agent, interval) {
|
|
44
|
+
ensureScheduleFile();
|
|
45
|
+
|
|
46
|
+
const schedule = loadSchedule();
|
|
47
|
+
|
|
48
|
+
const id = Date.now().toString();
|
|
49
|
+
schedule.push({
|
|
50
|
+
id,
|
|
51
|
+
agent,
|
|
52
|
+
interval,
|
|
53
|
+
lastRun: 0
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
saveSchedule(schedule);
|
|
57
|
+
|
|
58
|
+
console.log(chalk.green(`✔ Scheduled agent '${agent}' every '${interval}' (id: ${id})`));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------
|
|
62
|
+
// List scheduled agents
|
|
63
|
+
// ---------------------------------------------
|
|
64
|
+
export function schedulerList() {
|
|
65
|
+
ensureScheduleFile();
|
|
66
|
+
|
|
67
|
+
const schedule = loadSchedule();
|
|
68
|
+
|
|
69
|
+
if (schedule.length === 0) {
|
|
70
|
+
console.log(chalk.yellow("No scheduled agents."));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log(chalk.cyan("\nScheduled Agents:\n"));
|
|
75
|
+
schedule.forEach(item => {
|
|
76
|
+
console.log(`ID: ${item.id}`);
|
|
77
|
+
console.log(`Agent: ${item.agent}`);
|
|
78
|
+
console.log(`Interval: ${item.interval}`);
|
|
79
|
+
console.log(`Last Run: ${item.lastRun ? new Date(item.lastRun).toLocaleString() : "Never"}`);
|
|
80
|
+
console.log("");
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------
|
|
85
|
+
// Remove a scheduled agent
|
|
86
|
+
// ---------------------------------------------
|
|
87
|
+
export function schedulerRemove(id) {
|
|
88
|
+
ensureScheduleFile();
|
|
89
|
+
|
|
90
|
+
let schedule = loadSchedule();
|
|
91
|
+
const before = schedule.length;
|
|
92
|
+
|
|
93
|
+
schedule = schedule.filter(item => item.id !== id);
|
|
94
|
+
|
|
95
|
+
saveSchedule(schedule);
|
|
96
|
+
|
|
97
|
+
if (schedule.length < before) {
|
|
98
|
+
console.log(chalk.green(`✔ Removed scheduled task ${id}`));
|
|
99
|
+
} else {
|
|
100
|
+
console.log(chalk.red(`No scheduled task found with id ${id}`));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------
|
|
105
|
+
// Run scheduler daemon
|
|
106
|
+
// ---------------------------------------------
|
|
107
|
+
export function schedulerRun() {
|
|
108
|
+
ensureScheduleFile();
|
|
109
|
+
|
|
110
|
+
console.log(chalk.cyan("\nRunning scheduler daemon...\n"));
|
|
111
|
+
|
|
112
|
+
setInterval(() => {
|
|
113
|
+
const schedule = loadSchedule();
|
|
114
|
+
const now = Date.now();
|
|
115
|
+
|
|
116
|
+
schedule.forEach(item => {
|
|
117
|
+
const ms = parseInterval(item.interval);
|
|
118
|
+
|
|
119
|
+
if (!ms) return;
|
|
120
|
+
|
|
121
|
+
if (now - item.lastRun >= ms) {
|
|
122
|
+
console.log(chalk.green(`\nâ± Running agent '${item.agent}' (id: ${item.id})`));
|
|
123
|
+
|
|
124
|
+
// Run agent in a child process
|
|
125
|
+
const child = spawn("playnex", ["agent", "run", item.agent], {
|
|
126
|
+
stdio: "inherit",
|
|
127
|
+
shell: true
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
item.lastRun = now;
|
|
131
|
+
saveSchedule(schedule);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}, 5000); // check every 5 seconds
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ---------------------------------------------
|
|
138
|
+
// Parse interval strings like "1h", "30m", "10s"
|
|
139
|
+
// ---------------------------------------------
|
|
140
|
+
function parseInterval(str) {
|
|
141
|
+
const match = str.match(/^(\d+)(s|m|h|d)$/);
|
|
142
|
+
if (!match) return null;
|
|
143
|
+
|
|
144
|
+
const value = parseInt(match[1], 10);
|
|
145
|
+
const unit = match[2];
|
|
146
|
+
|
|
147
|
+
switch (unit) {
|
|
148
|
+
case "s": return value * 1000;
|
|
149
|
+
case "m": return value * 60 * 1000;
|
|
150
|
+
case "h": return value * 60 * 60 * 1000;
|
|
151
|
+
case "d": return value * 24 * 60 * 60 * 1000;
|
|
152
|
+
default: return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Tool Registry
|
|
8
|
+
* Stores metadata about tools:
|
|
9
|
+
* - name
|
|
10
|
+
* - description
|
|
11
|
+
* - parameters
|
|
12
|
+
* - version
|
|
13
|
+
* - author
|
|
14
|
+
* - capabilities
|
|
15
|
+
* - safety flags
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const WORKSPACE_DIR = path.join(os.homedir(), ".playnex", "workspace");
|
|
19
|
+
const REGISTRY_FILE = path.join(WORKSPACE_DIR, "tool-registry.json");
|
|
20
|
+
|
|
21
|
+
// Ensure registry file exists
|
|
22
|
+
function ensureRegistry() {
|
|
23
|
+
if (!fs.existsSync(WORKSPACE_DIR)) {
|
|
24
|
+
fs.mkdirSync(WORKSPACE_DIR, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
if (!fs.existsSync(REGISTRY_FILE)) {
|
|
27
|
+
fs.writeFileSync(REGISTRY_FILE, JSON.stringify([], null, 2), "utf8");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function loadRegistry() {
|
|
32
|
+
ensureRegistry();
|
|
33
|
+
return JSON.parse(fs.readFileSync(REGISTRY_FILE, "utf8"));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function saveRegistry(data) {
|
|
37
|
+
fs.writeFileSync(REGISTRY_FILE, JSON.stringify(data, null, 2), "utf8");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------
|
|
41
|
+
// Register a tool
|
|
42
|
+
// ---------------------------------------------
|
|
43
|
+
export function registerTool(toolName) {
|
|
44
|
+
ensureRegistry();
|
|
45
|
+
|
|
46
|
+
const registry = loadRegistry();
|
|
47
|
+
|
|
48
|
+
const toolPath = path.join(process.cwd(), "src", "tools", `${toolName}.js`);
|
|
49
|
+
if (!fs.existsSync(toolPath)) {
|
|
50
|
+
console.log(chalk.red(`Tool '${toolName}' does not exist in src/tools.`));
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Basic metadata scaffold
|
|
55
|
+
const entry = {
|
|
56
|
+
name: toolName,
|
|
57
|
+
description: "",
|
|
58
|
+
version: "1.0.0",
|
|
59
|
+
author: "local",
|
|
60
|
+
parameters: [],
|
|
61
|
+
capabilities: [],
|
|
62
|
+
requiresAuth: false,
|
|
63
|
+
safeForAutonomy: true,
|
|
64
|
+
registeredAt: new Date().toISOString()
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Avoid duplicates
|
|
68
|
+
if (registry.some(t => t.name === toolName)) {
|
|
69
|
+
console.log(chalk.yellow(`Tool '${toolName}' is already registered.`));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
registry.push(entry);
|
|
74
|
+
saveRegistry(registry);
|
|
75
|
+
|
|
76
|
+
console.log(chalk.green(`✔ Registered tool '${toolName}' in tool registry.`));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------
|
|
80
|
+
// List all tools in registry
|
|
81
|
+
// ---------------------------------------------
|
|
82
|
+
export function listTools() {
|
|
83
|
+
ensureRegistry();
|
|
84
|
+
|
|
85
|
+
const registry = loadRegistry();
|
|
86
|
+
|
|
87
|
+
if (registry.length === 0) {
|
|
88
|
+
console.log(chalk.yellow("No tools registered."));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log(chalk.cyan("\nRegistered Tools:\n"));
|
|
93
|
+
registry.forEach(t => {
|
|
94
|
+
console.log(`- ${t.name} (v${t.version})`);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ---------------------------------------------
|
|
99
|
+
// Show detailed info for a tool
|
|
100
|
+
// ---------------------------------------------
|
|
101
|
+
export function toolInfo(toolName) {
|
|
102
|
+
ensureRegistry();
|
|
103
|
+
|
|
104
|
+
const registry = loadRegistry();
|
|
105
|
+
const tool = registry.find(t => t.name === toolName);
|
|
106
|
+
|
|
107
|
+
if (!tool) {
|
|
108
|
+
console.log(chalk.red(`Tool '${toolName}' not found in registry.`));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(chalk.cyan(`\nTool: ${tool.name}\n`));
|
|
113
|
+
console.log(`Description: ${tool.description || "(none)"}`);
|
|
114
|
+
console.log(`Version: ${tool.version}`);
|
|
115
|
+
console.log(`Author: ${tool.author}`);
|
|
116
|
+
console.log(`Parameters: ${tool.parameters.join(", ") || "(none)"}`);
|
|
117
|
+
console.log(`Capabilities: ${tool.capabilities.join(", ") || "(none)"}`);
|
|
118
|
+
console.log(`Requires Auth: ${tool.requiresAuth}`);
|
|
119
|
+
console.log(`Safe for Autonomy: ${tool.safeForAutonomy}`);
|
|
120
|
+
console.log(`Registered At: ${tool.registeredAt}\n`);
|
|
121
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import axios from "axios";
|
|
6
|
+
import { loadConfig } from "../../services/config.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* workspace sync
|
|
10
|
+
* Syncs agents, capsules, and tool metadata from cloud → local.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const API_BASE = "https://playnex.app";
|
|
14
|
+
|
|
15
|
+
export default async function workspaceSync() {
|
|
16
|
+
const config = loadConfig();
|
|
17
|
+
|
|
18
|
+
if (!config.token) {
|
|
19
|
+
console.log(chalk.red("You must log in first."));
|
|
20
|
+
console.log("Run: playnex login");
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log(chalk.cyan("\nSyncing Playnex workspace..."));
|
|
25
|
+
|
|
26
|
+
const WORKSPACE_DIR = path.join(os.homedir(), ".playnex", "workspace");
|
|
27
|
+
const AGENTS_DIR = path.join(WORKSPACE_DIR, "agents");
|
|
28
|
+
const CAPSULES_DIR = path.join(WORKSPACE_DIR, "capsules");
|
|
29
|
+
const TOOLS_DIR = path.join(WORKSPACE_DIR, "tools");
|
|
30
|
+
|
|
31
|
+
// Ensure directories exist
|
|
32
|
+
[WORKSPACE_DIR, AGENTS_DIR, CAPSULES_DIR, TOOLS_DIR].forEach(dir => {
|
|
33
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// -------------------------------
|
|
37
|
+
// Sync Agents
|
|
38
|
+
// -------------------------------
|
|
39
|
+
console.log(chalk.gray("Fetching agents..."));
|
|
40
|
+
|
|
41
|
+
const agentsRes = await axios.get(
|
|
42
|
+
`${API_BASE}/agents/all`,
|
|
43
|
+
{
|
|
44
|
+
headers: { Authorization: `Bearer ${config.token}` }
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const agents = agentsRes.data || [];
|
|
49
|
+
|
|
50
|
+
agents.forEach(agent => {
|
|
51
|
+
const file = path.join(AGENTS_DIR, `${agent.name}.json`);
|
|
52
|
+
fs.writeFileSync(file, JSON.stringify(agent, null, 2), "utf8");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
console.log(chalk.green(`✔ Synced ${agents.length} agents.`));
|
|
56
|
+
|
|
57
|
+
// -------------------------------
|
|
58
|
+
// Sync Capsules
|
|
59
|
+
// -------------------------------
|
|
60
|
+
console.log(chalk.gray("Fetching capsules..."));
|
|
61
|
+
|
|
62
|
+
const capsulesRes = await axios.get(
|
|
63
|
+
`${API_BASE}/capsules/all`,
|
|
64
|
+
{
|
|
65
|
+
headers: { Authorization: `Bearer ${config.token}` }
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const capsules = capsulesRes.data || [];
|
|
70
|
+
|
|
71
|
+
capsules.forEach(capsule => {
|
|
72
|
+
const file = path.join(CAPSULES_DIR, `${capsule.id}.json`);
|
|
73
|
+
fs.writeFileSync(file, JSON.stringify(capsule, null, 2), "utf8");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
console.log(chalk.green(`✔ Synced ${capsules.length} capsules.`));
|
|
77
|
+
|
|
78
|
+
// -------------------------------
|
|
79
|
+
// Sync Tool Metadata
|
|
80
|
+
// -------------------------------
|
|
81
|
+
console.log(chalk.gray("Fetching tool metadata..."));
|
|
82
|
+
|
|
83
|
+
const toolsRes = await axios.get(
|
|
84
|
+
`${API_BASE}/tools/all`,
|
|
85
|
+
{
|
|
86
|
+
headers: { Authorization: `Bearer ${config.token}` }
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const tools = toolsRes.data || [];
|
|
91
|
+
|
|
92
|
+
const toolsFile = path.join(TOOLS_DIR, "tools.json");
|
|
93
|
+
fs.writeFileSync(toolsFile, JSON.stringify(tools, null, 2), "utf8");
|
|
94
|
+
|
|
95
|
+
console.log(chalk.green(`✔ Synced ${tools.length} tools.`));
|
|
96
|
+
|
|
97
|
+
console.log(chalk.green("\n✔ Workspace sync complete.\n"));
|
|
98
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = path.join(os.homedir(), '.playnex');
|
|
6
|
+
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
|
|
7
|
+
|
|
8
|
+
export function loadConfig() {
|
|
9
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
const raw = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(raw);
|
|
15
|
+
} catch {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function saveConfig(data) {
|
|
21
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
22
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(data, null, 2), 'utf8');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function updateConfig(patch) {
|
|
28
|
+
const current = loadConfig();
|
|
29
|
+
const next = { ...current, ...patch };
|
|
30
|
+
saveConfig(next);
|
|
31
|
+
return next;
|
|
32
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* browser tool
|
|
6
|
+
* Allows agents to fetch and read web pages.
|
|
7
|
+
*
|
|
8
|
+
* Expected JSON from agent:
|
|
9
|
+
* {
|
|
10
|
+
* "tool": "browser",
|
|
11
|
+
* "input": {
|
|
12
|
+
* "url": "https://example.com"
|
|
13
|
+
* }
|
|
14
|
+
* }
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export default async function browser(agent, input) {
|
|
18
|
+
if (!input || typeof input !== "object") {
|
|
19
|
+
console.log(chalk.red("browser: invalid input. Expected { url }."));
|
|
20
|
+
return "browser: invalid input.";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { url } = input;
|
|
24
|
+
|
|
25
|
+
if (!url || typeof url !== "string") {
|
|
26
|
+
console.log(chalk.red("browser: missing or invalid url."));
|
|
27
|
+
return "browser: missing or invalid url.";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.log(chalk.gray(`Fetching URL: ${url}`));
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const res = await axios.get(url, {
|
|
34
|
+
timeout: 15000,
|
|
35
|
+
maxContentLength: 1024 * 1024 * 2 // 2MB safety limit
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const contentType = res.headers["content-type"] || "";
|
|
39
|
+
|
|
40
|
+
// Only return text-based content
|
|
41
|
+
if (!contentType.includes("text") && !contentType.includes("json")) {
|
|
42
|
+
console.log(chalk.yellow("browser: URL does not contain readable text content."));
|
|
43
|
+
return "browser: URL does not contain readable text content.";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const text = res.data;
|
|
47
|
+
|
|
48
|
+
console.log(chalk.green("✔ Page fetched successfully."));
|
|
49
|
+
|
|
50
|
+
// Return first 5000 chars to avoid overload
|
|
51
|
+
return typeof text === "string"
|
|
52
|
+
? text.slice(0, 5000)
|
|
53
|
+
: JSON.stringify(text).slice(0, 5000);
|
|
54
|
+
|
|
55
|
+
} catch (err) {
|
|
56
|
+
console.log(chalk.red("browser: failed to fetch URL."));
|
|
57
|
+
return `browser: error fetching URL: ${err.message}`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { loadConfig } from "../services/config.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* capsule-append tool
|
|
7
|
+
* Allows agents to append content to an existing capsule.
|
|
8
|
+
*
|
|
9
|
+
* Expected JSON from agent:
|
|
10
|
+
* {
|
|
11
|
+
* "tool": "capsule-append",
|
|
12
|
+
* "input": {
|
|
13
|
+
* "id": "123",
|
|
14
|
+
* "content": "Additional text to append."
|
|
15
|
+
* }
|
|
16
|
+
* }
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const API_BASE = "https://playnex.app";
|
|
20
|
+
|
|
21
|
+
export default async function capsuleAppend(agent, input) {
|
|
22
|
+
const config = loadConfig();
|
|
23
|
+
|
|
24
|
+
if (!config.token) {
|
|
25
|
+
console.log(chalk.red("You must log in first."));
|
|
26
|
+
console.log("Run: playnex login");
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!input || typeof input !== "object") {
|
|
31
|
+
console.log(chalk.red("capsule-append: invalid input. Expected { id, content }."));
|
|
32
|
+
return "capsule-append: invalid input.";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const { id, content } = input;
|
|
36
|
+
|
|
37
|
+
if (!id || !content) {
|
|
38
|
+
console.log(chalk.red("capsule-append: missing id or content."));
|
|
39
|
+
return "capsule-append: missing id or content.";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log(chalk.gray(`Appending to capsule ${id}...`));
|
|
43
|
+
|
|
44
|
+
const payload = {
|
|
45
|
+
id,
|
|
46
|
+
append: content
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const res = await axios.post(
|
|
50
|
+
`${API_BASE}/capsules/append`,
|
|
51
|
+
payload,
|
|
52
|
+
{
|
|
53
|
+
headers: {
|
|
54
|
+
Authorization: `Bearer ${config.token}`
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (!res.data || !res.data.updated) {
|
|
60
|
+
console.log(chalk.red("Failed to append to capsule."));
|
|
61
|
+
return "Failed to append to capsule.";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(chalk.green(`✔ Capsule ${id} updated successfully.`));
|
|
65
|
+
|
|
66
|
+
return `Capsule ${id} updated: ${content}`;
|
|
67
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { loadConfig } from "../services/config.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* capsule-search tool
|
|
7
|
+
* Allows agents to search across all capsules using keyword relevance.
|
|
8
|
+
*
|
|
9
|
+
* Expected JSON from agent:
|
|
10
|
+
* {
|
|
11
|
+
* "tool": "capsule-search",
|
|
12
|
+
* "input": "local-first agents"
|
|
13
|
+
* }
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const API_BASE = "https://playnex.app";
|
|
17
|
+
|
|
18
|
+
export default async function capsuleSearch(agent, query) {
|
|
19
|
+
const config = loadConfig();
|
|
20
|
+
|
|
21
|
+
if (!config.token) {
|
|
22
|
+
console.log(chalk.red("You must log in first."));
|
|
23
|
+
console.log("Run: playnex login");
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!query || typeof query !== "string") {
|
|
28
|
+
console.log(chalk.red("capsule-search: invalid input. Expected a search string."));
|
|
29
|
+
return "capsule-search: invalid input.";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.log(chalk.gray(`Searching capsules for: '${query}'...`));
|
|
33
|
+
|
|
34
|
+
// Fetch all capsules
|
|
35
|
+
const res = await axios.get(
|
|
36
|
+
`${API_BASE}/capsules/all`,
|
|
37
|
+
{
|
|
38
|
+
headers: {
|
|
39
|
+
Authorization: `Bearer ${config.token}`
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const capsules = res.data || [];
|
|
45
|
+
|
|
46
|
+
if (!Array.isArray(capsules) || capsules.length === 0) {
|
|
47
|
+
console.log(chalk.yellow("No capsules found."));
|
|
48
|
+
return "No capsules found.";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const terms = query.toLowerCase().split(" ");
|
|
52
|
+
|
|
53
|
+
// Score capsules by keyword overlap
|
|
54
|
+
const results = capsules
|
|
55
|
+
.map(c => {
|
|
56
|
+
const text = (c.title + " " + c.description).toLowerCase();
|
|
57
|
+
const score = terms.reduce((acc, word) => acc + (text.includes(word) ? 1 : 0), 0);
|
|
58
|
+
return { capsule: c, score };
|
|
59
|
+
})
|
|
60
|
+
.filter(r => r.score > 0)
|
|
61
|
+
.sort((a, b) => b.score - a.score)
|
|
62
|
+
.slice(0, 5);
|
|
63
|
+
|
|
64
|
+
if (results.length === 0) {
|
|
65
|
+
console.log(chalk.yellow("No relevant capsules found."));
|
|
66
|
+
return "No relevant capsules found.";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log(chalk.green(`Found ${results.length} relevant capsules.`));
|
|
70
|
+
|
|
71
|
+
return results
|
|
72
|
+
.map(r => `[Capsule ${r.capsule.id}] ${r.capsule.title}: ${r.capsule.description}`)
|
|
73
|
+
.join("\\n\\n");
|
|
74
|
+
}
|