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
package/README.md
ADDED
package/bin/playnex.js
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { program } from "commander";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import login from "../src/commands/login.js";
|
|
5
|
+
|
|
6
|
+
// -----------------------------------------------------
|
|
7
|
+
// Custom Help Banner
|
|
8
|
+
// -----------------------------------------------------
|
|
9
|
+
program.configureHelp({
|
|
10
|
+
sortSubcommands: false,
|
|
11
|
+
sortOptions: false,
|
|
12
|
+
formatHelp: (cmd, helper) => {
|
|
13
|
+
const b = chalk.bold;
|
|
14
|
+
const g = chalk.gray;
|
|
15
|
+
|
|
16
|
+
return `
|
|
17
|
+
${b("Playnex CLI — Local‑First AI Agents")}
|
|
18
|
+
${g("────────────────────────────────────────────")}
|
|
19
|
+
|
|
20
|
+
Playnex is a local‑first automation platform.
|
|
21
|
+
Agents run on your machine, use local tools, and sync with your Playnex workspace.
|
|
22
|
+
|
|
23
|
+
${b("Quick Start")}
|
|
24
|
+
playnex login
|
|
25
|
+
playnex agent create
|
|
26
|
+
playnex agent chat myagent
|
|
27
|
+
|
|
28
|
+
${b("Core Concepts")}
|
|
29
|
+
Agents Autonomous workers you define (model, purpose, tools)
|
|
30
|
+
Workspace Your synced project files and context
|
|
31
|
+
Capsules Versioned knowledge bundles mirrored locally
|
|
32
|
+
Scheduler Background automation for agents
|
|
33
|
+
Tools Capabilities you attach to agents
|
|
34
|
+
|
|
35
|
+
${b("Agent Commands")}
|
|
36
|
+
agent create Create a new agent
|
|
37
|
+
agent chat <name> Chat with an agent
|
|
38
|
+
agent run <name> Run an agent in automation mode
|
|
39
|
+
agent add-tool <n> <t> Attach a tool to an agent
|
|
40
|
+
|
|
41
|
+
${b("Workspace")}
|
|
42
|
+
workspace sync Pull workspace from cloud
|
|
43
|
+
|
|
44
|
+
${b("Capsules")}
|
|
45
|
+
capsules mirror Mirror all capsules locally
|
|
46
|
+
|
|
47
|
+
${b("Scheduler")}
|
|
48
|
+
scheduler add <a> <int> Schedule an agent (e.g. 1h, 30m)
|
|
49
|
+
scheduler list List scheduled agents
|
|
50
|
+
scheduler remove <id> Remove a scheduled agent
|
|
51
|
+
scheduler run Run scheduler daemon
|
|
52
|
+
|
|
53
|
+
${b("Tools")}
|
|
54
|
+
tools register <tool> Register a tool
|
|
55
|
+
tools list List tools
|
|
56
|
+
tools info <tool> Show tool details
|
|
57
|
+
|
|
58
|
+
${b("Examples")}
|
|
59
|
+
playnex agent create
|
|
60
|
+
playnex agent chat writer
|
|
61
|
+
playnex scheduler add reporter 1h
|
|
62
|
+
playnex tools register websearch
|
|
63
|
+
|
|
64
|
+
${b("Options")}
|
|
65
|
+
-h, --help Show help
|
|
66
|
+
-V, --version Show version
|
|
67
|
+
|
|
68
|
+
${g("Tip: Run 'playnex <command> --help' for detailed usage.")}
|
|
69
|
+
`;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
// -----------------------------------------------------
|
|
75
|
+
// Base CLI metadata
|
|
76
|
+
// -----------------------------------------------------
|
|
77
|
+
program
|
|
78
|
+
.name("playnex")
|
|
79
|
+
.description("Playnex CLI — local-first AI agents")
|
|
80
|
+
.version("0.1.0");
|
|
81
|
+
|
|
82
|
+
// -----------------------------------------------------
|
|
83
|
+
// Authentication
|
|
84
|
+
// -----------------------------------------------------
|
|
85
|
+
program
|
|
86
|
+
.command("login")
|
|
87
|
+
.description("Log in to your Playnex account")
|
|
88
|
+
.action(login);
|
|
89
|
+
|
|
90
|
+
// -----------------------------------------------------
|
|
91
|
+
// Agent Commands
|
|
92
|
+
// -----------------------------------------------------
|
|
93
|
+
const agent = program.command("agent").description("Manage Playnex agents");
|
|
94
|
+
|
|
95
|
+
agent
|
|
96
|
+
.command("create")
|
|
97
|
+
.description("Create a new Playnex agent")
|
|
98
|
+
.action(() =>
|
|
99
|
+
import("../src/commands/agent/create.js").then((m) => m.default())
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
agent
|
|
103
|
+
.command("chat <name>")
|
|
104
|
+
.description("Chat with a Playnex agent")
|
|
105
|
+
.action((name) =>
|
|
106
|
+
import("../src/commands/agent/chat.js").then((m) => m.default(name))
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
agent
|
|
110
|
+
.command("run <name>")
|
|
111
|
+
.description("Run a Playnex agent in automation mode")
|
|
112
|
+
.action((name) =>
|
|
113
|
+
import("../src/commands/agent/run.js").then((m) => m.default(name))
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
agent
|
|
117
|
+
.command("add-tool <name> <tool>")
|
|
118
|
+
.description("Add a tool to a Playnex agent")
|
|
119
|
+
.action((name, tool) =>
|
|
120
|
+
import("../src/commands/agent/add-tool.js").then((m) =>
|
|
121
|
+
m.default(name, tool)
|
|
122
|
+
)
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// -----------------------------------------------------
|
|
126
|
+
// Workspace Commands
|
|
127
|
+
// -----------------------------------------------------
|
|
128
|
+
const workspace = program
|
|
129
|
+
.command("workspace")
|
|
130
|
+
.description("Workspace operations");
|
|
131
|
+
|
|
132
|
+
workspace
|
|
133
|
+
.command("sync")
|
|
134
|
+
.description("Sync Playnex workspace from cloud to local")
|
|
135
|
+
.action(() =>
|
|
136
|
+
import("../src/commands/workspace/sync.js").then((m) => m.default())
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// -----------------------------------------------------
|
|
140
|
+
// Capsule Commands
|
|
141
|
+
// -----------------------------------------------------
|
|
142
|
+
const capsules = program
|
|
143
|
+
.command("capsules")
|
|
144
|
+
.description("Capsule operations");
|
|
145
|
+
|
|
146
|
+
capsules
|
|
147
|
+
.command("mirror")
|
|
148
|
+
.description("Mirror all capsules locally for offline use")
|
|
149
|
+
.action(() =>
|
|
150
|
+
import("../src/commands/capsules/mirror.js").then((m) => m.default())
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// -----------------------------------------------------
|
|
154
|
+
// Scheduler Commands
|
|
155
|
+
// -----------------------------------------------------
|
|
156
|
+
const scheduler = program
|
|
157
|
+
.command("scheduler")
|
|
158
|
+
.description("Agent scheduler");
|
|
159
|
+
|
|
160
|
+
scheduler
|
|
161
|
+
.command("add <agent> <interval>")
|
|
162
|
+
.description("Schedule an agent to run automatically")
|
|
163
|
+
.action((agentName, interval) =>
|
|
164
|
+
import("../src/commands/scheduler/scheduler.js").then((m) =>
|
|
165
|
+
m.schedulerAdd(agentName, interval)
|
|
166
|
+
)
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
scheduler
|
|
170
|
+
.command("list")
|
|
171
|
+
.description("List scheduled agents")
|
|
172
|
+
.action(() =>
|
|
173
|
+
import("../src/commands/scheduler/scheduler.js").then((m) =>
|
|
174
|
+
m.schedulerList()
|
|
175
|
+
)
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
scheduler
|
|
179
|
+
.command("remove <id>")
|
|
180
|
+
.description("Remove a scheduled agent")
|
|
181
|
+
.action((id) =>
|
|
182
|
+
import("../src/commands/scheduler/scheduler.js").then((m) =>
|
|
183
|
+
m.schedulerRemove(id)
|
|
184
|
+
)
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
scheduler
|
|
188
|
+
.command("run")
|
|
189
|
+
.description("Run the scheduler daemon")
|
|
190
|
+
.action(() =>
|
|
191
|
+
import("../src/commands/scheduler/scheduler.js").then((m) =>
|
|
192
|
+
m.schedulerRun()
|
|
193
|
+
)
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
// -----------------------------------------------------
|
|
197
|
+
// Tool Registry Commands
|
|
198
|
+
// -----------------------------------------------------
|
|
199
|
+
const tools = program.command("tools").description("Tool registry operations");
|
|
200
|
+
|
|
201
|
+
tools
|
|
202
|
+
.command("register <tool>")
|
|
203
|
+
.description("Register a tool in the tool registry")
|
|
204
|
+
.action((tool) =>
|
|
205
|
+
import("../src/commands/tools/registry.js").then((m) =>
|
|
206
|
+
m.registerTool(tool)
|
|
207
|
+
)
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
tools
|
|
211
|
+
.command("list")
|
|
212
|
+
.description("List all registered tools")
|
|
213
|
+
.action(() =>
|
|
214
|
+
import("../src/commands/tools/registry.js").then((m) => m.listTools())
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
tools
|
|
218
|
+
.command("info <tool>")
|
|
219
|
+
.description("Show detailed info for a tool")
|
|
220
|
+
.action((tool) =>
|
|
221
|
+
import("../src/commands/tools/registry.js").then((m) =>
|
|
222
|
+
m.toolInfo(tool)
|
|
223
|
+
)
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// -----------------------------------------------------
|
|
227
|
+
// Parse CLI
|
|
228
|
+
// -----------------------------------------------------
|
|
229
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "playnex-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Playnex CLI — local-first AI agents, capsules, and tools.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"playnex": "./bin/playnex.js"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=18"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"axios": "^1.6.0",
|
|
14
|
+
"chalk": "^5.3.0",
|
|
15
|
+
"commander": "^12.0.0",
|
|
16
|
+
"inquirer": "^13.2.5",
|
|
17
|
+
"ollama": "^0.6.3"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT"
|
|
20
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
$ErrorActionPreference = 'Stop'
|
|
2
|
+
|
|
3
|
+
function Ensure-Dir($path) {
|
|
4
|
+
if (-not (Test-Path $path)) {
|
|
5
|
+
New-Item -ItemType Directory -Path $path | Out-Null
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
Write-Host 'Creating agent system...'
|
|
10
|
+
|
|
11
|
+
$root = Get-Location
|
|
12
|
+
$src = Join-Path $root 'src'
|
|
13
|
+
$services = Join-Path $src 'services'
|
|
14
|
+
$commands = Join-Path $src 'commands'
|
|
15
|
+
$agentCmd = Join-Path $commands 'agent'
|
|
16
|
+
|
|
17
|
+
Ensure-Dir $src
|
|
18
|
+
Ensure-Dir $services
|
|
19
|
+
Ensure-Dir $commands
|
|
20
|
+
Ensure-Dir $agentCmd
|
|
21
|
+
|
|
22
|
+
$agentsJs = @'
|
|
23
|
+
import fs from "fs";
|
|
24
|
+
export const test = true;
|
|
25
|
+
'@
|
|
26
|
+
|
|
27
|
+
Set-Content -Path (Join-Path $services 'agents.js') -Value $agentsJs -Encoding UTF8
|
|
28
|
+
|
|
29
|
+
Write-Host 'Done.'
|
|
30
|
+
Write-Host 'Run: npm install commander chalk inquirer ollama'
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
|
|
6
|
+
const AGENTS_DIR = path.join(os.homedir(), ".playnex", "workspace", "agents");
|
|
7
|
+
const TOOLS_DIR = path.join(process.cwd(), "src", "tools");
|
|
8
|
+
|
|
9
|
+
function loadAgent(name) {
|
|
10
|
+
const file = path.join(AGENTS_DIR, `${name}.json`);
|
|
11
|
+
if (!fs.existsSync(file)) {
|
|
12
|
+
console.log(chalk.red(`Agent '${name}' not found.`));
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
return { file, data: JSON.parse(fs.readFileSync(file, "utf8")) };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default async function addTool(name, toolName) {
|
|
19
|
+
const { file, data } = loadAgent(name);
|
|
20
|
+
|
|
21
|
+
console.log(chalk.cyan(`\nAdd a tool to agent: ${name}\n`));
|
|
22
|
+
|
|
23
|
+
const availableTools = fs
|
|
24
|
+
.readdirSync(TOOLS_DIR)
|
|
25
|
+
.filter((f) => f.endsWith(".js"))
|
|
26
|
+
.map((f) => f.replace(".js", ""));
|
|
27
|
+
|
|
28
|
+
if (!availableTools.includes(toolName)) {
|
|
29
|
+
console.log(chalk.red(`Tool '${toolName}' does not exist.`));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!data.tools.includes(toolName)) {
|
|
34
|
+
data.tools.push(toolName);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fs.writeFileSync(file, JSON.stringify(data, null, 2), "utf8");
|
|
38
|
+
|
|
39
|
+
console.log(chalk.green(`\n✔ Tool '${toolName}' added to agent '${name}'.\n`));
|
|
40
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import readline from "readline";
|
|
6
|
+
import ollama from "ollama";
|
|
7
|
+
|
|
8
|
+
const AGENTS_DIR = path.join(os.homedir(), ".playnex", "workspace", "agents");
|
|
9
|
+
|
|
10
|
+
function loadAgent(name) {
|
|
11
|
+
const file = path.join(AGENTS_DIR, `${name}.json`);
|
|
12
|
+
if (!fs.existsSync(file)) {
|
|
13
|
+
console.log(chalk.red(`Agent '${name}' not found.`));
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function askUser(promptText) {
|
|
20
|
+
const rl = readline.createInterface({
|
|
21
|
+
input: process.stdin,
|
|
22
|
+
output: process.stdout,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return new Promise((resolve) =>
|
|
26
|
+
rl.question(promptText, (answer) => {
|
|
27
|
+
rl.close();
|
|
28
|
+
resolve(answer.trim());
|
|
29
|
+
})
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default async function agentChat(name) {
|
|
34
|
+
const agent = loadAgent(name);
|
|
35
|
+
|
|
36
|
+
// ⭐ NEW: Validate model before doing anything else
|
|
37
|
+
if (!agent.model || typeof agent.model !== "string") {
|
|
38
|
+
console.log(chalk.red(`\nAgent '${name}' has no model assigned.`));
|
|
39
|
+
console.log(chalk.yellow("Edit the agent JSON and set a model, e.g.:"));
|
|
40
|
+
console.log(chalk.gray(` "model": "llama3"`));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log(chalk.cyan(`\nChatting with agent: ${name}`));
|
|
45
|
+
console.log(chalk.gray(`Model: ${agent.model}`));
|
|
46
|
+
console.log(chalk.gray(`Purpose: ${agent.purpose}\n`));
|
|
47
|
+
|
|
48
|
+
let messages = [
|
|
49
|
+
{
|
|
50
|
+
role: "system",
|
|
51
|
+
content: agent.purpose,
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
while (true) {
|
|
56
|
+
const userInput = await askUser(chalk.green("You: "));
|
|
57
|
+
|
|
58
|
+
if (!userInput) continue;
|
|
59
|
+
if (userInput.toLowerCase() === "exit") {
|
|
60
|
+
console.log(chalk.yellow("\nExiting chat.\n"));
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
messages.push({ role: "user", content: userInput });
|
|
65
|
+
|
|
66
|
+
console.log(chalk.cyan("\nAgent: "));
|
|
67
|
+
|
|
68
|
+
let responseText = "";
|
|
69
|
+
|
|
70
|
+
// ⭐ This now always has a valid model
|
|
71
|
+
const stream = await ollama.chat({
|
|
72
|
+
model: agent.model,
|
|
73
|
+
messages,
|
|
74
|
+
stream: true,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
for await (const chunk of stream) {
|
|
78
|
+
const token = chunk.message?.content || "";
|
|
79
|
+
responseText += token;
|
|
80
|
+
process.stdout.write(token);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log("\n");
|
|
84
|
+
|
|
85
|
+
messages.push({
|
|
86
|
+
role: "assistant",
|
|
87
|
+
content: responseText,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import readline from "readline";
|
|
6
|
+
import { loadConfig } from "../../services/config.js";
|
|
7
|
+
|
|
8
|
+
const AGENTS_DIR = path.join(os.homedir(), ".playnex", "workspace", "agents");
|
|
9
|
+
|
|
10
|
+
function ask(question) {
|
|
11
|
+
const rl = readline.createInterface({
|
|
12
|
+
input: process.stdin,
|
|
13
|
+
output: process.stdout,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return new Promise((resolve) =>
|
|
17
|
+
rl.question(question, (answer) => {
|
|
18
|
+
rl.close();
|
|
19
|
+
resolve(answer.trim());
|
|
20
|
+
})
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default async function agentCreate() {
|
|
25
|
+
console.log(chalk.cyan("\nCreate a New Playnex Agent\n"));
|
|
26
|
+
|
|
27
|
+
const config = loadConfig();
|
|
28
|
+
if (!config.token) {
|
|
29
|
+
console.log(chalk.red("You must log in first."));
|
|
30
|
+
console.log("Run: playnex login");
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const name = await ask("Agent name: ");
|
|
35
|
+
const purpose = await ask("Purpose (one sentence): ");
|
|
36
|
+
|
|
37
|
+
let model = "";
|
|
38
|
+
while (!model) {
|
|
39
|
+
model = await ask("Model (e.g., llama3): ");
|
|
40
|
+
if (!model) {
|
|
41
|
+
console.log(chalk.yellow("Model is required. Please enter a model name.\n"));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const agent = {
|
|
46
|
+
name,
|
|
47
|
+
purpose,
|
|
48
|
+
model,
|
|
49
|
+
tools: [],
|
|
50
|
+
createdAt: new Date().toISOString(),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
if (!fs.existsSync(AGENTS_DIR)) {
|
|
54
|
+
fs.mkdirSync(AGENTS_DIR, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const filePath = path.join(AGENTS_DIR, `${name}.json`);
|
|
58
|
+
fs.writeFileSync(filePath, JSON.stringify(agent, null, 2), "utf8");
|
|
59
|
+
|
|
60
|
+
console.log(chalk.green(`\n✔ Agent '${name}' created successfully.`));
|
|
61
|
+
console.log(chalk.gray(`Saved to: ${filePath}\n`));
|
|
62
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import ollama from "ollama";
|
|
6
|
+
|
|
7
|
+
const AGENTS_DIR = path.join(os.homedir(), ".playnex", "workspace", "agents");
|
|
8
|
+
const TOOLS_DIR = path.join(process.cwd(), "src", "tools");
|
|
9
|
+
|
|
10
|
+
function loadAgent(name) {
|
|
11
|
+
const file = path.join(AGENTS_DIR, `${name}.json`);
|
|
12
|
+
if (!fs.existsSync(file)) {
|
|
13
|
+
console.log(chalk.red(`Agent '${name}' not found.`));
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function loadTool(toolName) {
|
|
20
|
+
const toolPath = path.join(TOOLS_DIR, `${toolName}.js`);
|
|
21
|
+
if (!fs.existsSync(toolPath)) {
|
|
22
|
+
console.log(chalk.red(`Tool '${toolName}' not found in src/tools.`));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const module = await import(toolPath);
|
|
26
|
+
return module.default;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default async function agentRun(name) {
|
|
30
|
+
const agent = loadAgent(name);
|
|
31
|
+
|
|
32
|
+
console.log(chalk.cyan(`\nRunning agent: ${name}`));
|
|
33
|
+
console.log(chalk.gray(`Model: ${agent.model}`));
|
|
34
|
+
console.log(chalk.gray(`Tools: ${agent.tools.join(", ")}`));
|
|
35
|
+
console.log(chalk.gray(`Purpose: ${agent.purpose}\n`));
|
|
36
|
+
|
|
37
|
+
const systemPrompt = `
|
|
38
|
+
You are the agent: ${name}
|
|
39
|
+
Purpose: ${agent.purpose}
|
|
40
|
+
|
|
41
|
+
You are running in automation mode.
|
|
42
|
+
Think step-by-step.
|
|
43
|
+
If you want to use a tool, output JSON ONLY in this format:
|
|
44
|
+
|
|
45
|
+
{
|
|
46
|
+
"tool": "tool-name",
|
|
47
|
+
"input": "text to send to the tool"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
Otherwise, output your final result as plain text.
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
const messages = [
|
|
54
|
+
{ role: "system", content: systemPrompt },
|
|
55
|
+
{ role: "user", content: "Begin your autonomous reasoning cycle." },
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
console.log(chalk.cyan("Agent output:\n"));
|
|
59
|
+
|
|
60
|
+
let responseText = "";
|
|
61
|
+
|
|
62
|
+
const stream = await ollama.chat({
|
|
63
|
+
model: agent.model,
|
|
64
|
+
messages,
|
|
65
|
+
stream: true,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
for await (const chunk of stream) {
|
|
69
|
+
const token = chunk.message?.content || "";
|
|
70
|
+
responseText += token;
|
|
71
|
+
process.stdout.write(token);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log("\n");
|
|
75
|
+
|
|
76
|
+
// Try to detect tool JSON
|
|
77
|
+
let toolCall = null;
|
|
78
|
+
try {
|
|
79
|
+
const start = responseText.indexOf("{");
|
|
80
|
+
const end = responseText.lastIndexOf("}");
|
|
81
|
+
if (start !== -1 && end !== -1) {
|
|
82
|
+
const json = responseText.slice(start, end + 1);
|
|
83
|
+
toolCall = JSON.parse(json);
|
|
84
|
+
}
|
|
85
|
+
} catch {}
|
|
86
|
+
|
|
87
|
+
if (toolCall && toolCall.tool) {
|
|
88
|
+
console.log(chalk.yellow(`\n🔧 Agent requested tool: ${toolCall.tool}`));
|
|
89
|
+
|
|
90
|
+
if (!agent.tools.includes(toolCall.tool)) {
|
|
91
|
+
console.log(chalk.red(`Agent does not have access to tool '${toolCall.tool}'.`));
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const toolFn = await loadTool(toolCall.tool);
|
|
96
|
+
await toolFn(agent, toolCall.input);
|
|
97
|
+
|
|
98
|
+
console.log(chalk.green("\n✔ Tool execution complete.\n"));
|
|
99
|
+
} else {
|
|
100
|
+
console.log(chalk.green("\n✔ Automation cycle complete.\n"));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
* capsules mirror
|
|
10
|
+
* Creates a full local mirror of all capsules for offline use.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const API_BASE = "https://playnex.app";
|
|
14
|
+
|
|
15
|
+
export default async function capsuleMirror() {
|
|
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("\nMirroring capsules to local workspace..."));
|
|
25
|
+
|
|
26
|
+
const WORKSPACE_DIR = path.join(os.homedir(), ".playnex", "workspace");
|
|
27
|
+
const CAPSULES_DIR = path.join(WORKSPACE_DIR, "capsules-local");
|
|
28
|
+
|
|
29
|
+
// Ensure directory exists
|
|
30
|
+
if (!fs.existsSync(CAPSULES_DIR)) {
|
|
31
|
+
fs.mkdirSync(CAPSULES_DIR, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log(chalk.gray("Fetching capsules from cloud..."));
|
|
35
|
+
|
|
36
|
+
const res = await axios.get(
|
|
37
|
+
`${API_BASE}/capsules/all`,
|
|
38
|
+
{
|
|
39
|
+
headers: { Authorization: `Bearer ${config.token}` }
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const capsules = res.data || [];
|
|
44
|
+
|
|
45
|
+
if (!Array.isArray(capsules) || capsules.length === 0) {
|
|
46
|
+
console.log(chalk.yellow("No capsules found."));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Write each capsule locally
|
|
51
|
+
capsules.forEach(capsule => {
|
|
52
|
+
const file = path.join(CAPSULES_DIR, `${capsule.id}.json`);
|
|
53
|
+
fs.writeFileSync(file, JSON.stringify(capsule, null, 2), "utf8");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
console.log(chalk.green(`✔ Mirrored ${capsules.length} capsules locally.`));
|
|
57
|
+
console.log(chalk.green(`Local capsule directory: ${CAPSULES_DIR}\n`));
|
|
58
|
+
}
|