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,38 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { loadConfig } from '../services/config.js';
|
|
4
|
+
|
|
5
|
+
const API_BASE = 'https://playnex.app';
|
|
6
|
+
|
|
7
|
+
export default async function capsuleWriter(agent, text) {
|
|
8
|
+
const config = loadConfig();
|
|
9
|
+
|
|
10
|
+
if (!config.token) {
|
|
11
|
+
console.log(chalk.red('You must log in first.'));
|
|
12
|
+
console.log('Run: playnex login');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
console.log(chalk.gray('Creating capsule via capsule-writer tool...'));
|
|
17
|
+
|
|
18
|
+
const payload = {
|
|
19
|
+
title: text.slice(0, 60) || 'Untitled Capsule',
|
|
20
|
+
description: text,
|
|
21
|
+
tags: '',
|
|
22
|
+
estimatedTime: ''
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const res = await axios.post(
|
|
26
|
+
\\/capsules/create\,
|
|
27
|
+
payload,
|
|
28
|
+
{
|
|
29
|
+
headers: {
|
|
30
|
+
Authorization: \Bearer \\
|
|
31
|
+
},
|
|
32
|
+
maxRedirects: 0,
|
|
33
|
+
validateStatus: status => status < 400 || status === 302
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
console.log(chalk.green('✔ Capsule created successfully.'));
|
|
38
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* file-writer tool
|
|
8
|
+
* Allows agents to write files to the user's local filesystem.
|
|
9
|
+
*
|
|
10
|
+
* Expected JSON from agent:
|
|
11
|
+
* {
|
|
12
|
+
* "tool": "file-writer",
|
|
13
|
+
* "input": {
|
|
14
|
+
* "path": "notes/idea.txt",
|
|
15
|
+
* "content": "This is the file content."
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export default async function fileWriter(agent, input) {
|
|
21
|
+
if (!input || typeof input !== "object") {
|
|
22
|
+
console.log(chalk.red("file-writer: invalid input. Expected { path, content }."));
|
|
23
|
+
return "file-writer: invalid input.";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { path: relativePath, content } = input;
|
|
27
|
+
|
|
28
|
+
if (!relativePath || !content) {
|
|
29
|
+
console.log(chalk.red("file-writer: missing path or content."));
|
|
30
|
+
return "file-writer: missing path or content.";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Write files inside ~/.playnex/files/
|
|
34
|
+
const BASE_DIR = path.join(os.homedir(), ".playnex", "files");
|
|
35
|
+
const fullPath = path.join(BASE_DIR, relativePath);
|
|
36
|
+
|
|
37
|
+
// Ensure directory exists
|
|
38
|
+
const dir = path.dirname(fullPath);
|
|
39
|
+
if (!fs.existsSync(dir)) {
|
|
40
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Write file
|
|
44
|
+
fs.writeFileSync(fullPath, content, "utf8");
|
|
45
|
+
|
|
46
|
+
console.log(chalk.green(`✔ File written: ${fullPath}`));
|
|
47
|
+
|
|
48
|
+
return `File saved to ${fullPath}`;
|
|
49
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* local-capsule-search tool
|
|
8
|
+
* Searches locally mirrored capsules for offline memory.
|
|
9
|
+
*
|
|
10
|
+
* Expected JSON from agent:
|
|
11
|
+
* {
|
|
12
|
+
* "tool": "local-capsule-search",
|
|
13
|
+
* "input": "search terms"
|
|
14
|
+
* }
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export default async function localCapsuleSearch(agent, query) {
|
|
18
|
+
if (!query || typeof query !== "string") {
|
|
19
|
+
console.log(chalk.red("local-capsule-search: invalid input. Expected a search string."));
|
|
20
|
+
return "local-capsule-search: invalid input.";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
console.log(chalk.gray(`Searching local capsules for: '${query}'...`));
|
|
24
|
+
|
|
25
|
+
const WORKSPACE_DIR = path.join(os.homedir(), ".playnex", "workspace");
|
|
26
|
+
const CAPSULES_DIR = path.join(WORKSPACE_DIR, "capsules-local");
|
|
27
|
+
|
|
28
|
+
if (!fs.existsSync(CAPSULES_DIR)) {
|
|
29
|
+
console.log(chalk.red("Local capsule mirror not found. Run: playnex capsules mirror"));
|
|
30
|
+
return "Local capsule mirror not found.";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const files = fs.readdirSync(CAPSULES_DIR).filter(f => f.endsWith(".json"));
|
|
34
|
+
|
|
35
|
+
if (files.length === 0) {
|
|
36
|
+
console.log(chalk.yellow("No local capsules found."));
|
|
37
|
+
return "No local capsules found.";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const terms = query.toLowerCase().split(" ");
|
|
41
|
+
|
|
42
|
+
const results = files
|
|
43
|
+
.map(file => {
|
|
44
|
+
const capsule = JSON.parse(fs.readFileSync(path.join(CAPSULES_DIR, file), "utf8"));
|
|
45
|
+
const text = (capsule.title + " " + capsule.description).toLowerCase();
|
|
46
|
+
const score = terms.reduce((acc, word) => acc + (text.includes(word) ? 1 : 0), 0);
|
|
47
|
+
return { capsule, score };
|
|
48
|
+
})
|
|
49
|
+
.filter(r => r.score > 0)
|
|
50
|
+
.sort((a, b) => b.score - a.score)
|
|
51
|
+
.slice(0, 5);
|
|
52
|
+
|
|
53
|
+
if (results.length === 0) {
|
|
54
|
+
console.log(chalk.yellow("No relevant local capsules found."));
|
|
55
|
+
return "No relevant local capsules found.";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log(chalk.green(`Found ${results.length} relevant local capsules.`));
|
|
59
|
+
|
|
60
|
+
return results
|
|
61
|
+
.map(r => `[Capsule ${r.capsule.id}] ${r.capsule.title}: ${r.capsule.description}`)
|
|
62
|
+
.join("\\n\\n");
|
|
63
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
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
|
+
/**
|
|
8
|
+
* local-embeddings tool
|
|
9
|
+
* Generates embeddings for locally mirrored capsules.
|
|
10
|
+
*
|
|
11
|
+
* Expected JSON from agent:
|
|
12
|
+
* {
|
|
13
|
+
* "tool": "local-embeddings",
|
|
14
|
+
* "input": "nomic-embed-text" // or any embedding model
|
|
15
|
+
* }
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export default async function localEmbeddings(agent, modelName) {
|
|
19
|
+
if (!modelName || typeof modelName !== "string") {
|
|
20
|
+
console.log(chalk.red("local-embeddings: invalid input. Expected embedding model name."));
|
|
21
|
+
return "local-embeddings: invalid input.";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log(chalk.cyan(`\nGenerating local embeddings using model: ${modelName}\n`));
|
|
25
|
+
|
|
26
|
+
const WORKSPACE_DIR = path.join(os.homedir(), ".playnex", "workspace");
|
|
27
|
+
const CAPSULES_DIR = path.join(WORKSPACE_DIR, "capsules-local");
|
|
28
|
+
const EMBED_DIR = path.join(WORKSPACE_DIR, "embeddings");
|
|
29
|
+
|
|
30
|
+
if (!fs.existsSync(CAPSULES_DIR)) {
|
|
31
|
+
console.log(chalk.red("Local capsule mirror not found. Run: playnex capsules mirror"));
|
|
32
|
+
return "Local capsule mirror not found.";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!fs.existsSync(EMBED_DIR)) {
|
|
36
|
+
fs.mkdirSync(EMBED_DIR, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const files = fs.readdirSync(CAPSULES_DIR).filter(f => f.endsWith(".json"));
|
|
40
|
+
|
|
41
|
+
if (files.length === 0) {
|
|
42
|
+
console.log(chalk.yellow("No local capsules found."));
|
|
43
|
+
return "No local capsules found.";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(chalk.gray(`Found ${files.length} capsules. Embedding...`));
|
|
47
|
+
|
|
48
|
+
let count = 0;
|
|
49
|
+
|
|
50
|
+
for (const file of files) {
|
|
51
|
+
const capsule = JSON.parse(fs.readFileSync(path.join(CAPSULES_DIR, file), "utf8"));
|
|
52
|
+
|
|
53
|
+
const text = `${capsule.title}\n\n${capsule.description}`;
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const embedding = await ollama.embeddings({
|
|
57
|
+
model: modelName,
|
|
58
|
+
prompt: text
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const outFile = path.join(EMBED_DIR, `${capsule.id}.json`);
|
|
62
|
+
fs.writeFileSync(outFile, JSON.stringify(embedding, null, 2), "utf8");
|
|
63
|
+
|
|
64
|
+
count++;
|
|
65
|
+
} catch (err) {
|
|
66
|
+
console.log(chalk.red(`Failed to embed capsule ${capsule.id}: ${err.message}`));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log(chalk.green(`\n✔ Generated embeddings for ${count} capsules.`));
|
|
71
|
+
console.log(chalk.green(`Embeddings stored in: ${EMBED_DIR}\n`));
|
|
72
|
+
|
|
73
|
+
return `Generated embeddings for ${count} capsules.`;
|
|
74
|
+
}
|